/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ #include "theme.h" #include "util.h" #include #include #include #include #include struct _MetaTheme { ccss_grammar_t *grammar; ccss_stylesheet_t *stylesheet; gchar *directory; }; MetaTheme *the_theme = NULL; /* temporarily: */ #define SILLY_BORDER_SIZE 20 /****************************************************************/ /** * The element names of each CopperClass. * * \bug For heaven's sake change the identifier */ char *names[] = { "frame", "area", "area", "title", "button", "button", "button", "button", "button", "button", "button", "button", "button", "button", "area", "last" }; /** * The parent class of each CopperClass, or * CC_LAST if it has none. */ CopperClasses parents[] = { CC_LAST, CC_FRAME, CC_FRAME, CC_TITLEBAR, CC_TITLEBAR, CC_TITLEBAR, CC_TITLEBAR, CC_TITLEBAR, CC_TITLEBAR, CC_TITLEBAR, CC_TITLEBAR, CC_TITLEBAR, CC_TITLEBAR, CC_TITLEBAR, CC_TITLEBAR, CC_LAST }; /** * The class (in the CSS sense) of each * CopperClass, or NULL if it has none. */ char *cowbell_classnames[] = { NULL, "content", "titlebar", NULL, "menu", "minimize", "maximize", "close", "shade", "above", "stick", "unshade", "unabove", "unstick", "filler", NULL }; /** * Names of each CopperClass, for debugging. */ char *cowbell_human_names[] = { "Frame", "Content area", "Titlebar", "Title", "Menu button", "Min", "Max", "Close", "Shade", "Above", "Stick", "Unshade", "Unabove", "Unstick", "Filler", NULL }; typedef struct { ccss_node_t basic; CopperClasses cowbell_class; /* the next two make no sense for any class but CC_FRAME */ MetaFrameType type; MetaFrameFlags flags; } CopperNode; CopperNode cowbell_nodes[CC_LAST]; static void cowbell_style_title_text (ccss_stylesheet_t *stylesheet, PangoLayout *layout, cairo_t *cr); static void cowbell_get_edge_sizes (MetaTheme *theme, MetaFrameType type, MetaFrameFlags flags, CopperClasses style_id, int *top, int *bottom, int *left, int *right, int *top_margin, int *bottom_margin, int *left_margin, int *right_margin); /****************************************************************/ static char const* cowbell_get_type (ccss_node_t const *self) { g_warning ("It's %s", self->type_name); return self->type_name; } static ptrdiff_t cowbell_get_instance (ccss_node_t const *self) { return self->instance; } static char const* cowbell_get_style (ccss_node_t const *self) { /* this is always ""; why don't we just return ""? */ return self->inline_style; } static ccss_node_t* cowbell_get_container (ccss_node_t const *self) { CopperClasses candidate = parents[((CopperNode*)self)->cowbell_class]; if (candidate==CC_LAST) return NULL; else /* or should we allocate a new one? */ return (ccss_node_t*) &(cowbell_nodes[candidate]); } static const char* cowbell_get_class (ccss_node_t const *self) { return cowbell_classnames[((CopperNode*)self)->cowbell_class]; } /* Attributes of nodes representing windows */ typedef struct { const char *name; MetaFrameFlags flag; } MetaFrameFlagName; /* * Rationale for attribute names: * - The verb should be lexically identical at all times. * (So we shouldn't have "can-stick", "is-stuck".) * Otherwise people who aren't native English speakers * might get confused. * - The class name should nevertheless read as an * English phrase. */ MetaFrameFlagName meta_flag_names[] = { { "can-be-closed", META_FRAME_ALLOWS_DELETE }, { "can-have-menu", META_FRAME_ALLOWS_MENU }, { "can-be-minimized", META_FRAME_ALLOWS_MINIMIZE }, { "can-be-maximized", META_FRAME_ALLOWS_MAXIMIZE }, { "can-be-vertically-resized", META_FRAME_ALLOWS_VERTICAL_RESIZE }, { "can-be-horizontally-resized", META_FRAME_ALLOWS_HORIZONTAL_RESIZE }, { "is-focused", META_FRAME_HAS_FOCUS }, { "is-shaded", META_FRAME_SHADED }, { "is-stuck", META_FRAME_STUCK }, { "is-maximized", META_FRAME_MAXIMIZED }, { "can-be-shaded", META_FRAME_ALLOWS_SHADE }, { "can-be-moved", META_FRAME_ALLOWS_MOVE }, { "is-fullscreen", META_FRAME_FULLSCREEN }, { "is-flashing", META_FRAME_IS_FLASHING }, { "is-above", META_FRAME_ABOVE }, { NULL, 0 } }; static char* cowbell_get_attribute (ccss_node_t const *self, char const *name) { CopperNode *copper = (CopperNode*) self; if (strcmp(name, "type")==0) { /* Pretty simple: just look up the type. */ switch (copper->type) { case META_FRAME_TYPE_NORMAL: return g_strdup ("normal"); case META_FRAME_TYPE_DIALOG: return g_strdup ("dialog"); case META_FRAME_TYPE_MODAL_DIALOG: return g_strdup ("modal-dialog"); case META_FRAME_TYPE_UTILITY: return g_strdup ("utility"); case META_FRAME_TYPE_MENU: return g_strdup ("menu"); case META_FRAME_TYPE_BORDER: return g_strdup ("border"); default: return g_strdup ("unknown"); } } else { /* So try the flag names. */ MetaFrameFlagName *cursor; for (cursor = meta_flag_names; cursor->name; cursor++) { if (strcmp (name, cursor->name)==0) { if (copper->flags & cursor->flag) { return g_strdup ("1"); } else { return g_strdup ("0"); } } } } /* try some odd rare ones */ if (strcmp(name, "cowbell1")==0) { return g_strdup ("1"); } else if (strcmp(name, "wm")==0) { return g_strdup ("metacity"); } /* giving up here */ return NULL; } static ccss_node_class_t cowbell_node_class = { .get_type = (ccss_node_get_type_f) cowbell_get_type, .get_instance = (ccss_node_get_instance_f) cowbell_get_instance, .get_style = (ccss_node_get_style_f) cowbell_get_style, .get_container = (ccss_node_get_container_f) cowbell_get_container, .get_class = (ccss_node_get_class_f) cowbell_get_class, .get_attribute = (ccss_node_get_attribute_f) cowbell_get_attribute, }; /****************************************************************/ static void cowbell_initialise_classes (void) { int i; for (i=0; idata, NULL); filename = (char*) args->data; if (strcmp (filename, "wm:icon")==0) { return g_strdup ("file:///usr/share/icons/gnome/24x24/actions/gtk-edit.png"); } else { return g_strdup_printf ("file://%s/%s", (*theme)->directory, filename); } } static ccss_function_t const cowbell_functions[] = { { "url", cowbell_url, &the_theme }, { NULL } }; void meta_theme_set_current (const char *name, gboolean force_reload) { /* stub */ char *css_filename, *css_path; if (!the_theme) { cowbell_initialise_classes (); the_theme = g_new0 (MetaTheme, 1); } else { ccss_stylesheet_destroy (the_theme->stylesheet); ccss_grammar_destroy (the_theme->grammar); g_free (the_theme->directory); } the_theme->grammar = ccss_cairo_grammar_create (); ccss_grammar_add_functions (the_theme->grammar, cowbell_functions); the_theme->directory = g_build_filename (g_get_home_dir (), ".themes", name, NULL); g_warning ("Target is %s", the_theme->directory); css_filename = g_strdup_printf ("%s.css", name); css_path = g_build_filename (the_theme->directory, css_filename, NULL); g_free (css_filename); the_theme->stylesheet = ccss_grammar_create_stylesheet_from_file (the_theme->grammar, css_path, NULL); g_free (css_path); } double meta_theme_get_title_scale (MetaTheme *theme, MetaFrameType type, MetaFrameFlags flags) { /* * In v2 themes, the theme authors had the option of * asking for various sizes to scale the text by: * * xx-small,x-small,small,medium,large,x-large,xx-large * * We don't currently support any of these, and we * pretend that it's always given as "medium". */ return 1.0; } static void cowbell_style_title_text (ccss_stylesheet_t *stylesheet, PangoLayout *layout, cairo_t *cr) { PangoAttrList *attrs = NULL; ccss_style_t *style = ccss_stylesheet_query (stylesheet, (ccss_node_t*) &cowbell_nodes[CC_TITLE]); char *align; ccss_color_t const *colour; attrs = pango_attr_list_new (); /* We have to handle CSS text properties ourselves here because * libccss doesn't know how to render them yet. We don't try * to do all the funky effects like shadows and so on, * unfortunately. */ if (ccss_style_get_property (style, "color", (const ccss_property_base_t**) &colour)) { pango_attr_list_insert (attrs, pango_attr_foreground_new ((int)65535.0*colour->red, (int)65535.0*colour->green, (int)65535.0*colour->blue)); } /* Alignment */ if (ccss_style_get_string (style, "text-align", &align)) { if (strcmp (align, "left")==0 || strcmp (align, "justify")==0) { pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT); } else if (strcmp (align, "center")==0) { pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); } else if (strcmp (align, "right")==0) { pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT); } else g_warning ("Unknown alignment: %s", align); } else { /* default to centring the text */ pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); } pango_layout_set_attributes (layout, attrs); ccss_style_destroy (style); pango_attr_list_unref (attrs); } void meta_theme_draw_frame_with_style (MetaTheme *theme, GtkStyle *style_gtk, GtkWidget *widget, GdkDrawable *drawable, const GdkRectangle *clip, int x_offset, int y_offset, MetaFrameType type, MetaFrameFlags flags, int client_width, int client_height, PangoLayout *title_layout, int text_height, const MetaButtonLayout *button_layout, MetaButtonState button_states[META_BUTTON_TYPE_LAST], GdkPixbuf *mini_icon, GdkPixbuf *icon) { cairo_t *cr = gdk_cairo_create (drawable); ccss_stylesheet_t *stylesheet = theme->stylesheet; MetaFrameGeometry fgeom; int i; meta_theme_calc_geometry (theme, type, text_height, flags, client_width, client_height, button_layout, &fgeom); for (i=0; i 0 && height > 0) { ccss_cairo_style_draw_rectangle (style, cr, x, y, width, height); } ccss_style_destroy (style); } /* may be worth moving this inline? */ cowbell_style_title_text (stylesheet, title_layout, cr); cairo_translate (cr, fgeom.areas[CC_TITLE].x + fgeom.areas[CC_TITLE].left_edge, fgeom.areas[CC_TITLE].y + fgeom.areas[CC_TITLE].top_edge); pango_layout_set_width (title_layout, fgeom.areas[CC_TITLE].width); pango_layout_set_height (title_layout, fgeom.areas[CC_TITLE].height); switch (pango_layout_get_alignment (title_layout)) { case PANGO_ALIGN_RIGHT: cairo_translate (cr, fgeom.areas[CC_TITLE].width, 0); break; case PANGO_ALIGN_CENTER: cairo_translate (cr, fgeom.areas[CC_TITLE].width/2, 0); break; default: /* nothing */ break; } pango_cairo_show_layout (cr, title_layout); cairo_destroy (cr); } /** * Returns the style to use with the given type and flags. * When you're done with it, call ccss_style_destroy (style). * * Be warned: this function is not threadsafe. It modifies * a static copy of the node hierarchy in order to set the * attributes of the frame. */ static ccss_style_t * cowbell_get_current_style (MetaTheme *theme, MetaFrameType type, MetaFrameFlags flags, CopperClasses style_id) { ccss_stylesheet_t *stylesheet = theme->stylesheet; ccss_style_t *result; /* * Possibly we should set attributes on everything, * not just the frame. */ cowbell_nodes[CC_FRAME].type = type; cowbell_nodes[CC_FRAME].flags = flags; result = ccss_stylesheet_query (stylesheet, (ccss_node_t*) &cowbell_nodes[style_id]); return result; } /** * Finds the size of the edges (borders, margins, and optionally padding) of * an element with a given CSS style. Any of the out parameters can be NULL * in order not to receive the value; if they are not, the answer will be * added to the current value, rather than replacing it. * * \param style The style. * \param ignore_padding Whether to ignore padding in the calculation. * \param top Receives the size of the top edge. * \param bottom Receives the size of the bottom edge. * \param left Receives the size of the left edge. * \param right Receives the size of the right edge. */ static void cowbell_get_edge_sizes (MetaTheme *theme, MetaFrameType type, MetaFrameFlags flags, CopperClasses style_id, int *top, int *bottom, int *left, int *right, int *top_margin, int *bottom_margin, int *left_margin, int *right_margin) { double d; gboolean fallback_known; gint i, j, start; /* We are called far too often to mess around with string splicing */ gchar *properties[3][5] = { {"margin-top-width", "margin-bottom-width", "margin-left-width", "margin-right-width", "margin-width"}, {"border-top-width", "border-bottom-width", "border-left-width", "border-right-width", "border-width"}, {"padding-top-width", "padding-bottom-width", "padding-left-width", "padding-right-width", "padding-width"}, }; double results[4] = {0.0, 0.0, 0.0, 0.0}; double fallback = 0.0; ccss_style_t *style = cowbell_get_current_style (theme, type, flags, style_id); if (style_id==CC_FRAME) { /* the frame has no margin */ start = 1; if (top_margin) *top_margin = 0; if (bottom_margin) *bottom_margin = 0; if (left_margin) *left_margin = 0; if (right_margin) *right_margin = 0; } else start = 0; for (i=start; i<3; i++) { fallback_known = FALSE; for (j=0; j<4; j++) { if (ccss_style_get_double (style, properties[i][j], &d)) { /* it exists under foo-direction-width */ results[j] += d; } else { /* it doesn't exist under foo-direction-width; * try foo-width */ if (!fallback_known) { /* we haven't cached it yet */ fallback_known = TRUE; if (!ccss_style_get_double (style, properties[i][4], &fallback)) { fallback = 0.0; } } results[j] += fallback; } } if (i==0) { /* we have just checked the first entry, * which is the margin */ if (top_margin) *top_margin = (int) results[0]; if (bottom_margin) *bottom_margin = (int) results[1]; if (left_margin) *left_margin = (int) results[2]; if (right_margin) *right_margin = (int) results[3]; } } if (top) *top += (int) results[0]; if (bottom) *bottom += (int) results[1]; if (left) *left += (int) results[2]; if (right) *right += (int) results[3]; ccss_style_destroy (style); } void meta_theme_get_frame_borders (MetaTheme *theme, MetaFrameType type, int text_height, MetaFrameFlags flags, int *top_height, int *bottom_height, int *left_width, int *right_width) { *top_height = 0; *bottom_height = 0; *left_width = 0; *right_width = 0; cowbell_get_edge_sizes (theme, type, flags, CC_FRAME, top_height, bottom_height, left_width, right_width, NULL, NULL, NULL, NULL); cowbell_get_edge_sizes (theme, type, flags, CC_CONTENT, top_height, bottom_height, left_width, right_width, NULL, NULL, NULL, NULL); cowbell_get_edge_sizes (theme, type, flags, CC_TITLEBAR, top_height, top_height, NULL, NULL, NULL, NULL, NULL, NULL); cowbell_get_edge_sizes (theme, type, flags, CC_TITLE, top_height, top_height, NULL, NULL, NULL, NULL, NULL, NULL); if (top_height) { *top_height += text_height; } } #define MAX_MIDDLE_BACKGROUNDS (MAX_BUTTONS_PER_CORNER - 2) /** * This annoying function exists because we have * several enums which describe buttons, and we * need to map between them. */ static CopperClasses copper_class_for_button (int button) { switch (button) { case META_BUTTON_FUNCTION_MENU: return CC_MENU; case META_BUTTON_FUNCTION_MINIMIZE: return CC_MINIMIZE; case META_BUTTON_FUNCTION_MAXIMIZE: return CC_MAXIMIZE; case META_BUTTON_FUNCTION_CLOSE: return CC_CLOSE; case META_BUTTON_FUNCTION_SHADE: return CC_SHADE; case META_BUTTON_FUNCTION_ABOVE: return CC_ABOVE; case META_BUTTON_FUNCTION_STICK: return CC_STICK; case META_BUTTON_FUNCTION_UNSHADE: return CC_UNSHADE; case META_BUTTON_FUNCTION_UNABOVE: return CC_UNABOVE; case META_BUTTON_FUNCTION_UNSTICK: return CC_UNSTICK; default: /* there is no way we should ever get here */ meta_bug ("Unknown button type. %d", button); return CC_MENU; } } /** * Fills the public record of a button's dimensions * using data from private members. If we used an * enum for the public members, this wouldn't be necessary. * * \bug Does not distinguish clickable and visible areas. * Should we still do this with Cowbell? */ static void fill_button_rect (MetaButtonSpace *button, CopperClasses cclass, MetaFrameGeometry *fgeom) { button->visible.x = fgeom->areas[cclass].x; button->visible.y = fgeom->areas[cclass].y; button->visible.width = fgeom->areas[cclass].width; button->visible.height = fgeom->areas[cclass].height; button->clickable.x = button->visible.x; button->clickable.y = button->visible.y; button->clickable.width = button->visible.width; button->clickable.height = button->visible.height; } static int cowbell_get_button_width (MetaTheme *theme, MetaFrameType type, MetaFrameFlags flags, CopperClasses button, int button_height) { double min_width=0.0, max_width=0.0; double width=0.0, height=0.0; ccss_style_t *style = cowbell_get_current_style (theme, type, flags, button); double aspect_ratio; double result; ccss_style_get_double (style, "min-width", &min_width); ccss_style_get_double (style, "max-width", &max_width); if (min_width > max_width) { min_width = max_width; } if (min_width!=0.0 && min_width==max_width) { /* gosh, that was easy. */ return (int) min_width; } ccss_style_get_double (style, "width", &width); ccss_style_get_double (style, "height", &height); if (width==0.0 || height==0.0) { /* They didn't specify a width or height for the button. * * Ultimately I want to stop at this point and check for the * background image, and default to its dimensions. But * for now, let's assume it's square. */ aspect_ratio = 1.0; /* Warn them. This could be more informative... */ g_warning ("Button found with missing width or height"); } else { aspect_ratio = width/height; } result = width / aspect_ratio; if (min_width!=0 && resultmax_width) result = max_width; return (int) result; } void meta_theme_calc_geometry (MetaTheme *theme, MetaFrameType type, int text_height, MetaFrameFlags flags, int client_width, int client_height, const MetaButtonLayout *button_layout, MetaFrameGeometry *fgeom) { int i; int button_height, button_y; int x; /* TODO - We may want to cache the style objects in "areas" */ memset (&(fgeom->areas), 0, sizeof(fgeom->areas)); /* First of all, we need to calculate the edges for each element. */ for (i=0; iareas[i].top_edge), &(fgeom->areas[i].bottom_edge), &(fgeom->areas[i].left_edge), &(fgeom->areas[i].right_edge), &(fgeom->areas[i].top_margin), &(fgeom->areas[i].bottom_margin), &(fgeom->areas[i].left_margin), &(fgeom->areas[i].right_margin)); } /* Now let's look at the coordinates for each element. */ /* For now, we are assuming that the clickable and visible * areas of a button are the same. We may need to distinguish them * eventually. */ /* Let's begin with the frame. */ fgeom->areas[CC_FRAME].x = 0; /* It necessarily starts at the top left... */ fgeom->areas[CC_FRAME].y = 0; /* The width is simply the width of the content area, plus the edges * of the content area and frame. */ fgeom->areas[CC_FRAME].width = fgeom->areas[CC_FRAME].left_edge + fgeom->areas[CC_CONTENT].left_edge + client_width + fgeom->areas[CC_CONTENT].right_edge + fgeom->areas[CC_FRAME].right_edge; /* The height is the height of those elements, plus the height of the * title and its edges, and the edges of the titlebar area. */ fgeom->areas[CC_FRAME].height = fgeom->areas[CC_FRAME].top_edge + fgeom->areas[CC_TITLEBAR].top_edge + fgeom->areas[CC_TITLE].top_edge + text_height + fgeom->areas[CC_TITLE].bottom_edge + fgeom->areas[CC_TITLEBAR].bottom_edge + fgeom->areas[CC_CONTENT].top_edge + client_height + fgeom->areas[CC_CONTENT].bottom_edge + fgeom->areas[CC_FRAME].bottom_edge; /* Next, the content area. */ fgeom->areas[CC_CONTENT].x = fgeom->areas[CC_FRAME].left_edge; fgeom->areas[CC_CONTENT].y = fgeom->areas[CC_FRAME].top_edge + fgeom->areas[CC_TITLEBAR].top_edge + fgeom->areas[CC_TITLE].top_edge + text_height + fgeom->areas[CC_TITLE].bottom_edge + fgeom->areas[CC_TITLEBAR].bottom_edge; fgeom->areas[CC_CONTENT].width = fgeom->areas[CC_CONTENT].left_edge + client_width + fgeom->areas[CC_CONTENT].right_edge; fgeom->areas[CC_CONTENT].height = fgeom->areas[CC_CONTENT].top_edge + client_height + fgeom->areas[CC_CONTENT].bottom_edge; /* Next, the titlebar. */ fgeom->areas[CC_TITLEBAR].x = fgeom->areas[CC_FRAME].left_edge; fgeom->areas[CC_TITLEBAR].y = fgeom->areas[CC_FRAME].top_edge; fgeom->areas[CC_TITLEBAR].width = fgeom->areas[CC_CONTENT].width; fgeom->areas[CC_TITLEBAR].height = fgeom->areas[CC_TITLEBAR].top_edge + fgeom->areas[CC_TITLE].top_edge + text_height + fgeom->areas[CC_TITLE].bottom_edge + fgeom->areas[CC_TITLEBAR].bottom_edge; /* Now the titlebar. */ fgeom->areas[CC_TITLE].x = fgeom->areas[CC_FRAME].left_edge + fgeom->areas[CC_TITLEBAR].left_edge; fgeom->areas[CC_TITLE].y = fgeom->areas[CC_FRAME].top_edge + fgeom->areas[CC_TITLEBAR].top_edge; fgeom->areas[CC_TITLE].width = /* This is the most it can be; we'll possibly subtract from it */ fgeom->areas[CC_CONTENT].width; fgeom->areas[CC_TITLE].height = text_height; /* obviously */ /* And finally, the buttons. */ /* Just work out the width, height, and y coord first... */ button_height = fgeom->areas[CC_TITLEBAR].height - (fgeom->areas[CC_TITLEBAR].top_edge + fgeom->areas[CC_TITLEBAR].bottom_edge); button_y = fgeom->areas[CC_TITLEBAR].y + fgeom->areas[CC_TITLEBAR].top_edge; for (i=CC_BUTTON_FIRST; i<=CC_BUTTON_LAST; i++) { fgeom->areas[i].height = button_height; fgeom->areas[i].width = cowbell_get_button_width (theme, type, flags, i, button_height); fgeom->areas[i].width += fgeom->areas[i].left_margin + fgeom->areas[i].right_margin; /* * but we do not adjust the height; setting top and bottom * margins on a button just compresses the button */ fgeom->areas[i].x = -1; /* dummy value, to check * for when we haven't allocated it */ fgeom->areas[i].y = button_y; } /* FIXME: * Here we need to nuke any buttons (and the title) * that make the contents of the titlebar wider than * it can be. */ /* And now allocate the buttons as necessary. */ /* FIXME: We are not honouring spacers. * They should possibly be removed anyway. */ /* The left-hand side */ x = fgeom->areas[CC_TITLEBAR].x + fgeom->areas[CC_TITLEBAR].left_edge; for (i=0; ileft_buttons[i]; CopperClasses cc; if (button == META_BUTTON_FUNCTION_LAST) break; cc = copper_class_for_button(button); /* so allocate it */ fgeom->areas[cc].x = x; x += fgeom->areas[cc].width; fgeom->areas[CC_TITLE].x += fgeom->areas[cc].width; fgeom->areas[CC_TITLE].width -= fgeom->areas[cc].width; } /* The right-hand side */ x = fgeom->areas[CC_TITLEBAR].x + (fgeom->areas[CC_TITLEBAR].width - (fgeom->areas[CC_TITLEBAR].left_edge + fgeom->areas[CC_TITLEBAR].right_edge)); /* we want to go over them backwards */ i=0; while (i+1right_buttons[i]!=META_BUTTON_FUNCTION_LAST) i++; for (i--; i>=0; i--) { int button = button_layout->right_buttons[i]; CopperClasses cc; cc = copper_class_for_button(button); /* so allocate it */ x -= fgeom->areas[copper_class_for_button(button)].width; fgeom->areas[copper_class_for_button(button)].x = x; fgeom->areas[CC_TITLE].width -= fgeom->areas[cc].width; } /* Now find the ones we didn't use, and zero them out */ for (i=CC_BUTTON_FIRST; i<=CC_BUTTON_LAST; i++) { if (fgeom->areas[i].x==-1) { fgeom->areas[i].x = fgeom->areas[i].y = fgeom->areas[i].width = fgeom->areas[i].height = 0; } } /* We are not yet doing CC_FILLER. */ fgeom->areas[CC_FILLER].x = fgeom->areas[CC_FILLER].y = fgeom->areas[CC_FILLER].width = fgeom->areas[CC_FILLER].height = 0; /* Now we have our own private information. * Let's copy the necessary parts into the public members * of our struct. */ fgeom->top_height = fgeom->areas[CC_FRAME].top_edge + fgeom->areas[CC_TITLEBAR].height + fgeom->areas[CC_CONTENT].top_edge; fgeom->right_width = fgeom->areas[CC_CONTENT].right_edge + fgeom->areas[CC_FRAME].right_edge; fgeom->bottom_height = fgeom->areas[CC_CONTENT].bottom_edge + fgeom->areas[CC_FRAME].bottom_edge; fgeom->left_width = fgeom->areas[CC_CONTENT].left_edge + fgeom->areas[CC_FRAME].left_edge; fgeom->width = fgeom->areas[CC_FRAME].width; fgeom->height = fgeom->areas[CC_FRAME].height; fgeom->title_rect.x = fgeom->areas[CC_TITLE].x; fgeom->title_rect.y = fgeom->areas[CC_TITLE].y; fgeom->title_rect.width = fgeom->areas[CC_TITLE].width; fgeom->title_rect.height = fgeom->areas[CC_TITLE].height; /* Are these correct? */ fgeom->top_titlebar_edge = fgeom->areas[CC_TITLEBAR].left_edge; fgeom->right_titlebar_edge = fgeom->areas[CC_TITLEBAR].right_edge; fgeom->bottom_titlebar_edge = fgeom->areas[CC_TITLEBAR].bottom_edge; fgeom->left_titlebar_edge = fgeom->areas[CC_TITLEBAR].left_edge; /* I'll take "places we should have used an enum" for 25 */ fill_button_rect (&(fgeom->close_rect), CC_CLOSE, fgeom); fill_button_rect (&(fgeom->max_rect), CC_MAXIMIZE, fgeom); fill_button_rect (&(fgeom->min_rect), CC_MINIMIZE, fgeom); fill_button_rect (&(fgeom->menu_rect), CC_MENU, fgeom); fill_button_rect (&(fgeom->shade_rect), CC_SHADE, fgeom); fill_button_rect (&(fgeom->above_rect), CC_ABOVE, fgeom); fill_button_rect (&(fgeom->stick_rect), CC_STICK, fgeom); fill_button_rect (&(fgeom->unshade_rect), CC_UNSHADE, fgeom); fill_button_rect (&(fgeom->unabove_rect), CC_UNABOVE, fgeom); fill_button_rect (&(fgeom->unstick_rect), CC_UNSTICK, fgeom); #if 0 for (i=0; iareas[i].x, fgeom->areas[i].y, fgeom->areas[i].width, fgeom->areas[i].height); } #endif /****************************************************************/ /* Old code which needs replacing: */ /* Rounded corners; need to pick these up from the CSS. FIXME. */ fgeom->top_left_corner_rounded_radius = 5; fgeom->top_right_corner_rounded_radius = 5; fgeom->bottom_left_corner_rounded_radius = 5; fgeom->bottom_right_corner_rounded_radius = 5; } /* * This would be a stub, but actually the original code is * just what we need anyway. */ PangoFontDescription* meta_gtk_widget_get_font_desc (GtkWidget *widget, double scale, const PangoFontDescription *override) { PangoFontDescription *font_desc; g_return_val_if_fail (GTK_WIDGET_REALIZED (widget), NULL); font_desc = pango_font_description_copy (widget->style->font_desc); if (override) pango_font_description_merge (font_desc, override, TRUE); pango_font_description_set_size (font_desc, MAX (pango_font_description_get_size (font_desc) * scale, 1)); return font_desc; } /** * Returns the height of the letters in a particular font. * * \param font_desc the font * \param context the context of the font * \return the height of the letters */ int meta_pango_font_desc_get_text_height (const PangoFontDescription *font_desc, PangoContext *context) { PangoFontMetrics *metrics; PangoLanguage *lang; int retval; lang = pango_context_get_language (context); metrics = pango_context_get_metrics (context, font_desc, lang); retval = PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) + pango_font_metrics_get_descent (metrics)); pango_font_metrics_unref (metrics); return retval; } #if 0 /****************************************************************/ /* Metacity Theme Rendering */ /* * Copyright (C) 2001 Havoc Pennington * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ /** * \file theme.c Making Metacity look pretty * * The window decorations drawn by Metacity are described by files on disk * known internally as "themes" (externally as "window border themes" on * http://art.gnome.org/themes/metacity/ or "Metacity themes"). This file * contains most of the code necessary to support themes; it does not * contain the XML parser, which is in theme-parser.c. * * \bug This is a big file with lots of different subsystems, which might * be better split out into separate files. */ /** * \defgroup tokenizer The theme expression tokenizer * * Themes can use a simple expression language to represent the values of * things. This is the tokeniser used for that language. * * \bug We could remove almost all this code by using GScanner instead, * but we would also have to find every expression in every existing theme * we could and make sure the parse trees were the same. */ /** * \defgroup parser The theme expression parser * * Themes can use a simple expression language to represent the values of * things. This is the parser used for that language. */ #include #include "theme.h" #include "theme-parser.h" #include "util.h" #include "gradient.h" #include #include #include #include #define GDK_COLOR_RGBA(color) \ ((guint32) (0xff | \ (((color).red / 256) << 24) | \ (((color).green / 256) << 16) | \ (((color).blue / 256) << 8))) #define GDK_COLOR_RGB(color) \ ((guint32) ((((color).red / 256) << 16) | \ (((color).green / 256) << 8) | \ (((color).blue / 256)))) #define ALPHA_TO_UCHAR(d) ((unsigned char) ((d) * 255)) #define DEBUG_FILL_STRUCT(s) memset ((s), 0xef, sizeof (*(s))) #define CLAMP_UCHAR(v) ((guchar) (CLAMP (((int)v), (int)0, (int)255))) #define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11) static void gtk_style_shade (GdkColor *a, GdkColor *b, gdouble k); static void rgb_to_hls (gdouble *r, gdouble *g, gdouble *b); static void hls_to_rgb (gdouble *h, gdouble *l, gdouble *s); /** * The current theme. (Themes are singleton.) */ static MetaTheme *meta_current_theme = NULL; static GdkPixbuf * colorize_pixbuf (GdkPixbuf *orig, GdkColor *new_color) { GdkPixbuf *pixbuf; double intensity; int x, y; const guchar *src; guchar *dest; int orig_rowstride; int dest_rowstride; int width, height; gboolean has_alpha; const guchar *src_pixels; guchar *dest_pixels; pixbuf = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (orig), gdk_pixbuf_get_has_alpha (orig), gdk_pixbuf_get_bits_per_sample (orig), gdk_pixbuf_get_width (orig), gdk_pixbuf_get_height (orig)); if (pixbuf == NULL) return NULL; orig_rowstride = gdk_pixbuf_get_rowstride (orig); dest_rowstride = gdk_pixbuf_get_rowstride (pixbuf); width = gdk_pixbuf_get_width (pixbuf); height = gdk_pixbuf_get_height (pixbuf); has_alpha = gdk_pixbuf_get_has_alpha (orig); src_pixels = gdk_pixbuf_get_pixels (orig); dest_pixels = gdk_pixbuf_get_pixels (pixbuf); for (y = 0; y < height; y++) { src = src_pixels + y * orig_rowstride; dest = dest_pixels + y * dest_rowstride; for (x = 0; x < width; x++) { double dr, dg, db; intensity = INTENSITY (src[0], src[1], src[2]) / 255.0; if (intensity <= 0.5) { /* Go from black at intensity = 0.0 to new_color at intensity = 0.5 */ dr = (new_color->red * intensity * 2.0) / 65535.0; dg = (new_color->green * intensity * 2.0) / 65535.0; db = (new_color->blue * intensity * 2.0) / 65535.0; } else { /* Go from new_color at intensity = 0.5 to white at intensity = 1.0 */ dr = (new_color->red + (65535 - new_color->red) * (intensity - 0.5) * 2.0) / 65535.0; dg = (new_color->green + (65535 - new_color->green) * (intensity - 0.5) * 2.0) / 65535.0; db = (new_color->blue + (65535 - new_color->blue) * (intensity - 0.5) * 2.0) / 65535.0; } dest[0] = CLAMP_UCHAR (255 * dr); dest[1] = CLAMP_UCHAR (255 * dg); dest[2] = CLAMP_UCHAR (255 * db); if (has_alpha) { dest[3] = src[3]; src += 4; dest += 4; } else { src += 3; dest += 3; } } } return pixbuf; } static void color_composite (const GdkColor *bg, const GdkColor *fg, double alpha_d, GdkColor *color) { guint16 alpha; *color = *bg; alpha = alpha_d * 0xffff; color->red = color->red + (((fg->red - color->red) * alpha + 0x8000) >> 16); color->green = color->green + (((fg->green - color->green) * alpha + 0x8000) >> 16); color->blue = color->blue + (((fg->blue - color->blue) * alpha + 0x8000) >> 16); } /** * Sets all the fields of a border to dummy values. * * \param border The border whose fields should be reset. */ static void init_border (GtkBorder *border) { border->top = -1; border->bottom = -1; border->left = -1; border->right = -1; } /** * Creates a new, empty MetaFrameLayout. The fields will be set to dummy * values. * * \return The newly created MetaFrameLayout. */ MetaFrameLayout* meta_frame_layout_new (void) { MetaFrameLayout *layout; layout = g_new0 (MetaFrameLayout, 1); layout->refcount = 1; /* Fill with -1 values to detect invalid themes */ layout->left_width = -1; layout->right_width = -1; layout->bottom_height = -1; init_border (&layout->title_border); layout->title_vertical_pad = -1; layout->right_titlebar_edge = -1; layout->left_titlebar_edge = -1; layout->button_sizing = META_BUTTON_SIZING_LAST; layout->button_aspect = 1.0; layout->button_width = -1; layout->button_height = -1; layout->has_title = TRUE; layout->title_scale = 1.0; init_border (&layout->button_border); return layout; } /** * */ static gboolean validate_border (const GtkBorder *border, const char **bad) { *bad = NULL; if (border->top < 0) *bad = _("top"); else if (border->bottom < 0) *bad = _("bottom"); else if (border->left < 0) *bad = _("left"); else if (border->right < 0) *bad = _("right"); return *bad == NULL; } /** * Ensures that the theme supplied a particular dimension. When a * MetaFrameLayout is created, all its integer fields are set to -1 * by meta_frame_layout_new(). After an instance of this type * should have been initialised, this function checks that * a given field is not still at -1. It is never called directly, but * rather via the CHECK_GEOMETRY_VALUE and CHECK_GEOMETRY_BORDER * macros. * * \param val The value to check * \param name The name to use in the error message * \param[out] error Set to an error if val was not initialised */ static gboolean validate_geometry_value (int val, const char *name, GError **error) { if (val < 0) { g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FRAME_GEOMETRY, _("frame geometry does not specify \"%s\" dimension"), name); return FALSE; } else return TRUE; } static gboolean validate_geometry_border (const GtkBorder *border, const char *name, GError **error) { const char *bad; if (!validate_border (border, &bad)) { g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FRAME_GEOMETRY, _("frame geometry does not specify dimension \"%s\" for border \"%s\""), bad, name); return FALSE; } else return TRUE; } gboolean meta_frame_layout_validate (const MetaFrameLayout *layout, GError **error) { g_return_val_if_fail (layout != NULL, FALSE); #define CHECK_GEOMETRY_VALUE(vname) if (!validate_geometry_value (layout->vname, #vname, error)) return FALSE #define CHECK_GEOMETRY_BORDER(bname) if (!validate_geometry_border (&layout->bname, #bname, error)) return FALSE CHECK_GEOMETRY_VALUE (left_width); CHECK_GEOMETRY_VALUE (right_width); CHECK_GEOMETRY_VALUE (bottom_height); CHECK_GEOMETRY_BORDER (title_border); CHECK_GEOMETRY_VALUE (title_vertical_pad); CHECK_GEOMETRY_VALUE (right_titlebar_edge); CHECK_GEOMETRY_VALUE (left_titlebar_edge); switch (layout->button_sizing) { case META_BUTTON_SIZING_ASPECT: if (layout->button_aspect < (0.1) || layout->button_aspect > (15.0)) { g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FRAME_GEOMETRY, _("Button aspect ratio %g is not reasonable"), layout->button_aspect); return FALSE; } break; case META_BUTTON_SIZING_FIXED: CHECK_GEOMETRY_VALUE (button_width); CHECK_GEOMETRY_VALUE (button_height); break; case META_BUTTON_SIZING_LAST: g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FRAME_GEOMETRY, _("Frame geometry does not specify size of buttons")); return FALSE; } CHECK_GEOMETRY_BORDER (button_border); return TRUE; } MetaFrameLayout* meta_frame_layout_copy (const MetaFrameLayout *src) { MetaFrameLayout *layout; layout = g_new0 (MetaFrameLayout, 1); *layout = *src; layout->refcount = 1; return layout; } void meta_frame_layout_ref (MetaFrameLayout *layout) { g_return_if_fail (layout != NULL); layout->refcount += 1; } void meta_frame_layout_unref (MetaFrameLayout *layout) { g_return_if_fail (layout != NULL); g_return_if_fail (layout->refcount > 0); layout->refcount -= 1; if (layout->refcount == 0) { DEBUG_FILL_STRUCT (layout); g_free (layout); } } void meta_frame_layout_get_borders (const MetaFrameLayout *layout, int text_height, MetaFrameFlags flags, int *top_height, int *bottom_height, int *left_width, int *right_width) { int buttons_height, title_height; g_return_if_fail (top_height != NULL); g_return_if_fail (bottom_height != NULL); g_return_if_fail (left_width != NULL); g_return_if_fail (right_width != NULL); if (!layout->has_title) text_height = 0; buttons_height = layout->button_height + layout->button_border.top + layout->button_border.bottom; title_height = text_height + layout->title_vertical_pad + layout->title_border.top + layout->title_border.bottom; if (top_height) { *top_height = MAX (buttons_height, title_height); } if (left_width) *left_width = layout->left_width; if (right_width) *right_width = layout->right_width; if (bottom_height) { if (flags & META_FRAME_SHADED) *bottom_height = 0; else *bottom_height = layout->bottom_height; } if (flags & META_FRAME_FULLSCREEN) { if (top_height) *top_height = 0; if (bottom_height) *bottom_height = 0; if (left_width) *left_width = 0; if (right_width) *right_width = 0; } } static MetaButtonSpace* rect_for_function (MetaFrameGeometry *fgeom, MetaFrameFlags flags, MetaButtonFunction function, MetaTheme *theme) { /* Firstly, check version-specific things. */ if (META_THEME_ALLOWS(theme, META_THEME_SHADE_STICK_ABOVE_BUTTONS)) { switch (function) { case META_BUTTON_FUNCTION_SHADE: if ((flags & META_FRAME_ALLOWS_SHADE) && !(flags & META_FRAME_SHADED)) return &fgeom->shade_rect; else return NULL; case META_BUTTON_FUNCTION_ABOVE: if (!(flags & META_FRAME_ABOVE)) return &fgeom->above_rect; else return NULL; case META_BUTTON_FUNCTION_STICK: if (!(flags & META_FRAME_STUCK)) return &fgeom->stick_rect; else return NULL; case META_BUTTON_FUNCTION_UNSHADE: if ((flags & META_FRAME_ALLOWS_SHADE) && (flags & META_FRAME_SHADED)) return &fgeom->unshade_rect; else return NULL; case META_BUTTON_FUNCTION_UNABOVE: if (flags & META_FRAME_ABOVE) return &fgeom->unabove_rect; else return NULL; case META_BUTTON_FUNCTION_UNSTICK: if (flags & META_FRAME_STUCK) return &fgeom->unstick_rect; default: /* just go on to the next switch block */; } } /* now consider the buttons which exist in all versions */ switch (function) { case META_BUTTON_FUNCTION_MENU: if (flags & META_FRAME_ALLOWS_MENU) return &fgeom->menu_rect; else return NULL; case META_BUTTON_FUNCTION_MINIMIZE: if (flags & META_FRAME_ALLOWS_MINIMIZE) return &fgeom->min_rect; else return NULL; case META_BUTTON_FUNCTION_MAXIMIZE: if (flags & META_FRAME_ALLOWS_MAXIMIZE) return &fgeom->max_rect; else return NULL; case META_BUTTON_FUNCTION_CLOSE: if (flags & META_FRAME_ALLOWS_DELETE) return &fgeom->close_rect; else return NULL; case META_BUTTON_FUNCTION_STICK: case META_BUTTON_FUNCTION_SHADE: case META_BUTTON_FUNCTION_ABOVE: case META_BUTTON_FUNCTION_UNSTICK: case META_BUTTON_FUNCTION_UNSHADE: case META_BUTTON_FUNCTION_UNABOVE: /* we are being asked for a >v1 button which hasn't been handled yet, * so obviously we're not in a theme which supports that version. * therefore, we don't show the button. return NULL and all will * be well. */ return NULL; case META_BUTTON_FUNCTION_LAST: return NULL; } return NULL; } static gboolean strip_button (MetaButtonSpace *func_rects[MAX_BUTTONS_PER_CORNER], GdkRectangle *bg_rects[MAX_BUTTONS_PER_CORNER], int *n_rects, MetaButtonSpace *to_strip) { int i; i = 0; while (i < *n_rects) { if (func_rects[i] == to_strip) { *n_rects -= 1; /* shift the other rects back in the array */ while (i < *n_rects) { func_rects[i] = func_rects[i+1]; bg_rects[i] = bg_rects[i+1]; ++i; } func_rects[i] = NULL; bg_rects[i] = NULL; return TRUE; } ++i; } return FALSE; /* did not strip anything */ } void meta_frame_layout_calc_geometry (const MetaFrameLayout *layout, int text_height, MetaFrameFlags flags, int client_width, int client_height, const MetaButtonLayout *button_layout, MetaFrameGeometry *fgeom, MetaTheme *theme) { int i, n_left, n_right, n_left_spacers, n_right_spacers; int x; int button_y; int title_right_edge; int width, height; int button_width, button_height; int min_size_for_rounding; /* the left/right rects in order; the max # of rects * is the number of button functions */ MetaButtonSpace *left_func_rects[MAX_BUTTONS_PER_CORNER]; MetaButtonSpace *right_func_rects[MAX_BUTTONS_PER_CORNER]; GdkRectangle *left_bg_rects[MAX_BUTTONS_PER_CORNER]; gboolean left_buttons_has_spacer[MAX_BUTTONS_PER_CORNER]; GdkRectangle *right_bg_rects[MAX_BUTTONS_PER_CORNER]; gboolean right_buttons_has_spacer[MAX_BUTTONS_PER_CORNER]; meta_frame_layout_get_borders (layout, text_height, flags, &fgeom->top_height, &fgeom->bottom_height, &fgeom->left_width, &fgeom->right_width); width = client_width + fgeom->left_width + fgeom->right_width; height = ((flags & META_FRAME_SHADED) ? 0: client_height) + fgeom->top_height + fgeom->bottom_height; fgeom->width = width; fgeom->height = height; fgeom->top_titlebar_edge = layout->title_border.top; fgeom->bottom_titlebar_edge = layout->title_border.bottom; fgeom->left_titlebar_edge = layout->left_titlebar_edge; fgeom->right_titlebar_edge = layout->right_titlebar_edge; /* gcc warnings */ button_width = -1; button_height = -1; switch (layout->button_sizing) { case META_BUTTON_SIZING_ASPECT: button_height = fgeom->top_height - layout->button_border.top - layout->button_border.bottom; button_width = button_height / layout->button_aspect; break; case META_BUTTON_SIZING_FIXED: button_width = layout->button_width; button_height = layout->button_height; break; case META_BUTTON_SIZING_LAST: g_assert_not_reached (); break; } /* FIXME all this code sort of pretends that duplicate buttons * with the same function are allowed, but that breaks the * code in frames.c, so isn't really allowed right now. * Would need left_close_rect, right_close_rect, etc. */ /* Init all button rects to 0, lame hack */ memset (ADDRESS_OF_BUTTON_RECTS (fgeom), '\0', LENGTH_OF_BUTTON_RECTS); n_left = 0; n_right = 0; n_left_spacers = 0; n_right_spacers = 0; if (!layout->hide_buttons) { /* Try to fill in rects */ for (i = 0; i < MAX_BUTTONS_PER_CORNER && button_layout->left_buttons[i] != META_BUTTON_FUNCTION_LAST; i++) { left_func_rects[n_left] = rect_for_function (fgeom, flags, button_layout->left_buttons[i], theme); if (left_func_rects[n_left] != NULL) { left_buttons_has_spacer[n_left] = button_layout->left_buttons_has_spacer[i]; if (button_layout->left_buttons_has_spacer[i]) ++n_left_spacers; ++n_left; } } for (i = 0; i < MAX_BUTTONS_PER_CORNER && button_layout->right_buttons[i] != META_BUTTON_FUNCTION_LAST; i++) { right_func_rects[n_right] = rect_for_function (fgeom, flags, button_layout->right_buttons[i], theme); if (right_func_rects[n_right] != NULL) { right_buttons_has_spacer[n_right] = button_layout->right_buttons_has_spacer[i]; if (button_layout->right_buttons_has_spacer[i]) ++n_right_spacers; ++n_right; } } } for (i = 0; i < MAX_BUTTONS_PER_CORNER; i++) { left_bg_rects[i] = NULL; right_bg_rects[i] = NULL; } for (i = 0; i < n_left; i++) { if (i == 0) /* prefer left background if only one button */ left_bg_rects[i] = &fgeom->left_left_background; else if (i == (n_left - 1)) left_bg_rects[i] = &fgeom->left_right_background; else left_bg_rects[i] = &fgeom->left_middle_backgrounds[i - 1]; } for (i = 0; i < n_right; i++) { /* prefer right background if only one button */ if (i == (n_right - 1)) right_bg_rects[i] = &fgeom->right_right_background; else if (i == 0) right_bg_rects[i] = &fgeom->right_left_background; else right_bg_rects[i] = &fgeom->right_middle_backgrounds[i - 1]; } /* Be sure buttons fit */ while (n_left > 0 || n_right > 0) { int space_used_by_buttons; int space_available; space_available = fgeom->width - layout->left_titlebar_edge - layout->right_titlebar_edge; space_used_by_buttons = 0; space_used_by_buttons += button_width * n_left; space_used_by_buttons += (button_width * 0.75) * n_left_spacers; space_used_by_buttons += layout->button_border.left * n_left; space_used_by_buttons += layout->button_border.right * n_left; space_used_by_buttons += button_width * n_right; space_used_by_buttons += (button_width * 0.75) * n_right_spacers; space_used_by_buttons += layout->button_border.left * n_right; space_used_by_buttons += layout->button_border.right * n_right; if (space_used_by_buttons <= space_available) break; /* Everything fits, bail out */ /* First try to remove separators */ if (n_left_spacers > 0) { left_buttons_has_spacer[--n_left_spacers] = FALSE; continue; } else if (n_right_spacers > 0) { right_buttons_has_spacer[--n_right_spacers] = FALSE; continue; } /* Otherwise we need to shave out a button. Shave * above, stick, shade, min, max, close, then menu (menu is most useful); * prefer the default button locations. */ if (strip_button (left_func_rects, left_bg_rects, &n_left, &fgeom->above_rect)) continue; else if (strip_button (right_func_rects, right_bg_rects, &n_right, &fgeom->above_rect)) continue; else if (strip_button (left_func_rects, left_bg_rects, &n_left, &fgeom->stick_rect)) continue; else if (strip_button (right_func_rects, right_bg_rects, &n_right, &fgeom->stick_rect)) continue; else if (strip_button (left_func_rects, left_bg_rects, &n_left, &fgeom->shade_rect)) continue; else if (strip_button (right_func_rects, right_bg_rects, &n_right, &fgeom->shade_rect)) continue; else if (strip_button (left_func_rects, left_bg_rects, &n_left, &fgeom->min_rect)) continue; else if (strip_button (right_func_rects, right_bg_rects, &n_right, &fgeom->min_rect)) continue; else if (strip_button (left_func_rects, left_bg_rects, &n_left, &fgeom->max_rect)) continue; else if (strip_button (right_func_rects, right_bg_rects, &n_right, &fgeom->max_rect)) continue; else if (strip_button (left_func_rects, left_bg_rects, &n_left, &fgeom->close_rect)) continue; else if (strip_button (right_func_rects, right_bg_rects, &n_right, &fgeom->close_rect)) continue; else if (strip_button (right_func_rects, right_bg_rects, &n_right, &fgeom->menu_rect)) continue; else if (strip_button (left_func_rects, left_bg_rects, &n_left, &fgeom->menu_rect)) continue; else { meta_bug ("Could not find a button to strip. n_left = %d n_right = %d\n", n_left, n_right); } } /* center buttons vertically */ button_y = (fgeom->top_height - (button_height + layout->button_border.top + layout->button_border.bottom)) / 2 + layout->button_border.top; /* right edge of farthest-right button */ x = width - layout->right_titlebar_edge; i = n_right - 1; while (i >= 0) { MetaButtonSpace *rect; if (x < 0) /* if we go negative, leave the buttons we don't get to as 0-width */ break; rect = right_func_rects[i]; rect->visible.x = x - layout->button_border.right - button_width; if (right_buttons_has_spacer[i]) rect->visible.x -= (button_width * 0.75); rect->visible.y = button_y; rect->visible.width = button_width; rect->visible.height = button_height; if (flags & META_FRAME_MAXIMIZED) { rect->clickable.x = rect->visible.x; rect->clickable.y = 0; rect->clickable.width = rect->visible.width; rect->clickable.height = button_height + button_y; if (i == n_right - 1) rect->clickable.width += layout->right_titlebar_edge + layout->right_width + layout->button_border.right; } else g_memmove (&(rect->clickable), &(rect->visible), sizeof(rect->clickable)); *(right_bg_rects[i]) = rect->visible; x = rect->visible.x - layout->button_border.left; --i; } /* save right edge of titlebar for later use */ title_right_edge = x - layout->title_border.right; /* Now x changes to be position from the left and we go through * the left-side buttons */ x = layout->left_titlebar_edge; for (i = 0; i < n_left; i++) { MetaButtonSpace *rect; rect = left_func_rects[i]; rect->visible.x = x + layout->button_border.left; rect->visible.y = button_y; rect->visible.width = button_width; rect->visible.height = button_height; if (flags & META_FRAME_MAXIMIZED) { if (i==0) { rect->clickable.x = 0; rect->clickable.width = button_width + x; } else { rect->clickable.x = rect->visible.x; rect->clickable.width = button_width; } rect->clickable.y = 0; rect->clickable.height = button_height + button_y; } else g_memmove (&(rect->clickable), &(rect->visible), sizeof(rect->clickable)); x = rect->visible.x + rect->visible.width + layout->button_border.right; if (left_buttons_has_spacer[i]) x += (button_width * 0.75); *(left_bg_rects[i]) = rect->visible; } /* We always fill as much vertical space as possible with title rect, * rather than centering it like the buttons */ fgeom->title_rect.x = x + layout->title_border.left; fgeom->title_rect.y = layout->title_border.top; fgeom->title_rect.width = title_right_edge - fgeom->title_rect.x; fgeom->title_rect.height = fgeom->top_height - layout->title_border.top - layout->title_border.bottom; /* Nuke title if it won't fit */ if (fgeom->title_rect.width < 0 || fgeom->title_rect.height < 0) { fgeom->title_rect.width = 0; fgeom->title_rect.height = 0; } if (flags & META_FRAME_SHADED) min_size_for_rounding = 0; else min_size_for_rounding = 5; fgeom->top_left_corner_rounded_radius = 0; fgeom->top_right_corner_rounded_radius = 0; fgeom->bottom_left_corner_rounded_radius = 0; fgeom->bottom_right_corner_rounded_radius = 0; if (fgeom->top_height + fgeom->left_width >= min_size_for_rounding) fgeom->top_left_corner_rounded_radius = layout->top_left_corner_rounded_radius; if (fgeom->top_height + fgeom->right_width >= min_size_for_rounding) fgeom->top_right_corner_rounded_radius = layout->top_right_corner_rounded_radius; if (fgeom->bottom_height + fgeom->left_width >= min_size_for_rounding) fgeom->bottom_left_corner_rounded_radius = layout->bottom_left_corner_rounded_radius; if (fgeom->bottom_height + fgeom->right_width >= min_size_for_rounding) fgeom->bottom_right_corner_rounded_radius = layout->bottom_right_corner_rounded_radius; } MetaGradientSpec* meta_gradient_spec_new (MetaGradientType type) { MetaGradientSpec *spec; spec = g_new (MetaGradientSpec, 1); spec->type = type; spec->color_specs = NULL; return spec; } static void free_color_spec (gpointer spec, gpointer user_data) { meta_color_spec_free (spec); } void meta_gradient_spec_free (MetaGradientSpec *spec) { g_return_if_fail (spec != NULL); g_slist_foreach (spec->color_specs, free_color_spec, NULL); g_slist_free (spec->color_specs); DEBUG_FILL_STRUCT (spec); g_free (spec); } GdkPixbuf* meta_gradient_spec_render (const MetaGradientSpec *spec, GtkWidget *widget, int width, int height) { int n_colors; GdkColor *colors; GSList *tmp; int i; GdkPixbuf *pixbuf; n_colors = g_slist_length (spec->color_specs); if (n_colors == 0) return NULL; colors = g_new (GdkColor, n_colors); i = 0; tmp = spec->color_specs; while (tmp != NULL) { meta_color_spec_render (tmp->data, widget, &colors[i]); tmp = tmp->next; ++i; } pixbuf = meta_gradient_create_multi (width, height, colors, n_colors, spec->type); g_free (colors); return pixbuf; } gboolean meta_gradient_spec_validate (MetaGradientSpec *spec, GError **error) { g_return_val_if_fail (spec != NULL, FALSE); if (g_slist_length (spec->color_specs) < 2) { g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Gradients should have at least two colors")); return FALSE; } return TRUE; } MetaAlphaGradientSpec* meta_alpha_gradient_spec_new (MetaGradientType type, int n_alphas) { MetaAlphaGradientSpec *spec; g_return_val_if_fail (n_alphas > 0, NULL); spec = g_new0 (MetaAlphaGradientSpec, 1); spec->type = type; spec->alphas = g_new0 (unsigned char, n_alphas); spec->n_alphas = n_alphas; return spec; } void meta_alpha_gradient_spec_free (MetaAlphaGradientSpec *spec) { g_return_if_fail (spec != NULL); g_free (spec->alphas); g_free (spec); } MetaColorSpec* meta_color_spec_new (MetaColorSpecType type) { MetaColorSpec *spec; MetaColorSpec dummy; int size; size = G_STRUCT_OFFSET (MetaColorSpec, data); switch (type) { case META_COLOR_SPEC_BASIC: size += sizeof (dummy.data.basic); break; case META_COLOR_SPEC_GTK: size += sizeof (dummy.data.gtk); break; case META_COLOR_SPEC_BLEND: size += sizeof (dummy.data.blend); break; case META_COLOR_SPEC_SHADE: size += sizeof (dummy.data.shade); break; } spec = g_malloc0 (size); spec->type = type; return spec; } void meta_color_spec_free (MetaColorSpec *spec) { g_return_if_fail (spec != NULL); switch (spec->type) { case META_COLOR_SPEC_BASIC: DEBUG_FILL_STRUCT (&spec->data.basic); break; case META_COLOR_SPEC_GTK: DEBUG_FILL_STRUCT (&spec->data.gtk); break; case META_COLOR_SPEC_BLEND: if (spec->data.blend.foreground) meta_color_spec_free (spec->data.blend.foreground); if (spec->data.blend.background) meta_color_spec_free (spec->data.blend.background); DEBUG_FILL_STRUCT (&spec->data.blend); break; case META_COLOR_SPEC_SHADE: if (spec->data.shade.base) meta_color_spec_free (spec->data.shade.base); DEBUG_FILL_STRUCT (&spec->data.shade); break; } g_free (spec); } MetaColorSpec* meta_color_spec_new_from_string (const char *str, GError **err) { MetaColorSpec *spec; spec = NULL; if (str[0] == 'g' && str[1] == 't' && str[2] == 'k' && str[3] == ':') { /* GTK color */ const char *bracket; const char *end_bracket; char *tmp; GtkStateType state; MetaGtkColorComponent component; bracket = str; while (*bracket && *bracket != '[') ++bracket; if (*bracket == '\0') { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("GTK color specification must have the state in brackets, e.g. gtk:fg[NORMAL] where NORMAL is the state; could not parse \"%s\""), str); return NULL; } end_bracket = bracket; ++end_bracket; while (*end_bracket && *end_bracket != ']') ++end_bracket; if (*end_bracket == '\0') { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("GTK color specification must have a close bracket after the state, e.g. gtk:fg[NORMAL] where NORMAL is the state; could not parse \"%s\""), str); return NULL; } tmp = g_strndup (bracket + 1, end_bracket - bracket - 1); state = meta_gtk_state_from_string (tmp); if (((int) state) == -1) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Did not understand state \"%s\" in color specification"), tmp); g_free (tmp); return NULL; } g_free (tmp); tmp = g_strndup (str + 4, bracket - str - 4); component = meta_color_component_from_string (tmp); if (component == META_GTK_COLOR_LAST) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Did not understand color component \"%s\" in color specification"), tmp); g_free (tmp); return NULL; } g_free (tmp); spec = meta_color_spec_new (META_COLOR_SPEC_GTK); spec->data.gtk.state = state; spec->data.gtk.component = component; g_assert (spec->data.gtk.state < N_GTK_STATES); g_assert (spec->data.gtk.component < META_GTK_COLOR_LAST); } else if (str[0] == 'b' && str[1] == 'l' && str[2] == 'e' && str[3] == 'n' && str[4] == 'd' && str[5] == '/') { /* blend */ char **split; double alpha; char *end; MetaColorSpec *fg; MetaColorSpec *bg; split = g_strsplit (str, "/", 4); if (split[0] == NULL || split[1] == NULL || split[2] == NULL || split[3] == NULL) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Blend format is \"blend/bg_color/fg_color/alpha\", \"%s\" does not fit the format"), str); g_strfreev (split); return NULL; } alpha = g_ascii_strtod (split[3], &end); if (end == split[3]) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Could not parse alpha value \"%s\" in blended color"), split[3]); g_strfreev (split); return NULL; } if (alpha < (0.0 - 1e6) || alpha > (1.0 + 1e6)) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Alpha value \"%s\" in blended color is not between 0.0 and 1.0"), split[3]); g_strfreev (split); return NULL; } fg = NULL; bg = NULL; bg = meta_color_spec_new_from_string (split[1], err); if (bg == NULL) { g_strfreev (split); return NULL; } fg = meta_color_spec_new_from_string (split[2], err); if (fg == NULL) { meta_color_spec_free (bg); g_strfreev (split); return NULL; } g_strfreev (split); spec = meta_color_spec_new (META_COLOR_SPEC_BLEND); spec->data.blend.alpha = alpha; spec->data.blend.background = bg; spec->data.blend.foreground = fg; } else if (str[0] == 's' && str[1] == 'h' && str[2] == 'a' && str[3] == 'd' && str[4] == 'e' && str[5] == '/') { /* shade */ char **split; double factor; char *end; MetaColorSpec *base; split = g_strsplit (str, "/", 3); if (split[0] == NULL || split[1] == NULL || split[2] == NULL) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Shade format is \"shade/base_color/factor\", \"%s\" does not fit the format"), str); g_strfreev (split); return NULL; } factor = g_ascii_strtod (split[2], &end); if (end == split[2]) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Could not parse shade factor \"%s\" in shaded color"), split[2]); g_strfreev (split); return NULL; } if (factor < (0.0 - 1e6)) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Shade factor \"%s\" in shaded color is negative"), split[2]); g_strfreev (split); return NULL; } base = NULL; base = meta_color_spec_new_from_string (split[1], err); if (base == NULL) { g_strfreev (split); return NULL; } g_strfreev (split); spec = meta_color_spec_new (META_COLOR_SPEC_SHADE); spec->data.shade.factor = factor; spec->data.shade.base = base; } else { spec = meta_color_spec_new (META_COLOR_SPEC_BASIC); if (!gdk_color_parse (str, &spec->data.basic.color)) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Could not parse color \"%s\""), str); meta_color_spec_free (spec); return NULL; } } g_assert (spec); return spec; } MetaColorSpec* meta_color_spec_new_gtk (MetaGtkColorComponent component, GtkStateType state) { MetaColorSpec *spec; spec = meta_color_spec_new (META_COLOR_SPEC_GTK); spec->data.gtk.component = component; spec->data.gtk.state = state; return spec; } void meta_color_spec_render (MetaColorSpec *spec, GtkWidget *widget, GdkColor *color) { g_return_if_fail (spec != NULL); g_return_if_fail (GTK_IS_WIDGET (widget)); g_return_if_fail (widget->style != NULL); switch (spec->type) { case META_COLOR_SPEC_BASIC: *color = spec->data.basic.color; break; case META_COLOR_SPEC_GTK: switch (spec->data.gtk.component) { case META_GTK_COLOR_BG: *color = widget->style->bg[spec->data.gtk.state]; break; case META_GTK_COLOR_FG: *color = widget->style->fg[spec->data.gtk.state]; break; case META_GTK_COLOR_BASE: *color = widget->style->base[spec->data.gtk.state]; break; case META_GTK_COLOR_TEXT: *color = widget->style->text[spec->data.gtk.state]; break; case META_GTK_COLOR_LIGHT: *color = widget->style->light[spec->data.gtk.state]; break; case META_GTK_COLOR_DARK: *color = widget->style->dark[spec->data.gtk.state]; break; case META_GTK_COLOR_MID: *color = widget->style->mid[spec->data.gtk.state]; break; case META_GTK_COLOR_TEXT_AA: *color = widget->style->text_aa[spec->data.gtk.state]; break; case META_GTK_COLOR_LAST: g_assert_not_reached (); break; } break; case META_COLOR_SPEC_BLEND: { GdkColor bg, fg; meta_color_spec_render (spec->data.blend.background, widget, &bg); meta_color_spec_render (spec->data.blend.foreground, widget, &fg); color_composite (&bg, &fg, spec->data.blend.alpha, &spec->data.blend.color); *color = spec->data.blend.color; } break; case META_COLOR_SPEC_SHADE: { meta_color_spec_render (spec->data.shade.base, widget, &spec->data.shade.color); gtk_style_shade (&spec->data.shade.color, &spec->data.shade.color, spec->data.shade.factor); *color = spec->data.shade.color; } break; } } /** * Represents an operation as a string. * * \param type an operation, such as addition * \return a string, such as "+" */ static const char* op_name (PosOperatorType type) { switch (type) { case POS_OP_ADD: return "+"; case POS_OP_SUBTRACT: return "-"; case POS_OP_MULTIPLY: return "*"; case POS_OP_DIVIDE: return "/"; case POS_OP_MOD: return "%"; case POS_OP_MAX: return "`max`"; case POS_OP_MIN: return "`min`"; case POS_OP_NONE: break; } return ""; } /** * Parses a string and returns an operation. * * \param p a pointer into a string representing an operation; part of an * expression somewhere, so not null-terminated * \param len set to the length of the string found. Set to 0 if none is. * \return the operation found. If none was, returns POS_OP_NONE. */ static PosOperatorType op_from_string (const char *p, int *len) { *len = 0; switch (*p) { case '+': *len = 1; return POS_OP_ADD; case '-': *len = 1; return POS_OP_SUBTRACT; case '*': *len = 1; return POS_OP_MULTIPLY; case '/': *len = 1; return POS_OP_DIVIDE; case '%': *len = 1; return POS_OP_MOD; case '`': if (p[0] == '`' && p[1] == 'm' && p[2] == 'a' && p[3] == 'x' && p[4] == '`') { *len = 5; return POS_OP_MAX; } else if (p[0] == '`' && p[1] == 'm' && p[2] == 'i' && p[3] == 'n' && p[4] == '`') { *len = 5; return POS_OP_MIN; } } return POS_OP_NONE; } /** * Frees an array of tokens. All the tokens and their associated memory * will be freed. * * \param tokens an array of tokens to be freed * \param n_tokens how many tokens are in the array. */ static void free_tokens (PosToken *tokens, int n_tokens) { int i; /* n_tokens can be 0 since tokens may have been allocated more than * it was initialized */ for (i = 0; i < n_tokens; i++) if (tokens[i].type == POS_TOKEN_VARIABLE) g_free (tokens[i].d.v.name); g_free (tokens); } /** * Tokenises a number in an expression. * * \param p a pointer into a string representing an operation; part of an * expression somewhere, so not null-terminated * \param end_return set to a pointer to the end of the number found; but * not updated if no number was found at all * \param next set to either an integer or a float token * \param[out] err set to the problem if there was a problem * \return TRUE if a valid number was found, FALSE otherwise (and "err" will * have been set) * * \bug The "while (*start)..." part: what's wrong with strchr-ish things? * \bug The name is wrong: it doesn't parse anything. * \ingroup tokenizer */ static gboolean parse_number (const char *p, const char **end_return, PosToken *next, GError **err) { const char *start = p; char *end; gboolean is_float; char *num_str; while (*p && (*p == '.' || g_ascii_isdigit (*p))) ++p; if (p == start) { char buf[7] = { '\0' }; buf[g_unichar_to_utf8 (g_utf8_get_char (p), buf)] = '\0'; g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_BAD_CHARACTER, _("Coordinate expression contains character '%s' which is not allowed"), buf); return FALSE; } *end_return = p; /* we need this to exclude floats like "1e6" */ num_str = g_strndup (start, p - start); start = num_str; is_float = FALSE; while (*start) { if (*start == '.') is_float = TRUE; ++start; } if (is_float) { next->type = POS_TOKEN_DOUBLE; next->d.d.val = g_ascii_strtod (num_str, &end); if (end == num_str) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Coordinate expression contains floating point number '%s' which could not be parsed"), num_str); g_free (num_str); return FALSE; } } else { next->type = POS_TOKEN_INT; next->d.i.val = strtol (num_str, &end, 10); if (end == num_str) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Coordinate expression contains integer '%s' which could not be parsed"), num_str); g_free (num_str); return FALSE; } } g_free (num_str); g_assert (next->type == POS_TOKEN_INT || next->type == POS_TOKEN_DOUBLE); return TRUE; } /** * Whether a variable can validly appear as part of the name of a variable. */ #define IS_VARIABLE_CHAR(c) (g_ascii_isalpha ((c)) || (c) == '_') #if 0 static void debug_print_tokens (PosToken *tokens, int n_tokens) { int i; for (i = 0; i < n_tokens; i++) { PosToken *t = &tokens[i]; g_print (" "); switch (t->type) { case POS_TOKEN_INT: g_print ("\"%d\"", t->d.i.val); break; case POS_TOKEN_DOUBLE: g_print ("\"%g\"", t->d.d.val); break; case POS_TOKEN_OPEN_PAREN: g_print ("\"(\""); break; case POS_TOKEN_CLOSE_PAREN: g_print ("\")\""); break; case POS_TOKEN_VARIABLE: g_print ("\"%s\"", t->d.v.name); break; case POS_TOKEN_OPERATOR: g_print ("\"%s\"", op_name (t->d.o.op)); break; } } g_print ("\n"); } #endif /** * Tokenises an expression. * * \param expr The expression * \param[out] tokens_p The resulting tokens * \param[out] n_tokens_p The number of resulting tokens * \param[out] err set to the problem if there was a problem * * \return True if the expression was successfully tokenised; false otherwise. * * \ingroup tokenizer */ static gboolean pos_tokenize (const char *expr, PosToken **tokens_p, int *n_tokens_p, GError **err) { PosToken *tokens; int n_tokens; int allocated; const char *p; *tokens_p = NULL; *n_tokens_p = 0; allocated = 3; n_tokens = 0; tokens = g_new (PosToken, allocated); p = expr; while (*p) { PosToken *next; int len; if (n_tokens == allocated) { allocated *= 2; tokens = g_renew (PosToken, tokens, allocated); } next = &tokens[n_tokens]; switch (*p) { case '*': case '/': case '+': case '-': /* negative numbers aren't allowed so this is easy */ case '%': case '`': next->type = POS_TOKEN_OPERATOR; next->d.o.op = op_from_string (p, &len); if (next->d.o.op != POS_OP_NONE) { ++n_tokens; p = p + (len - 1); /* -1 since we ++p later */ } else { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Coordinate expression contained unknown operator at the start of this text: \"%s\""), p); goto error; } break; case '(': next->type = POS_TOKEN_OPEN_PAREN; ++n_tokens; break; case ')': next->type = POS_TOKEN_CLOSE_PAREN; ++n_tokens; break; case ' ': case '\t': case '\n': break; default: if (IS_VARIABLE_CHAR (*p)) { /* Assume variable */ const char *start = p; while (*p && IS_VARIABLE_CHAR (*p)) ++p; g_assert (p != start); next->type = POS_TOKEN_VARIABLE; next->d.v.name = g_strndup (start, p - start); ++n_tokens; --p; /* since we ++p again at the end of while loop */ } else { /* Assume number */ const char *end; if (!parse_number (p, &end, next, err)) goto error; ++n_tokens; p = end - 1; /* -1 since we ++p again at the end of while loop */ } break; } ++p; } if (n_tokens == 0) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Coordinate expression was empty or not understood")); goto error; } *tokens_p = tokens; *n_tokens_p = n_tokens; return TRUE; error: g_assert (err == NULL || *err != NULL); free_tokens (tokens, n_tokens); return FALSE; } /** * The type of a PosExpr: either integer, double, or an operation. * \ingroup parser */ typedef enum { POS_EXPR_INT, POS_EXPR_DOUBLE, POS_EXPR_OPERATOR } PosExprType; /** * Type and value of an expression in a parsed sequence. We don't * keep expressions in a tree; if this is of type POS_EXPR_OPERATOR, * the arguments of the operator will be in the array positions * immediately preceding and following this operator; they cannot * themselves be operators. * * \bug operator is char; it should really be of PosOperatorType. * \ingroup parser */ typedef struct { PosExprType type; union { double double_val; int int_val; char operator; } d; } PosExpr; #if 0 static void debug_print_exprs (PosExpr *exprs, int n_exprs) { int i; for (i = 0; i < n_exprs; i++) { switch (exprs[i].type) { case POS_EXPR_INT: g_print (" %d", exprs[i].d.int_val); break; case POS_EXPR_DOUBLE: g_print (" %g", exprs[i].d.double_val); break; case POS_EXPR_OPERATOR: g_print (" %s", op_name (exprs[i].d.operator)); break; } } g_print ("\n"); } #endif static gboolean do_operation (PosExpr *a, PosExpr *b, PosOperatorType op, GError **err) { /* Promote types to double if required */ if (a->type == POS_EXPR_DOUBLE || b->type == POS_EXPR_DOUBLE) { if (a->type != POS_EXPR_DOUBLE) { a->type = POS_EXPR_DOUBLE; a->d.double_val = a->d.int_val; } if (b->type != POS_EXPR_DOUBLE) { b->type = POS_EXPR_DOUBLE; b->d.double_val = b->d.int_val; } } g_assert (a->type == b->type); if (a->type == POS_EXPR_INT) { switch (op) { case POS_OP_MULTIPLY: a->d.int_val = a->d.int_val * b->d.int_val; break; case POS_OP_DIVIDE: if (b->d.int_val == 0) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_DIVIDE_BY_ZERO, _("Coordinate expression results in division by zero")); return FALSE; } a->d.int_val = a->d.int_val / b->d.int_val; break; case POS_OP_MOD: if (b->d.int_val == 0) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_DIVIDE_BY_ZERO, _("Coordinate expression results in division by zero")); return FALSE; } a->d.int_val = a->d.int_val % b->d.int_val; break; case POS_OP_ADD: a->d.int_val = a->d.int_val + b->d.int_val; break; case POS_OP_SUBTRACT: a->d.int_val = a->d.int_val - b->d.int_val; break; case POS_OP_MAX: a->d.int_val = MAX (a->d.int_val, b->d.int_val); break; case POS_OP_MIN: a->d.int_val = MIN (a->d.int_val, b->d.int_val); break; case POS_OP_NONE: g_assert_not_reached (); break; } } else if (a->type == POS_EXPR_DOUBLE) { switch (op) { case POS_OP_MULTIPLY: a->d.double_val = a->d.double_val * b->d.double_val; break; case POS_OP_DIVIDE: if (b->d.double_val == 0.0) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_DIVIDE_BY_ZERO, _("Coordinate expression results in division by zero")); return FALSE; } a->d.double_val = a->d.double_val / b->d.double_val; break; case POS_OP_MOD: g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_MOD_ON_FLOAT, _("Coordinate expression tries to use mod operator on a floating-point number")); return FALSE; case POS_OP_ADD: a->d.double_val = a->d.double_val + b->d.double_val; break; case POS_OP_SUBTRACT: a->d.double_val = a->d.double_val - b->d.double_val; break; case POS_OP_MAX: a->d.double_val = MAX (a->d.double_val, b->d.double_val); break; case POS_OP_MIN: a->d.double_val = MIN (a->d.double_val, b->d.double_val); break; case POS_OP_NONE: g_assert_not_reached (); break; } } else g_assert_not_reached (); return TRUE; } static gboolean do_operations (PosExpr *exprs, int *n_exprs, int precedence, GError **err) { int i; #if 0 g_print ("Doing prec %d ops on %d exprs\n", precedence, *n_exprs); debug_print_exprs (exprs, *n_exprs); #endif i = 1; while (i < *n_exprs) { gboolean compress; /* exprs[i-1] first operand * exprs[i] operator * exprs[i+1] second operand * * we replace first operand with result of mul/div/mod, * or skip over operator and second operand if we have * an add/subtract */ if (exprs[i-1].type == POS_EXPR_OPERATOR) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Coordinate expression has an operator \"%s\" where an operand was expected"), op_name (exprs[i-1].d.operator)); return FALSE; } if (exprs[i].type != POS_EXPR_OPERATOR) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Coordinate expression had an operand where an operator was expected")); return FALSE; } if (i == (*n_exprs - 1)) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Coordinate expression ended with an operator instead of an operand")); return FALSE; } g_assert ((i+1) < *n_exprs); if (exprs[i+1].type == POS_EXPR_OPERATOR) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Coordinate expression has operator \"%c\" following operator \"%c\" with no operand in between"), exprs[i+1].d.operator, exprs[i].d.operator); return FALSE; } compress = FALSE; switch (precedence) { case 2: switch (exprs[i].d.operator) { case POS_OP_DIVIDE: case POS_OP_MOD: case POS_OP_MULTIPLY: compress = TRUE; if (!do_operation (&exprs[i-1], &exprs[i+1], exprs[i].d.operator, err)) return FALSE; break; } break; case 1: switch (exprs[i].d.operator) { case POS_OP_ADD: case POS_OP_SUBTRACT: compress = TRUE; if (!do_operation (&exprs[i-1], &exprs[i+1], exprs[i].d.operator, err)) return FALSE; break; } break; /* I have no rationale at all for making these low-precedence */ case 0: switch (exprs[i].d.operator) { case POS_OP_MAX: case POS_OP_MIN: compress = TRUE; if (!do_operation (&exprs[i-1], &exprs[i+1], exprs[i].d.operator, err)) return FALSE; break; } break; } if (compress) { /* exprs[i-1] first operand (now result) * exprs[i] operator * exprs[i+1] second operand * exprs[i+2] new operator * * we move new operator just after first operand */ if ((i+2) < *n_exprs) { g_memmove (&exprs[i], &exprs[i+2], sizeof (PosExpr) * (*n_exprs - i - 2)); } *n_exprs -= 2; } else { /* Skip operator and next operand */ i += 2; } } return TRUE; } /** * There is a predefined set of variables which can appear in an expression. * Here we take a token representing a variable, and return the current value * of that variable in a particular environment. * (The value is always an integer.) * * There are supposedly some circumstances in which this function can be * called from outside Metacity, in which case env->theme will be NULL, and * therefore we can't use it to find out quark values, so we do the comparison * using strcmp, which is slower. * * \param t The token representing a variable * \param[out] result The value of that variable; not set if the token did * not represent a known variable * \param env The environment within which t should be evaluated * \param[out] err set to the problem if there was a problem * * \return true if we found the variable asked for, false if we didn't * * \bug shouldn't t be const? * \bug we should perhaps consider some sort of lookup arrangement into an * array; also, the duplication of code is unlovely; perhaps using glib * string hashes instead of quarks would fix both problems? * \ingroup parser */ static gboolean pos_eval_get_variable (PosToken *t, int *result, const MetaPositionExprEnv *env, GError **err) { if (env->theme) { if (t->d.v.name_quark == env->theme->quark_width) *result = env->rect.width; else if (t->d.v.name_quark == env->theme->quark_height) *result = env->rect.height; else if (env->object_width >= 0 && t->d.v.name_quark == env->theme->quark_object_width) *result = env->object_width; else if (env->object_height >= 0 && t->d.v.name_quark == env->theme->quark_object_height) *result = env->object_height; else if (t->d.v.name_quark == env->theme->quark_left_width) *result = env->left_width; else if (t->d.v.name_quark == env->theme->quark_right_width) *result = env->right_width; else if (t->d.v.name_quark == env->theme->quark_top_height) *result = env->top_height; else if (t->d.v.name_quark == env->theme->quark_bottom_height) *result = env->bottom_height; else if (t->d.v.name_quark == env->theme->quark_mini_icon_width) *result = env->mini_icon_width; else if (t->d.v.name_quark == env->theme->quark_mini_icon_height) *result = env->mini_icon_height; else if (t->d.v.name_quark == env->theme->quark_icon_width) *result = env->icon_width; else if (t->d.v.name_quark == env->theme->quark_icon_height) *result = env->icon_height; else if (t->d.v.name_quark == env->theme->quark_title_width) *result = env->title_width; else if (t->d.v.name_quark == env->theme->quark_title_height) *result = env->title_height; else { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_UNKNOWN_VARIABLE, _("Coordinate expression had unknown variable or constant \"%s\""), t->d.v.name); return FALSE; } } else { if (strcmp (t->d.v.name, "width") == 0) *result = env->rect.width; else if (strcmp (t->d.v.name, "height") == 0) *result = env->rect.height; else if (env->object_width >= 0 && strcmp (t->d.v.name, "object_width") == 0) *result = env->object_width; else if (env->object_height >= 0 && strcmp (t->d.v.name, "object_height") == 0) *result = env->object_height; else if (strcmp (t->d.v.name, "left_width") == 0) *result = env->left_width; else if (strcmp (t->d.v.name, "right_width") == 0) *result = env->right_width; else if (strcmp (t->d.v.name, "top_height") == 0) *result = env->top_height; else if (strcmp (t->d.v.name, "bottom_height") == 0) *result = env->bottom_height; else if (strcmp (t->d.v.name, "mini_icon_width") == 0) *result = env->mini_icon_width; else if (strcmp (t->d.v.name, "mini_icon_height") == 0) *result = env->mini_icon_height; else if (strcmp (t->d.v.name, "icon_width") == 0) *result = env->icon_width; else if (strcmp (t->d.v.name, "icon_height") == 0) *result = env->icon_height; else if (strcmp (t->d.v.name, "title_width") == 0) *result = env->title_width; else if (strcmp (t->d.v.name, "title_height") == 0) *result = env->title_height; else { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_UNKNOWN_VARIABLE, _("Coordinate expression had unknown variable or constant \"%s\""), t->d.v.name); return FALSE; } } return TRUE; } /** * Evaluates a sequence of tokens within a particular environment context, * and returns the current value. May recur if parantheses are found. * * \param tokens A list of tokens to evaluate. * \param n_tokens How many tokens are in the list. * \param env The environment context in which to evaluate the expression. * \param[out] result The current value of the expression * * \bug Yes, we really do reparse the expression every time it's evaluated. * We should keep the parse tree around all the time and just * run the new values through it. * \ingroup parser */ static gboolean pos_eval_helper (PosToken *tokens, int n_tokens, const MetaPositionExprEnv *env, PosExpr *result, GError **err) { /* Lazy-ass hardcoded limit on number of terms in expression */ #define MAX_EXPRS 32 int paren_level; int first_paren; int i; PosExpr exprs[MAX_EXPRS]; int n_exprs; int precedence; /* Our first goal is to get a list of PosExpr, essentially * substituting variables and handling parentheses. */ first_paren = 0; paren_level = 0; n_exprs = 0; for (i = 0; i < n_tokens; i++) { PosToken *t = &tokens[i]; if (n_exprs >= MAX_EXPRS) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Coordinate expression parser overflowed its buffer.")); return FALSE; } if (paren_level == 0) { switch (t->type) { case POS_TOKEN_INT: exprs[n_exprs].type = POS_EXPR_INT; exprs[n_exprs].d.int_val = t->d.i.val; ++n_exprs; break; case POS_TOKEN_DOUBLE: exprs[n_exprs].type = POS_EXPR_DOUBLE; exprs[n_exprs].d.double_val = t->d.d.val; ++n_exprs; break; case POS_TOKEN_OPEN_PAREN: ++paren_level; if (paren_level == 1) first_paren = i; break; case POS_TOKEN_CLOSE_PAREN: g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_BAD_PARENS, _("Coordinate expression had a close parenthesis with no open parenthesis")); return FALSE; case POS_TOKEN_VARIABLE: exprs[n_exprs].type = POS_EXPR_INT; /* FIXME we should just dump all this crap * in a hash, maybe keep width/height out * for optimization purposes */ if (!pos_eval_get_variable (t, &exprs[n_exprs].d.int_val, env, err)) return FALSE; ++n_exprs; break; case POS_TOKEN_OPERATOR: exprs[n_exprs].type = POS_EXPR_OPERATOR; exprs[n_exprs].d.operator = t->d.o.op; ++n_exprs; break; } } else { g_assert (paren_level > 0); switch (t->type) { case POS_TOKEN_INT: case POS_TOKEN_DOUBLE: case POS_TOKEN_VARIABLE: case POS_TOKEN_OPERATOR: break; case POS_TOKEN_OPEN_PAREN: ++paren_level; break; case POS_TOKEN_CLOSE_PAREN: if (paren_level == 1) { /* We closed a toplevel paren group, so recurse */ if (!pos_eval_helper (&tokens[first_paren+1], i - first_paren - 1, env, &exprs[n_exprs], err)) return FALSE; ++n_exprs; } --paren_level; break; } } } if (paren_level > 0) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_BAD_PARENS, _("Coordinate expression had an open parenthesis with no close parenthesis")); return FALSE; } /* Now we have no parens and no vars; so we just do all the multiplies * and divides, then all the add and subtract. */ if (n_exprs == 0) { g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("Coordinate expression doesn't seem to have any operators or operands")); return FALSE; } /* precedence 1 ops */ precedence = 2; while (precedence >= 0) { if (!do_operations (exprs, &n_exprs, precedence, err)) return FALSE; --precedence; } g_assert (n_exprs == 1); *result = *exprs; return TRUE; } /* * expr = int | double | expr * expr | expr / expr | * expr + expr | expr - expr | (expr) * * so very not worth fooling with bison, yet so very painful by hand. */ /** * Evaluates an expression. * * \param spec The expression to evaluate. * \param env The environment context to evaluate the expression in. * \param[out] val_p The integer value of the expression; if the expression * is of type float, this will be rounded. If we return * FALSE because the expression is invalid, this will be * zero. * \param[out] err The error, if anything went wrong. * * \return True if we evaluated the expression successfully; false otherwise. * * \bug Shouldn't spec be const? * \ingroup parser */ static gboolean pos_eval (MetaDrawSpec *spec, const MetaPositionExprEnv *env, int *val_p, GError **err) { PosExpr expr; *val_p = 0; if (pos_eval_helper (spec->tokens, spec->n_tokens, env, &expr, err)) { switch (expr.type) { case POS_EXPR_INT: *val_p = expr.d.int_val; break; case POS_EXPR_DOUBLE: *val_p = expr.d.double_val; break; case POS_EXPR_OPERATOR: g_assert_not_reached (); break; } return TRUE; } else { return FALSE; } } /* We always return both X and Y, but only one will be meaningful in * most contexts. */ gboolean meta_parse_position_expression (MetaDrawSpec *spec, const MetaPositionExprEnv *env, int *x_return, int *y_return, GError **err) { /* All positions are in a coordinate system with x, y at the origin. * The expression can have -, +, *, / as operators, floating point * or integer constants, and the variables "width" and "height" and * optionally "object_width" and object_height". Negative numbers * aren't allowed. */ int val; if (spec->constant) val = spec->value; else { if (pos_eval (spec, env, &spec->value, err) == FALSE) { g_assert (err == NULL || *err != NULL); return FALSE; } val = spec->value; } if (x_return) *x_return = env->rect.x + val; if (y_return) *y_return = env->rect.y + val; return TRUE; } gboolean meta_parse_size_expression (MetaDrawSpec *spec, const MetaPositionExprEnv *env, int *val_return, GError **err) { int val; if (spec->constant) val = spec->value; else { if (pos_eval (spec, env, &spec->value, err) == FALSE) { g_assert (err == NULL || *err != NULL); return FALSE; } val = spec->value; } if (val_return) *val_return = MAX (val, 1); /* require that sizes be at least 1x1 */ return TRUE; } /* To do this we tokenize, replace variable tokens * that are constants, then reassemble. The purpose * here is to optimize expressions so we don't do hash * lookups to eval them. Obviously it's a tradeoff that * slows down theme load times. */ gboolean meta_theme_replace_constants (MetaTheme *theme, PosToken *tokens, int n_tokens, GError **err) { int i; double dval; int ival; gboolean is_constant = TRUE; /* Loop through tokenized string looking for variables to replace */ for (i = 0; i < n_tokens; i++) { PosToken *t = &tokens[i]; if (t->type == POS_TOKEN_VARIABLE) { if (meta_theme_lookup_int_constant (theme, t->d.v.name, &ival)) { t->type = POS_TOKEN_INT; t->d.i.val = ival; } else if (meta_theme_lookup_float_constant (theme, t->d.v.name, &dval)) { t->type = POS_TOKEN_DOUBLE; t->d.d.val = dval; } else { /* If we've found a variable that cannot be replaced then the expression is not a constant expression and we want to replace it with a GQuark */ t->d.v.name_quark = g_quark_from_string (t->d.v.name); is_constant = FALSE; } } } return is_constant; } static int parse_x_position_unchecked (MetaDrawSpec *spec, const MetaPositionExprEnv *env) { int retval; GError *error; retval = 0; error = NULL; if (!meta_parse_position_expression (spec, env, &retval, NULL, &error)) { meta_warning (_("Theme contained an expression that resulted in an error: %s\n"), error->message); g_error_free (error); } return retval; } static int parse_y_position_unchecked (MetaDrawSpec *spec, const MetaPositionExprEnv *env) { int retval; GError *error; retval = 0; error = NULL; if (!meta_parse_position_expression (spec, env, NULL, &retval, &error)) { meta_warning (_("Theme contained an expression that resulted in an error: %s\n"), error->message); g_error_free (error); } return retval; } static int parse_size_unchecked (MetaDrawSpec *spec, MetaPositionExprEnv *env) { int retval; GError *error; retval = 0; error = NULL; if (!meta_parse_size_expression (spec, env, &retval, &error)) { meta_warning (_("Theme contained an expression that resulted in an error: %s\n"), error->message); g_error_free (error); } return retval; } void meta_draw_spec_free (MetaDrawSpec *spec) { if (!spec) return; free_tokens (spec->tokens, spec->n_tokens); g_slice_free (MetaDrawSpec, spec); } MetaDrawSpec * meta_draw_spec_new (MetaTheme *theme, const char *expr, GError **error) { MetaDrawSpec *spec; spec = g_slice_new0 (MetaDrawSpec); pos_tokenize (expr, &spec->tokens, &spec->n_tokens, NULL); spec->constant = meta_theme_replace_constants (theme, spec->tokens, spec->n_tokens, NULL); if (spec->constant) { gboolean result; result = pos_eval (spec, NULL, &spec->value, error); if (result == FALSE) { meta_draw_spec_free (spec); return NULL; } } return spec; } MetaDrawOp* meta_draw_op_new (MetaDrawType type) { MetaDrawOp *op; MetaDrawOp dummy; int size; size = G_STRUCT_OFFSET (MetaDrawOp, data); switch (type) { case META_DRAW_LINE: size += sizeof (dummy.data.line); break; case META_DRAW_RECTANGLE: size += sizeof (dummy.data.rectangle); break; case META_DRAW_ARC: size += sizeof (dummy.data.arc); break; case META_DRAW_CLIP: size += sizeof (dummy.data.clip); break; case META_DRAW_TINT: size += sizeof (dummy.data.tint); break; case META_DRAW_GRADIENT: size += sizeof (dummy.data.gradient); break; case META_DRAW_IMAGE: size += sizeof (dummy.data.image); break; case META_DRAW_GTK_ARROW: size += sizeof (dummy.data.gtk_arrow); break; case META_DRAW_GTK_BOX: size += sizeof (dummy.data.gtk_box); break; case META_DRAW_GTK_VLINE: size += sizeof (dummy.data.gtk_vline); break; case META_DRAW_ICON: size += sizeof (dummy.data.icon); break; case META_DRAW_TITLE: size += sizeof (dummy.data.title); break; case META_DRAW_OP_LIST: size += sizeof (dummy.data.op_list); break; case META_DRAW_TILE: size += sizeof (dummy.data.tile); break; } op = g_malloc0 (size); op->type = type; return op; } void meta_draw_op_free (MetaDrawOp *op) { g_return_if_fail (op != NULL); switch (op->type) { case META_DRAW_LINE: if (op->data.line.color_spec) meta_color_spec_free (op->data.line.color_spec); meta_draw_spec_free (op->data.line.x1); meta_draw_spec_free (op->data.line.y1); meta_draw_spec_free (op->data.line.x2); meta_draw_spec_free (op->data.line.y2); break; case META_DRAW_RECTANGLE: if (op->data.rectangle.color_spec) g_free (op->data.rectangle.color_spec); meta_draw_spec_free (op->data.rectangle.x); meta_draw_spec_free (op->data.rectangle.y); meta_draw_spec_free (op->data.rectangle.width); meta_draw_spec_free (op->data.rectangle.height); break; case META_DRAW_ARC: if (op->data.arc.color_spec) g_free (op->data.arc.color_spec); meta_draw_spec_free (op->data.arc.x); meta_draw_spec_free (op->data.arc.y); meta_draw_spec_free (op->data.arc.width); meta_draw_spec_free (op->data.arc.height); break; case META_DRAW_CLIP: meta_draw_spec_free (op->data.clip.x); meta_draw_spec_free (op->data.clip.y); meta_draw_spec_free (op->data.clip.width); meta_draw_spec_free (op->data.clip.height); break; case META_DRAW_TINT: if (op->data.tint.color_spec) meta_color_spec_free (op->data.tint.color_spec); if (op->data.tint.alpha_spec) meta_alpha_gradient_spec_free (op->data.tint.alpha_spec); meta_draw_spec_free (op->data.tint.x); meta_draw_spec_free (op->data.tint.y); meta_draw_spec_free (op->data.tint.width); meta_draw_spec_free (op->data.tint.height); break; case META_DRAW_GRADIENT: if (op->data.gradient.gradient_spec) meta_gradient_spec_free (op->data.gradient.gradient_spec); if (op->data.gradient.alpha_spec) meta_alpha_gradient_spec_free (op->data.gradient.alpha_spec); meta_draw_spec_free (op->data.gradient.x); meta_draw_spec_free (op->data.gradient.y); meta_draw_spec_free (op->data.gradient.width); meta_draw_spec_free (op->data.gradient.height); break; case META_DRAW_IMAGE: if (op->data.image.alpha_spec) meta_alpha_gradient_spec_free (op->data.image.alpha_spec); if (op->data.image.pixbuf) g_object_unref (G_OBJECT (op->data.image.pixbuf)); if (op->data.image.colorize_spec) meta_color_spec_free (op->data.image.colorize_spec); if (op->data.image.colorize_cache_pixbuf) g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf)); meta_draw_spec_free (op->data.image.x); meta_draw_spec_free (op->data.image.y); meta_draw_spec_free (op->data.image.width); meta_draw_spec_free (op->data.image.height); break; case META_DRAW_GTK_ARROW: meta_draw_spec_free (op->data.gtk_arrow.x); meta_draw_spec_free (op->data.gtk_arrow.y); meta_draw_spec_free (op->data.gtk_arrow.width); meta_draw_spec_free (op->data.gtk_arrow.height); break; case META_DRAW_GTK_BOX: meta_draw_spec_free (op->data.gtk_box.x); meta_draw_spec_free (op->data.gtk_box.y); meta_draw_spec_free (op->data.gtk_box.width); meta_draw_spec_free (op->data.gtk_box.height); break; case META_DRAW_GTK_VLINE: meta_draw_spec_free (op->data.gtk_vline.x); meta_draw_spec_free (op->data.gtk_vline.y1); meta_draw_spec_free (op->data.gtk_vline.y2); break; case META_DRAW_ICON: if (op->data.icon.alpha_spec) meta_alpha_gradient_spec_free (op->data.icon.alpha_spec); meta_draw_spec_free (op->data.icon.x); meta_draw_spec_free (op->data.icon.y); meta_draw_spec_free (op->data.icon.width); meta_draw_spec_free (op->data.icon.height); break; case META_DRAW_TITLE: if (op->data.title.color_spec) meta_color_spec_free (op->data.title.color_spec); meta_draw_spec_free (op->data.title.x); meta_draw_spec_free (op->data.title.y); break; case META_DRAW_OP_LIST: if (op->data.op_list.op_list) meta_draw_op_list_unref (op->data.op_list.op_list); meta_draw_spec_free (op->data.op_list.x); meta_draw_spec_free (op->data.op_list.y); meta_draw_spec_free (op->data.op_list.width); meta_draw_spec_free (op->data.op_list.height); break; case META_DRAW_TILE: if (op->data.tile.op_list) meta_draw_op_list_unref (op->data.tile.op_list); meta_draw_spec_free (op->data.tile.x); meta_draw_spec_free (op->data.tile.y); meta_draw_spec_free (op->data.tile.width); meta_draw_spec_free (op->data.tile.height); meta_draw_spec_free (op->data.tile.tile_xoffset); meta_draw_spec_free (op->data.tile.tile_yoffset); meta_draw_spec_free (op->data.tile.tile_width); meta_draw_spec_free (op->data.tile.tile_height); break; } g_free (op); } static GdkGC* get_gc_for_primitive (GtkWidget *widget, GdkDrawable *drawable, MetaColorSpec *color_spec, const GdkRectangle *clip, int line_width) { GdkGC *gc; GdkGCValues values; GdkColor color; meta_color_spec_render (color_spec, widget, &color); values.foreground = color; gdk_rgb_find_color (gdk_drawable_get_colormap (drawable), &values.foreground); values.line_width = line_width; gc = gdk_gc_new_with_values (drawable, &values, GDK_GC_FOREGROUND | GDK_GC_LINE_WIDTH); if (clip) gdk_gc_set_clip_rectangle (gc, (GdkRectangle*) clip); /* const cast */ return gc; } static GdkPixbuf* apply_alpha (GdkPixbuf *pixbuf, MetaAlphaGradientSpec *spec, gboolean force_copy) { GdkPixbuf *new_pixbuf; gboolean needs_alpha; g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL); needs_alpha = spec && (spec->n_alphas > 1 || spec->alphas[0] != 0xff); if (!needs_alpha) return pixbuf; if (!gdk_pixbuf_get_has_alpha (pixbuf)) { new_pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0); g_object_unref (G_OBJECT (pixbuf)); pixbuf = new_pixbuf; } else if (force_copy) { new_pixbuf = gdk_pixbuf_copy (pixbuf); g_object_unref (G_OBJECT (pixbuf)); pixbuf = new_pixbuf; } g_assert (gdk_pixbuf_get_has_alpha (pixbuf)); meta_gradient_add_alpha (pixbuf, spec->alphas, spec->n_alphas, spec->type); return pixbuf; } static void render_pixbuf (GdkDrawable *drawable, const GdkRectangle *clip, GdkPixbuf *pixbuf, int x, int y) { /* grumble, render_to_drawable_alpha does not accept a clip * mask, so we have to go through some BS */ /* FIXME once GTK 1.3.13 has been out a while we can use * render_to_drawable() which now does alpha with clip. * * Though the gdk_rectangle_intersect() check may be a useful * optimization anyway. */ GdkRectangle pixbuf_rect; GdkRectangle draw_rect; pixbuf_rect.x = x; pixbuf_rect.y = y; pixbuf_rect.width = gdk_pixbuf_get_width (pixbuf); pixbuf_rect.height = gdk_pixbuf_get_height (pixbuf); if (clip) { if (!gdk_rectangle_intersect ((GdkRectangle*)clip, &pixbuf_rect, &draw_rect)) return; } else { draw_rect = pixbuf_rect; } gdk_draw_pixbuf (drawable, NULL, pixbuf, draw_rect.x - pixbuf_rect.x, draw_rect.y - pixbuf_rect.y, draw_rect.x, draw_rect.y, draw_rect.width, draw_rect.height, GDK_RGB_DITHER_NORMAL, draw_rect.x - pixbuf_rect.x, draw_rect.y - pixbuf_rect.y); } static GdkPixbuf* pixbuf_tile (GdkPixbuf *tile, int width, int height) { GdkPixbuf *pixbuf; int tile_width; int tile_height; int i, j; tile_width = gdk_pixbuf_get_width (tile); tile_height = gdk_pixbuf_get_height (tile); pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, gdk_pixbuf_get_has_alpha (tile), 8, width, height); i = 0; while (i < width) { j = 0; while (j < height) { int w, h; w = MIN (tile_width, width - i); h = MIN (tile_height, height - j); gdk_pixbuf_copy_area (tile, 0, 0, w, h, pixbuf, i, j); j += tile_height; } i += tile_width; } return pixbuf; } static GdkPixbuf * replicate_rows (GdkPixbuf *src, int src_x, int src_y, int width, int height) { unsigned int n_channels = gdk_pixbuf_get_n_channels (src); unsigned int src_rowstride = gdk_pixbuf_get_rowstride (src); unsigned char *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x * n_channels); unsigned char *dest_pixels; GdkPixbuf *result; unsigned int dest_rowstride; int i; result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8, width, height); dest_rowstride = gdk_pixbuf_get_rowstride (result); dest_pixels = gdk_pixbuf_get_pixels (result); for (i = 0; i < height; i++) memcpy (dest_pixels + dest_rowstride * i, pixels, n_channels * width); return result; } static GdkPixbuf * replicate_cols (GdkPixbuf *src, int src_x, int src_y, int width, int height) { unsigned int n_channels = gdk_pixbuf_get_n_channels (src); unsigned int src_rowstride = gdk_pixbuf_get_rowstride (src); unsigned char *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x * n_channels); unsigned char *dest_pixels; GdkPixbuf *result; unsigned int dest_rowstride; int i, j; result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8, width, height); dest_rowstride = gdk_pixbuf_get_rowstride (result); dest_pixels = gdk_pixbuf_get_pixels (result); for (i = 0; i < height; i++) { unsigned char *p = dest_pixels + dest_rowstride * i; unsigned char *q = pixels + src_rowstride * i; unsigned char r = *(q++); unsigned char g = *(q++); unsigned char b = *(q++); if (n_channels == 4) { unsigned char a; a = *(q++); for (j = 0; j < width; j++) { *(p++) = r; *(p++) = g; *(p++) = b; *(p++) = a; } } else { for (j = 0; j < width; j++) { *(p++) = r; *(p++) = g; *(p++) = b; } } } return result; } static GdkPixbuf* scale_and_alpha_pixbuf (GdkPixbuf *src, MetaAlphaGradientSpec *alpha_spec, MetaImageFillType fill_type, int width, int height, gboolean vertical_stripes, gboolean horizontal_stripes) { GdkPixbuf *pixbuf; GdkPixbuf *temp_pixbuf; pixbuf = NULL; pixbuf = src; if (gdk_pixbuf_get_width (pixbuf) == width && gdk_pixbuf_get_height (pixbuf) == height) { g_object_ref (G_OBJECT (pixbuf)); } else { if (fill_type == META_IMAGE_FILL_TILE) { pixbuf = pixbuf_tile (pixbuf, width, height); } else { int src_h, src_w, dest_h, dest_w; src_h = gdk_pixbuf_get_height (src); src_w = gdk_pixbuf_get_width (src); /* prefer to replicate_cols if possible, as that * is faster (no memory reads) */ if (horizontal_stripes) { dest_w = gdk_pixbuf_get_width (src); dest_h = height; } else if (vertical_stripes) { dest_w = width; dest_h = gdk_pixbuf_get_height (src); } else { dest_w = width; dest_h = height; } if (dest_w == src_w && dest_h == src_h) { temp_pixbuf = src; g_object_ref (G_OBJECT (temp_pixbuf)); } else { temp_pixbuf = gdk_pixbuf_scale_simple (src, dest_w, dest_h, GDK_INTERP_BILINEAR); } /* prefer to replicate_cols if possible, as that * is faster (no memory reads) */ if (horizontal_stripes) { pixbuf = replicate_cols (temp_pixbuf, 0, 0, width, height); g_object_unref (G_OBJECT (temp_pixbuf)); } else if (vertical_stripes) { pixbuf = replicate_rows (temp_pixbuf, 0, 0, width, height); g_object_unref (G_OBJECT (temp_pixbuf)); } else { pixbuf = temp_pixbuf; } } } if (pixbuf) pixbuf = apply_alpha (pixbuf, alpha_spec, pixbuf == src); return pixbuf; } static GdkPixbuf* draw_op_as_pixbuf (const MetaDrawOp *op, GtkWidget *widget, const MetaDrawInfo *info, int width, int height) { /* Try to get the op as a pixbuf, assuming w/h in the op * matches the width/height passed in. return NULL * if the op can't be converted to an equivalent pixbuf. */ GdkPixbuf *pixbuf; pixbuf = NULL; switch (op->type) { case META_DRAW_LINE: break; case META_DRAW_RECTANGLE: if (op->data.rectangle.filled) { GdkColor color; meta_color_spec_render (op->data.rectangle.color_spec, widget, &color); pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, width, height); gdk_pixbuf_fill (pixbuf, GDK_COLOR_RGBA (color)); } break; case META_DRAW_ARC: break; case META_DRAW_CLIP: break; case META_DRAW_TINT: { GdkColor color; guint32 rgba; gboolean has_alpha; meta_color_spec_render (op->data.rectangle.color_spec, widget, &color); has_alpha = op->data.tint.alpha_spec && (op->data.tint.alpha_spec->n_alphas > 1 || op->data.tint.alpha_spec->alphas[0] != 0xff); pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, has_alpha, 8, width, height); if (!has_alpha) { rgba = GDK_COLOR_RGBA (color); gdk_pixbuf_fill (pixbuf, rgba); } else if (op->data.tint.alpha_spec->n_alphas == 1) { rgba = GDK_COLOR_RGBA (color); rgba &= ~0xff; rgba |= op->data.tint.alpha_spec->alphas[0]; gdk_pixbuf_fill (pixbuf, rgba); } else { rgba = GDK_COLOR_RGBA (color); gdk_pixbuf_fill (pixbuf, rgba); meta_gradient_add_alpha (pixbuf, op->data.tint.alpha_spec->alphas, op->data.tint.alpha_spec->n_alphas, op->data.tint.alpha_spec->type); } } break; case META_DRAW_GRADIENT: { pixbuf = meta_gradient_spec_render (op->data.gradient.gradient_spec, widget, width, height); pixbuf = apply_alpha (pixbuf, op->data.gradient.alpha_spec, FALSE); } break; case META_DRAW_IMAGE: { if (op->data.image.colorize_spec) { GdkColor color; meta_color_spec_render (op->data.image.colorize_spec, widget, &color); if (op->data.image.colorize_cache_pixbuf == NULL || op->data.image.colorize_cache_pixel != GDK_COLOR_RGB (color)) { if (op->data.image.colorize_cache_pixbuf) g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf)); /* const cast here */ ((MetaDrawOp*)op)->data.image.colorize_cache_pixbuf = colorize_pixbuf (op->data.image.pixbuf, &color); ((MetaDrawOp*)op)->data.image.colorize_cache_pixel = GDK_COLOR_RGB (color); } if (op->data.image.colorize_cache_pixbuf) { pixbuf = scale_and_alpha_pixbuf (op->data.image.colorize_cache_pixbuf, op->data.image.alpha_spec, op->data.image.fill_type, width, height, op->data.image.vertical_stripes, op->data.image.horizontal_stripes); } } else { pixbuf = scale_and_alpha_pixbuf (op->data.image.pixbuf, op->data.image.alpha_spec, op->data.image.fill_type, width, height, op->data.image.vertical_stripes, op->data.image.horizontal_stripes); } break; } case META_DRAW_GTK_ARROW: case META_DRAW_GTK_BOX: case META_DRAW_GTK_VLINE: break; case META_DRAW_ICON: if (info->mini_icon && width <= gdk_pixbuf_get_width (info->mini_icon) && height <= gdk_pixbuf_get_height (info->mini_icon)) pixbuf = scale_and_alpha_pixbuf (info->mini_icon, op->data.icon.alpha_spec, op->data.icon.fill_type, width, height, FALSE, FALSE); else if (info->icon) pixbuf = scale_and_alpha_pixbuf (info->icon, op->data.icon.alpha_spec, op->data.icon.fill_type, width, height, FALSE, FALSE); break; case META_DRAW_TITLE: break; case META_DRAW_OP_LIST: break; case META_DRAW_TILE: break; } return pixbuf; } static void fill_env (MetaPositionExprEnv *env, const MetaDrawInfo *info, MetaRectangle logical_region) { /* FIXME this stuff could be raised into draw_op_list_draw() probably */ env->rect = logical_region; env->object_width = -1; env->object_height = -1; if (info->fgeom) { env->left_width = info->fgeom->left_width; env->right_width = info->fgeom->right_width; env->top_height = info->fgeom->top_height; env->bottom_height = info->fgeom->bottom_height; } else { env->left_width = 0; env->right_width = 0; env->top_height = 0; env->bottom_height = 0; } env->mini_icon_width = info->mini_icon ? gdk_pixbuf_get_width (info->mini_icon) : 0; env->mini_icon_height = info->mini_icon ? gdk_pixbuf_get_height (info->mini_icon) : 0; env->icon_width = info->icon ? gdk_pixbuf_get_width (info->icon) : 0; env->icon_height = info->icon ? gdk_pixbuf_get_height (info->icon) : 0; env->title_width = info->title_layout_width; env->title_height = info->title_layout_height; env->theme = meta_current_theme; } static void meta_draw_op_draw_with_env (const MetaDrawOp *op, GtkStyle *style_gtk, GtkWidget *widget, GdkDrawable *drawable, const GdkRectangle *clip, const MetaDrawInfo *info, MetaRectangle rect, MetaPositionExprEnv *env) { GdkGC *gc; switch (op->type) { case META_DRAW_LINE: { int x1, x2, y1, y2; gc = get_gc_for_primitive (widget, drawable, op->data.line.color_spec, clip, op->data.line.width); if (op->data.line.dash_on_length > 0 && op->data.line.dash_off_length > 0) { gint8 dash_list[2]; dash_list[0] = op->data.line.dash_on_length; dash_list[1] = op->data.line.dash_off_length; gdk_gc_set_dashes (gc, 0, dash_list, 2); } x1 = parse_x_position_unchecked (op->data.line.x1, env); y1 = parse_y_position_unchecked (op->data.line.y1, env); if (!op->data.line.x2 && !op->data.line.y2 && op->data.line.width==0) gdk_draw_point (drawable, gc, x1, y1); else { if (op->data.line.x2) x2 = parse_x_position_unchecked (op->data.line.x2, env); else x2 = x1; if (op->data.line.y2) y2 = parse_y_position_unchecked (op->data.line.y2, env); else y2 = y1; gdk_draw_line (drawable, gc, x1, y1, x2, y2); } g_object_unref (G_OBJECT (gc)); } break; case META_DRAW_RECTANGLE: { int rx, ry, rwidth, rheight; gc = get_gc_for_primitive (widget, drawable, op->data.rectangle.color_spec, clip, 0); rx = parse_x_position_unchecked (op->data.rectangle.x, env); ry = parse_y_position_unchecked (op->data.rectangle.y, env); rwidth = parse_size_unchecked (op->data.rectangle.width, env); rheight = parse_size_unchecked (op->data.rectangle.height, env); gdk_draw_rectangle (drawable, gc, op->data.rectangle.filled, rx, ry, rwidth, rheight); g_object_unref (G_OBJECT (gc)); } break; case META_DRAW_ARC: { int rx, ry, rwidth, rheight; gc = get_gc_for_primitive (widget, drawable, op->data.arc.color_spec, clip, 0); rx = parse_x_position_unchecked (op->data.arc.x, env); ry = parse_y_position_unchecked (op->data.arc.y, env); rwidth = parse_size_unchecked (op->data.arc.width, env); rheight = parse_size_unchecked (op->data.arc.height, env); gdk_draw_arc (drawable, gc, op->data.arc.filled, rx, ry, rwidth, rheight, op->data.arc.start_angle * (360.0 * 64.0) - (90.0 * 64.0), /* start at 12 instead of 3 oclock */ op->data.arc.extent_angle * (360.0 * 64.0)); g_object_unref (G_OBJECT (gc)); } break; case META_DRAW_CLIP: break; case META_DRAW_TINT: { int rx, ry, rwidth, rheight; gboolean needs_alpha; needs_alpha = op->data.tint.alpha_spec && (op->data.tint.alpha_spec->n_alphas > 1 || op->data.tint.alpha_spec->alphas[0] != 0xff); rx = parse_x_position_unchecked (op->data.tint.x, env); ry = parse_y_position_unchecked (op->data.tint.y, env); rwidth = parse_size_unchecked (op->data.tint.width, env); rheight = parse_size_unchecked (op->data.tint.height, env); if (!needs_alpha) { gc = get_gc_for_primitive (widget, drawable, op->data.tint.color_spec, clip, 0); gdk_draw_rectangle (drawable, gc, TRUE, rx, ry, rwidth, rheight); g_object_unref (G_OBJECT (gc)); } else { GdkPixbuf *pixbuf; pixbuf = draw_op_as_pixbuf (op, widget, info, rwidth, rheight); if (pixbuf) { render_pixbuf (drawable, clip, pixbuf, rx, ry); g_object_unref (G_OBJECT (pixbuf)); } } } break; case META_DRAW_GRADIENT: { int rx, ry, rwidth, rheight; GdkPixbuf *pixbuf; rx = parse_x_position_unchecked (op->data.gradient.x, env); ry = parse_y_position_unchecked (op->data.gradient.y, env); rwidth = parse_size_unchecked (op->data.gradient.width, env); rheight = parse_size_unchecked (op->data.gradient.height, env); pixbuf = draw_op_as_pixbuf (op, widget, info, rwidth, rheight); if (pixbuf) { render_pixbuf (drawable, clip, pixbuf, rx, ry); g_object_unref (G_OBJECT (pixbuf)); } } break; case META_DRAW_IMAGE: { int rx, ry, rwidth, rheight; GdkPixbuf *pixbuf; if (op->data.image.pixbuf) { env->object_width = gdk_pixbuf_get_width (op->data.image.pixbuf); env->object_height = gdk_pixbuf_get_height (op->data.image.pixbuf); } rwidth = parse_size_unchecked (op->data.image.width, env); rheight = parse_size_unchecked (op->data.image.height, env); pixbuf = draw_op_as_pixbuf (op, widget, info, rwidth, rheight); if (pixbuf) { rx = parse_x_position_unchecked (op->data.image.x, env); ry = parse_y_position_unchecked (op->data.image.y, env); render_pixbuf (drawable, clip, pixbuf, rx, ry); g_object_unref (G_OBJECT (pixbuf)); } } break; case META_DRAW_GTK_ARROW: { int rx, ry, rwidth, rheight; rx = parse_x_position_unchecked (op->data.gtk_arrow.x, env); ry = parse_y_position_unchecked (op->data.gtk_arrow.y, env); rwidth = parse_size_unchecked (op->data.gtk_arrow.width, env); rheight = parse_size_unchecked (op->data.gtk_arrow.height, env); gtk_paint_arrow (style_gtk, drawable, op->data.gtk_arrow.state, op->data.gtk_arrow.shadow, (GdkRectangle*) clip, widget, "metacity", op->data.gtk_arrow.arrow, op->data.gtk_arrow.filled, rx, ry, rwidth, rheight); } break; case META_DRAW_GTK_BOX: { int rx, ry, rwidth, rheight; rx = parse_x_position_unchecked (op->data.gtk_box.x, env); ry = parse_y_position_unchecked (op->data.gtk_box.y, env); rwidth = parse_size_unchecked (op->data.gtk_box.width, env); rheight = parse_size_unchecked (op->data.gtk_box.height, env); gtk_paint_box (style_gtk, drawable, op->data.gtk_box.state, op->data.gtk_box.shadow, (GdkRectangle*) clip, widget, "metacity", rx, ry, rwidth, rheight); } break; case META_DRAW_GTK_VLINE: { int rx, ry1, ry2; rx = parse_x_position_unchecked (op->data.gtk_vline.x, env); ry1 = parse_y_position_unchecked (op->data.gtk_vline.y1, env); ry2 = parse_y_position_unchecked (op->data.gtk_vline.y2, env); gtk_paint_vline (style_gtk, drawable, op->data.gtk_vline.state, (GdkRectangle*) clip, widget, "metacity", ry1, ry2, rx); } break; case META_DRAW_ICON: { int rx, ry, rwidth, rheight; GdkPixbuf *pixbuf; rwidth = parse_size_unchecked (op->data.icon.width, env); rheight = parse_size_unchecked (op->data.icon.height, env); pixbuf = draw_op_as_pixbuf (op, widget, info, rwidth, rheight); if (pixbuf) { rx = parse_x_position_unchecked (op->data.icon.x, env); ry = parse_y_position_unchecked (op->data.icon.y, env); render_pixbuf (drawable, clip, pixbuf, rx, ry); g_object_unref (G_OBJECT (pixbuf)); } } break; case META_DRAW_TITLE: if (info->title_layout) { int rx, ry; gc = get_gc_for_primitive (widget, drawable, op->data.title.color_spec, clip, 0); rx = parse_x_position_unchecked (op->data.title.x, env); ry = parse_y_position_unchecked (op->data.title.y, env); gdk_draw_layout (drawable, gc, rx, ry, info->title_layout); g_object_unref (G_OBJECT (gc)); } break; case META_DRAW_OP_LIST: { MetaRectangle d_rect; d_rect.x = parse_x_position_unchecked (op->data.op_list.x, env); d_rect.y = parse_y_position_unchecked (op->data.op_list.y, env); d_rect.width = parse_size_unchecked (op->data.op_list.width, env); d_rect.height = parse_size_unchecked (op->data.op_list.height, env); meta_draw_op_list_draw_with_style (op->data.op_list.op_list, style_gtk, widget, drawable, clip, info, d_rect); } break; case META_DRAW_TILE: { int rx, ry, rwidth, rheight; int tile_xoffset, tile_yoffset; GdkRectangle new_clip; MetaRectangle tile; rx = parse_x_position_unchecked (op->data.tile.x, env); ry = parse_y_position_unchecked (op->data.tile.y, env); rwidth = parse_size_unchecked (op->data.tile.width, env); rheight = parse_size_unchecked (op->data.tile.height, env); new_clip.x = rx; new_clip.y = ry; new_clip.width = rwidth; new_clip.height = rheight; if (clip == NULL || gdk_rectangle_intersect ((GdkRectangle*)clip, &new_clip, &new_clip)) { tile_xoffset = parse_x_position_unchecked (op->data.tile.tile_xoffset, env); tile_yoffset = parse_y_position_unchecked (op->data.tile.tile_yoffset, env); /* tile offset should not include x/y */ tile_xoffset -= rect.x; tile_yoffset -= rect.y; tile.width = parse_size_unchecked (op->data.tile.tile_width, env); tile.height = parse_size_unchecked (op->data.tile.tile_height, env); tile.x = rx - tile_xoffset; while (tile.x < (rx + rwidth)) { tile.y = ry - tile_yoffset; while (tile.y < (ry + rheight)) { meta_draw_op_list_draw_with_style (op->data.tile.op_list, style_gtk, widget, drawable, &new_clip, info, tile); tile.y += tile.height; } tile.x += tile.width; } } } break; } } void meta_draw_op_draw_with_style (const MetaDrawOp *op, GtkStyle *style_gtk, GtkWidget *widget, GdkDrawable *drawable, const GdkRectangle *clip, const MetaDrawInfo *info, MetaRectangle logical_region) { MetaPositionExprEnv env; g_return_if_fail (style_gtk->colormap == gdk_drawable_get_colormap (drawable)); fill_env (&env, info, logical_region); meta_draw_op_draw_with_env (op, style_gtk, widget, drawable, clip, info, logical_region, &env); } void meta_draw_op_draw (const MetaDrawOp *op, GtkWidget *widget, GdkDrawable *drawable, const GdkRectangle *clip, const MetaDrawInfo *info, MetaRectangle logical_region) { meta_draw_op_draw_with_style (op, widget->style, widget, drawable, clip, info, logical_region); } MetaDrawOpList* meta_draw_op_list_new (int n_preallocs) { MetaDrawOpList *op_list; g_return_val_if_fail (n_preallocs >= 0, NULL); op_list = g_new (MetaDrawOpList, 1); op_list->refcount = 1; op_list->n_allocated = n_preallocs; op_list->ops = g_new (MetaDrawOp*, op_list->n_allocated); op_list->n_ops = 0; return op_list; } void meta_draw_op_list_ref (MetaDrawOpList *op_list) { g_return_if_fail (op_list != NULL); op_list->refcount += 1; } void meta_draw_op_list_unref (MetaDrawOpList *op_list) { g_return_if_fail (op_list != NULL); g_return_if_fail (op_list->refcount > 0); op_list->refcount -= 1; if (op_list->refcount == 0) { int i; for (i = 0; i < op_list->n_ops; i++) meta_draw_op_free (op_list->ops[i]); g_free (op_list->ops); DEBUG_FILL_STRUCT (op_list); g_free (op_list); } } void meta_draw_op_list_draw_with_style (const MetaDrawOpList *op_list, GtkStyle *style_gtk, GtkWidget *widget, GdkDrawable *drawable, const GdkRectangle *clip, const MetaDrawInfo *info, MetaRectangle rect) { int i; GdkRectangle active_clip; GdkRectangle orig_clip; MetaPositionExprEnv env; g_return_if_fail (style_gtk->colormap == gdk_drawable_get_colormap (drawable)); if (op_list->n_ops == 0) return; fill_env (&env, info, rect); /* FIXME this can be optimized, potentially a lot, by * compressing multiple ops when possible. For example, * anything convertible to a pixbuf can be composited * client-side, and putting a color tint over a pixbuf * can be done without creating the solid-color pixbuf. * * To implement this my plan is to have the idea of a * compiled draw op (with the string expressions already * evaluated), we make an array of those, and then fold * adjacent items when possible. */ if (clip) { orig_clip = *clip; } else { orig_clip.x = rect.x; orig_clip.y = rect.y; orig_clip.width = rect.width; orig_clip.height = rect.height; } active_clip = orig_clip; for (i = 0; i < op_list->n_ops; i++) { MetaDrawOp *op = op_list->ops[i]; if (op->type == META_DRAW_CLIP) { active_clip.x = parse_x_position_unchecked (op->data.clip.x, &env); active_clip.y = parse_y_position_unchecked (op->data.clip.y, &env); active_clip.width = parse_size_unchecked (op->data.clip.width, &env); active_clip.height = parse_size_unchecked (op->data.clip.height, &env); gdk_rectangle_intersect (&orig_clip, &active_clip, &active_clip); } else if (active_clip.width > 0 && active_clip.height > 0) { meta_draw_op_draw_with_env (op, style_gtk, widget, drawable, &active_clip, info, rect, &env); } } } void meta_draw_op_list_draw (const MetaDrawOpList *op_list, GtkWidget *widget, GdkDrawable *drawable, const GdkRectangle *clip, const MetaDrawInfo *info, MetaRectangle rect) { meta_draw_op_list_draw_with_style (op_list, widget->style, widget, drawable, clip, info, rect); } void meta_draw_op_list_append (MetaDrawOpList *op_list, MetaDrawOp *op) { if (op_list->n_ops == op_list->n_allocated) { op_list->n_allocated *= 2; op_list->ops = g_renew (MetaDrawOp*, op_list->ops, op_list->n_allocated); } op_list->ops[op_list->n_ops] = op; op_list->n_ops += 1; } gboolean meta_draw_op_list_validate (MetaDrawOpList *op_list, GError **error) { g_return_val_if_fail (op_list != NULL, FALSE); /* empty lists are OK, nothing else to check really */ return TRUE; } /* This is not done in validate, since we wouldn't know the name * of the list to report the error. It might be nice to * store names inside the list sometime. */ gboolean meta_draw_op_list_contains (MetaDrawOpList *op_list, MetaDrawOpList *child) { int i; /* mmm, huge tree recursion */ for (i = 0; i < op_list->n_ops; i++) { if (op_list->ops[i]->type == META_DRAW_OP_LIST) { if (op_list->ops[i]->data.op_list.op_list == child) return TRUE; if (meta_draw_op_list_contains (op_list->ops[i]->data.op_list.op_list, child)) return TRUE; } else if (op_list->ops[i]->type == META_DRAW_TILE) { if (op_list->ops[i]->data.tile.op_list == child) return TRUE; if (meta_draw_op_list_contains (op_list->ops[i]->data.tile.op_list, child)) return TRUE; } } return FALSE; } /** * Constructor for a MetaFrameStyle. * * \param parent The parent style. Data not filled in here will be * looked for in the parent style, and in its parent * style, and so on. * * \return The newly-constructed style. */ MetaFrameStyle* meta_frame_style_new (MetaFrameStyle *parent) { MetaFrameStyle *style; style = g_new0 (MetaFrameStyle, 1); style->refcount = 1; /* Default alpha is fully opaque */ style->window_background_alpha = 255; style->parent = parent; if (parent) meta_frame_style_ref (parent); return style; } /** * Increases the reference count of a frame style. * If the style is NULL, this is a no-op. * * \param style The style. */ void meta_frame_style_ref (MetaFrameStyle *style) { g_return_if_fail (style != NULL); style->refcount += 1; } static void free_button_ops (MetaDrawOpList *op_lists[META_BUTTON_TYPE_LAST][META_BUTTON_STATE_LAST]) { int i, j; for (i = 0; i < META_BUTTON_TYPE_LAST; i++) for (j = 0; j < META_BUTTON_STATE_LAST; j++) if (op_lists[i][j]) meta_draw_op_list_unref (op_lists[i][j]); } void meta_frame_style_unref (MetaFrameStyle *style) { g_return_if_fail (style != NULL); g_return_if_fail (style->refcount > 0); style->refcount -= 1; if (style->refcount == 0) { int i; free_button_ops (style->buttons); for (i = 0; i < META_FRAME_PIECE_LAST; i++) if (style->pieces[i]) meta_draw_op_list_unref (style->pieces[i]); if (style->layout) meta_frame_layout_unref (style->layout); if (style->window_background_color) meta_color_spec_free (style->window_background_color); /* we hold a reference to any parent style */ if (style->parent) meta_frame_style_unref (style->parent); DEBUG_FILL_STRUCT (style); g_free (style); } } static MetaDrawOpList* get_button (MetaFrameStyle *style, MetaButtonType type, MetaButtonState state) { MetaDrawOpList *op_list; MetaFrameStyle *parent; parent = style; op_list = NULL; while (parent && op_list == NULL) { op_list = parent->buttons[type][state]; parent = parent->parent; } /* We fall back to middle button backgrounds if we don't * have the ones on the sides */ if (op_list == NULL && (type == META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND || type == META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND)) return get_button (style, META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND, state); if (op_list == NULL && (type == META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND || type == META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND)) return get_button (style, META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND, state); /* We fall back to normal if no prelight */ if (op_list == NULL && state == META_BUTTON_STATE_PRELIGHT) return get_button (style, type, META_BUTTON_STATE_NORMAL); return op_list; } gboolean meta_frame_style_validate (MetaFrameStyle *style, guint current_theme_version, GError **error) { int i, j; g_return_val_if_fail (style != NULL, FALSE); g_return_val_if_fail (style->layout != NULL, FALSE); for (i = 0; i < META_BUTTON_TYPE_LAST; i++) { /* for now the "positional" buttons are optional */ if (i >= META_BUTTON_TYPE_CLOSE) { for (j = 0; j < META_BUTTON_STATE_LAST; j++) { if (get_button (style, i, j) == NULL && meta_theme_earliest_version_with_button (i) <= current_theme_version ) { g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, _("