diff options
-rw-r--r-- | ChangeLog | 42 | ||||
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | configure.in | 46 | ||||
-rw-r--r-- | src/Makefile.am | 10 | ||||
-rw-r--r-- | src/common.h | 2 | ||||
-rw-r--r-- | src/core.c | 59 | ||||
-rw-r--r-- | src/core.h | 4 | ||||
-rw-r--r-- | src/display.c | 31 | ||||
-rw-r--r-- | src/display.h | 1 | ||||
-rw-r--r-- | src/frames.c | 739 | ||||
-rw-r--r-- | src/frames.h | 10 | ||||
-rw-r--r-- | src/gradient.c | 5 | ||||
-rw-r--r-- | src/gradient.h | 3 | ||||
-rw-r--r-- | src/main.c | 37 | ||||
-rw-r--r-- | src/menu.c | 85 | ||||
-rw-r--r-- | src/metacity.schemas | 15 | ||||
-rw-r--r-- | src/prefs.c | 67 | ||||
-rw-r--r-- | src/prefs.h | 3 | ||||
-rw-r--r-- | src/screen.c | 2 | ||||
-rw-r--r-- | src/stack.c | 9 | ||||
-rw-r--r-- | src/tabpopup.c | 2 | ||||
-rw-r--r-- | src/theme-parser.c | 3920 | ||||
-rw-r--r-- | src/theme-parser.h | 30 | ||||
-rw-r--r-- | src/theme-viewer.c | 287 | ||||
-rw-r--r-- | src/theme.c | 3305 | ||||
-rw-r--r-- | src/theme.h | 325 | ||||
-rw-r--r-- | src/themes/Atlanta/metacity-theme-1.xml | 239 | ||||
-rw-r--r-- | src/themes/Makefile.am | 29 | ||||
-rw-r--r-- | src/themewidget.c | 181 | ||||
-rw-r--r-- | src/themewidget.h | 76 | ||||
-rw-r--r-- | src/tools/Makefile.am | 8 | ||||
-rw-r--r-- | src/tools/metacity-reload-theme.c | 56 | ||||
-rw-r--r-- | src/ui.c | 14 | ||||
-rw-r--r-- | src/ui.h | 5 | ||||
-rw-r--r-- | src/window.c | 16 | ||||
-rw-r--r-- | theme-format.txt | 218 |
36 files changed, 8382 insertions, 1501 deletions
@@ -1,3 +1,45 @@ +2002-02-06 Havoc Pennington <hp@pobox.com> + + * src/main.c (main): disable custom log handler and fatal mask for + now + + * src/theme.c (meta_draw_op_list_draw): + Add META_DRAW_CLIP + + * src/main.c: load theme, monitor current theme setting + + * src/prefs.c: add "current theme" setting + + * src/stack.c (meta_stack_free): don't try to free + last_root_children_stacked if it doesn't exist + + * src/themewidget.c: pluggable GtkMisc subclass to use + for menu icons + + * src/screen.c (meta_screen_manage_all_windows): fix + signed/unsigned warning + + * src/frames.c: port to theme system + (meta_frames_style_set): chain up + + * theme-format.txt: new file + + * configure.in: add more compiler warnings + + * src/theme.c: add various stuff needed to get theme parser + working. Remove the "spacer" concept from FrameLayout object. + Add draw op that references a draw op list. + + * configure.in: require GTK 1.3.13 + + * src/Makefile.am: add theme-parser.[hc], implement loading a + theme + + * src/theme.c: add "draw title" and "draw window icon" operations + (meta_draw_op_draw): put object_width/object_height in expression + environment before computing x/y. Handle out-of-memory when + creating pixbufs. Assorted other cleanups. + 2002-02-07 Anders Carlsson <andersca@gnu.org> * src/themes/Crux/metacity-theme-1.xml: diff --git a/Makefile.am b/Makefile.am index e7cccc70..d439b104 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ SUBDIRS=src -EXTRA_DIST=HACKING +EXTRA_DIST=HACKING theme-format.txt diff --git a/configure.in b/configure.in index 07b33adf..af9444c0 100644 --- a/configure.in +++ b/configure.in @@ -26,6 +26,46 @@ if test "x$GCC" = "xyes"; then *) CFLAGS="$CFLAGS -Wall" ;; esac + case " $CFLAGS " in + *[\ \ ]-Wshadow[\ \ ]*) ;; + *) CFLAGS="$CFLAGS -Wshadow" ;; + esac + + case " $CFLAGS " in + *[\ \ ]-Wchar-subscripts[\ \ ]*) ;; + *) CFLAGS="$CFLAGS -Wchar-subscripts" ;; + esac + + case " $CFLAGS " in + *[\ \ ]-Wmissing-declarations[\ \ ]*) ;; + *) CFLAGS="$CFLAGS -Wmissing-declarations" ;; + esac + + case " $CFLAGS " in + *[\ \ ]-Wmissing-prototypes[\ \ ]*) ;; + *) CFLAGS="$CFLAGS -Wmissing-prototypes" ;; + esac + + case " $CFLAGS " in + *[\ \ ]-Wnested-externs[\ \ ]*) ;; + *) CFLAGS="$CFLAGS -Wnested-externs" ;; + esac + + case " $CFLAGS " in + *[\ \ ]-Wpointer-arith[\ \ ]*) ;; + *) CFLAGS="$CFLAGS -Wpointer-arith" ;; + esac + + case " $CFLAGS " in + *[\ \ ]-Wcast-align[\ \ ]*) ;; + *) CFLAGS="$CFLAGS -Wcast-align" ;; + esac + + case " $CFLAGS " in + *[\ \ ]-Wsign-compare[\ \ ]*) ;; + *) CFLAGS="$CFLAGS -Wsign-compare" ;; + esac + if test "x$enable_ansi" = "xyes"; then case " $CFLAGS " in *[\ \ ]-ansi[\ \ ]*) ;; @@ -44,8 +84,9 @@ ALL_LINGUAS="da es gl lv ms no pt ru sk sv tr uk" AM_GLIB_GNU_GETTEXT ## here we get the flags we'll actually use -PKG_CHECK_MODULES(METACITY, gtk+-2.0 >= 1.3.11 gconf-2.0 >= 1.1.5) -PKG_CHECK_MODULES(METACITY_RESTART, gtk+-2.0 >= 1.3.11) +PKG_CHECK_MODULES(METACITY, gtk+-2.0 >= 1.3.13 gconf-2.0 >= 1.1.5) +PKG_CHECK_MODULES(METACITY_RESTART, gtk+-2.0 >= 1.3.13) +PKG_CHECK_MODULES(METACITY_RELOAD_THEME, gtk+-2.0 >= 1.3.13) CFLAGS="$METACITY_CFLAGS $CFLAGS" @@ -98,4 +139,5 @@ Makefile src/Makefile src/wm-tester/Makefile src/tools/Makefile +src/themes/Makefile ]) diff --git a/src/Makefile.am b/src/Makefile.am index 8391041a..6ba04979 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,7 @@ -SUBDIRS=wm-tester tools +SUBDIRS=wm-tester tools themes -INCLUDES=@METACITY_CFLAGS@ -DMETACITY_LIBEXECDIR=\"$(libexecdir)\" -DHOST_ALIAS=\"@HOST_ALIAS@\" -DMETACITY_LOCALEDIR=\"$(datadir)/locale\" +INCLUDES=@METACITY_CFLAGS@ -DMETACITY_LIBEXECDIR=\"$(libexecdir)\" -DHOST_ALIAS=\"@HOST_ALIAS@\" -DMETACITY_LOCALEDIR=\"$(datadir)/locale\" -DMETACITY_PKGDATADIR=\"$(pkgdatadir)\" metacity_SOURCES= \ common.h \ @@ -44,6 +44,10 @@ metacity_SOURCES= \ tabpopup.h \ theme.c \ theme.h \ + theme-parser.c \ + theme-parser.h \ + themewidget.c \ + themewidget.h \ ui.c \ ui.h \ util.c \ @@ -60,6 +64,8 @@ metacity_theme_viewer_SOURCES= \ gradient.h \ theme.c \ theme.h \ + theme-parser.c \ + theme-parser.h \ theme-viewer.c \ util.c \ util.h diff --git a/src/common.h b/src/common.h index 62369c40..de34df8a 100644 --- a/src/common.h +++ b/src/common.h @@ -135,7 +135,7 @@ typedef enum META_FRAME_TYPE_MODAL_DIALOG, META_FRAME_TYPE_UTILITY, META_FRAME_TYPE_MENU, - META_FRAME_TYPE_TOOLBAR, + /* META_FRAME_TYPE_TOOLBAR, */ META_FRAME_TYPE_LAST } MetaFrameType; @@ -60,6 +60,49 @@ meta_core_get_frame_flags (Display *xdisplay, return meta_frame_get_flags (window->frame); } +MetaFrameType +meta_core_get_frame_type (Display *xdisplay, + Window frame_xwindow) +{ + MetaDisplay *display; + MetaWindow *window; + + display = meta_display_for_x_display (xdisplay); + window = meta_display_lookup_x_window (display, frame_xwindow); + + if (window == NULL || window->frame == NULL) + meta_bug ("No such frame window 0x%lx!\n", frame_xwindow); + + switch (window->type) + { + case META_WINDOW_NORMAL: + return META_FRAME_TYPE_NORMAL; + break; + + case META_WINDOW_DIALOG: + return META_FRAME_TYPE_DIALOG; + break; + + case META_WINDOW_MODAL_DIALOG: + return META_FRAME_TYPE_MODAL_DIALOG; + break; + + case META_WINDOW_MENU: + return META_FRAME_TYPE_MENU; + break; + + case META_WINDOW_DESKTOP: + case META_WINDOW_DOCK: + case META_WINDOW_TOOLBAR: + /* No frame */ + return META_FRAME_TYPE_LAST; + break; + } + + g_assert_not_reached (); + return META_FRAME_TYPE_LAST; +} + GdkPixbuf* meta_core_get_mini_icon (Display *xdisplay, Window frame_xwindow) @@ -76,6 +119,22 @@ meta_core_get_mini_icon (Display *xdisplay, return window->mini_icon; } +GdkPixbuf* +meta_core_get_icon (Display *xdisplay, + Window frame_xwindow) +{ + MetaDisplay *display; + MetaWindow *window; + + display = meta_display_for_x_display (xdisplay); + window = meta_display_lookup_x_window (display, frame_xwindow); + + if (window == NULL || window->frame == NULL) + meta_bug ("No such frame window 0x%lx!\n", frame_xwindow); + + return window->icon; +} + void meta_core_queue_frame_resize (Display *xdisplay, Window frame_xwindow) @@ -33,9 +33,13 @@ void meta_core_get_client_size (Display *xdisplay, MetaFrameFlags meta_core_get_frame_flags (Display *xdisplay, Window frame_xwindow); +MetaFrameType meta_core_get_frame_type (Display *xdisplay, + Window frame_xwindow); GdkPixbuf* meta_core_get_mini_icon (Display *xdisplay, Window frame_xwindow); +GdkPixbuf* meta_core_get_icon (Display *xdisplay, + Window frame_xwindow); void meta_core_queue_frame_resize (Display *xdisplay, Window frame_xwindow); diff --git a/src/display.c b/src/display.c index 45511d54..073232d4 100644 --- a/src/display.c +++ b/src/display.c @@ -139,9 +139,10 @@ meta_display_open (const char *name) "_KWM_WIN_ICON", "_NET_WM_MOVERESIZE", "_NET_ACTIVE_WINDOW", - "_METACITY_RESTART_MESSAGE", + "_METACITY_RESTART_MESSAGE", "_NET_WM_STRUT", - "_WIN_HINTS" + "_WIN_HINTS", + "_METACITY_RELOAD_THEME_MESSAGE" }; Atom atoms[G_N_ELEMENTS(atom_names)]; @@ -237,6 +238,7 @@ meta_display_open (const char *name) display->atom_metacity_restart_message = atoms[44]; display->atom_net_wm_strut = atoms[45]; display->atom_win_hints = atoms[46]; + display->atom_metacity_reload_theme_message = atoms[47]; /* Offscreen unmapped window used for _NET_SUPPORTING_WM_CHECK, * created in screen_new @@ -668,7 +670,7 @@ event_callback (XEvent *event, /* mark double click events, kind of a hack, oh well. */ if (event->type == ButtonPress) { - if (event->xbutton.button == display->last_button_num && + if (((int)event->xbutton.button) == display->last_button_num && event->xbutton.window == display->last_button_xwindow && event->xbutton.time < (display->last_button_time + display->double_click_time)) { @@ -716,7 +718,7 @@ event_callback (XEvent *event, break; case ButtonPress: if ((grab_op_is_mouse (display->grab_op) && - display->grab_button != event->xbutton.button && + display->grab_button != (int) event->xbutton.button && display->grab_window == window) || grab_op_is_keyboard (display->grab_op)) { @@ -1104,6 +1106,13 @@ event_callback (XEvent *event, meta_verbose ("Received restart request\n"); meta_restart (); } + else if (event->xclient.message_type == + display->atom_metacity_reload_theme_message) + { + meta_verbose ("Received reload theme request\n"); + meta_ui_set_current_theme (meta_prefs_get_theme (), + TRUE); + } } } break; @@ -1567,15 +1576,15 @@ meta_display_unregister_x_window (MetaDisplay *display, MetaWorkspace* meta_display_get_workspace_by_index (MetaDisplay *display, - int index) + int idx) { GList *tmp; /* should be robust, index is maybe from an app */ - if (index < 0) + if (idx < 0) return NULL; - tmp = g_list_nth (display->workspaces, index); + tmp = g_list_nth (display->workspaces, idx); if (tmp == NULL) return NULL; @@ -1586,13 +1595,13 @@ meta_display_get_workspace_by_index (MetaDisplay *display, MetaWorkspace* meta_display_get_workspace_by_screen_index (MetaDisplay *display, MetaScreen *screen, - int index) + int idx) { GList *tmp; int i; - /* should be robust, index is maybe from an app */ - if (index < 0) + /* should be robust, idx is maybe from an app */ + if (idx < 0) return NULL; i = 0; @@ -1603,7 +1612,7 @@ meta_display_get_workspace_by_screen_index (MetaDisplay *display, if (w->screen == screen) { - if (i == index) + if (i == idx) return w; else ++i; diff --git a/src/display.h b/src/display.h index bf5186c6..f1ef4240 100644 --- a/src/display.h +++ b/src/display.h @@ -106,6 +106,7 @@ struct _MetaDisplay Atom atom_metacity_restart_message; Atom atom_net_wm_strut; Atom atom_win_hints; + Atom atom_metacity_reload_theme_message; /* This is the actual window from focus events, * not the one we last set diff --git a/src/frames.c b/src/frames.c index e2c6f5fa..990eb04b 100644 --- a/src/frames.c +++ b/src/frames.c @@ -128,24 +128,6 @@ meta_frames_get_type (void) return frames_type; } -#define BORDER_PROPERTY(name, blurb, docs) \ - gtk_widget_class_install_style_property (widget_class, \ - g_param_spec_boxed (name, \ - blurb, \ - docs, \ - GTK_TYPE_BORDER, \ - G_PARAM_READABLE)) - -#define INT_PROPERTY(name, default, blurb, docs) \ - gtk_widget_class_install_style_property (widget_class, \ - g_param_spec_int (name, \ - blurb, \ - docs, \ - 0, \ - G_MAXINT, \ - default, \ - G_PARAM_READABLE)) - static void meta_frames_class_init (MetaFramesClass *class) { @@ -174,27 +156,6 @@ meta_frames_class_init (MetaFramesClass *class) widget_class->button_release_event = meta_frames_button_release_event; widget_class->motion_notify_event = meta_frames_motion_notify_event; widget_class->leave_notify_event = meta_frames_leave_notify_event; - - INT_PROPERTY ("left_width", 6, _("Left edge"), _("Left window edge width")); - INT_PROPERTY ("right_width", 6, _("Right edge"), _("Right window edge width")); - INT_PROPERTY ("bottom_height", 7, _("Bottom edge"), _("Bottom window edge height")); - - BORDER_PROPERTY ("title_border", _("Title border"), _("Border around title area")); - BORDER_PROPERTY ("text_border", _("Text border"), _("Border around window title text")); - - INT_PROPERTY ("spacer_padding", 3, _("Spacer padding"), _("Padding on either side of spacer")); - INT_PROPERTY ("spacer_width", 2, _("Spacer width"), _("Width of spacer")); - INT_PROPERTY ("spacer_height", 11, _("Spacer height"), _("Height of spacer")); - - /* same as right_width left_width by default */ - INT_PROPERTY ("right_inset", 6, _("Right inset"), _("Distance of buttons from right edge of frame")); - INT_PROPERTY ("left_inset", 6, _("Left inset"), _("Distance of menu button from left edge of frame")); - - INT_PROPERTY ("button_width", 17, _("Button width"), _("Width of buttons")); - INT_PROPERTY ("button_height", 17, _("Button height"), _("Height of buttons")); - - BORDER_PROPERTY ("button_border", _("Button border"), _("Border around buttons")); - BORDER_PROPERTY ("inner_button_border", _("Inner button border"), _("Border around the icon inside buttons")); } static gint @@ -222,8 +183,6 @@ meta_frames_init (MetaFrames *frames) { GTK_WINDOW (frames)->type = GTK_WINDOW_POPUP; - frames->layout = meta_frame_layout_new (); - frames->frames = g_hash_table_new (unsigned_long_hash, unsigned_long_equal); frames->tooltip_timeout = 0; @@ -282,8 +241,6 @@ meta_frames_finalize (GObject *object) g_assert (g_hash_table_size (frames->frames) == 0); g_hash_table_destroy (frames->frames); - - meta_frame_layout_free (frames->layout); G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -327,107 +284,23 @@ meta_frames_style_set (GtkWidget *widget, GtkStyle *prev_style) { MetaFrames *frames; - /* left, right, top, bottom */ - static GtkBorder default_title_border = { 3, 4, 4, 3 }; - static GtkBorder default_text_border = { 2, 2, 2, 2 }; - static GtkBorder default_button_border = { 0, 0, 1, 1 }; - static GtkBorder default_inner_button_border = { - DEFAULT_INNER_BUTTON_BORDER, - DEFAULT_INNER_BUTTON_BORDER, - DEFAULT_INNER_BUTTON_BORDER, - DEFAULT_INNER_BUTTON_BORDER - }; - GtkBorder *title_border; - GtkBorder *text_border; - GtkBorder *button_border; - GtkBorder *inner_button_border; - MetaFrameLayout layout; frames = META_FRAMES (widget); - - gtk_widget_style_get (widget, - "left_width", - &layout.left_width, - "right_width", - &layout.right_width, - "bottom_height", - &layout.bottom_height, - "title_border", - &title_border, - "text_border", - &text_border, - "spacer_padding", - &layout.spacer_padding, - "spacer_width", - &layout.spacer_width, - "spacer_height", - &layout.spacer_height, - "right_inset", - &layout.right_inset, - "left_inset", - &layout.left_inset, - "button_width", - &layout.button_width, - "button_height", - &layout.button_height, - "button_border", - &button_border, - "inner_button_border", - &inner_button_border, - NULL); - - if (title_border) - layout.title_border = *title_border; - else - layout.title_border = default_title_border; - g_free (title_border); - - if (text_border) - layout.text_border = *text_border; - else - layout.text_border = default_text_border; - - g_free (text_border); - - if (button_border) - layout.button_border = *button_border; - else - layout.button_border = default_button_border; - - g_free (button_border); - - if (inner_button_border) - layout.inner_button_border = *inner_button_border; + if (GTK_WIDGET_REALIZED (widget)) + { + frames->text_height = meta_gtk_widget_get_text_height (widget); + } else - layout.inner_button_border = default_inner_button_border; - - g_free (inner_button_border); - - *(frames->layout) = layout; - - { - PangoFontMetrics *metrics; - PangoFont *font; - PangoLanguage *lang; - - font = pango_context_load_font (gtk_widget_get_pango_context (widget), - widget->style->font_desc); - lang = pango_context_get_language (gtk_widget_get_pango_context (widget)); - metrics = pango_font_get_metrics (font, lang); - - g_object_unref (G_OBJECT (font)); - - frames->text_height = - PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) + - pango_font_metrics_get_descent (metrics)); - - pango_font_metrics_unref (metrics); - } - + { + frames->text_height = 0; + } + /* Queue a draw/resize on all frames */ g_hash_table_foreach (frames->frames, queue_recalc_func, frames); + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); } static void @@ -437,18 +310,20 @@ meta_frames_calc_geometry (MetaFrames *frames, { int width, height; MetaFrameFlags flags; + MetaFrameType type; meta_core_get_client_size (gdk_display, frame->xwindow, &width, &height); flags = meta_core_get_frame_flags (gdk_display, frame->xwindow); - - meta_frame_layout_calc_geometry (frames->layout, - GTK_WIDGET (frames), - frames->text_height, - flags, - width, height, - fgeom); + type = meta_core_get_frame_type (gdk_display, frame->xwindow); + + meta_theme_calc_geometry (meta_theme_get_current (), + type, + frames->text_height, + flags, + width, height, + fgeom); } MetaFrames* @@ -526,6 +401,8 @@ meta_frames_realize (GtkWidget *widget) if (GTK_WIDGET_CLASS (parent_class)->realize) GTK_WIDGET_CLASS (parent_class)->realize (widget); + + frames->text_height = meta_gtk_widget_get_text_height (widget); } static void @@ -537,6 +414,8 @@ meta_frames_unrealize (GtkWidget *widget) if (GTK_WIDGET_CLASS (parent_class)->unrealize) GTK_WIDGET_CLASS (parent_class)->unrealize (widget); + + frames->text_height = 0; } static MetaUIFrame* @@ -556,27 +435,31 @@ meta_frames_get_geometry (MetaFrames *frames, int *top_height, int *bottom_height, int *left_width, int *right_width) { - MetaFrameFlags flags; + MetaFrameFlags flags; MetaUIFrame *frame; - + MetaFrameType type; + frame = meta_frames_lookup_window (frames, xwindow); if (frame == NULL) meta_bug ("No such frame 0x%lx\n", xwindow); - flags = meta_core_get_frame_flags (gdk_display, frame->xwindow); + flags = meta_core_get_frame_flags (gdk_display, frame->xwindow); + type = meta_core_get_frame_type (gdk_display, frame->xwindow); + g_return_if_fail (type < META_FRAME_TYPE_LAST); + /* We can't get the full geometry, because that depends on * the client window size and probably we're being called * by the core move/resize code to decide on the client * window size */ - meta_frame_layout_get_borders (frames->layout, - GTK_WIDGET (frames), - frames->text_height, - flags, - top_height, bottom_height, - left_width, right_width); + meta_theme_get_frame_borders (meta_theme_get_current (), + type, + frames->text_height, + flags, + top_height, bottom_height, + left_width, right_width); } void @@ -1082,7 +965,7 @@ meta_frames_button_release_event (GtkWidget *widget, * frame are handled in the Xlib part of the code, display.c/window.c */ if (frame->xwindow == meta_core_get_grab_frame (gdk_display) && - event->button == meta_core_get_grab_button (gdk_display)) + ((int) event->button) == meta_core_get_grab_button (gdk_display)) { gboolean end_grab; @@ -1264,228 +1147,6 @@ meta_frames_destroy_event (GtkWidget *widget, return TRUE; } -#define THICK_LINE_WIDTH 3 -static void -draw_mini_window (MetaFrames *frames, - GdkDrawable *drawable, - GdkGC *fg_gc, - GdkGC *bg_gc, - gboolean thin_title, - int x, int y, int width, int height) -{ - GdkGCValues vals; - - gdk_draw_rectangle (drawable, - bg_gc, - TRUE, - x, y, width - 1, height - 1); - - gdk_draw_rectangle (drawable, - fg_gc, - FALSE, - x, y, width - 1, height - 1); - - vals.line_width = thin_title ? THICK_LINE_WIDTH - 1 : THICK_LINE_WIDTH; - gdk_gc_set_values (fg_gc, - &vals, - GDK_GC_LINE_WIDTH); - - gdk_draw_line (drawable, - fg_gc, - x, y + 1, x + width, y + 1); - - vals.line_width = 0; - gdk_gc_set_values (fg_gc, - &vals, - GDK_GC_LINE_WIDTH); -} - -static void -draw_control (MetaFrames *frames, - GdkDrawable *drawable, - GdkGC *fg_override, - GdkGC *bg_override, - MetaFrameControl control, - int x, int y, int width, int height) -{ - GtkWidget *widget; - GdkGCValues vals; - GdkGC *fg_gc; - GdkGC *bg_gc; - - widget = GTK_WIDGET (frames); - - fg_gc = fg_override ? fg_override : widget->style->fg_gc[GTK_STATE_NORMAL]; - bg_gc = bg_override ? bg_override : widget->style->bg_gc[GTK_STATE_NORMAL]; - - switch (control) - { - case META_FRAME_CONTROL_DELETE: - { - gdk_draw_line (drawable, - fg_gc, - x, y, x + width - 1, y + height - 1); - - gdk_draw_line (drawable, - fg_gc, - x, y + height - 1, x + width - 1, y); - } - break; - - case META_FRAME_CONTROL_MAXIMIZE: - { - draw_mini_window (frames, drawable, fg_gc, bg_gc, FALSE, - x, y, width, height); - } - break; - - case META_FRAME_CONTROL_UNMAXIMIZE: - { - int w_delta = width * 0.3; - int h_delta = height * 0.3; - - w_delta = MAX (w_delta, 3); - h_delta = MAX (h_delta, 3); - - draw_mini_window (frames, drawable, fg_gc, bg_gc, TRUE, - x, y, width - w_delta, height - h_delta); - draw_mini_window (frames, drawable, fg_gc, bg_gc, TRUE, - x + w_delta, y + h_delta, - width - w_delta, height - h_delta); - } - break; - - case META_FRAME_CONTROL_MINIMIZE: - { - - vals.line_width = THICK_LINE_WIDTH; - gdk_gc_set_values (fg_gc, - &vals, - GDK_GC_LINE_WIDTH); - - gdk_draw_line (drawable, - fg_gc, - x, y + height - THICK_LINE_WIDTH + 1, - x + width, y + height - THICK_LINE_WIDTH + 1); - - vals.line_width = 0; - gdk_gc_set_values (fg_gc, - &vals, - GDK_GC_LINE_WIDTH); - } - break; - - default: - break; - } -} -#undef THICK_LINE_WIDTH - -void -meta_frames_get_pixmap_for_control (MetaFrames *frames, - MetaFrameControl control, - GdkPixmap **pixmapp, - GdkBitmap **maskp) -{ - int w, h; - GdkPixmap *pix; - GdkBitmap *mask; - GtkWidget *widget; - GdkGC *mgc, *mgc_bg; - GdkColor color; - - widget = GTK_WIDGET (frames); - - gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h); - - w -= DEFAULT_INNER_BUTTON_BORDER * 2; - h -= DEFAULT_INNER_BUTTON_BORDER * 2; - - /* avoid crashing on bizarre icon sizes */ - if (w < 1) - w = 1; - if (h < 1) - h = 1; - - pix = gdk_pixmap_new (NULL, w, h, gtk_widget_get_visual (widget)->depth); - mask = gdk_pixmap_new (NULL, w, h, 1); - - mgc = gdk_gc_new (mask); - mgc_bg = gdk_gc_new (mask); - - color.pixel = 0; - gdk_gc_set_foreground (mgc_bg, &color); - color.pixel = 1; - gdk_gc_set_foreground (mgc, &color); - - gdk_draw_rectangle (mask, mgc_bg, TRUE, 0, 0, -1, -1); - - draw_control (frames, mask, mgc, mgc_bg, control, 0, 0, w, h); - - gdk_gc_unref (mgc); - gdk_gc_unref (mgc_bg); - - draw_control (frames, pix, NULL, NULL, control, 0, 0, w, h); - - *pixmapp = pix; - *maskp = mask; -} - -static void -draw_control_bg (MetaFrames *frames, - MetaUIFrame *frame, - GdkDrawable *drawable, - MetaFrameControl control, - MetaFrameGeometry *fgeom) -{ - GdkRectangle *rect; - GtkWidget *widget; - gboolean draw = FALSE; - Window grab_frame; - - widget = GTK_WIDGET (frames); - - grab_frame = meta_core_get_grab_frame (gdk_display); - - if (frame->xwindow == grab_frame) - { - switch (meta_core_get_grab_op (gdk_display)) - { - case META_GRAB_OP_CLICKING_MENU: - draw = control == META_FRAME_CONTROL_MENU; - break; - case META_GRAB_OP_CLICKING_DELETE: - draw = control == META_FRAME_CONTROL_DELETE; - break; - case META_GRAB_OP_CLICKING_MAXIMIZE: - draw = control == META_FRAME_CONTROL_MAXIMIZE; - break; - case META_GRAB_OP_CLICKING_UNMAXIMIZE: - draw = control == META_FRAME_CONTROL_UNMAXIMIZE; - break; - case META_GRAB_OP_CLICKING_MINIMIZE: - draw = control == META_FRAME_CONTROL_MINIMIZE; - break; - default: - break; - } - } - - if (draw) - { - rect = control_rect (control, fgeom); - - if (rect == NULL) - return; - - gtk_paint_box (widget->style, drawable, - GTK_STATE_ACTIVE, - GTK_SHADOW_IN, NULL, - widget, "button", - rect->x, rect->y, rect->width, rect->height); - } -} - static gboolean meta_frames_expose_event (GtkWidget *widget, GdkEventExpose *event) @@ -1518,285 +1179,77 @@ meta_frames_paint_to_drawable (MetaFrames *frames, GdkRectangle *area) { GtkWidget *widget; - MetaFrameGeometry fgeom; MetaFrameFlags flags; - int width, height; - GtkBorder inner; - - widget = GTK_WIDGET (frames); - - meta_frames_calc_geometry (frames, frame, &fgeom); - flags = meta_core_get_frame_flags (gdk_display, frame->xwindow); - width = fgeom.width; - height = fgeom.height; + MetaFrameType type; + GdkPixbuf *mini_icon; + GdkPixbuf *icon; + int w, h; + MetaButtonState button_states[META_BUTTON_TYPE_LAST]; + Window grab_frame; + int i; - /* Black line around outside to give definition */ - gdk_draw_rectangle (drawable, - widget->style->black_gc, - FALSE, - 0, 0, width - 1, height - 1); - - /* Light GC on top/left edges */ - gdk_draw_line (drawable, - widget->style->light_gc[GTK_STATE_NORMAL], - 1, 1, - 1, height - 2); - gdk_draw_line (drawable, - widget->style->light_gc[GTK_STATE_NORMAL], - 1, 1, - width - 2, 1); - /* Dark on bottom/right */ - gdk_draw_line (drawable, - widget->style->dark_gc[GTK_STATE_NORMAL], - width - 2, 1, - width - 2, height - 2); - gdk_draw_line (drawable, - widget->style->dark_gc[GTK_STATE_NORMAL], - 1, height - 2, - width - 2, height - 2); - - if (flags & META_FRAME_HAS_FOCUS) - { - /* Black line around inside while we have focus */ - - gdk_draw_rectangle (drawable, - widget->style->black_gc, - FALSE, - fgeom.left_width - 1, - fgeom.top_height - 1, - width - fgeom.right_width - fgeom.left_width + 1, - height - fgeom.bottom_height - fgeom.top_height + 1); - } + widget = GTK_WIDGET (frames); - if (area->y < fgeom.top_height && - fgeom.title_rect.width > 0 && fgeom.title_rect.height > 0) + /* note, prelight not implemented yet */ + i = 0; + while (i < META_BUTTON_TYPE_LAST) { - GdkRectangle clip; - GdkGC *layout_gc; + button_states[i] = META_BUTTON_STATE_NORMAL; - clip = fgeom.title_rect; - clip.x += frames->layout->text_border.left; - clip.width -= frames->layout->text_border.left + - frames->layout->text_border.right; - - layout_gc = widget->style->fg_gc[GTK_STATE_NORMAL]; - if (flags & META_FRAME_HAS_FOCUS) - { - GdkPixbuf *gradient; - GdkColor selected_faded; - const GdkColor *bg = &widget->style->bg[GTK_STATE_NORMAL]; - - /* alpha blend selection color into normal color */ -#define ALPHA 25000 - selected_faded = widget->style->bg[GTK_STATE_SELECTED]; - selected_faded.red = selected_faded.red + (((bg->red - selected_faded.red) * ALPHA + 32768) >> 16); - selected_faded.green = selected_faded.green + (((bg->green - selected_faded.green) * ALPHA + 32768) >> 16); - selected_faded.blue = selected_faded.blue + (((bg->blue - selected_faded.blue) * ALPHA + 32768) >> 16); - - layout_gc = widget->style->fg_gc[GTK_STATE_SELECTED]; - - gradient = meta_gradient_create_simple (fgeom.title_rect.width, - fgeom.title_rect.height, - &selected_faded, - &widget->style->bg[GTK_STATE_SELECTED], - META_GRADIENT_DIAGONAL); - - if (gradient != NULL) - { - gdk_pixbuf_render_to_drawable (gradient, - drawable, - widget->style->bg_gc[GTK_STATE_SELECTED], - 0, 0, - fgeom.title_rect.x, - fgeom.title_rect.y, - fgeom.title_rect.width, - fgeom.title_rect.height, - GDK_RGB_DITHER_MAX, - 0, 0); - - g_object_unref (G_OBJECT (gradient)); - } - else - { - /* Fallback to plain selection color */ - gdk_draw_rectangle (drawable, - widget->style->bg_gc[GTK_STATE_SELECTED], - TRUE, - fgeom.title_rect.x, - fgeom.title_rect.y, - fgeom.title_rect.width, - fgeom.title_rect.height); - } - } - - if (frame->layout) - { - PangoRectangle layout_rect; - int x, y, icon_x, icon_y; - GdkPixbuf *icon; - int icon_w, icon_h; - int area_w, area_h; - -#define ICON_TEXT_SPACING 2 - - icon = meta_core_get_mini_icon (gdk_display, - frame->xwindow); - - icon_w = gdk_pixbuf_get_width (icon); - icon_h = gdk_pixbuf_get_height (icon); - - pango_layout_get_pixel_extents (frame->layout, - NULL, - &layout_rect); - - /* corner of whole title area */ - x = fgeom.title_rect.x + frames->layout->text_border.left; - y = fgeom.title_rect.y + frames->layout->text_border.top; - - area_w = fgeom.title_rect.width - - frames->layout->text_border.left - - frames->layout->text_border.right; - - area_h = fgeom.title_rect.height - - frames->layout->text_border.top - - frames->layout->text_border.bottom; - - /* center icon vertically */ - icon_y = y + MAX ((area_h - icon_h) / 2, 0); - /* center text vertically */ - y = y + MAX ((area_h - layout_rect.height) / 2, 0); - - /* Center icon + text combo */ - icon_x = x + MAX ((area_w - layout_rect.width - icon_w - ICON_TEXT_SPACING) / 2, 0); - x = icon_x + icon_w + ICON_TEXT_SPACING; - - gdk_gc_set_clip_rectangle (layout_gc, &clip); - - { - /* grumble, render_to_drawable_alpha does not accept a clip - * mask, so we have to go through some BS - */ - GdkRectangle pixbuf_rect; - GdkRectangle draw_rect; - - pixbuf_rect.x = icon_x; - pixbuf_rect.y = icon_y; - pixbuf_rect.width = icon_w; - pixbuf_rect.height = icon_h; - - if (gdk_rectangle_intersect (&clip, &pixbuf_rect, &draw_rect)) - { - gdk_pixbuf_render_to_drawable_alpha (icon, - drawable, - 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_PIXBUF_ALPHA_FULL, - 128, - GDK_RGB_DITHER_NORMAL, - 0, 0); - } - } - - gdk_draw_layout (drawable, - layout_gc, - x, y, - frame->layout); - gdk_gc_set_clip_rectangle (layout_gc, NULL); - } + ++i; } - - inner = frames->layout->inner_button_border; - if (fgeom.close_rect.width > 0 && fgeom.close_rect.height > 0) - { - draw_control_bg (frames, frame, drawable, - META_FRAME_CONTROL_DELETE, &fgeom); - - draw_control (frames, drawable, - NULL, NULL, - META_FRAME_CONTROL_DELETE, - fgeom.close_rect.x + inner.left, - fgeom.close_rect.y + inner.top, - fgeom.close_rect.width - inner.right - inner.left, - fgeom.close_rect.height - inner.bottom - inner.top); - } - - if (fgeom.max_rect.width > 0 && fgeom.max_rect.height > 0) - { - MetaFrameControl ctrl; - - if (flags & META_FRAME_MAXIMIZED) - ctrl = META_FRAME_CONTROL_UNMAXIMIZE; - else - ctrl = META_FRAME_CONTROL_MAXIMIZE; - - draw_control_bg (frames, frame, drawable, ctrl, &fgeom); - - draw_control (frames, drawable, - NULL, NULL, - ctrl, - fgeom.max_rect.x + inner.left, - fgeom.max_rect.y + inner.top, - fgeom.max_rect.width - inner.left - inner.right, - fgeom.max_rect.height - inner.top - inner.bottom); - } + grab_frame = meta_core_get_grab_frame (gdk_display); - if (fgeom.min_rect.width > 0 && fgeom.min_rect.height > 0) + if (frame->xwindow == grab_frame) { - draw_control_bg (frames, frame, drawable, - META_FRAME_CONTROL_MINIMIZE, &fgeom); - - draw_control (frames, drawable, - NULL, NULL, - META_FRAME_CONTROL_MINIMIZE, - fgeom.min_rect.x + inner.left, - fgeom.min_rect.y + inner.top, - fgeom.min_rect.width - inner.left - inner.right, - fgeom.min_rect.height - inner.top - inner.bottom); + switch (meta_core_get_grab_op (gdk_display)) + { + case META_GRAB_OP_CLICKING_MENU: + button_states[META_BUTTON_TYPE_MENU] = + META_BUTTON_STATE_PRESSED; + break; + case META_GRAB_OP_CLICKING_DELETE: + button_states[META_BUTTON_TYPE_CLOSE] = + META_BUTTON_STATE_PRESSED; + break; + case META_GRAB_OP_CLICKING_MAXIMIZE: + button_states[META_BUTTON_TYPE_MAXIMIZE] = + META_BUTTON_STATE_PRESSED; + break; + case META_GRAB_OP_CLICKING_UNMAXIMIZE: + button_states[META_BUTTON_TYPE_MAXIMIZE] = + META_BUTTON_STATE_PRESSED; + break; + case META_GRAB_OP_CLICKING_MINIMIZE: + button_states[META_BUTTON_TYPE_MINIMIZE] = + META_BUTTON_STATE_PRESSED; + break; + default: + break; + } } - if (fgeom.spacer_rect.width > 0 && fgeom.spacer_rect.height > 0) - { - gtk_paint_vline (widget->style, - drawable, - GTK_STATE_NORMAL, - area, - widget, - "metacity_frame_spacer", - fgeom.spacer_rect.y, - fgeom.spacer_rect.y + fgeom.spacer_rect.height, - fgeom.spacer_rect.x + fgeom.spacer_rect.width / 2); - } + flags = meta_core_get_frame_flags (gdk_display, frame->xwindow); + type = meta_core_get_frame_type (gdk_display, frame->xwindow); + mini_icon = meta_core_get_mini_icon (gdk_display, frame->xwindow); + icon = meta_core_get_icon (gdk_display, frame->xwindow); - if (fgeom.menu_rect.width > 0 && fgeom.menu_rect.height > 0) - { - int x, y; -#define ARROW_WIDTH 7 -#define ARROW_HEIGHT 5 - - draw_control_bg (frames, frame, - drawable, - META_FRAME_CONTROL_MENU, &fgeom); - - x = fgeom.menu_rect.x; - y = fgeom.menu_rect.y; - x += (fgeom.menu_rect.width - ARROW_WIDTH) / 2; - y += (fgeom.menu_rect.height - ARROW_HEIGHT) / 2; - - gtk_paint_arrow (widget->style, - drawable, - GTK_STATE_NORMAL, - GTK_SHADOW_OUT, - area, - widget, - "metacity_menu_button", - GTK_ARROW_DOWN, - TRUE, - x, y, ARROW_WIDTH, ARROW_HEIGHT); - } + meta_core_get_client_size (gdk_display, frame->xwindow, + &w, &h); + + meta_theme_draw_frame (meta_theme_get_current (), + widget, + drawable, + area, + 0, 0, + type, + flags, + w, h, + frame->layout, + frames->text_height, + button_states, + mini_icon, icon); } static gboolean diff --git a/src/frames.h b/src/frames.h index 99794326..e756ec5b 100644 --- a/src/frames.h +++ b/src/frames.h @@ -74,10 +74,7 @@ struct _MetaUIFrame struct _MetaFrames { GtkWindow parent_instance; - - /* If we did a widget per frame, we wouldn't want to cache this. */ - MetaFrameLayout *layout; - + int text_height; GHashTable *frames; @@ -121,11 +118,6 @@ void meta_frames_unflicker_bg (MetaFrames *frames, void meta_frames_queue_draw (MetaFrames *frames, Window xwindow); -void meta_frames_get_pixmap_for_control (MetaFrames *frames, - MetaFrameControl control, - GdkPixmap **pixmap, - GdkBitmap **mask); - void meta_frames_notify_menu_hide (MetaFrames *frames); Window meta_frames_get_moving_frame (MetaFrames *frames); diff --git a/src/gradient.c b/src/gradient.c index 7be76d9b..ee1f9215 100644 --- a/src/gradient.c +++ b/src/gradient.c @@ -103,6 +103,8 @@ meta_gradient_create_simple (int width, case META_GRADIENT_DIAGONAL: return meta_gradient_create_diagonal (width, height, from, to); + case META_GRADIENT_LAST: + break; } g_assert_not_reached (); return NULL; @@ -126,6 +128,9 @@ meta_gradient_create_multi (int width, return meta_gradient_create_multi_vertical (width, height, colors, n_colors); case META_GRADIENT_DIAGONAL: return meta_gradient_create_multi_diagonal (width, height, colors, n_colors); + case META_GRADIENT_LAST: + g_assert_not_reached (); + break; } } else if (n_colors > 1) diff --git a/src/gradient.h b/src/gradient.h index 785a1a86..206c6e4e 100644 --- a/src/gradient.h +++ b/src/gradient.h @@ -29,7 +29,8 @@ typedef enum { META_GRADIENT_VERTICAL, META_GRADIENT_HORIZONTAL, - META_GRADIENT_DIAGONAL + META_GRADIENT_DIAGONAL, + META_GRADIENT_LAST } MetaGradientType; GdkPixbuf* meta_gradient_create_simple (int width, @@ -43,6 +43,9 @@ static MetaExitCode meta_exit_code = META_EXIT_SUCCESS; static GMainLoop *meta_main_loop = NULL; static gboolean meta_restart_after_quit = FALSE; +static void prefs_changed_callback (MetaPreference pref, + gpointer data); + static void log_handler (const gchar *log_domain, GLogLevelFlags log_level, @@ -175,12 +178,14 @@ main (int argc, char **argv) /* Load prefs */ meta_prefs_init (); + meta_prefs_add_listener (prefs_changed_callback, NULL); meta_ui_init (&argc, &argv); /* must be after UI init so we can override GDK handlers */ meta_errors_init (); +#if 0 g_log_set_handler (NULL, G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, log_handler, NULL); @@ -205,6 +210,22 @@ main (int argc, char **argv) if (meta_is_debugging ()) g_log_set_always_fatal (G_LOG_LEVEL_MASK); +#endif + + meta_ui_set_current_theme (meta_prefs_get_theme (), FALSE); + + /* Try some panic stuff, this is lame but we really + * don't want users to lose their WM :-/ + */ + if (!meta_ui_have_a_theme ()) + meta_ui_set_current_theme ("Atlanta", FALSE); + + if (!meta_ui_have_a_theme ()) + meta_ui_set_current_theme ("Crux", FALSE); + + if (!meta_ui_have_a_theme ()) + meta_fatal (_("Could not find a theme! Be sure %s exits and contains the usual themes."), + METACITY_PKGDATADIR"/themes"); /* Connect to SM as late as possible - but before managing display, * or we might try to manage a window before we have the session @@ -280,3 +301,19 @@ meta_restart (void) meta_restart_after_quit = TRUE; meta_quit (META_EXIT_SUCCESS); } + +static void +prefs_changed_callback (MetaPreference pref, + gpointer data) +{ + switch (pref) + { + case META_PREF_THEME: + meta_ui_set_current_theme (meta_prefs_get_theme (), FALSE); + break; + + default: + /* handled elsewhere or otherwise */ + break; + } +} @@ -24,6 +24,7 @@ #include "main.h" #include "util.h" #include "core.h" +#include "themewidget.h" typedef struct _MenuItem MenuItem; typedef struct _MenuData MenuData; @@ -117,6 +118,41 @@ activate_cb (GtkWidget *menuitem, gpointer data) /* menu may now be freed */ } +static void +menu_icon_size_func (MetaArea *area, + int *width, + int *height, + void *user_data) +{ + gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, + width, height); +} + +static void +menu_icon_expose_func (MetaArea *area, + GdkEventExpose *event, + int x_offset, + int y_offset, + void *user_data) +{ + int width, height; + MetaMenuIconType type; + + type = GPOINTER_TO_INT (user_data); + + gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, + &width, &height); + + meta_theme_draw_menu_icon (meta_theme_get_current (), + GTK_WIDGET (area), + GTK_WIDGET (area)->window, + &event->area, + x_offset, y_offset, + width, height, + type); +} + + MetaWindowMenu* meta_window_menu_new (MetaFrames *frames, MetaMenuOp ops, @@ -141,7 +177,7 @@ meta_window_menu_new (MetaFrames *frames, menu->menu = gtk_menu_new (); i = 0; - while (i < G_N_ELEMENTS (menuitems)) + while (i < (int) G_N_ELEMENTS (menuitems)) { if (ops & menuitems[i].op || menuitems[i].op == 0) { @@ -155,48 +191,49 @@ meta_window_menu_new (MetaFrames *frames, else { GtkWidget *image; - GdkPixmap *pix; - GdkBitmap *mask; image = NULL; - pix = NULL; - mask = NULL; switch (menuitems[i].op) { case META_MENU_OP_MAXIMIZE: - meta_frames_get_pixmap_for_control (frames, - META_FRAME_CONTROL_MAXIMIZE, - &pix, &mask); + image = meta_area_new (); + meta_area_setup (META_AREA (image), + menu_icon_size_func, + menu_icon_expose_func, + GINT_TO_POINTER (META_MENU_ICON_TYPE_MAXIMIZE), + NULL); break; case META_MENU_OP_UNMAXIMIZE: - meta_frames_get_pixmap_for_control (frames, - META_FRAME_CONTROL_UNMAXIMIZE, - &pix, &mask); + image = meta_area_new (); + meta_area_setup (META_AREA (image), + menu_icon_size_func, + menu_icon_expose_func, + GINT_TO_POINTER (META_MENU_ICON_TYPE_UNMAXIMIZE), + NULL); break; case META_MENU_OP_MINIMIZE: - meta_frames_get_pixmap_for_control (frames, - META_FRAME_CONTROL_MINIMIZE, - &pix, &mask); + image = meta_area_new (); + meta_area_setup (META_AREA (image), + menu_icon_size_func, + menu_icon_expose_func, + GINT_TO_POINTER (META_MENU_ICON_TYPE_MINIMIZE), + NULL); break; case META_MENU_OP_DELETE: - meta_frames_get_pixmap_for_control (frames, - META_FRAME_CONTROL_DELETE, - &pix, &mask); + image = meta_area_new (); + meta_area_setup (META_AREA (image), + menu_icon_size_func, + menu_icon_expose_func, + GINT_TO_POINTER (META_MENU_ICON_TYPE_CLOSE), + NULL); break; default: break; } - - if (pix) - { - image = gtk_image_new_from_pixmap (pix, mask); - g_object_unref (G_OBJECT (pix)); - g_object_unref (G_OBJECT (mask)); - } if (image == NULL && menuitems[i].stock_id) diff --git a/src/metacity.schemas b/src/metacity.schemas index 4f6fb4b6..e8aa7b34 100644 --- a/src/metacity.schemas +++ b/src/metacity.schemas @@ -23,6 +23,21 @@ </schema> <schema> + <key>/schemas/apps/metacity/general/theme</key> + <applyto>/apps/metacity/general/theme</applyto> + <owner>metacity</owner> + <type>string</type> + <default>Atlanta</default> + <locale name="C"> + <short>Current theme</short> + <long> + The theme determines the appearance of window borders, + titlebar, and so forth. + </long> + </locale> + </schema> + + <schema> <key>/schemas/apps/metacity/general/titlebar_uses_desktop_font</key> <applyto>/apps/metacity/general/titlebar_uses_desktop_font</applyto> <owner>metacity</owner> diff --git a/src/prefs.c b/src/prefs.c index 79a2d1a0..a5fe823e 100644 --- a/src/prefs.c +++ b/src/prefs.c @@ -29,6 +29,7 @@ * notify listener and of course in the .schemas file */ #define KEY_FOCUS_MODE "/apps/metacity/general/focus_mode" +#define KEY_THEME "/apps/metacity/general/theme" #define KEY_USE_DESKTOP_FONT "/apps/metacity/general/titlebar_uses_desktop_font" #define KEY_TITLEBAR_FONT "/apps/metacity/general/titlebar_font" #define KEY_TITLEBAR_FONT_SIZE "/apps/metacity/general/titlebar_font_size" @@ -43,6 +44,7 @@ static gboolean use_desktop_font = TRUE; static PangoFontDescription *titlebar_font = NULL; static int titlebar_font_size = 0; static MetaFocusMode focus_mode = META_FOCUS_MODE_CLICK; +static char* current_theme = NULL; static int num_workspaces = 4; static gboolean application_based = FALSE; @@ -50,6 +52,7 @@ static gboolean update_use_desktop_font (gboolean value); static gboolean update_titlebar_font (const char *value); static gboolean update_titlebar_font_size (int value); static gboolean update_focus_mode (const char *value); +static gboolean update_theme (const char *value); static gboolean update_num_workspaces (int value); static gboolean update_application_based (gboolean value); @@ -212,6 +215,12 @@ meta_prefs_init (void) update_focus_mode (str_val); g_free (str_val); + str_val = gconf_client_get_string (client, KEY_THEME, + &err); + cleanup_error (&err); + update_theme (str_val); + g_free (str_val); + /* If the keys aren't set in the database, we use essentially * bogus values instead of any kind of default. This is * just lazy. But they keys ought to be set, anyhow. @@ -279,6 +288,22 @@ change_notify (GConfClient *client, if (update_focus_mode (str)) queue_changed (META_PREF_FOCUS_MODE); } + if (strcmp (key, KEY_THEME) == 0) + { + const char *str; + + if (value && value->type != GCONF_VALUE_STRING) + { + meta_warning (_("GConf key \"%s\" is set to an invalid type\n"), + KEY_THEME); + goto out; + } + + str = value ? gconf_value_get_string (value) : NULL; + + if (update_focus_mode (str)) + queue_changed (META_PREF_THEME); + } else if (strcmp (key, KEY_TITLEBAR_FONT) == 0) { const char *str; @@ -394,12 +419,50 @@ update_focus_mode (const char *value) return (old_mode != focus_mode); } +static gboolean +update_theme (const char *value) +{ + const char *old_theme; + gboolean changed; + + old_theme = current_theme; + + if (value != NULL && *value) + { + current_theme = g_strdup (value); + } + + changed = TRUE; + if ((old_theme && current_theme && + strcmp (old_theme, current_theme) == 0) || + (old_theme == NULL && current_theme == NULL)) + changed = FALSE; + + if (old_theme != current_theme) + g_free (old_theme); + + if (current_theme == NULL) + { + /* Fallback crackrock */ + current_theme = g_strdup ("Atlanta"); + changed = TRUE; + } + + return changed; +} + MetaFocusMode meta_prefs_get_focus_mode (void) { return focus_mode; } +const char* +meta_prefs_get_theme (void) +{ + return current_theme; +} + static gboolean update_use_desktop_font (gboolean value) { @@ -528,6 +591,10 @@ meta_preference_to_string (MetaPreference pref) return "FOCUS_MODE"; break; + case META_PREF_THEME: + return "THEME"; + break; + case META_PREF_TITLEBAR_FONT: return "TITLEBAR_FONT"; break; diff --git a/src/prefs.h b/src/prefs.h index 3abdd36c..617d7307 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -29,11 +29,11 @@ typedef enum { META_PREF_FOCUS_MODE, + META_PREF_THEME, META_PREF_TITLEBAR_FONT, META_PREF_TITLEBAR_FONT_SIZE, META_PREF_NUM_WORKSPACES, META_PREF_APPLICATION_BASED - } MetaPreference; typedef void (* MetaPrefsChangedFunc) (MetaPreference pref, @@ -48,6 +48,7 @@ void meta_prefs_init (void); const char* meta_preference_to_string (MetaPreference pref); MetaFocusMode meta_prefs_get_focus_mode (void); +const char* meta_prefs_get_theme (void); /* returns NULL if GTK default should be used */ const PangoFontDescription* meta_prefs_get_titlebar_font (void); /* returns 0 if default should be used */ diff --git a/src/screen.c b/src/screen.c index 01e4b8fd..719d7533 100644 --- a/src/screen.c +++ b/src/screen.c @@ -277,7 +277,7 @@ meta_screen_manage_all_windows (MetaScreen *screen) { Window ignored1, ignored2; Window *children; - unsigned int n_children; + int n_children; int i; /* Must grab server to avoid obvious race condition */ diff --git a/src/stack.c b/src/stack.c index e9672a85..c78a62b4 100644 --- a/src/stack.c +++ b/src/stack.c @@ -98,7 +98,8 @@ meta_stack_free (MetaStack *stack) g_list_free (stack->pending); - g_array_free (stack->last_root_children_stacked, TRUE); + if (stack->last_root_children_stacked) + g_array_free (stack->last_root_children_stacked, TRUE); g_free (stack); } @@ -940,7 +941,7 @@ find_tab_forward (MetaStack *stack, /* start may be -1 to find any tab window at all */ i = start + 1; - while (i < stack->windows->len) + while (i < (int) stack->windows->len) { MetaWindow *window; @@ -1045,7 +1046,7 @@ meta_stack_get_tab_next (MetaStack *stack, if (window != NULL) { i = 0; - while (i < stack->windows->len) + while (i < (int) stack->windows->len) { Window w; @@ -1083,7 +1084,7 @@ meta_stack_get_tab_list (MetaStack *stack, list = NULL; i = 0; - while (i < stack->windows->len) + while (i < (int) stack->windows->len) { MetaWindow *window; diff --git a/src/tabpopup.c b/src/tabpopup.c index 08c4ac7f..ddd7553f 100644 --- a/src/tabpopup.c +++ b/src/tabpopup.c @@ -22,8 +22,8 @@ #include "util.h" #include "core.h" #include "tabpopup.h" -#include <math.h> #include <gtk/gtk.h> +#include <math.h> #define OUTSIDE_SELECT_RECT 2 #define INSIDE_SELECT_RECT 2 diff --git a/src/theme-parser.c b/src/theme-parser.c new file mode 100644 index 00000000..dfeed94a --- /dev/null +++ b/src/theme-parser.c @@ -0,0 +1,3920 @@ +/* Metacity theme parsing */ + +/* + * 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. + */ + +#include <config.h> +#include "theme-parser.h" +#include "util.h" +#include <string.h> +#include <stdlib.h> + +typedef enum +{ + STATE_START, + STATE_THEME, + /* info section */ + STATE_INFO, + STATE_NAME, + STATE_AUTHOR, + STATE_COPYRIGHT, + STATE_DATE, + STATE_DESCRIPTION, + /* constants */ + STATE_CONSTANT, + /* geometry */ + STATE_FRAME_GEOMETRY, + STATE_DISTANCE, + STATE_BORDER, + /* draw ops */ + STATE_DRAW_OPS, + STATE_LINE, + STATE_RECTANGLE, + STATE_ARC, + STATE_CLIP, + STATE_TINT, + STATE_GRADIENT, + STATE_IMAGE, + STATE_GTK_ARROW, + STATE_GTK_BOX, + STATE_GTK_VLINE, + STATE_ICON, + STATE_TITLE, + STATE_INCLUDE, /* include another draw op list */ + /* sub-parts of gradient */ + STATE_COLOR, + /* frame style */ + STATE_FRAME_STYLE, + STATE_PIECE, + STATE_BUTTON, + /* style set */ + STATE_FRAME_STYLE_SET, + STATE_FRAME, + /* assigning style sets to windows */ + STATE_WINDOW, + /* and menu icons */ + STATE_MENU_ICON +} ParseState; + +typedef struct +{ + GSList *states; + + const char *theme_name; /* name of theme (directory it's in) */ + char *theme_file; /* theme filename */ + char *theme_dir; /* dir the theme is inside */ + MetaTheme *theme; /* theme being parsed */ + char *name; /* name of named thing being parsed */ + MetaFrameLayout *layout; /* layout being parsed if any */ + MetaDrawOpList *op_list; /* op list being parsed if any */ + MetaDrawOp *op; /* op being parsed if any */ + MetaFrameStyle *style; /* frame style being parsed if any */ + MetaFrameStyleSet *style_set; /* frame style set being parsed if any */ + MetaFramePiece piece; /* position of piece being parsed */ + MetaButtonType button_type; /* type of button/menuitem being parsed */ + MetaButtonState button_state; /* state of button being parsed */ + MetaMenuIconType menu_icon_type; /* type of menu icon being parsed */ + GtkStateType menu_icon_state; /* state of menu icon being parsed */ +} ParseInfo; + +static void set_error (GError **err, + GMarkupParseContext *context, + int error_domain, + int error_code, + const char *format, + ...) G_GNUC_PRINTF (5, 6); + +static void add_context_to_error (GError **err, + GMarkupParseContext *context); + +static void parse_info_init (ParseInfo *info); +static void parse_info_free (ParseInfo *info); + +static void push_state (ParseInfo *info, + ParseState state); +static void pop_state (ParseInfo *info); +static ParseState peek_state (ParseInfo *info); + + +static void parse_toplevel_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +static void parse_info_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +static void parse_geometry_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +static void parse_draw_op_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +static void parse_gradient_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +static void parse_style_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +static void parse_style_set_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); + +static void parse_piece_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); + +static void parse_button_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); + +static void parse_menu_icon_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); + +static void start_element_handler (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error); +static void end_element_handler (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error); +static void text_handler (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error); + +static GMarkupParser metacity_theme_parser = { + start_element_handler, + end_element_handler, + text_handler, + NULL, + NULL +}; + +static void +set_error (GError **err, + GMarkupParseContext *context, + int error_domain, + int error_code, + const char *format, + ...) +{ + int line, ch; + va_list args; + char *str; + + g_markup_parse_context_get_position (context, &line, &ch); + + va_start (args, format); + str = g_strdup_vprintf (format, args); + va_end (args); + + g_set_error (err, error_domain, error_code, + _("Line %d character %d: %s"), + line, ch, str); + + g_free (str); +} + +static void +add_context_to_error (GError **err, + GMarkupParseContext *context) +{ + int line, ch; + char *str; + + if (err == NULL || *err == NULL) + return; + + g_markup_parse_context_get_position (context, &line, &ch); + + str = g_strdup_printf (_("Line %d character %d: %s"), + line, ch, (*err)->message); + g_free ((*err)->message); + (*err)->message = str; +} + +static void +parse_info_init (ParseInfo *info) +{ + info->theme_file = NULL; + info->states = g_slist_prepend (NULL, GINT_TO_POINTER (STATE_START)); + info->theme = NULL; + info->name = NULL; + info->layout = NULL; + info->op_list = NULL; + info->op = NULL; + info->style = NULL; + info->style_set = NULL; + info->piece = META_FRAME_PIECE_LAST; + info->button_type = META_BUTTON_TYPE_LAST; + info->button_state = META_BUTTON_STATE_LAST; +} + +static void +parse_info_free (ParseInfo *info) +{ + g_free (info->theme_file); + g_free (info->theme_dir); + + g_slist_free (info->states); + + if (info->theme) + meta_theme_free (info->theme); + + if (info->layout) + meta_frame_layout_unref (info->layout); + + if (info->op_list) + meta_draw_op_list_unref (info->op_list); + + if (info->op) + meta_draw_op_free (info->op); + + if (info->style) + meta_frame_style_unref (info->style); + + if (info->style_set) + meta_frame_style_set_unref (info->style_set); +} + +static void +push_state (ParseInfo *info, + ParseState state) +{ + info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state)); +} + +static void +pop_state (ParseInfo *info) +{ + g_return_if_fail (info->states != NULL); + + info->states = g_slist_remove (info->states, info->states->data); +} + +static ParseState +peek_state (ParseInfo *info) +{ + g_return_val_if_fail (info->states != NULL, STATE_START); + + return GPOINTER_TO_INT (info->states->data); +} + +#define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0) + +typedef struct +{ + const char *name; + const char **retloc; +} LocateAttr; + +static gboolean +locate_attributes (GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + GError **error, + const char *first_attribute_name, + const char **first_attribute_retloc, + ...) +{ + va_list args; + const char *name; + const char **retloc; + int n_attrs; +#define MAX_ATTRS 24 + LocateAttr attrs[MAX_ATTRS]; + gboolean retval; + int i; + + g_return_val_if_fail (first_attribute_name != NULL, FALSE); + g_return_val_if_fail (first_attribute_retloc != NULL, FALSE); + + retval = TRUE; + + n_attrs = 1; + attrs[0].name = first_attribute_name; + attrs[0].retloc = first_attribute_retloc; + *first_attribute_retloc = NULL; + + va_start (args, first_attribute_retloc); + + name = va_arg (args, const char*); + retloc = va_arg (args, const char**); + + while (name != NULL) + { + g_return_val_if_fail (retloc != NULL, FALSE); + + if (n_attrs == MAX_ATTRS) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> has more than %d attributes, can't possibly be valid"), + element_name, MAX_ATTRS); + retval = FALSE; + goto out; + } + + attrs[n_attrs].name = name; + attrs[n_attrs].retloc = retloc; + n_attrs += 1; + *retloc = NULL; + + name = va_arg (args, const char*); + retloc = va_arg (args, const char**); + } + + out: + va_end (args); + + if (!retval) + return retval; + + i = 0; + while (attribute_names[i]) + { + int j; + gboolean found; + + found = FALSE; + j = 0; + while (j < n_attrs) + { + if (strcmp (attrs[j].name, attribute_names[i]) == 0) + { + retloc = attrs[j].retloc; + + if (*retloc != NULL) + { + set_error (error, context, + G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Attribute \"%s\" repeated twice on the same <%s> element"), + attrs[j].name, element_name); + retval = FALSE; + goto out2; + } + + *retloc = attribute_values[i]; + found = TRUE; + } + + ++j; + } + + if (!found) + { + set_error (error, context, + G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Attribute \"%s\" is invalid on <%s> element in this context"), + attribute_names[i], element_name); + retval = FALSE; + goto out2; + } + + ++i; + } + + out2: + return retval; +} + +static gboolean +check_no_attributes (GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + GError **error) +{ + if (attribute_names[0] != NULL) + { + set_error (error, context, + G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Attribute \"%s\" is invalid on <%s> element in this context"), + attribute_names[0], element_name); + return FALSE; + } + + return TRUE; +} + +#define MAX_REASONABLE 4096 +static gboolean +parse_positive_integer (const char *str, + int *val, + GMarkupParseContext *context, + GError **error) +{ + char *end; + long l; + + *val = 0; + + end = NULL; + + l = strtol (str, &end, 10); + + if (end == NULL || end == str) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Could not parse \"%s\" as an integer"), + str); + return FALSE; + } + + if (*end != '\0') + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand trailing characters \"%s\" in string \"%s\""), + end, str); + return FALSE; + } + + if (l < 0) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Integer %ld must be positive"), l); + return FALSE; + } + + if (l > MAX_REASONABLE) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Integer %ld is too large, current max is %d"), + l, MAX_REASONABLE); + return FALSE; + } + + *val = (int) l; + + return TRUE; +} + +static gboolean +parse_double (const char *str, + double *val, + GMarkupParseContext *context, + GError **error) +{ + char *end; + + *val = 0; + + end = NULL; + + *val = g_ascii_strtod (str, &end); + + if (end == NULL || end == str) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Could not parse \"%s\" as a floating point number"), + str); + return FALSE; + } + + if (*end != '\0') + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand trailing characters \"%s\" in string \"%s\""), + end, str); + return FALSE; + } + + return TRUE; +} + +static gboolean +parse_angle (const char *str, + double *val, + GMarkupParseContext *context, + GError **error) +{ + if (!parse_double (str, val, context, error)) + return FALSE; + + if (*val < (0.0 - 1e6) || *val > (360.0 + 1e6)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Angle must be between 0.0 and 360.0, was %g\n"), + *val); + return FALSE; + } + + return TRUE; +} + +static gboolean +parse_alpha (const char *str, + double *val, + GMarkupParseContext *context, + GError **error) +{ + if (!parse_double (str, val, context, error)) + return FALSE; + + if (*val < (0.0 - 1e6) || *val > (1.0 + 1e6)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Alpha must be between 0.0 (invisible) and 1.0 (fully opaque), was %g\n"), + *val); + return FALSE; + } + + return TRUE; +} + +static void +parse_toplevel_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_THEME); + + if (ELEMENT_IS ("info")) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_INFO); + } + else if (ELEMENT_IS ("constant")) + { + const char *name; + const char *value; + int ival; + double dval; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "name", &name, "value", &value, + NULL)) + return; + + if (name == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"name\" attribute on element <%s>"), element_name); + return; + } + + if (value == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"value\" attribute on element <%s>"), element_name); + return; + } + + if (strchr (value, '.')) + { + dval = 0.0; + if (!parse_double (value, &dval, context, error)) + return; + + if (!meta_theme_define_float_constant (info->theme, + name, + dval, + error)) + { + add_context_to_error (error, context); + return; + } + } + else + { + ival = 0; + if (!parse_positive_integer (value, &ival, context, error)) + return; + + if (!meta_theme_define_int_constant (info->theme, + name, + ival, + error)) + { + add_context_to_error (error, context); + return; + } + } + + push_state (info, STATE_CONSTANT); + } + else if (ELEMENT_IS ("frame_geometry")) + { + const char *name = NULL; + const char *parent = NULL; + MetaFrameLayout *parent_layout; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "name", &name, "parent", &parent, + NULL)) + return; + + if (name == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"name\" attribute on <%s> element"), + element_name); + return; + } + + if (meta_theme_lookup_layout (info->theme, name)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> name \"%s\" used a second time"), + element_name, name); + return; + } + + parent_layout = NULL; + if (parent) + { + parent_layout = meta_theme_lookup_layout (info->theme, parent); + if (parent_layout == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> parent \"%s\" has not been defined"), + element_name, parent); + return; + } + } + + g_assert (info->layout == NULL); + + if (parent_layout) + info->layout = meta_frame_layout_copy (parent_layout); + else + info->layout = meta_frame_layout_new (); + + meta_theme_insert_layout (info->theme, name, info->layout); + + push_state (info, STATE_FRAME_GEOMETRY); + } + else if (ELEMENT_IS ("draw_ops")) + { + const char *name = NULL; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "name", &name, + NULL)) + return; + + if (name == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"name\" attribute on <%s> element"), + element_name); + return; + } + + if (meta_theme_lookup_draw_op_list (info->theme, name)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> name \"%s\" used a second time"), + element_name, name); + return; + } + + g_assert (info->op_list == NULL); + info->op_list = meta_draw_op_list_new (2); + + meta_theme_insert_draw_op_list (info->theme, name, info->op_list); + + push_state (info, STATE_DRAW_OPS); + } + else if (ELEMENT_IS ("frame_style")) + { + const char *name = NULL; + const char *parent = NULL; + const char *geometry = NULL; + MetaFrameStyle *parent_style; + MetaFrameLayout *layout; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "name", &name, "parent", &parent, + "geometry", &geometry, + NULL)) + return; + + if (name == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"name\" attribute on <%s> element"), + element_name); + return; + } + + if (meta_theme_lookup_style (info->theme, name)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> name \"%s\" used a second time"), + element_name, name); + return; + } + + parent_style = NULL; + if (parent) + { + parent_style = meta_theme_lookup_style (info->theme, parent); + if (parent_style == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> parent \"%s\" has not been defined"), + element_name, parent); + return; + } + } + + layout = NULL; + if (geometry) + { + layout = meta_theme_lookup_layout (info->theme, geometry); + if (layout == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> geometry \"%s\" has not been defined"), + element_name, geometry); + return; + } + } + else if (parent_style) + { + layout = parent_style->layout; + } + + if (layout == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> must specify either a geometry or a parent that has a geometry"), + element_name); + return; + } + + g_assert (info->style == NULL); + + info->style = meta_frame_style_new (parent_style); + g_assert (info->style->layout == NULL); + meta_frame_layout_ref (layout); + info->style->layout = layout; + + meta_theme_insert_style (info->theme, name, info->style); + + push_state (info, STATE_FRAME_STYLE); + } + else if (ELEMENT_IS ("frame_style_set")) + { + const char *name = NULL; + const char *parent = NULL; + MetaFrameStyleSet *parent_set; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "name", &name, "parent", &parent, + NULL)) + return; + + if (name == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"name\" attribute on <%s> element"), + element_name); + return; + } + + if (meta_theme_lookup_style_set (info->theme, name)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> name \"%s\" used a second time"), + element_name, name); + return; + } + + parent_set = NULL; + if (parent) + { + parent_set = meta_theme_lookup_style_set (info->theme, parent); + if (parent_set == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> parent \"%s\" has not been defined"), + element_name, parent); + return; + } + } + + g_assert (info->style_set == NULL); + + info->style_set = meta_frame_style_set_new (parent_set); + + meta_theme_insert_style_set (info->theme, name, info->style_set); + + push_state (info, STATE_FRAME_STYLE_SET); + } + else if (ELEMENT_IS ("window")) + { + const char *type_name = NULL; + const char *style_set_name = NULL; + MetaFrameStyleSet *style_set; + MetaFrameType type; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "type", &type_name, "style_set", &style_set_name, + NULL)) + return; + + if (type_name == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"type\" attribute on <%s> element"), + element_name); + return; + } + + if (style_set_name == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"style_set\" attribute on <%s> element"), + element_name); + return; + } + + type = meta_frame_type_from_string (type_name); + + if (type == META_FRAME_TYPE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Unknown type \"%s\" on <%s> element"), + type_name, element_name); + return; + } + + style_set = meta_theme_lookup_style_set (info->theme, + style_set_name); + + if (style_set == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Unknown style_set \"%s\" on <%s> element"), + style_set_name, element_name); + return; + } + + if (info->theme->style_sets_by_type[type] != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Window type \"%s\" has already been assigned a style set"), + type_name); + return; + } + + meta_frame_style_set_ref (style_set); + info->theme->style_sets_by_type[type] = style_set; + + push_state (info, STATE_WINDOW); + } + else if (ELEMENT_IS ("menu_icon")) + { + const char *function = NULL; + const char *state = NULL; + const char *draw_ops = NULL; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "function", &function, + "state", &state, + "draw_ops", &draw_ops, + NULL)) + return; + + if (function == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"function\" attribute on <%s> element"), + element_name); + return; + } + + if (state == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"state\" attribute on <%s> element"), + element_name); + return; + } + + info->menu_icon_type = meta_menu_icon_type_from_string (function); + if (info->menu_icon_type == META_BUTTON_TYPE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Unknown function \"%s\" for menu icon"), + function); + return; + } + + info->menu_icon_state = meta_gtk_state_from_string (state); + if (((int) info->menu_icon_state) == -1) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Unknown state \"%s\" for menu icon"), + state); + return; + } + + if (info->theme->menu_icons[info->menu_icon_type][info->menu_icon_state] != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Theme already has a menu icon for function %s state %s"), + function, state); + return; + } + + g_assert (info->op_list == NULL); + + if (draw_ops) + { + MetaDrawOpList *op_list; + + op_list = meta_theme_lookup_draw_op_list (info->theme, + draw_ops); + + if (op_list == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No <draw_ops> with the name \"%s\" has been defined"), + draw_ops); + return; + } + + meta_draw_op_list_ref (op_list); + info->op_list = op_list; + } + + push_state (info, STATE_MENU_ICON); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <metacity_theme>"), + element_name); + } +} + +static void +parse_info_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_INFO); + + if (ELEMENT_IS ("name")) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_NAME); + } + else if (ELEMENT_IS ("author")) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_AUTHOR); + } + else if (ELEMENT_IS ("copyright")) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_COPYRIGHT); + } + else if (ELEMENT_IS ("description")) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_DESCRIPTION); + } + else if (ELEMENT_IS ("date")) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_DATE); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <info>"), + element_name); + } +} + +static void +parse_distance (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + const char *name; + const char *value; + int val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "name", &name, "value", &value, + NULL)) + return; + + if (name == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"name\" attribute on element <%s>"), element_name); + return; + } + + if (value == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"value\" attribute on element <%s>"), element_name); + return; + } + + val = 0; + if (!parse_positive_integer (value, &val, context, error)) + return; + + g_assert (val >= 0); /* yeah, "non-negative" not "positive" get over it */ + g_assert (info->layout); + + if (strcmp (name, "left_width") == 0) + info->layout->left_width = val; + else if (strcmp (name, "right_width") == 0) + info->layout->right_width = val; + else if (strcmp (name, "bottom_height") == 0) + info->layout->bottom_height = val; + else if (strcmp (name, "title_vertical_pad") == 0) + info->layout->title_vertical_pad = val; + else if (strcmp (name, "right_titlebar_edge") == 0) + info->layout->right_titlebar_edge = val; + else if (strcmp (name, "left_titlebar_edge") == 0) + info->layout->left_titlebar_edge = val; + else if (strcmp (name, "button_width") == 0) + info->layout->button_width = val; + else if (strcmp (name, "button_height") == 0) + info->layout->button_height = val; + else + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Distance \"%s\" is unknown"), name); + return; + } +} + +static void +parse_border (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + const char *name; + const char *top; + const char *bottom; + const char *left; + const char *right; + int top_val; + int bottom_val; + int left_val; + int right_val; + GtkBorder *border; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "name", &name, + "top", &top, + "bottom", &bottom, + "left", &left, + "right", &right, + NULL)) + return; + + if (name == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"name\" attribute on element <%s>"), element_name); + return; + } + + if (top == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"top\" attribute on element <%s>"), element_name); + return; + } + + if (bottom == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"bottom\" attribute on element <%s>"), element_name); + return; + } + + if (left == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"left\" attribute on element <%s>"), element_name); + return; + } + + if (right == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"right\" attribute on element <%s>"), element_name); + return; + } + + top_val = 0; + if (!parse_positive_integer (top, &top_val, context, error)) + return; + + bottom_val = 0; + if (!parse_positive_integer (bottom, &bottom_val, context, error)) + return; + + left_val = 0; + if (!parse_positive_integer (left, &left_val, context, error)) + return; + + right_val = 0; + if (!parse_positive_integer (right, &right_val, context, error)) + return; + + g_assert (info->layout); + + border = NULL; + + if (strcmp (name, "title_border") == 0) + border = &info->layout->title_border; + else if (strcmp (name, "button_border") == 0) + border = &info->layout->button_border; + + if (border == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Border \"%s\" is unknown"), name); + return; + } + + border->top = top_val; + border->bottom = bottom_val; + border->left = left_val; + border->right = right_val; +} + +static void +parse_geometry_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_FRAME_GEOMETRY); + + if (ELEMENT_IS ("distance")) + { + parse_distance (context, element_name, + attribute_names, attribute_values, + info, error); + push_state (info, STATE_DISTANCE); + } + else if (ELEMENT_IS ("border")) + { + parse_border (context, element_name, + attribute_names, attribute_values, + info, error); + push_state (info, STATE_BORDER); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <frame_geometry>"), + element_name); + } +} + +static gboolean +check_expression (const char *expr, + gboolean has_object, + MetaTheme *theme, + GMarkupParseContext *context, + GError **error) +{ + MetaPositionExprEnv env; + int x, y; + + /* We set it all to 0 to try and catch divide-by-zero screwups. + * it's possible we should instead guarantee that widths and heights + * are at least 1. + */ + + env.x = 0; + env.y = 0; + env.width = 0; + env.height = 0; + if (has_object) + { + env.object_width = 0; + env.object_height = 0; + } + else + { + env.object_width = -1; + env.object_height = -1; + } + + env.left_width = 0; + env.right_width = 0; + env.top_height = 0; + env.bottom_height = 0; + env.title_width = 0; + env.title_height = 0; + + env.icon_width = 0; + env.icon_height = 0; + env.mini_icon_width = 0; + env.mini_icon_height = 0; + env.theme = theme; + + if (!meta_parse_position_expression (expr, + &env, + &x, &y, + error)) + { + add_context_to_error (error, context); + return FALSE; + } + + return TRUE; +} + +static char* +optimize_expression (MetaTheme *theme, + const char *expr) +{ + /* We aren't expecting an error here, since we already + * did check_expression + */ + return meta_theme_replace_constants (theme, expr, NULL); +} + +static gboolean +parse_boolean (const char *str, + gboolean *val, + GMarkupParseContext *context, + GError **error) +{ + if (strcmp ("true", str) == 0) + *val = TRUE; + else if (strcmp ("false", str) == 0) + *val = FALSE; + else + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Boolean values must be \"true\" or \"false\" not \"%s\""), + str); + return FALSE; + } + + return TRUE; +} + +static void +parse_draw_op_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_DRAW_OPS); + + if (ELEMENT_IS ("line")) + { + MetaDrawOp *op; + const char *color; + const char *x1; + const char *y1; + const char *x2; + const char *y2; + const char *dash_on_length; + const char *dash_off_length; + const char *width; + MetaColorSpec *color_spec; + int dash_on_val; + int dash_off_val; + int width_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "color", &color, + "x1", &x1, "y1", &y1, + "x2", &x2, "y2", &y2, + "dash_on_length", &dash_on_length, + "dash_off_length", &dash_off_length, + "width", &width, + NULL)) + return; + + if (color == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"color\" attribute on element <%s>"), element_name); + return; + } + + if (x1 == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"x1\" attribute on element <%s>"), element_name); + return; + } + + if (y1 == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"y1\" attribute on element <%s>"), element_name); + return; + } + + if (x2 == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"x2\" attribute on element <%s>"), element_name); + return; + } + + if (y2 == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"y2\" attribute on element <%s>"), element_name); + return; + } + + if (!check_expression (x1, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y1, FALSE, info->theme, context, error)) + return; + + if (!check_expression (x2, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y2, FALSE, info->theme, context, error)) + return; + + dash_on_val = 0; + if (dash_on_length && + !parse_positive_integer (dash_on_length, &dash_on_val, context, error)) + return; + + dash_off_val = 0; + if (dash_off_length && + !parse_positive_integer (dash_off_length, &dash_off_val, context, error)) + return; + + width_val = 0; + if (width && + !parse_positive_integer (width, &width_val, context, error)) + return; + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = meta_color_spec_new_from_string (color, error); + if (color_spec == NULL) + { + add_context_to_error (error, context); + return; + } + + op = meta_draw_op_new (META_DRAW_LINE); + + op->data.line.color_spec = color_spec; + op->data.line.x1 = optimize_expression (info->theme, x1); + op->data.line.y1 = optimize_expression (info->theme, y1); + op->data.line.x2 = optimize_expression (info->theme, x2); + op->data.line.y2 = optimize_expression (info->theme, y2); + op->data.line.width = width_val; + op->data.line.dash_on_length = dash_on_val; + op->data.line.dash_off_length = dash_off_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_LINE); + } + else if (ELEMENT_IS ("rectangle")) + { + MetaDrawOp *op; + const char *color; + const char *x; + const char *y; + const char *width; + const char *height; + const char *filled; + gboolean filled_val; + MetaColorSpec *color_spec; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "color", &color, + "x", &x, "y", &y, + "width", &width, "height", &height, + "filled", &filled, + NULL)) + return; + + if (color == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"color\" attribute on element <%s>"), element_name); + return; + } + + if (x == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"x\" attribute on element <%s>"), element_name); + return; + } + + if (y == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"y\" attribute on element <%s>"), element_name); + return; + } + + if (width == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"width\" attribute on element <%s>"), element_name); + return; + } + + if (height == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"height\" attribute on element <%s>"), element_name); + return; + } + + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; + + filled_val = FALSE; + if (filled && !parse_boolean (filled, &filled_val, context, error)) + return; + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = meta_color_spec_new_from_string (color, error); + if (color_spec == NULL) + { + add_context_to_error (error, context); + return; + } + + op = meta_draw_op_new (META_DRAW_RECTANGLE); + + op->data.rectangle.color_spec = color_spec; + op->data.rectangle.x = optimize_expression (info->theme, x); + op->data.rectangle.y = optimize_expression (info->theme, y); + op->data.rectangle.width = optimize_expression (info->theme, width); + op->data.rectangle.height = optimize_expression (info->theme, height); + op->data.rectangle.filled = filled_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_RECTANGLE); + } + else if (ELEMENT_IS ("arc")) + { + MetaDrawOp *op; + const char *color; + const char *x; + const char *y; + const char *width; + const char *height; + const char *filled; + const char *start_angle; + const char *extent_angle; + gboolean filled_val; + double start_angle_val; + double extent_angle_val; + MetaColorSpec *color_spec; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "color", &color, + "x", &x, "y", &y, + "width", &width, "height", &height, + "filled", &filled, + "start_angle", &start_angle, + "extent_angle", &extent_angle, + NULL)) + return; + + if (color == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"color\" attribute on element <%s>"), element_name); + return; + } + + if (x == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"x\" attribute on element <%s>"), element_name); + return; + } + + if (y == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"y\" attribute on element <%s>"), element_name); + return; + } + + if (width == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"width\" attribute on element <%s>"), element_name); + return; + } + + if (height == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"height\" attribute on element <%s>"), element_name); + return; + } + + if (start_angle == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"start_angle\" attribute on element <%s>"), element_name); + return; + } + + if (extent_angle == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"extent_angle\" attribute on element <%s>"), element_name); + return; + } + + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; + + if (!parse_angle (start_angle, &start_angle_val, context, error)) + return; + + if (!parse_angle (extent_angle, &extent_angle_val, context, error)) + return; + + filled_val = FALSE; + if (filled && !parse_boolean (filled, &filled_val, context, error)) + return; + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = meta_color_spec_new_from_string (color, error); + if (color_spec == NULL) + { + add_context_to_error (error, context); + return; + } + + op = meta_draw_op_new (META_DRAW_ARC); + + op->data.arc.color_spec = color_spec; + op->data.arc.x = optimize_expression (info->theme, x); + op->data.arc.y = optimize_expression (info->theme, y); + op->data.arc.width = optimize_expression (info->theme, width); + op->data.arc.height = optimize_expression (info->theme, height); + op->data.arc.filled = filled_val; + op->data.arc.start_angle = start_angle_val; + op->data.arc.extent_angle = extent_angle_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_ARC); + } + else if (ELEMENT_IS ("clip")) + { + MetaDrawOp *op; + const char *x; + const char *y; + const char *width; + const char *height; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "x", &x, "y", &y, + "width", &width, "height", &height, + NULL)) + return; + + if (x == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"x\" attribute on element <%s>"), element_name); + return; + } + + if (y == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"y\" attribute on element <%s>"), element_name); + return; + } + + if (width == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"width\" attribute on element <%s>"), element_name); + return; + } + + if (height == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"height\" attribute on element <%s>"), element_name); + return; + } + + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; + + op = meta_draw_op_new (META_DRAW_CLIP); + + op->data.clip.x = optimize_expression (info->theme, x); + op->data.clip.y = optimize_expression (info->theme, y); + op->data.clip.width = optimize_expression (info->theme, width); + op->data.clip.height = optimize_expression (info->theme, height); + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_CLIP); + } + else if (ELEMENT_IS ("tint")) + { + MetaDrawOp *op; + const char *color; + const char *x; + const char *y; + const char *width; + const char *height; + const char *alpha; + double alpha_val; + MetaColorSpec *color_spec; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "color", &color, + "x", &x, "y", &y, + "width", &width, "height", &height, + "alpha", &alpha, + NULL)) + return; + + if (color == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"color\" attribute on element <%s>"), element_name); + return; + } + + if (x == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"x\" attribute on element <%s>"), element_name); + return; + } + + if (y == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"y\" attribute on element <%s>"), element_name); + return; + } + + if (width == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"width\" attribute on element <%s>"), element_name); + return; + } + + if (height == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"height\" attribute on element <%s>"), element_name); + return; + } + + if (alpha == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"alpha\" attribute on element <%s>"), element_name); + return; + } + + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; + + if (!parse_alpha (alpha, &alpha_val, context, error)) + return; + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = meta_color_spec_new_from_string (color, error); + if (color_spec == NULL) + { + add_context_to_error (error, context); + return; + } + + op = meta_draw_op_new (META_DRAW_TINT); + + op->data.tint.color_spec = color_spec; + op->data.tint.x = optimize_expression (info->theme, x); + op->data.tint.y = optimize_expression (info->theme, y); + op->data.tint.width = optimize_expression (info->theme, width); + op->data.tint.height = optimize_expression (info->theme, height); + op->data.tint.alpha = alpha_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_TINT); + } + else if (ELEMENT_IS ("gradient")) + { + const char *x; + const char *y; + const char *width; + const char *height; + const char *type; + const char *alpha; + double alpha_val; + MetaGradientType type_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "type", &type, + "x", &x, "y", &y, + "width", &width, "height", &height, + "alpha", &alpha, + NULL)) + return; + + if (type == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"type\" attribute on element <%s>"), element_name); + return; + } + + if (x == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"x\" attribute on element <%s>"), element_name); + return; + } + + if (y == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"y\" attribute on element <%s>"), element_name); + return; + } + + if (width == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"width\" attribute on element <%s>"), element_name); + return; + } + + if (height == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"height\" attribute on element <%s>"), element_name); + return; + } + + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; + + alpha_val = 1.0; + if (alpha && !parse_alpha (alpha, &alpha_val, context, error)) + return; + + type_val = meta_gradient_type_from_string (type); + if (type_val == META_GRADIENT_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Did not understand value \"%s\" for type of gradient"), + type); + return; + } + + g_assert (info->op == NULL); + info->op = meta_draw_op_new (META_DRAW_GRADIENT); + + info->op->data.gradient.x = optimize_expression (info->theme, x); + info->op->data.gradient.y = optimize_expression (info->theme, y); + info->op->data.gradient.width = optimize_expression (info->theme, width); + info->op->data.gradient.height = optimize_expression (info->theme, height); + + info->op->data.gradient.gradient_spec = + meta_gradient_spec_new (type_val); + + info->op->data.gradient.alpha = alpha_val; + + push_state (info, STATE_GRADIENT); + + /* op gets appended on close tag */ + } + else if (ELEMENT_IS ("image")) + { + MetaDrawOp *op; + const char *filename; + const char *x; + const char *y; + const char *width; + const char *height; + const char *alpha; + double alpha_val; + GdkPixbuf *pixbuf; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "x", &x, "y", &y, + "width", &width, "height", &height, + "alpha", &alpha, "filename", &filename, + NULL)) + return; + + if (x == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"x\" attribute on element <%s>"), element_name); + return; + } + + if (y == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"y\" attribute on element <%s>"), element_name); + return; + } + + if (width == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"width\" attribute on element <%s>"), element_name); + return; + } + + if (height == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"height\" attribute on element <%s>"), element_name); + return; + } + + if (filename == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"filename\" attribute on element <%s>"), element_name); + return; + } + + if (!check_expression (x, TRUE, info->theme, context, error)) + return; + + if (!check_expression (y, TRUE, info->theme, context, error)) + return; + + if (!check_expression (width, TRUE, info->theme, context, error)) + return; + + if (!check_expression (height, TRUE, info->theme, context, error)) + return; + + alpha_val = 1.0; + if (alpha && !parse_alpha (alpha, &alpha_val, context, error)) + return; + + /* Check last so we don't have to free it when other + * stuff fails + */ + pixbuf = meta_theme_load_image (info->theme, filename, error); + + if (pixbuf == NULL) + { + add_context_to_error (error, context); + return; + } + + op = meta_draw_op_new (META_DRAW_IMAGE); + + op->data.image.pixbuf = pixbuf; + op->data.image.x = optimize_expression (info->theme, x); + op->data.image.y = optimize_expression (info->theme, y); + op->data.image.width = optimize_expression (info->theme, width); + op->data.image.height = optimize_expression (info->theme, height); + op->data.image.alpha = alpha_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_IMAGE); + } + else if (ELEMENT_IS ("gtk_arrow")) + { + MetaDrawOp *op; + const char *state; + const char *shadow; + const char *arrow; + const char *x; + const char *y; + const char *width; + const char *height; + const char *filled; + gboolean filled_val; + GtkStateType state_val; + GtkShadowType shadow_val; + GtkArrowType arrow_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "state", &state, + "shadow", &shadow, + "arrow", &arrow, + "x", &x, "y", &y, + "width", &width, "height", &height, + "filled", &filled, + NULL)) + return; + + if (state == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"state\" attribute on element <%s>"), element_name); + return; + } + + if (shadow == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"shadow\" attribute on element <%s>"), element_name); + return; + } + + if (arrow == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"arrow\" attribute on element <%s>"), element_name); + return; + } + + if (x == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"x\" attribute on element <%s>"), element_name); + return; + } + + if (y == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"y\" attribute on element <%s>"), element_name); + return; + } + + if (width == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"width\" attribute on element <%s>"), element_name); + return; + } + + if (height == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"height\" attribute on element <%s>"), element_name); + return; + } + + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; + + filled_val = TRUE; + if (filled && !parse_boolean (filled, &filled_val, context, error)) + return; + + state_val = meta_gtk_state_from_string (state); + if (((int) state_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand state \"%s\" for <%s> element"), + state, element_name); + return; + } + + shadow_val = meta_gtk_shadow_from_string (shadow); + if (((int) shadow_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand shadow \"%s\" for <%s> element"), + shadow, element_name); + return; + } + + arrow_val = meta_gtk_arrow_from_string (arrow); + if (((int) arrow_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand arrow \"%s\" for <%s> element"), + arrow, element_name); + return; + } + + op = meta_draw_op_new (META_DRAW_GTK_ARROW); + + op->data.gtk_arrow.x = optimize_expression (info->theme, x); + op->data.gtk_arrow.y = optimize_expression (info->theme, y); + op->data.gtk_arrow.width = optimize_expression (info->theme, width); + op->data.gtk_arrow.height = optimize_expression (info->theme, height); + op->data.gtk_arrow.filled = filled_val; + op->data.gtk_arrow.state = state_val; + op->data.gtk_arrow.shadow = shadow_val; + op->data.gtk_arrow.arrow = arrow_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_GTK_ARROW); + } + else if (ELEMENT_IS ("gtk_box")) + { + MetaDrawOp *op; + const char *state; + const char *shadow; + const char *x; + const char *y; + const char *width; + const char *height; + GtkStateType state_val; + GtkShadowType shadow_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "state", &state, + "shadow", &shadow, + "x", &x, "y", &y, + "width", &width, "height", &height, + NULL)) + return; + + if (state == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"state\" attribute on element <%s>"), element_name); + return; + } + + if (shadow == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"shadow\" attribute on element <%s>"), element_name); + return; + } + + if (x == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"x\" attribute on element <%s>"), element_name); + return; + } + + if (y == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"y\" attribute on element <%s>"), element_name); + return; + } + + if (width == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"width\" attribute on element <%s>"), element_name); + return; + } + + if (height == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"height\" attribute on element <%s>"), element_name); + return; + } + + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; + + state_val = meta_gtk_state_from_string (state); + if (((int) state_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand state \"%s\" for <%s> element"), + state, element_name); + return; + } + + shadow_val = meta_gtk_shadow_from_string (shadow); + if (((int) shadow_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand shadow \"%s\" for <%s> element"), + shadow, element_name); + return; + } + + op = meta_draw_op_new (META_DRAW_GTK_BOX); + + op->data.gtk_box.x = optimize_expression (info->theme, x); + op->data.gtk_box.y = optimize_expression (info->theme, y); + op->data.gtk_box.width = optimize_expression (info->theme, width); + op->data.gtk_box.height = optimize_expression (info->theme, height); + op->data.gtk_box.state = state_val; + op->data.gtk_box.shadow = shadow_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_GTK_BOX); + } + else if (ELEMENT_IS ("gtk_vline")) + { + MetaDrawOp *op; + const char *state; + const char *x; + const char *y1; + const char *y2; + GtkStateType state_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "state", &state, + "x", &x, "y1", &y1, "y2", &y2, + NULL)) + return; + + if (state == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"state\" attribute on element <%s>"), element_name); + return; + } + + if (x == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"x\" attribute on element <%s>"), element_name); + return; + } + + if (y1 == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"y1\" attribute on element <%s>"), element_name); + return; + } + + if (y2 == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"y2\" attribute on element <%s>"), element_name); + return; + } + + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y1, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y2, FALSE, info->theme, context, error)) + return; + + state_val = meta_gtk_state_from_string (state); + if (((int) state_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand state \"%s\" for <%s> element"), + state, element_name); + return; + } + + op = meta_draw_op_new (META_DRAW_GTK_VLINE); + + op->data.gtk_vline.x = optimize_expression (info->theme, x); + op->data.gtk_vline.y1 = optimize_expression (info->theme, y1); + op->data.gtk_vline.y2 = optimize_expression (info->theme, y2); + op->data.gtk_vline.state = state_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_GTK_VLINE); + } + else if (ELEMENT_IS ("icon")) + { + MetaDrawOp *op; + const char *x; + const char *y; + const char *width; + const char *height; + const char *alpha; + double alpha_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "x", &x, "y", &y, + "width", &width, "height", &height, + "alpha", &alpha, + NULL)) + return; + + if (x == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"x\" attribute on element <%s>"), element_name); + return; + } + + if (y == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"y\" attribute on element <%s>"), element_name); + return; + } + + if (width == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"width\" attribute on element <%s>"), element_name); + return; + } + + if (height == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"height\" attribute on element <%s>"), element_name); + return; + } + + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; + + alpha_val = 1.0; + if (alpha && !parse_alpha (alpha, &alpha_val, context, error)) + return; + + op = meta_draw_op_new (META_DRAW_ICON); + + op->data.icon.x = optimize_expression (info->theme, x); + op->data.icon.y = optimize_expression (info->theme, y); + op->data.icon.width = optimize_expression (info->theme, width); + op->data.icon.height = optimize_expression (info->theme, height); + op->data.icon.alpha = alpha_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_ICON); + } + else if (ELEMENT_IS ("title")) + { + MetaDrawOp *op; + const char *color; + const char *x; + const char *y; + MetaColorSpec *color_spec; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "color", &color, + "x", &x, "y", &y, + NULL)) + return; + + if (color == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"color\" attribute on element <%s>"), element_name); + return; + } + + if (x == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"x\" attribute on element <%s>"), element_name); + return; + } + + if (y == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"y\" attribute on element <%s>"), element_name); + return; + } + + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = meta_color_spec_new_from_string (color, error); + if (color_spec == NULL) + { + add_context_to_error (error, context); + return; + } + + op = meta_draw_op_new (META_DRAW_TITLE); + + op->data.title.color_spec = color_spec; + op->data.title.x = optimize_expression (info->theme, x); + op->data.title.y = optimize_expression (info->theme, y); + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_TITLE); + } + else if (ELEMENT_IS ("include")) + { + MetaDrawOp *op; + const char *name; + const char *x; + const char *y; + const char *width; + const char *height; + MetaDrawOpList *op_list; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "x", &x, "y", &y, + "width", &width, "height", &height, + "name", &name, + NULL)) + return; + + if (name == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"name\" attribute on element <%s>"), element_name); + return; + } + + /* x/y/width/height default to 0,0,width,height - should + * probably do this for all the draw ops + */ + + if (x && !check_expression (x, FALSE, info->theme, context, error)) + return; + + if (y && !check_expression (y, FALSE, info->theme, context, error)) + return; + + if (width && !check_expression (width, FALSE, info->theme, context, error)) + return; + + if (height && !check_expression (height, FALSE, info->theme, context, error)) + return; + + op_list = meta_theme_lookup_draw_op_list (info->theme, + name); + if (op_list == NULL) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("No <draw_ops> called \"%s\" has been defined"), + name); + return; + } + + g_assert (info->op_list); + + if (op_list == info->op_list || + meta_draw_op_list_contains (op_list, info->op_list)) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Including draw_ops \"%s\" here would create a circular reference"), + name); + return; + } + + op = meta_draw_op_new (META_DRAW_OP_LIST); + + meta_draw_op_list_ref (op_list); + op->data.op_list.op_list = op_list; + op->data.op_list.x = x ? optimize_expression (info->theme, x) : + g_strdup ("0"); + op->data.op_list.y = y ? optimize_expression (info->theme, y) : + g_strdup ("0"); + op->data.op_list.width = width ? optimize_expression (info->theme, width) : + g_strdup ("width"); + op->data.op_list.height = height ? optimize_expression (info->theme, height) : + g_strdup ("height"); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_INCLUDE); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <draw_ops>"), + element_name); + } +} + +static void +parse_gradient_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_GRADIENT); + + if (ELEMENT_IS ("color")) + { + const char *value = NULL; + MetaColorSpec *color_spec; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "value", &value, + NULL)) + return; + + if (value == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"value\" attribute on <%s> element"), + element_name); + return; + } + + color_spec = meta_color_spec_new_from_string (value, error); + if (color_spec == NULL) + { + add_context_to_error (error, context); + return; + } + + g_assert (info->op); + g_assert (info->op->type == META_DRAW_GRADIENT); + g_assert (info->op->data.gradient.gradient_spec != NULL); + info->op->data.gradient.gradient_spec->color_specs = + g_slist_append (info->op->data.gradient.gradient_spec->color_specs, + color_spec); + + push_state (info, STATE_COLOR); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <gradient>"), + element_name); + } +} + +static void +parse_style_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_FRAME_STYLE); + + g_assert (info->style); + + if (ELEMENT_IS ("piece")) + { + const char *position = NULL; + const char *draw_ops = NULL; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "position", &position, + "draw_ops", &draw_ops, + NULL)) + return; + + if (position == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"position\" attribute on <%s> element"), + element_name); + return; + } + + info->piece = meta_frame_piece_from_string (position); + if (info->piece == META_FRAME_PIECE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Unknown position \"%s\" for frame piece"), + position); + return; + } + + if (info->style->pieces[info->piece] != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Frame style already has a piece at position %s"), + position); + return; + } + + g_assert (info->op_list == NULL); + + if (draw_ops) + { + MetaDrawOpList *op_list; + + op_list = meta_theme_lookup_draw_op_list (info->theme, + draw_ops); + + if (op_list == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No <draw_ops> with the name \"%s\" has been defined"), + draw_ops); + return; + } + + meta_draw_op_list_ref (op_list); + info->op_list = op_list; + } + + push_state (info, STATE_PIECE); + } + else if (ELEMENT_IS ("button")) + { + const char *function = NULL; + const char *state = NULL; + const char *draw_ops = NULL; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "function", &function, + "state", &state, + "draw_ops", &draw_ops, + NULL)) + return; + + if (function == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"function\" attribute on <%s> element"), + element_name); + return; + } + + if (state == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"state\" attribute on <%s> element"), + element_name); + return; + } + + info->button_type = meta_button_type_from_string (function); + if (info->button_type == META_BUTTON_TYPE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Unknown function \"%s\" for button"), + function); + return; + } + + info->button_state = meta_button_state_from_string (state); + if (info->button_state == META_BUTTON_STATE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Unknown state \"%s\" for button"), + state); + return; + } + + if (info->style->buttons[info->button_type][info->button_state] != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Frame style already has a button for function %s state %s"), + function, state); + return; + } + + g_assert (info->op_list == NULL); + + if (draw_ops) + { + MetaDrawOpList *op_list; + + op_list = meta_theme_lookup_draw_op_list (info->theme, + draw_ops); + + if (op_list == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No <draw_ops> with the name \"%s\" has been defined"), + draw_ops); + return; + } + + meta_draw_op_list_ref (op_list); + info->op_list = op_list; + } + + push_state (info, STATE_BUTTON); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <frame_style>"), + element_name); + } +} + +static void +parse_style_set_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_FRAME_STYLE_SET); + + if (ELEMENT_IS ("frame")) + { + const char *focus = NULL; + const char *state = NULL; + const char *resize = NULL; + const char *style = NULL; + MetaFrameFocus frame_focus; + MetaFrameState frame_state; + MetaFrameResize frame_resize; + MetaFrameStyle *frame_style; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "focus", &focus, + "state", &state, + "resize", &resize, + "style", &style, + NULL)) + return; + + if (focus == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"focus\" attribute on <%s> element"), + element_name); + return; + } + + if (state == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"state\" attribute on <%s> element"), + element_name); + return; + } + + if (style == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"style\" attribute on <%s> element"), + element_name); + return; + } + + frame_focus = meta_frame_focus_from_string (focus); + if (frame_focus == META_FRAME_FOCUS_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("\"%s\" is not a valid value for focus attribute"), + focus); + return; + } + + frame_state = meta_frame_state_from_string (state); + if (frame_state == META_FRAME_STATE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("\"%s\" is not a valid value for state attribute"), + focus); + return; + } + + frame_style = meta_theme_lookup_style (info->theme, style); + + if (frame_style == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("A style called \"%s\" has not been defined"), + style); + return; + } + + if (frame_state == META_FRAME_STATE_NORMAL) + { + if (resize == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"resize\" attribute on <%s> element"), + element_name); + return; + } + + + frame_resize = meta_frame_resize_from_string (resize); + if (frame_resize == META_FRAME_RESIZE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("\"%s\" is not a valid value for resize attribute"), + focus); + return; + } + } + else + { + if (resize != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Should not have \"resize\" attribute on <%s> element for maximized/shaded states"), + element_name); + return; + } + + frame_resize = META_FRAME_RESIZE_LAST; + } + + switch (frame_state) + { + case META_FRAME_STATE_NORMAL: + if (info->style_set->normal_styles[frame_resize][frame_focus]) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Style has already been specified for state %s resize %s focus %s"), + state, resize, focus); + return; + } + meta_frame_style_ref (frame_style); + info->style_set->normal_styles[frame_resize][frame_focus] = frame_style; + break; + case META_FRAME_STATE_MAXIMIZED: + if (info->style_set->maximized_styles[frame_focus]) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Style has already been specified for state %s focus %s"), + state, focus); + return; + } + meta_frame_style_ref (frame_style); + info->style_set->maximized_styles[frame_focus] = frame_style; + break; + case META_FRAME_STATE_SHADED: + if (info->style_set->shaded_styles[frame_focus]) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Style has already been specified for state %s focus %s"), + state, focus); + return; + } + meta_frame_style_ref (frame_style); + info->style_set->shaded_styles[frame_focus] = frame_style; + break; + case META_FRAME_STATE_MAXIMIZED_AND_SHADED: + if (info->style_set->maximized_and_shaded_styles[frame_focus]) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Style has already been specified for state %s focus %s"), + state, focus); + return; + } + meta_frame_style_ref (frame_style); + info->style_set->maximized_and_shaded_styles[frame_focus] = frame_style; + break; + case META_FRAME_STATE_LAST: + g_assert_not_reached (); + break; + } + + push_state (info, STATE_FRAME); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <frame_style_set>"), + element_name); + } +} + +static void +parse_piece_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_PIECE); + + if (ELEMENT_IS ("draw_ops")) + { + if (info->op_list) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Can't have a two draw_ops for a <piece> element (theme specified a draw_ops attribute and also a <draw_ops> element, or specified two elements)")); + return; + } + + if (!check_no_attributes (context, element_name, attribute_names, attribute_values, + error)) + return; + + g_assert (info->op_list == NULL); + info->op_list = meta_draw_op_list_new (2); + + push_state (info, STATE_DRAW_OPS); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <piece>"), + element_name); + } +} + +static void +parse_button_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_BUTTON); + + if (ELEMENT_IS ("draw_ops")) + { + if (info->op_list) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Can't have a two draw_ops for a <button> element (theme specified a draw_ops attribute and also a <draw_ops> element, or specified two elements)")); + return; + } + + if (!check_no_attributes (context, element_name, attribute_names, attribute_values, + error)) + return; + + g_assert (info->op_list == NULL); + info->op_list = meta_draw_op_list_new (2); + + push_state (info, STATE_DRAW_OPS); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <piece>"), + element_name); + } +} + +static void +parse_menu_icon_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_MENU_ICON); + + if (ELEMENT_IS ("draw_ops")) + { + if (info->op_list) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Can't have a two draw_ops for a <menu_icon> element (theme specified a draw_ops attribute and also a <draw_ops> element, or specified two elements)")); + return; + } + + if (!check_no_attributes (context, element_name, attribute_names, attribute_values, + error)) + return; + + g_assert (info->op_list == NULL); + info->op_list = meta_draw_op_list_new (2); + + push_state (info, STATE_DRAW_OPS); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <piece>"), + element_name); + } +} + + +static void +start_element_handler (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + ParseInfo *info = user_data; + + switch (peek_state (info)) + { + case STATE_START: + if (strcmp (element_name, "metacity_theme") == 0) + { + info->theme = meta_theme_new (); + info->theme->name = g_strdup (info->theme_name); + info->theme->filename = g_strdup (info->theme_file); + info->theme->dirname = g_strdup (info->theme_dir); + + push_state (info, STATE_THEME); + } + else + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Outermost element in theme must be <metacity_theme> not <%s>"), + element_name); + break; + + case STATE_THEME: + parse_toplevel_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_INFO: + parse_info_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_NAME: + case STATE_AUTHOR: + case STATE_COPYRIGHT: + case STATE_DATE: + case STATE_DESCRIPTION: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed inside a name/author/date/description element"), + element_name); + break; + case STATE_CONSTANT: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed inside a <constant> element"), + element_name); + break; + case STATE_FRAME_GEOMETRY: + parse_geometry_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_DISTANCE: + case STATE_BORDER: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed inside a distance/border element"), + element_name); + break; + case STATE_DRAW_OPS: + parse_draw_op_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_LINE: + case STATE_RECTANGLE: + case STATE_ARC: + case STATE_CLIP: + case STATE_TINT: + case STATE_IMAGE: + case STATE_GTK_ARROW: + case STATE_GTK_BOX: + case STATE_GTK_VLINE: + case STATE_ICON: + case STATE_TITLE: + case STATE_INCLUDE: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed inside a draw operation element"), + element_name); + break; + case STATE_GRADIENT: + parse_gradient_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_COLOR: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed inside a <color> element"), + element_name); + break; + case STATE_FRAME_STYLE: + parse_style_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_PIECE: + parse_piece_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_BUTTON: + parse_button_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_MENU_ICON: + parse_menu_icon_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_FRAME_STYLE_SET: + parse_style_set_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_FRAME: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed inside a <frame> element"), + element_name); + break; + case STATE_WINDOW: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed inside a <window> element"), + element_name); + break; + } +} + +static void +end_element_handler (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + ParseInfo *info = user_data; + + switch (peek_state (info)) + { + case STATE_START: + break; + case STATE_THEME: + g_assert (info->theme); + + if (!meta_theme_validate (info->theme, error)) + { + add_context_to_error (error, context); + meta_theme_free (info->theme); + info->theme = NULL; + } + + pop_state (info); + g_assert (peek_state (info) == STATE_START); + break; + case STATE_INFO: + pop_state (info); + g_assert (peek_state (info) == STATE_THEME); + break; + case STATE_NAME: + pop_state (info); + g_assert (peek_state (info) == STATE_INFO); + break; + case STATE_AUTHOR: + pop_state (info); + g_assert (peek_state (info) == STATE_INFO); + break; + case STATE_COPYRIGHT: + pop_state (info); + g_assert (peek_state (info) == STATE_INFO); + break; + case STATE_DATE: + pop_state (info); + g_assert (peek_state (info) == STATE_INFO); + break; + case STATE_DESCRIPTION: + pop_state (info); + g_assert (peek_state (info) == STATE_INFO); + break; + case STATE_CONSTANT: + pop_state (info); + g_assert (peek_state (info) == STATE_THEME); + break; + case STATE_FRAME_GEOMETRY: + g_assert (info->layout); + + if (!meta_frame_layout_validate (info->layout, + error)) + { + add_context_to_error (error, context); + } + + /* layout will already be stored in the theme under + * its name + */ + meta_frame_layout_unref (info->layout); + info->layout = NULL; + pop_state (info); + g_assert (peek_state (info) == STATE_THEME); + break; + case STATE_DISTANCE: + pop_state (info); + g_assert (peek_state (info) == STATE_FRAME_GEOMETRY); + break; + case STATE_BORDER: + pop_state (info); + g_assert (peek_state (info) == STATE_FRAME_GEOMETRY); + break; + case STATE_DRAW_OPS: + { + g_assert (info->op_list); + + if (!meta_draw_op_list_validate (info->op_list, + error)) + { + add_context_to_error (error, context); + meta_draw_op_list_unref (info->op_list); + info->op_list = NULL; + } + + pop_state (info); + + switch (peek_state (info)) + { + case STATE_BUTTON: + case STATE_PIECE: + case STATE_MENU_ICON: + /* Leave info->op_list to be picked up + * when these elements are closed + */ + g_assert (info->op_list); + break; + case STATE_THEME: + g_assert (info->op_list); + meta_draw_op_list_unref (info->op_list); + info->op_list = NULL; + break; + default: + /* Op list can't occur in other contexts */ + g_assert_not_reached (); + break; + } + } + break; + case STATE_LINE: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_RECTANGLE: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_ARC: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_CLIP: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_TINT: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_GRADIENT: + g_assert (info->op); + g_assert (info->op->type == META_DRAW_GRADIENT); + if (!meta_gradient_spec_validate (info->op->data.gradient.gradient_spec, + error)) + { + add_context_to_error (error, context); + meta_draw_op_free (info->op); + info->op = NULL; + } + else + { + g_assert (info->op_list); + meta_draw_op_list_append (info->op_list, info->op); + info->op = NULL; + } + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_IMAGE: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_GTK_ARROW: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_GTK_BOX: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_GTK_VLINE: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_ICON: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_TITLE: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_INCLUDE: + pop_state (info); + g_assert (peek_state (info) == STATE_DRAW_OPS); + break; + case STATE_COLOR: + pop_state (info); + g_assert (peek_state (info) == STATE_GRADIENT); + break; + case STATE_FRAME_STYLE: + g_assert (info->style); + + if (!meta_frame_style_validate (info->style, + error)) + { + add_context_to_error (error, context); + } + + /* Frame style is in the theme hash table and a ref + * is held there + */ + meta_frame_style_unref (info->style); + info->style = NULL; + pop_state (info); + g_assert (peek_state (info) == STATE_THEME); + break; + case STATE_PIECE: + g_assert (info->style); + if (info->op_list == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No draw_ops provided for frame piece")); + } + else + { + info->style->pieces[info->piece] = info->op_list; + info->op_list = NULL; + } + pop_state (info); + g_assert (peek_state (info) == STATE_FRAME_STYLE); + break; + case STATE_BUTTON: + g_assert (info->style); + if (info->op_list == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No draw_ops provided for button")); + } + else + { + info->style->buttons[info->button_type][info->button_state] = + info->op_list; + info->op_list = NULL; + } + pop_state (info); + break; + case STATE_MENU_ICON: + g_assert (info->theme); + if (info->op_list == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No draw_ops provided for menu icon")); + } + else + { + g_assert (info->theme->menu_icons[info->menu_icon_type][info->menu_icon_state] == NULL); + info->theme->menu_icons[info->menu_icon_type][info->menu_icon_state] = + info->op_list; + info->op_list = NULL; + } + pop_state (info); + g_assert (peek_state (info) == STATE_THEME); + break; + case STATE_FRAME_STYLE_SET: + g_assert (info->style_set); + + if (!meta_frame_style_set_validate (info->style_set, + error)) + { + add_context_to_error (error, context); + } + + /* Style set is in the theme hash table and a reference + * is held there. + */ + meta_frame_style_set_unref (info->style_set); + info->style_set = NULL; + pop_state (info); + g_assert (peek_state (info) == STATE_THEME); + break; + case STATE_FRAME: + pop_state (info); + g_assert (peek_state (info) == STATE_FRAME_STYLE_SET); + break; + case STATE_WINDOW: + pop_state (info); + g_assert (peek_state (info) == STATE_THEME); + break; + } +} + +#define NO_TEXT(element_name) set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, _("No text is allowed inside element <%s>"), element_name) + +static gboolean +all_whitespace (const char *text, + int text_len) +{ + const char *p; + const char *end; + + p = text; + end = text + text_len; + + while (p != end) + { + if (!g_ascii_isspace (*p)) + return FALSE; + + p = g_utf8_next_char (p); + } + + return TRUE; +} + +static void +text_handler (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + ParseInfo *info = user_data; + + if (all_whitespace (text, text_len)) + return; + + /* FIXME http://bugzilla.gnome.org/show_bug.cgi?id=70448 would + * allow a nice cleanup here. + */ + + switch (peek_state (info)) + { + case STATE_START: + g_assert_not_reached (); /* gmarkup shouldn't do this */ + break; + case STATE_THEME: + NO_TEXT ("metacity_theme"); + break; + case STATE_INFO: + NO_TEXT ("info"); + break; + case STATE_NAME: + if (info->theme->readable_name != NULL) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("<name> specified twice for this theme")); + return; + } + + info->theme->readable_name = g_strndup (text, text_len); + break; + case STATE_AUTHOR: + if (info->theme->author != NULL) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("<author> specified twice for this theme")); + return; + } + + info->theme->author = g_strndup (text, text_len); + break; + case STATE_COPYRIGHT: + if (info->theme->copyright != NULL) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("<copyright> specified twice for this theme")); + return; + } + + info->theme->copyright = g_strndup (text, text_len); + break; + case STATE_DATE: + if (info->theme->date != NULL) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("<date> specified twice for this theme")); + return; + } + + info->theme->date = g_strndup (text, text_len); + break; + case STATE_DESCRIPTION: + if (info->theme->description != NULL) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("<description> specified twice for this theme")); + return; + } + + info->theme->description = g_strndup (text, text_len); + break; + case STATE_CONSTANT: + NO_TEXT ("constant"); + break; + case STATE_FRAME_GEOMETRY: + NO_TEXT ("frame_geometry"); + break; + case STATE_DISTANCE: + NO_TEXT ("distance"); + break; + case STATE_BORDER: + NO_TEXT ("border"); + break; + case STATE_DRAW_OPS: + NO_TEXT ("draw_ops"); + break; + case STATE_LINE: + NO_TEXT ("line"); + break; + case STATE_RECTANGLE: + NO_TEXT ("rectangle"); + break; + case STATE_ARC: + NO_TEXT ("arc"); + break; + case STATE_CLIP: + NO_TEXT ("clip"); + break; + case STATE_TINT: + NO_TEXT ("tint"); + break; + case STATE_GRADIENT: + NO_TEXT ("gradient"); + break; + case STATE_IMAGE: + NO_TEXT ("image"); + break; + case STATE_GTK_ARROW: + NO_TEXT ("gtk_arrow"); + break; + case STATE_GTK_BOX: + NO_TEXT ("gtk_box"); + break; + case STATE_GTK_VLINE: + NO_TEXT ("gtk_vline"); + break; + case STATE_ICON: + NO_TEXT ("icon"); + break; + case STATE_TITLE: + NO_TEXT ("title"); + break; + case STATE_INCLUDE: + NO_TEXT ("include"); + break; + case STATE_COLOR: + NO_TEXT ("color"); + break; + case STATE_FRAME_STYLE: + NO_TEXT ("frame_style"); + break; + case STATE_PIECE: + NO_TEXT ("piece"); + break; + case STATE_BUTTON: + NO_TEXT ("button"); + break; + case STATE_MENU_ICON: + NO_TEXT ("menu_icon"); + break; + case STATE_FRAME_STYLE_SET: + NO_TEXT ("frame_style_set"); + break; + case STATE_FRAME: + NO_TEXT ("frame"); + break; + case STATE_WINDOW: + NO_TEXT ("window"); + break; + } +} + +/* We change the filename when we break the format, + * so themes can work with various metacity versions + */ +#define THEME_FILENAME "metacity-theme-1.xml" + +MetaTheme* +meta_theme_load (const char *theme_name, + GError **err) +{ + GMarkupParseContext *context; + GError *error; + ParseInfo info; + char *text; + int length; + char *theme_file; + char *theme_dir; + MetaTheme *retval; + + text = NULL; + length = 0; + retval = NULL; + + theme_dir = NULL; + theme_file = NULL; + + if (meta_is_debugging ()) + { + theme_dir = g_build_filename ("./themes", theme_name, NULL); + + theme_file = g_build_filename (theme_dir, + THEME_FILENAME, + NULL); + + error = NULL; + if (!g_file_get_contents (theme_file, + &text, + &length, + &error)) + { + meta_verbose ("Failed to read theme from file %s: %s\n", + theme_file, error->message); + g_error_free (error); + g_free (theme_dir); + g_free (theme_file); + theme_file = NULL; + } + } + + /* We try in current dir, then home dir, then system dir for themes */ + if (text == NULL) + { + theme_dir = g_build_filename ("./", theme_name, NULL); + + theme_file = g_build_filename (theme_dir, + THEME_FILENAME, + NULL); + + error = NULL; + if (!g_file_get_contents (theme_file, + &text, + &length, + &error)) + { + meta_verbose ("Failed to read theme from file %s: %s\n", + theme_file, error->message); + g_error_free (error); + g_free (theme_dir); + g_free (theme_file); + theme_file = NULL; + } + } + + if (text == NULL) + { + theme_dir = g_build_filename (g_get_home_dir (), + ".metacity/themes/", theme_name, NULL); + + theme_file = g_build_filename (theme_dir, + THEME_FILENAME, + NULL); + + error = NULL; + if (!g_file_get_contents (theme_file, + &text, + &length, + &error)) + { + meta_verbose ("Failed to read theme from file %s: %s\n", + theme_file, error->message); + g_error_free (error); + g_free (theme_dir); + g_free (theme_file); + theme_file = NULL; + } + } + + if (text == NULL) + { + theme_dir = g_build_filename (METACITY_PKGDATADIR, + "themes", + theme_name, NULL); + + theme_file = g_build_filename (theme_dir, + THEME_FILENAME, + NULL); + + error = NULL; + if (!g_file_get_contents (theme_file, + &text, + &length, + &error)) + { + meta_warning (_("Failed to read theme from file %s: %s\n"), + theme_file, error->message); + g_propagate_error (err, error); + g_free (theme_file); + g_free (theme_dir); + return FALSE; /* all fallbacks failed */ + } + } + + g_assert (text); + + meta_verbose ("Parsing theme file %s\n", theme_file); + + parse_info_init (&info); + info.theme_name = theme_name; + + /* pass ownership to info so we free it with the info */ + info.theme_file = theme_file; + info.theme_dir = theme_dir; + + context = g_markup_parse_context_new (&metacity_theme_parser, + 0, &info, NULL); + + error = NULL; + if (!g_markup_parse_context_parse (context, + text, + length, + &error)) + goto out; + + error = NULL; + if (!g_markup_parse_context_end_parse (context, &error)) + goto out; + + g_markup_parse_context_free (context); + + goto out; + + out: + + g_free (text); + + if (error) + { + g_propagate_error (err, error); + } + else if (info.theme) + { + /* Steal theme from info */ + retval = info.theme; + info.theme = NULL; + } + else + { + g_set_error (err, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Theme file %s did not contain a root <metacity_theme> element"), + info.theme_file); + } + + parse_info_free (&info); + + return retval; +} diff --git a/src/theme-parser.h b/src/theme-parser.h new file mode 100644 index 00000000..2ba198e4 --- /dev/null +++ b/src/theme-parser.h @@ -0,0 +1,30 @@ +/* Metacity theme parsing */ + +/* + * 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. + */ + +#include "theme.h" + +#ifndef META_THEME_PARSER_H +#define META_THEME_PARSER_H + +MetaTheme* meta_theme_load (const char *theme_name, + GError **err); + +#endif diff --git a/src/theme-viewer.c b/src/theme-viewer.c index db06bf68..dd2ecdf9 100644 --- a/src/theme-viewer.c +++ b/src/theme-viewer.c @@ -1,8 +1,8 @@ /* Metacity theme viewer and test app main() */ -/* +/* * Copyright (C) 2002 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 @@ -12,7 +12,7 @@ * 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 @@ -22,29 +22,121 @@ #include <config.h> #include "util.h" #include "theme.h" +#include "theme-parser.h" +#include "inlinepixbufs.h" #include <gtk/gtk.h> #include <time.h> - -static void run_position_expression_tests (void); -static void run_position_expression_timings (void); +#include <stdlib.h> static int client_width = 200; static int client_height = 200; +static MetaTheme *global_theme = NULL; + +static void run_position_expression_tests (void); +static void run_position_expression_timings (void); + +static MetaFrameFlags +get_flags (GtkWidget *widget) +{ + return META_FRAME_ALLOWS_DELETE | + META_FRAME_ALLOWS_MENU | + META_FRAME_ALLOWS_MINIMIZE | + META_FRAME_ALLOWS_MAXIMIZE | + META_FRAME_ALLOWS_VERTICAL_RESIZE | + META_FRAME_ALLOWS_HORIZONTAL_RESIZE | + META_FRAME_HAS_FOCUS | + META_FRAME_ALLOWS_SHADE | + META_FRAME_ALLOWS_MOVE; +} + +static int +get_text_height (GtkWidget *widget) +{ + return meta_gtk_widget_get_text_height (widget); +} + +static PangoLayout* +create_title_layout (GtkWidget *widget) +{ + PangoLayout *layout; + + layout = gtk_widget_create_pango_layout (widget, "Window Title Goes Here"); + + return layout; +} + + +#ifdef HAVE_GDK_PIXBUF_NEW_FROM_STREAM +#define gdk_pixbuf_new_from_inline gdk_pixbuf_new_from_stream +#endif + +static GdkPixbuf* +get_icon (void) +{ + static GdkPixbuf *default_icon = NULL; + + if (default_icon == NULL) + { + GdkPixbuf *base; + + base = gdk_pixbuf_new_from_inline (-1, default_icon_data, + FALSE, + NULL); + + g_assert (base); + + default_icon = gdk_pixbuf_scale_simple (base, + META_ICON_WIDTH, + META_ICON_HEIGHT, + GDK_INTERP_BILINEAR); + + g_object_unref (G_OBJECT (base)); + } + + return default_icon; +} + +static GdkPixbuf* +get_mini_icon (void) +{ + static GdkPixbuf *default_icon = NULL; + + if (default_icon == NULL) + { + GdkPixbuf *base; + + base = gdk_pixbuf_new_from_inline (-1, default_icon_data, + FALSE, + NULL); + + g_assert (base); + + default_icon = gdk_pixbuf_scale_simple (base, + META_MINI_ICON_WIDTH, + META_MINI_ICON_HEIGHT, + GDK_INTERP_BILINEAR); + + g_object_unref (G_OBJECT (base)); + } + + return default_icon; +} + + static void -set_widget_to_frame_size (MetaFrameStyle *style, - GtkWidget *widget) +set_widget_to_frame_size (GtkWidget *widget) { int top_height, bottom_height, left_width, right_width; - meta_frame_layout_get_borders (style->layout, - widget, - 15, /* FIXME */ - 0, /* FIXME */ - &top_height, - &bottom_height, - &left_width, - &right_width); + meta_theme_get_frame_borders (global_theme, + META_FRAME_TYPE_NORMAL, + get_text_height (widget), + get_flags (widget), + &top_height, + &bottom_height, + &left_width, + &right_width); gtk_widget_set_size_request (widget, client_width + left_width + right_width, @@ -56,7 +148,6 @@ expose_handler (GtkWidget *widget, GdkEventExpose *event, gpointer data) { - MetaFrameStyle *style; MetaButtonState button_states[META_BUTTON_TYPE_LAST] = { META_BUTTON_STATE_NORMAL, @@ -65,36 +156,42 @@ expose_handler (GtkWidget *widget, META_BUTTON_STATE_NORMAL }; int top_height, bottom_height, left_width, right_width; + PangoLayout *layout; - style = meta_frame_style_get_test (); + layout = create_title_layout (widget); - meta_frame_layout_get_borders (style->layout, - widget, - 15, /* FIXME */ - 0, /* FIXME */ - &top_height, - &bottom_height, - &left_width, - &right_width); - - meta_frame_style_draw (style, + meta_theme_get_frame_borders (global_theme, + META_FRAME_TYPE_NORMAL, + get_text_height (widget), + get_flags (widget), + &top_height, + &bottom_height, + &left_width, + &right_width); + + meta_theme_draw_frame (global_theme, widget, widget->window, - 0, 0, &event->area, - 0, /* flags */ + 0, 0, + META_FRAME_TYPE_NORMAL, + get_flags (widget), client_width, client_height, - NULL, /* FIXME */ - 15, /* FIXME */ - button_states); + layout, + get_text_height (widget), + button_states, + get_mini_icon (), + get_icon ()); /* Draw the "client" */ gdk_draw_rectangle (widget->window, - widget->style->white_gc, + widget->style->white_gc, TRUE, left_width, top_height, client_width, client_height); + + g_object_unref (G_OBJECT (layout)); return TRUE; } @@ -107,16 +204,43 @@ main (int argc, char **argv) GtkWidget *sw; GtkWidget *da; GdkColor desktop_color; - + GError *err; + clock_t start, end; + bindtextdomain (GETTEXT_PACKAGE, METACITY_LOCALEDIR); - + run_position_expression_tests (); #if 0 run_position_expression_timings (); #endif - + gtk_init (&argc, &argv); + start = clock (); + err = NULL; + if (argc == 1) + global_theme = meta_theme_load ("Default", &err); + else if (argc == 2) + global_theme = meta_theme_load (argv[1], &err); + else + { + g_printerr ("Usage: metacity-theme-viewer [THEMENAME]\n"); + exit (1); + } + end = clock (); + + if (global_theme == NULL) + { + g_printerr ("Error loading theme: %s\n", + err->message); + g_error_free (err); + exit (1); + } + + g_print ("Loaded theme \"%s\" in %g seconds\n", + global_theme->name, + (end - start) / (double) CLOCKS_PER_SEC); + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_default_size (GTK_WINDOW (window), 270, 270); @@ -129,8 +253,8 @@ main (int argc, char **argv) layout = gtk_layout_new (NULL, NULL); - gtk_layout_set_size (GTK_LAYOUT (layout), 500, 500); - + gtk_layout_set_size (GTK_LAYOUT (layout), 250, 250); + gtk_container_add (GTK_CONTAINER (sw), layout); g_signal_connect (G_OBJECT (window), "destroy", @@ -139,12 +263,10 @@ main (int argc, char **argv) desktop_color.red = 0x5144; desktop_color.green = 0x75D6; desktop_color.blue = 0xA699; - + gtk_widget_modify_bg (layout, GTK_STATE_NORMAL, &desktop_color); da = gtk_drawing_area_new (); - set_widget_to_frame_size (meta_frame_style_get_test (), - da); g_signal_connect (G_OBJECT (da), "expose_event", G_CALLBACK (expose_handler), NULL); @@ -152,11 +274,14 @@ main (int argc, char **argv) gtk_layout_put (GTK_LAYOUT (layout), da, 5, 5); + + gtk_widget_realize (da); + set_widget_to_frame_size (da); gtk_widget_show_all (window); - + gtk_main (); - + return 0; } @@ -166,7 +291,7 @@ typedef struct const char *expr; int expected_x; int expected_y; - MetaPositionExprError expected_error; + MetaThemeError expected_error; } PositionExpressionTest; #define NO_ERROR -1 @@ -290,29 +415,29 @@ static const PositionExpressionTest position_expression_tests[] = { "(8 + 8) / 2", 18, 28, NO_ERROR }, /* Errors */ { { 10, 20, 40, 50 }, - "2 * foo", 0, 0, META_POSITION_EXPR_ERROR_UNKNOWN_VARIABLE }, + "2 * foo", 0, 0, META_THEME_ERROR_UNKNOWN_VARIABLE }, { { 10, 20, 40, 50 }, - "2 *", 0, 0, META_POSITION_EXPR_ERROR_FAILED }, + "2 *", 0, 0, META_THEME_ERROR_FAILED }, { { 10, 20, 40, 50 }, - "- width", 0, 0, META_POSITION_EXPR_ERROR_FAILED }, + "- width", 0, 0, META_THEME_ERROR_FAILED }, { { 10, 20, 40, 50 }, - "5 % 1.0", 0, 0, META_POSITION_EXPR_ERROR_MOD_ON_FLOAT }, + "5 % 1.0", 0, 0, META_THEME_ERROR_MOD_ON_FLOAT }, { { 10, 20, 40, 50 }, - "1.0 % 5", 0, 0, META_POSITION_EXPR_ERROR_MOD_ON_FLOAT }, + "1.0 % 5", 0, 0, META_THEME_ERROR_MOD_ON_FLOAT }, { { 10, 20, 40, 50 }, - "! * 2", 0, 0, META_POSITION_EXPR_ERROR_BAD_CHARACTER }, + "! * 2", 0, 0, META_THEME_ERROR_BAD_CHARACTER }, { { 10, 20, 40, 50 }, - " ", 0, 0, META_POSITION_EXPR_ERROR_FAILED }, + " ", 0, 0, META_THEME_ERROR_FAILED }, { { 10, 20, 40, 50 }, - "() () (( ) ()) ((()))", 0, 0, META_POSITION_EXPR_ERROR_FAILED }, + "() () (( ) ()) ((()))", 0, 0, META_THEME_ERROR_FAILED }, { { 10, 20, 40, 50 }, - "(*) () ((/) ()) ((()))", 0, 0, META_POSITION_EXPR_ERROR_FAILED }, + "(*) () ((/) ()) ((()))", 0, 0, META_THEME_ERROR_FAILED }, { { 10, 20, 40, 50 }, - "2 * 5 /", 0, 0, META_POSITION_EXPR_ERROR_FAILED }, + "2 * 5 /", 0, 0, META_THEME_ERROR_FAILED }, { { 10, 20, 40, 50 }, - "+ 2 * 5", 0, 0, META_POSITION_EXPR_ERROR_FAILED }, + "+ 2 * 5", 0, 0, META_THEME_ERROR_FAILED }, { { 10, 20, 40, 50 }, - "+ 2 * 5", 0, 0, META_POSITION_EXPR_ERROR_FAILED } + "+ 2 * 5", 0, 0, META_THEME_ERROR_FAILED } }; static void @@ -320,22 +445,22 @@ run_position_expression_tests (void) { int i; MetaPositionExprEnv env; - + i = 0; - while (i < G_N_ELEMENTS (position_expression_tests)) + while (i < (int) G_N_ELEMENTS (position_expression_tests)) { GError *err; gboolean retval; const PositionExpressionTest *test; int x, y; - + test = &position_expression_tests[i]; if (g_getenv ("META_PRINT_TESTS") != NULL) g_print ("Test expression: \"%s\" expecting x = %d y = %d", test->expr, test->expected_x, test->expected_y); - - err = NULL; + + err = NULL; env.x = test->rect.x; env.y = test->rect.y; @@ -343,7 +468,18 @@ run_position_expression_tests (void) env.height = test->rect.height; env.object_width = -1; env.object_height = -1; - + env.left_width = 0; + env.right_width = 0; + env.top_height = 0; + env.bottom_height = 0; + env.title_width = 5; + env.title_height = 5; + env.icon_width = 32; + env.icon_height = 32; + env.mini_icon_width = 16; + env.mini_icon_height = 16; + env.theme = NULL; + retval = meta_parse_position_expression (test->expr, &env, &x, &y, @@ -353,11 +489,11 @@ run_position_expression_tests (void) g_error ("position expression test returned TRUE but set error"); if (!retval && err == NULL) g_error ("position expression test returned FALSE but didn't set error"); - if (test->expected_error != NO_ERROR) + if (((int) test->expected_error) != NO_ERROR) { if (err == NULL) g_error ("Error was expected but none given"); - if (err->code != test->expected_error) + if (err->code != (int) test->expected_error) g_error ("Error %d was expected but %d given", test->expected_error, err->code); } @@ -389,18 +525,18 @@ run_position_expression_timings (void) clock_t start; clock_t end; MetaPositionExprEnv env; - + #define ITERATIONS 100000 start = clock (); - + iters = 0; i = 0; while (iters < ITERATIONS) { const PositionExpressionTest *test; int x, y; - + test = &position_expression_tests[i]; env.x = test->rect.x; @@ -409,7 +545,18 @@ run_position_expression_timings (void) env.height = test->rect.height; env.object_width = -1; env.object_height = -1; - + env.left_width = 0; + env.right_width = 0; + env.top_height = 0; + env.bottom_height = 0; + env.title_width = 5; + env.title_height = 5; + env.icon_width = 32; + env.icon_height = 32; + env.mini_icon_width = 16; + env.mini_icon_height = 16; + env.theme = NULL; + meta_parse_position_expression (test->expr, &env, &x, &y, NULL); @@ -426,5 +573,5 @@ run_position_expression_timings (void) ITERATIONS, ((double)end - (double)start) / CLOCKS_PER_SEC, ((double)end - (double)start) / CLOCKS_PER_SEC / (double) ITERATIONS); - + } diff --git a/src/theme.c b/src/theme.c index e7add4e4..bab18c70 100644 --- a/src/theme.c +++ b/src/theme.c @@ -1,8 +1,8 @@ /* 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 @@ -12,7 +12,7 @@ * 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 @@ -21,6 +21,7 @@ #include <config.h> #include "theme.h" +#include "theme-parser.h" #include "util.h" #include "gradient.h" #include <gtk/gtkwidget.h> @@ -40,6 +41,8 @@ #define ALPHA_TO_UCHAR(d) ((unsigned char) ((d) * 255)) +#define DEBUG_FILL_STRUCT(s) memset ((s), 0xef, sizeof (*(s))) + static void color_composite (const GdkColor *bg, const GdkColor *fg, @@ -47,7 +50,7 @@ color_composite (const GdkColor *bg, GdkColor *color) { guint16 alpha; - + *color = *bg; alpha = alpha_d * 0xffff; color->red = color->red + (((fg->red - color->red) * alpha + 0x8000) >> 16); @@ -55,6 +58,15 @@ color_composite (const GdkColor *bg, color->blue = color->blue + (((fg->blue - color->blue) * alpha + 0x8000) >> 16); } +static void +init_border (GtkBorder *border) +{ + border->top = -1; + border->bottom = -1; + border->left = -1; + border->right = -1; +} + MetaFrameLayout* meta_frame_layout_new (void) { @@ -62,20 +74,150 @@ meta_frame_layout_new (void) 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_width = -1; + layout->button_height = -1; + + 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; +} + +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); + + CHECK_GEOMETRY_VALUE (button_width); + CHECK_GEOMETRY_VALUE (button_height); + + 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_free (MetaFrameLayout *layout) +meta_frame_layout_ref (MetaFrameLayout *layout) { g_return_if_fail (layout != NULL); - - g_free (layout); + + 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, - GtkWidget *widget, int text_height, MetaFrameFlags flags, int *top_height, @@ -83,24 +225,22 @@ meta_frame_layout_get_borders (const MetaFrameLayout *layout, int *left_width, int *right_width) { - int buttons_height, title_height, spacer_height; + 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); - + buttons_height = layout->button_height + layout->button_border.top + layout->button_border.bottom; title_height = text_height + - layout->text_border.top + layout->text_border.bottom + + layout->title_vertical_pad + layout->title_border.top + layout->title_border.bottom; - spacer_height = layout->spacer_height; if (top_height) { *top_height = MAX (buttons_height, title_height); - *top_height = MAX (*top_height, spacer_height); } if (left_width) @@ -119,7 +259,6 @@ meta_frame_layout_get_borders (const MetaFrameLayout *layout, void meta_frame_layout_calc_geometry (const MetaFrameLayout *layout, - GtkWidget *widget, int text_height, MetaFrameFlags flags, int client_width, @@ -131,25 +270,25 @@ meta_frame_layout_calc_geometry (const MetaFrameLayout *layout, int title_right_edge; int width, height; - meta_frame_layout_get_borders (layout, widget, text_height, + 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 = 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_inset; - fgeom->right_titlebar_edge = layout->right_inset; - - x = width - layout->right_inset; + fgeom->left_titlebar_edge = layout->left_titlebar_edge; + fgeom->right_titlebar_edge = layout->right_titlebar_edge; + + x = width - layout->right_titlebar_edge; /* center buttons */ button_y = (fgeom->top_height - @@ -190,7 +329,7 @@ meta_frame_layout_calc_geometry (const MetaFrameLayout *layout, fgeom->max_rect.width = 0; fgeom->max_rect.height = 0; } - + if ((flags & META_FRAME_ALLOWS_MINIMIZE) && x >= 0) { @@ -209,31 +348,11 @@ meta_frame_layout_calc_geometry (const MetaFrameLayout *layout, fgeom->min_rect.height = 0; } - if ((fgeom->close_rect.width > 0 || - fgeom->max_rect.width > 0 || - fgeom->min_rect.width > 0) && - x >= 0) - { - fgeom->spacer_rect.x = x - layout->spacer_padding - layout->spacer_width; - fgeom->spacer_rect.y = (fgeom->top_height - layout->spacer_height) / 2; - fgeom->spacer_rect.width = layout->spacer_width; - fgeom->spacer_rect.height = layout->spacer_height; - - x = fgeom->spacer_rect.x - layout->spacer_padding; - } - else - { - fgeom->spacer_rect.x = 0; - fgeom->spacer_rect.y = 0; - fgeom->spacer_rect.width = 0; - fgeom->spacer_rect.height = 0; - } - title_right_edge = x - layout->title_border.right; - + /* Now x changes to be position from the left */ - x = layout->left_inset; - + x = layout->left_titlebar_edge; + if (flags & META_FRAME_ALLOWS_MENU) { fgeom->menu_rect.x = x + layout->button_border.left; @@ -268,7 +387,7 @@ meta_frame_layout_calc_geometry (const MetaFrameLayout *layout, fgeom->max_rect.width = 0; fgeom->max_rect.height = 0; } - + /* Check for minimize overlap */ if (fgeom->min_rect.width > 0 && fgeom->min_rect.x < (fgeom->menu_rect.x + fgeom->menu_rect.height)) @@ -277,16 +396,8 @@ meta_frame_layout_calc_geometry (const MetaFrameLayout *layout, fgeom->min_rect.height = 0; } - /* Check for spacer overlap */ - if (fgeom->spacer_rect.width > 0 && - fgeom->spacer_rect.x < (fgeom->menu_rect.x + fgeom->menu_rect.height)) - { - fgeom->spacer_rect.width = 0; - fgeom->spacer_rect.height = 0; - } - /* We always fill as much vertical space as possible with title rect, - * rather than centering it like the buttons and spacer + * rather than centering it like the buttons */ fgeom->title_rect.x = x + layout->title_border.left; fgeom->title_rect.y = layout->title_border.top; @@ -312,17 +423,25 @@ meta_gradient_spec_new (MetaGradientType type) 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, (GFunc) meta_color_spec_free, NULL); + + g_slist_foreach (spec->color_specs, free_color_spec, NULL); g_slist_free (spec->color_specs); + + DEBUG_FILL_STRUCT (spec); g_free (spec); } @@ -337,12 +456,12 @@ meta_gradient_spec_render (const MetaGradientSpec *spec, 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; @@ -350,11 +469,11 @@ meta_gradient_spec_render (const MetaGradientSpec *spec, 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); @@ -364,6 +483,23 @@ meta_gradient_spec_render (const MetaGradientSpec *spec, 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; +} + MetaColorSpec* meta_color_spec_new (MetaColorSpecType type) { @@ -372,7 +508,7 @@ meta_color_spec_new (MetaColorSpecType type) int size; size = G_STRUCT_OFFSET (MetaColorSpec, data); - + switch (type) { case META_COLOR_SPEC_BASIC: @@ -387,11 +523,11 @@ meta_color_spec_new (MetaColorSpecType type) size += sizeof (dummy.data.blend); break; } - + spec = g_malloc0 (size); - spec->type = type; - + spec->type = type; + return spec; } @@ -399,23 +535,26 @@ void meta_color_spec_free (MetaColorSpec *spec) { g_return_if_fail (spec != NULL); - + switch (spec->type) { - case META_COLOR_SPEC_BASIC: + 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); + 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; } - + g_free (spec); } @@ -423,13 +562,164 @@ MetaColorSpec* meta_color_spec_new_from_string (const char *str, GError **err) { - /* FIXME handle GTK colors, etc. */ MetaColorSpec *spec; - spec = meta_color_spec_new (META_COLOR_SPEC_BASIC); + spec = NULL; - gdk_color_parse (str, &spec->data.basic.color); + 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 + { + 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; } @@ -437,11 +727,10 @@ MetaColorSpec* meta_color_spec_new_gtk (MetaGtkColorComponent component, GtkStateType state) { - /* FIXME handle GTK colors, etc. */ MetaColorSpec *spec; spec = meta_color_spec_new (META_COLOR_SPEC_GTK); - + spec->data.gtk.component = component; spec->data.gtk.state = state; @@ -456,7 +745,7 @@ meta_color_spec_render (MetaColorSpec *spec, 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: @@ -490,6 +779,9 @@ meta_color_spec_render (MetaColorSpec *spec, 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; @@ -516,6 +808,92 @@ typedef enum POS_TOKEN_CLOSE_PAREN } PosTokenType; +typedef enum +{ + POS_OP_NONE, + POS_OP_ADD, + POS_OP_SUBTRACT, + POS_OP_MULTIPLY, + POS_OP_DIVIDE, + POS_OP_MOD, + POS_OP_MAX, + POS_OP_MIN +} PosOperatorType; + +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 "<unknown>"; +} + +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; +} + typedef struct { PosTokenType type; @@ -531,13 +909,13 @@ typedef struct } d; struct { - char op; + PosOperatorType op; } o; struct { char *name; } v; - + } d; } PosToken; @@ -552,7 +930,7 @@ free_tokens (PosToken *tokens, /* n_tokens can be 0 since tokens may have been allocated more than * it was initialized */ - + i = 0; while (i < n_tokens) { @@ -573,21 +951,23 @@ parse_number (const char *p, char *end; gboolean is_float; char *num_str; - + while (*p && (*p == '.' || g_ascii_isdigit (*p))) ++p; - + if (p == start) { - g_set_error (err, META_POSITION_EXPR_ERROR, - META_POSITION_EXPR_ERROR_BAD_CHARACTER, - _("Coordinate expression contains character '%c' which is not allowed"), - *p); + 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; @@ -598,7 +978,7 @@ parse_number (const char *p, is_float = TRUE; ++start; } - + if (is_float) { next->type = POS_TOKEN_DOUBLE; @@ -606,8 +986,8 @@ parse_number (const char *p, if (end == num_str) { - g_set_error (err, META_POSITION_EXPR_ERROR, - META_POSITION_EXPR_ERROR_FAILED, + 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); @@ -620,8 +1000,8 @@ parse_number (const char *p, next->d.i.val = strtol (num_str, &end, 10); if (end == num_str) { - g_set_error (err, META_POSITION_EXPR_ERROR, - META_POSITION_EXPR_ERROR_FAILED, + 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); @@ -632,10 +1012,12 @@ parse_number (const char *p, g_free (num_str); g_assert (next->type == POS_TOKEN_INT || next->type == POS_TOKEN_DOUBLE); - + return TRUE; } +#define IS_VARIABLE_CHAR(c) (g_ascii_isalpha ((c)) || (c) == '_') + static gboolean pos_tokenize (const char *expr, PosToken **tokens_p, @@ -658,6 +1040,7 @@ pos_tokenize (const char *expr, while (*p) { PosToken *next; + int len; if (n_tokens == allocated) { @@ -666,7 +1049,7 @@ pos_tokenize (const char *expr, } next = &tokens[n_tokens]; - + switch (*p) { case '*': @@ -674,11 +1057,25 @@ pos_tokenize (const char *expr, case '+': case '-': /* negative numbers aren't allowed so this is easy */ case '%': + case '`': next->type = POS_TOKEN_OPERATOR; - next->d.o.op = *p; - ++n_tokens; + 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; @@ -694,11 +1091,11 @@ pos_tokenize (const char *expr, break; default: - if (g_ascii_isalpha (*p)) + if (IS_VARIABLE_CHAR (*p)) { /* Assume variable */ const char *start = p; - while (*p && g_ascii_isalpha (*p)) + while (*p && IS_VARIABLE_CHAR (*p)) ++p; g_assert (p != start); next->type = POS_TOKEN_VARIABLE; @@ -713,11 +1110,11 @@ pos_tokenize (const char *expr, 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; } @@ -726,16 +1123,16 @@ pos_tokenize (const char *expr, if (n_tokens == 0) { - g_set_error (err, META_POSITION_EXPR_ERROR, - META_POSITION_EXPR_ERROR_FAILED, + 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: @@ -757,7 +1154,7 @@ debug_print_tokens (PosToken *tokens, PosToken *t = &tokens[i]; g_print (" "); - + switch (t->type) { case POS_TOKEN_INT: @@ -776,7 +1173,7 @@ debug_print_tokens (PosToken *tokens, g_print ("\"%s\"", t->d.v.name); break; case POS_TOKEN_OPERATOR: - g_print ("\"%c\"", t->d.o.op); + g_print ("\"%s\"", op_name (t->d.o.op)); break; } @@ -787,7 +1184,7 @@ debug_print_tokens (PosToken *tokens, } typedef enum -{ +{ POS_EXPR_INT, POS_EXPR_DOUBLE, POS_EXPR_OPERATOR @@ -822,10 +1219,10 @@ debug_print_exprs (PosExpr *exprs, g_print (" %g", exprs[i].d.double_val); break; case POS_EXPR_OPERATOR: - g_print (" %c", exprs[i].d.operator); + g_print (" %s", op_name (exprs[i].d.operator)); break; } - + ++i; } g_print ("\n"); @@ -834,7 +1231,7 @@ debug_print_exprs (PosExpr *exprs, static gboolean do_operation (PosExpr *a, PosExpr *b, - char op, + PosOperatorType op, GError **err) { /* Promote types to double if required */ @@ -854,71 +1251,89 @@ do_operation (PosExpr *a, } g_assert (a->type == b->type); - + if (a->type == POS_EXPR_INT) { switch (op) { - case '*': + case POS_OP_MULTIPLY: a->d.int_val = a->d.int_val * b->d.int_val; break; - case '/': + case POS_OP_DIVIDE: if (b->d.int_val == 0) { - g_set_error (err, META_POSITION_EXPR_ERROR, - META_POSITION_EXPR_ERROR_DIVIDE_BY_ZERO, + 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 '%': + case POS_OP_MOD: if (b->d.int_val == 0) { - g_set_error (err, META_POSITION_EXPR_ERROR, - META_POSITION_EXPR_ERROR_DIVIDE_BY_ZERO, + 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 '+': + case POS_OP_ADD: a->d.int_val = a->d.int_val + b->d.int_val; break; - case '-': + 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 '*': + case POS_OP_MULTIPLY: a->d.double_val = a->d.double_val * b->d.double_val; break; - case '/': + case POS_OP_DIVIDE: if (b->d.double_val == 0.0) { - g_set_error (err, META_POSITION_EXPR_ERROR, - META_POSITION_EXPR_ERROR_DIVIDE_BY_ZERO, + 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 '%': - g_set_error (err, META_POSITION_EXPR_ERROR, - META_POSITION_EXPR_ERROR_MOD_ON_FLOAT, + 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; break; - case '+': + case POS_OP_ADD: a->d.double_val = a->d.double_val + b->d.double_val; break; - case '-': + 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 @@ -939,13 +1354,13 @@ do_operations (PosExpr *exprs, 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-1] first operand * exprs[i] operator * exprs[i+1] second operand * @@ -956,35 +1371,35 @@ do_operations (PosExpr *exprs, if (exprs[i-1].type == POS_EXPR_OPERATOR) { - g_set_error (err, META_POSITION_EXPR_ERROR, - META_POSITION_EXPR_ERROR_FAILED, - _("Coordinate expression has an operator \"%c\" where an operand was expected"), - exprs[i-1].d.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_POSITION_EXPR_ERROR, - META_POSITION_EXPR_ERROR_FAILED, + 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_POSITION_EXPR_ERROR, - META_POSITION_EXPR_ERROR_FAILED, + 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_POSITION_EXPR_ERROR, - META_POSITION_EXPR_ERROR_FAILED, + 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); @@ -992,14 +1407,15 @@ do_operations (PosExpr *exprs, } compress = FALSE; - - if (precedence == 1) + + switch (precedence) { + case 2: switch (exprs[i].d.operator) { - case '*': - case '/': - case '%': + 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, @@ -1007,13 +1423,26 @@ do_operations (PosExpr *exprs, return FALSE; break; } - } - else if (precedence == 0) - { + 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 '-': - case '+': + case POS_OP_MAX: + case POS_OP_MIN: compress = TRUE; if (!do_operation (&exprs[i-1], &exprs[i+1], exprs[i].d.operator, @@ -1021,6 +1450,7 @@ do_operations (PosExpr *exprs, return FALSE; break; } + break; } if (compress) @@ -1037,7 +1467,7 @@ do_operations (PosExpr *exprs, g_memmove (&exprs[i], &exprs[i+2], sizeof (PosExpr) * (*n_exprs - i - 2)); } - + *n_exprs -= 2; } else @@ -1064,16 +1494,19 @@ pos_eval_helper (PosToken *tokens, int i; PosExpr exprs[MAX_EXPRS]; int n_exprs; - + int ival; + double dval; + int precedence; + #if 0 g_print ("Pos eval helper on %d tokens:\n", n_tokens); debug_print_tokens (tokens, n_tokens); #endif - + /* 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; @@ -1084,8 +1517,8 @@ pos_eval_helper (PosToken *tokens, if (n_exprs >= MAX_EXPRS) { - g_set_error (err, META_POSITION_EXPR_ERROR, - META_POSITION_EXPR_ERROR_FAILED, + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, _("Coordinate expression parser overflowed its buffer, this is really a Metacity bug, but are you sure you need a huge expression like that?")); return FALSE; } @@ -1113,15 +1546,19 @@ pos_eval_helper (PosToken *tokens, break; case POS_TOKEN_CLOSE_PAREN: - g_set_error (err, META_POSITION_EXPR_ERROR, - META_POSITION_EXPR_ERROR_BAD_PARENS, + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_BAD_PARENS, _("Coordinate expression had a close parenthesis with no open parenthesis")); return FALSE; break; - + 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 (strcmp (t->d.v.name, "width") == 0) exprs[n_exprs].d.int_val = env->width; else if (strcmp (t->d.v.name, "height") == 0) @@ -1132,11 +1569,49 @@ pos_eval_helper (PosToken *tokens, else if (env->object_height >= 0 && strcmp (t->d.v.name, "object_height") == 0) exprs[n_exprs].d.int_val = env->object_height; + else if (strcmp (t->d.v.name, "left_width") == 0) + exprs[n_exprs].d.int_val = env->left_width; + else if (strcmp (t->d.v.name, "right_width") == 0) + exprs[n_exprs].d.int_val = env->right_width; + else if (strcmp (t->d.v.name, "top_height") == 0) + exprs[n_exprs].d.int_val = env->top_height; + else if (strcmp (t->d.v.name, "bottom_height") == 0) + exprs[n_exprs].d.int_val = env->bottom_height; + else if (strcmp (t->d.v.name, "mini_icon_width") == 0) + exprs[n_exprs].d.int_val = env->mini_icon_width; + else if (strcmp (t->d.v.name, "mini_icon_height") == 0) + exprs[n_exprs].d.int_val = env->mini_icon_height; + else if (strcmp (t->d.v.name, "icon_width") == 0) + exprs[n_exprs].d.int_val = env->icon_width; + else if (strcmp (t->d.v.name, "icon_height") == 0) + exprs[n_exprs].d.int_val = env->icon_height; + else if (strcmp (t->d.v.name, "title_width") == 0) + exprs[n_exprs].d.int_val = env->title_width; + else if (strcmp (t->d.v.name, "title_height") == 0) + exprs[n_exprs].d.int_val = env->title_height; + /* In practice we only hit this code on initial theme + * parse; after that we always optimize constants away + */ + else if (env->theme && + meta_theme_lookup_int_constant (env->theme, + t->d.v.name, + &ival)) + { + exprs[n_exprs].d.int_val = ival; + } + else if (env->theme && + meta_theme_lookup_float_constant (env->theme, + t->d.v.name, + &dval)) + { + exprs[n_exprs].type = POS_EXPR_DOUBLE; + exprs[n_exprs].d.double_val = dval; + } else { - g_set_error (err, META_POSITION_EXPR_ERROR, - META_POSITION_EXPR_ERROR_UNKNOWN_VARIABLE, - _("Coordinate expression had unknown variable \"%s\""), + 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; } @@ -1147,13 +1622,13 @@ pos_eval_helper (PosToken *tokens, exprs[n_exprs].type = POS_EXPR_OPERATOR; exprs[n_exprs].d.operator = t->d.o.op; ++n_exprs; - break; + break; } } else { g_assert (paren_level > 0); - + switch (t->type) { case POS_TOKEN_INT: @@ -1161,7 +1636,7 @@ pos_eval_helper (PosToken *tokens, case POS_TOKEN_VARIABLE: case POS_TOKEN_OPERATOR: break; - + case POS_TOKEN_OPEN_PAREN: ++paren_level; break; @@ -1176,13 +1651,13 @@ pos_eval_helper (PosToken *tokens, &exprs[n_exprs], err)) return FALSE; - + ++n_exprs; } - + --paren_level; break; - + } } @@ -1191,10 +1666,10 @@ pos_eval_helper (PosToken *tokens, if (paren_level > 0) { - g_set_error (err, META_POSITION_EXPR_ERROR, - META_POSITION_EXPR_ERROR_BAD_PARENS, + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_BAD_PARENS, _("Coordinate expression had an open parenthesis with no close parenthesis")); - return FALSE; + return FALSE; } /* Now we have no parens and no vars; so we just do all the multiplies @@ -1202,19 +1677,20 @@ pos_eval_helper (PosToken *tokens, */ if (n_exprs == 0) { - g_set_error (err, META_POSITION_EXPR_ERROR, - META_POSITION_EXPR_ERROR_FAILED, + 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 */ - if (!do_operations (exprs, &n_exprs, 1, err)) - return FALSE; - - /* precedence 0 ops */ - if (!do_operations (exprs, &n_exprs, 0, err)) - return FALSE; + precedence = 2; + while (precedence >= 0) + { + if (!do_operations (exprs, &n_exprs, precedence, err)) + return FALSE; + --precedence; + } g_assert (n_exprs == 1); @@ -1237,7 +1713,7 @@ pos_eval (PosToken *tokens, GError **err) { PosExpr expr; - + *val_p = 0; if (pos_eval_helper (tokens, n_tokens, env, &expr, err)) @@ -1265,7 +1741,7 @@ pos_eval (PosToken *tokens, /* We always return both X and Y, but only one will be meaningful in * most contexts. */ - + gboolean meta_parse_position_expression (const char *expr, const MetaPositionExprEnv *env, @@ -1282,7 +1758,7 @@ meta_parse_position_expression (const char *expr, PosToken *tokens; int n_tokens; int val; - + if (!pos_tokenize (expr, &tokens, &n_tokens, err)) { g_assert (err == NULL || *err != NULL); @@ -1296,9 +1772,9 @@ meta_parse_position_expression (const char *expr, if (pos_eval (tokens, n_tokens, env, &val, err)) { - if (x_return) + if (x_return) *x_return = env->x + val; - if (y_return) + if (y_return) *y_return = env->y + val; free_tokens (tokens, n_tokens); return TRUE; @@ -1321,7 +1797,7 @@ meta_parse_size_expression (const char *expr, PosToken *tokens; int n_tokens; int val; - + if (!pos_tokenize (expr, &tokens, &n_tokens, err)) { g_assert (err == NULL || *err != NULL); @@ -1335,8 +1811,8 @@ meta_parse_size_expression (const char *expr, if (pos_eval (tokens, n_tokens, env, &val, err)) { - if (val_return) - *val_return = val; + if (val_return) + *val_return = MAX (val, 1); /* require that sizes be at least 1x1 */ free_tokens (tokens, n_tokens); return TRUE; } @@ -1348,13 +1824,99 @@ meta_parse_size_expression (const char *expr, } } +/* 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. + */ +char* +meta_theme_replace_constants (MetaTheme *theme, + const char *expr, + GError **err) +{ + PosToken *tokens; + int n_tokens; + int i; + GString *str; + char buf[G_ASCII_DTOSTR_BUF_SIZE]; + double dval; + int ival; + + if (!pos_tokenize (expr, &tokens, &n_tokens, err)) + { + g_assert (err == NULL || *err != NULL); + return NULL; + } + +#if 0 + g_print ("Tokenized \"%s\" to --->\n", expr); + debug_print_tokens (tokens, n_tokens); +#endif + + str = g_string_new (NULL); + + i = 0; + while (i < n_tokens) + { + PosToken *t = &tokens[i]; + + /* spaces so we don't accidentally merge variables + * or anything like that + */ + if (i > 0) + g_string_append_c (str, ' '); + + switch (t->type) + { + case POS_TOKEN_INT: + g_string_append_printf (str, "%d", t->d.i.val); + break; + case POS_TOKEN_DOUBLE: + g_ascii_formatd (buf, G_ASCII_DTOSTR_BUF_SIZE, + "%g", t->d.d.val); + g_string_append (str, buf); + break; + case POS_TOKEN_OPEN_PAREN: + g_string_append_c (str, '('); + break; + case POS_TOKEN_CLOSE_PAREN: + g_string_append_c (str, ')'); + break; + case POS_TOKEN_VARIABLE: + if (meta_theme_lookup_int_constant (theme, t->d.v.name, &ival)) + g_string_append_printf (str, "%d", ival); + else if (meta_theme_lookup_float_constant (theme, t->d.v.name, &dval)) + { + g_ascii_formatd (buf, G_ASCII_DTOSTR_BUF_SIZE, + "%g", dval); + g_string_append (str, buf); + } + else + { + g_string_append (str, t->d.v.name); + } + break; + case POS_TOKEN_OPERATOR: + g_string_append (str, op_name (t->d.o.op)); + break; + } + + ++i; + } + + free_tokens (tokens, n_tokens); + + return g_string_free (str, FALSE); +} + static int parse_x_position_unchecked (const char *expr, const MetaPositionExprEnv *env) { int retval; - GError *error; - + GError *error; + retval = 0; error = NULL; if (!meta_parse_position_expression (expr, env, @@ -1366,7 +1928,7 @@ parse_x_position_unchecked (const char *expr, g_error_free (error); } - + return retval; } @@ -1375,8 +1937,8 @@ parse_y_position_unchecked (const char *expr, const MetaPositionExprEnv *env) { int retval; - GError *error; - + GError *error; + retval = 0; error = NULL; if (!meta_parse_position_expression (expr, env, @@ -1388,7 +1950,7 @@ parse_y_position_unchecked (const char *expr, g_error_free (error); } - + return retval; } @@ -1397,8 +1959,8 @@ parse_size_unchecked (const char *expr, MetaPositionExprEnv *env) { int retval; - GError *error; - + GError *error; + retval = 0; error = NULL; if (!meta_parse_size_expression (expr, env, @@ -1409,7 +1971,7 @@ parse_size_unchecked (const char *expr, g_error_free (error); } - + return retval; } @@ -1422,7 +1984,7 @@ meta_draw_op_new (MetaDrawType type) int size; size = G_STRUCT_OFFSET (MetaDrawOp, data); - + switch (type) { case META_DRAW_LINE: @@ -1437,6 +1999,10 @@ meta_draw_op_new (MetaDrawType type) 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; @@ -1448,7 +2014,7 @@ meta_draw_op_new (MetaDrawType type) case META_DRAW_IMAGE: size += sizeof (dummy.data.image); break; - + case META_DRAW_GTK_ARROW: size += sizeof (dummy.data.gtk_arrow); break; @@ -1460,12 +2026,23 @@ meta_draw_op_new (MetaDrawType type) 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; } - + op = g_malloc0 (size); - op->type = type; - + op->type = type; + return op; } @@ -1473,7 +2050,7 @@ void meta_draw_op_free (MetaDrawOp *op) { g_return_if_fail (op != NULL); - + switch (op->type) { case META_DRAW_LINE: @@ -1501,6 +2078,13 @@ meta_draw_op_free (MetaDrawOp *op) g_free (op->data.arc.height); break; + case META_DRAW_CLIP: + g_free (op->data.clip.x); + g_free (op->data.clip.y); + g_free (op->data.clip.width); + g_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); @@ -1528,7 +2112,7 @@ meta_draw_op_free (MetaDrawOp *op) g_free (op->data.image.height); break; - + case META_DRAW_GTK_ARROW: g_free (op->data.gtk_arrow.x); g_free (op->data.gtk_arrow.y); @@ -1548,6 +2132,28 @@ meta_draw_op_free (MetaDrawOp *op) g_free (op->data.gtk_vline.y1); g_free (op->data.gtk_vline.y2); break; + + case META_DRAW_ICON: + g_free (op->data.icon.x); + g_free (op->data.icon.y); + g_free (op->data.icon.width); + g_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); + g_free (op->data.title.x); + g_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); + g_free (op->data.op_list.x); + g_free (op->data.op_list.y); + g_free (op->data.op_list.width); + g_free (op->data.op_list.height); + break; } g_free (op); @@ -1561,15 +2167,15 @@ get_gc_for_primitive (GtkWidget *widget, int line_width) { GdkGC *gc; - GdkGCValues values; + GdkGCValues values; GdkColor color; - + meta_color_spec_render (color_spec, widget, &color); - + values.foreground = color; gdk_rgb_find_color (widget->style->colormap, &values.foreground); values.line_width = line_width; - + gc = gdk_gc_new_with_values (drawable, &values, GDK_GC_FOREGROUND | GDK_GC_LINE_WIDTH); @@ -1591,7 +2197,7 @@ multiply_alpha (GdkPixbuf *pixbuf, int row; g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL); - + if (alpha == 255) return pixbuf; @@ -1601,7 +2207,7 @@ multiply_alpha (GdkPixbuf *pixbuf, g_object_unref (G_OBJECT (pixbuf)); pixbuf = new_pixbuf; } - + pixels = gdk_pixbuf_get_pixels (pixbuf); rowstride = gdk_pixbuf_get_rowstride (pixbuf); height = gdk_pixbuf_get_height (pixbuf); @@ -1625,10 +2231,10 @@ multiply_alpha (GdkPixbuf *pixbuf, * pixbuf contains 0, it should remain 0. */ *p = (*p * alpha) / 65025; /* (*p / 255) * (alpha / 255); */ - + ++p; /* skip A */ } - + ++row; } @@ -1646,11 +2252,14 @@ render_pixbuf (GdkDrawable *drawable, * 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 + * 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); @@ -1666,7 +2275,7 @@ render_pixbuf (GdkDrawable *drawable, { draw_rect = pixbuf_rect; } - + gdk_pixbuf_render_to_drawable_alpha (pixbuf, drawable, draw_rect.x - pixbuf_rect.x, @@ -1682,8 +2291,40 @@ render_pixbuf (GdkDrawable *drawable, } static GdkPixbuf* +scale_and_alpha_pixbuf (GdkPixbuf *src, + double alpha, + int width, + int height) +{ + GdkPixbuf *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 + { + pixbuf = gdk_pixbuf_scale_simple (pixbuf, + width, height, + GDK_INTERP_BILINEAR); + } + + if (pixbuf) + pixbuf = multiply_alpha (pixbuf, + ALPHA_TO_UCHAR (alpha)); + + return pixbuf; +} + +static GdkPixbuf* draw_op_as_pixbuf (const MetaDrawOp *op, GtkWidget *widget, + const MetaDrawInfo *info, int width, int height) { @@ -1694,7 +2335,7 @@ draw_op_as_pixbuf (const MetaDrawOp *op, GdkPixbuf *pixbuf; pixbuf = NULL; - + switch (op->type) { case META_DRAW_LINE: @@ -1708,11 +2349,11 @@ draw_op_as_pixbuf (const MetaDrawOp *op, 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; @@ -1720,15 +2361,18 @@ draw_op_as_pixbuf (const MetaDrawOp *op, case META_DRAW_ARC: break; + case META_DRAW_CLIP: + break; + case META_DRAW_TINT: { GdkColor color; guint32 rgba; - + meta_color_spec_render (op->data.rectangle.color_spec, widget, &color); - + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, ALPHA_TO_UCHAR (op->data.tint.alpha) < 255, 8, width, height); @@ -1736,7 +2380,7 @@ draw_op_as_pixbuf (const MetaDrawOp *op, rgba = GDK_COLOR_RGBA (color); rgba &= ~0xff; rgba |= ALPHA_TO_UCHAR (op->data.tint.alpha); - + gdk_pixbuf_fill (pixbuf, rgba); } break; @@ -1752,103 +2396,108 @@ draw_op_as_pixbuf (const MetaDrawOp *op, break; case META_DRAW_IMAGE: - { - switch (op->data.image.scale_mode) - { - case META_SCALE_NONE: - pixbuf = op->data.image.pixbuf; - g_object_ref (G_OBJECT (pixbuf)); - break; - case META_SCALE_VERTICALLY: - pixbuf = op->data.image.pixbuf; - if (gdk_pixbuf_get_height (pixbuf) == height) - { - g_object_ref (G_OBJECT (pixbuf)); - } - else - { - pixbuf = gdk_pixbuf_scale_simple (pixbuf, - gdk_pixbuf_get_width (pixbuf), - height, - GDK_INTERP_BILINEAR); - } - break; - case META_SCALE_HORIZONTALLY: - pixbuf = op->data.image.pixbuf; - if (gdk_pixbuf_get_width (pixbuf) == width) - { - g_object_ref (G_OBJECT (pixbuf)); - } - else - { - pixbuf = gdk_pixbuf_scale_simple (pixbuf, - width, - gdk_pixbuf_get_height (pixbuf), - GDK_INTERP_BILINEAR); - } - break; - case META_SCALE_BOTH: - pixbuf = op->data.image.pixbuf; - if (gdk_pixbuf_get_width (pixbuf) == width && - gdk_pixbuf_get_height (pixbuf) == height) - { - g_object_ref (G_OBJECT (pixbuf)); - } - else - { - pixbuf = gdk_pixbuf_scale_simple (pixbuf, - width, height, - GDK_INTERP_BILINEAR); - } - break; - } - - pixbuf = multiply_alpha (pixbuf, - ALPHA_TO_UCHAR (op->data.image.alpha)); + { + pixbuf = scale_and_alpha_pixbuf (op->data.image.pixbuf, + op->data.image.alpha, + width, height); } break; - + case META_DRAW_GTK_ARROW: case META_DRAW_GTK_BOX: case META_DRAW_GTK_VLINE: - break; + 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, + width, height); + else if (info->icon) + pixbuf = scale_and_alpha_pixbuf (info->icon, + op->data.icon.alpha, + width, height); + break; + + case META_DRAW_TITLE: + break; + + case META_DRAW_OP_LIST: + break; } return pixbuf; } -void -meta_draw_op_draw (const MetaDrawOp *op, - GtkWidget *widget, - GdkDrawable *drawable, - const GdkRectangle *clip, - int x, - int y, - int width, - int height) +static void +fill_env (MetaPositionExprEnv *env, + const MetaDrawInfo *info, + int x, + int y, + int width, + int height) { - GdkGC *gc; - MetaPositionExprEnv env; + /* FIXME this stuff could be raised into draw_op_list_draw() probably + */ + env->x = x; + env->y = y; + env->width = width; + env->height = height; + 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 = NULL; /* not required, constants have been optimized out */ +} - env.x = x; - env.y = y; - env.width = width; - env.height = height; - env.object_width = -1; - env.object_height = -1; +static void +meta_draw_op_draw_with_env (const MetaDrawOp *op, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + const MetaDrawInfo *info, + int x, + int y, + int width, + int height, + 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) { @@ -1858,13 +2507,13 @@ meta_draw_op_draw (const MetaDrawOp *op, 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); - x2 = parse_x_position_unchecked (op->data.line.x2, &env); - y2 = parse_y_position_unchecked (op->data.line.y2, &env); + x1 = parse_x_position_unchecked (op->data.line.x1, env); + y1 = parse_y_position_unchecked (op->data.line.y1, env); + x2 = parse_x_position_unchecked (op->data.line.x2, env); + y2 = parse_y_position_unchecked (op->data.line.y2, env); gdk_draw_line (drawable, gc, x1, y1, x2, y2); - + g_object_unref (G_OBJECT (gc)); } break; @@ -1872,20 +2521,20 @@ meta_draw_op_draw (const MetaDrawOp *op, case META_DRAW_RECTANGLE: { int rx, ry, rwidth, rheight; - - gc = get_gc_for_primitive (widget, drawable, + + 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); + + 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; @@ -1893,15 +2542,15 @@ meta_draw_op_draw (const MetaDrawOp *op, 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); + + 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, @@ -1910,19 +2559,22 @@ meta_draw_op_draw (const MetaDrawOp *op, 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; - 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); + 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 (ALPHA_TO_UCHAR (op->data.tint.alpha) == 255) { @@ -1933,18 +2585,22 @@ meta_draw_op_draw (const MetaDrawOp *op, 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, rwidth, rheight); + pixbuf = draw_op_as_pixbuf (op, widget, info, + rwidth, rheight); - render_pixbuf (drawable, clip, pixbuf, rx, ry); + if (pixbuf) + { + render_pixbuf (drawable, clip, pixbuf, rx, ry); - g_object_unref (G_OBJECT (pixbuf)); + g_object_unref (G_OBJECT (pixbuf)); + } } } break; @@ -1953,49 +2609,61 @@ meta_draw_op_draw (const MetaDrawOp *op, { 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, rwidth, rheight); - - render_pixbuf (drawable, clip, pixbuf, rx, ry); - - g_object_unref (G_OBJECT (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; - - rx = parse_x_position_unchecked (op->data.image.x, &env); - ry = parse_y_position_unchecked (op->data.image.y, &env); - 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, rwidth, rheight); + 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); + } - env.object_width = gdk_pixbuf_get_width (pixbuf); - env.object_height = gdk_pixbuf_get_height (pixbuf); + rwidth = parse_size_unchecked (op->data.image.width, env); + rheight = parse_size_unchecked (op->data.image.height, env); - render_pixbuf (drawable, clip, pixbuf, rx, ry); - - g_object_unref (G_OBJECT (pixbuf)); + 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); + 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 (widget->style, drawable, @@ -2013,11 +2681,11 @@ meta_draw_op_draw (const MetaDrawOp *op, 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); + + 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 (widget->style, drawable, @@ -2033,37 +2701,114 @@ meta_draw_op_draw (const MetaDrawOp *op, 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); + 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 (widget->style, drawable, op->data.gtk_vline.state, (GdkRectangle*) clip, widget, "metacity", - rx, ry1, ry2); + 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: + { + int rx, ry, rwidth, rheight; + + rx = parse_x_position_unchecked (op->data.op_list.x, env); + ry = parse_y_position_unchecked (op->data.op_list.y, env); + rwidth = parse_size_unchecked (op->data.op_list.width, env); + rheight = parse_size_unchecked (op->data.op_list.height, env); + + meta_draw_op_list_draw (op->data.op_list.op_list, + widget, drawable, clip, info, + rx, ry, rwidth, rheight); } break; } } +void +meta_draw_op_draw (const MetaDrawOp *op, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + const MetaDrawInfo *info, + int x, + int y, + int width, + int height) +{ + MetaPositionExprEnv env; + + fill_env (&env, info, x, y, width, height); + + meta_draw_op_draw_with_env (op, widget, drawable, clip, + info, x, y, width, height, + &env); +} + 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; } @@ -2095,7 +2840,8 @@ meta_draw_op_list_unref (MetaDrawOpList *op_list) } g_free (op_list->ops); - + + DEBUG_FILL_STRUCT (op_list); g_free (op_list); } } @@ -2105,26 +2851,68 @@ meta_draw_op_list_draw (const MetaDrawOpList *op_list, GtkWidget *widget, GdkDrawable *drawable, const GdkRectangle *clip, + const MetaDrawInfo *info, int x, int y, int width, int height) { int i; + GdkRectangle active_clip; + GdkRectangle orig_clip; + MetaPositionExprEnv env; + if (op_list->n_ops == 0) + return; + + fill_env (&env, info, x, y, width, height); + /* 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 = x; + orig_clip.y = y; + orig_clip.width = width; + orig_clip.height = height; + } + + active_clip = orig_clip; i = 0; while (i < op_list->n_ops) { - meta_draw_op_draw (op_list->ops[i], - widget, drawable, clip, - x, y, width, height); + 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, + widget, drawable, &active_clip, info, + x, y, width, height, + &env); + } + ++i; } } @@ -2143,6 +2931,48 @@ meta_draw_op_list_append (MetaDrawOpList *op_list, 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 */ + + i = 0; + while (i < op_list->n_ops) + { + 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; + } + + ++i; + } + + return FALSE; +} + MetaFrameStyle* meta_frame_style_new (MetaFrameStyle *parent) { @@ -2155,7 +2985,7 @@ meta_frame_style_new (MetaFrameStyle *parent) style->parent = parent; if (parent) meta_frame_style_ref (parent); - + return style; } @@ -2163,7 +2993,7 @@ void meta_frame_style_ref (MetaFrameStyle *style) { g_return_if_fail (style != NULL); - + style->refcount += 1; } @@ -2171,7 +3001,7 @@ static void free_button_ops (MetaDrawOpList *op_lists[META_BUTTON_TYPE_LAST][META_BUTTON_STATE_LAST]) { int i, j; - + i = 0; while (i < META_BUTTON_TYPE_LAST) { @@ -2180,10 +3010,10 @@ free_button_ops (MetaDrawOpList *op_lists[META_BUTTON_TYPE_LAST][META_BUTTON_STA { if (op_lists[i][j]) meta_draw_op_list_unref (op_lists[i][j]); - + ++j; } - + ++i; } } @@ -2193,39 +3023,97 @@ 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->button_icons); - free_button_ops (style->button_backgrounds); + free_button_ops (style->buttons); + i = 0; while (i < META_FRAME_PIECE_LAST) { if (style->pieces[i]) meta_draw_op_list_unref (style->pieces[i]); - + ++i; } if (style->layout) - meta_frame_layout_free (style->layout); + meta_frame_layout_unref (style->layout); /* 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 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, + GError **error) +{ + int i, j; + + g_return_val_if_fail (style != NULL, FALSE); + g_return_val_if_fail (style->layout != NULL, FALSE); + + i = 0; + while (i < META_BUTTON_TYPE_LAST) + { + j = 0; + while (j < META_BUTTON_STATE_LAST) + { + if (get_button (style, i, j) == NULL) + { + g_set_error (error, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("<button function=\"%s\" state=\"%s\" draw_ops=\"whatever\"/> must be specified for this frame style"), + meta_button_type_to_string (i), + meta_button_state_to_string (j)); + return FALSE; + } + + ++j; + } + + ++i; + } + + return TRUE; +} + static void button_rect (MetaButtonType type, - MetaFrameGeometry *fgeom, + const MetaFrameGeometry *fgeom, GdkRectangle *rect) { switch (type) @@ -2233,15 +3121,15 @@ button_rect (MetaButtonType type, case META_BUTTON_TYPE_CLOSE: *rect = fgeom->close_rect; break; - + case META_BUTTON_TYPE_MAXIMIZE: *rect = fgeom->max_rect; break; - + case META_BUTTON_TYPE_MINIMIZE: *rect = fgeom->min_rect; break; - + case META_BUTTON_TYPE_MENU: *rect = fgeom->menu_rect; break; @@ -2253,74 +3141,81 @@ button_rect (MetaButtonType type, } void -meta_frame_style_draw (MetaFrameStyle *style, - GtkWidget *widget, - GdkDrawable *drawable, - int x_offset, - int y_offset, - const GdkRectangle *clip, - MetaFrameFlags flags, - int client_width, - int client_height, - PangoLayout *title_layout, - int text_height, - MetaButtonState button_states[META_BUTTON_TYPE_LAST]) +meta_frame_style_draw (MetaFrameStyle *style, + GtkWidget *widget, + GdkDrawable *drawable, + int x_offset, + int y_offset, + const GdkRectangle *clip, + const MetaFrameGeometry *fgeom, + int client_width, + int client_height, + PangoLayout *title_layout, + int text_height, + MetaButtonState button_states[META_BUTTON_TYPE_LAST], + GdkPixbuf *mini_icon, + GdkPixbuf *icon) { int i; - MetaFrameGeometry fgeom; GdkRectangle titlebar_rect; GdkRectangle left_titlebar_edge; GdkRectangle right_titlebar_edge; GdkRectangle bottom_titlebar_edge; GdkRectangle top_titlebar_edge; GdkRectangle left_edge, right_edge, bottom_edge; + PangoRectangle extents; + MetaDrawInfo draw_info; - meta_frame_layout_calc_geometry (style->layout, - widget, - text_height, - flags, - client_width, client_height, - &fgeom); - titlebar_rect.x = 0; titlebar_rect.y = 0; - titlebar_rect.width = fgeom.width; - titlebar_rect.height = fgeom.top_height; + titlebar_rect.width = fgeom->width; + titlebar_rect.height = fgeom->top_height; left_titlebar_edge.x = titlebar_rect.x; - left_titlebar_edge.y = titlebar_rect.y + fgeom.top_titlebar_edge; - left_titlebar_edge.width = fgeom.left_titlebar_edge; - left_titlebar_edge.height = titlebar_rect.height - fgeom.top_titlebar_edge - fgeom.bottom_titlebar_edge; + left_titlebar_edge.y = titlebar_rect.y + fgeom->top_titlebar_edge; + left_titlebar_edge.width = fgeom->left_titlebar_edge; + left_titlebar_edge.height = titlebar_rect.height - fgeom->top_titlebar_edge - fgeom->bottom_titlebar_edge; right_titlebar_edge.y = left_titlebar_edge.y; right_titlebar_edge.height = left_titlebar_edge.height; - right_titlebar_edge.width = fgeom.right_titlebar_edge; + right_titlebar_edge.width = fgeom->right_titlebar_edge; right_titlebar_edge.x = titlebar_rect.x + titlebar_rect.width - right_titlebar_edge.width; - + top_titlebar_edge.x = titlebar_rect.x; top_titlebar_edge.y = titlebar_rect.y; top_titlebar_edge.width = titlebar_rect.width; - top_titlebar_edge.height = fgeom.top_titlebar_edge; + top_titlebar_edge.height = fgeom->top_titlebar_edge; bottom_titlebar_edge.x = titlebar_rect.x; bottom_titlebar_edge.width = titlebar_rect.width; - bottom_titlebar_edge.height = fgeom.bottom_titlebar_edge; - bottom_titlebar_edge.y = titlebar_rect.y + titlebar_rect.height - bottom_titlebar_edge.height; + bottom_titlebar_edge.height = fgeom->bottom_titlebar_edge; + bottom_titlebar_edge.y = titlebar_rect.y + titlebar_rect.height - bottom_titlebar_edge.height; left_edge.x = 0; - left_edge.y = fgeom.top_height; - left_edge.width = fgeom.left_width; - left_edge.height = fgeom.height - fgeom.top_height - fgeom.bottom_height; + left_edge.y = fgeom->top_height; + left_edge.width = fgeom->left_width; + left_edge.height = fgeom->height - fgeom->top_height - fgeom->bottom_height; - right_edge.x = fgeom.width - fgeom.right_width; - right_edge.y = fgeom.top_height; - right_edge.width = fgeom.right_width; - right_edge.height = fgeom.height - fgeom.top_height - fgeom.bottom_height; + right_edge.x = fgeom->width - fgeom->right_width; + right_edge.y = fgeom->top_height; + right_edge.width = fgeom->right_width; + right_edge.height = fgeom->height - fgeom->top_height - fgeom->bottom_height; bottom_edge.x = 0; - bottom_edge.y = fgeom.height - fgeom.bottom_height; - bottom_edge.width = fgeom.width; - bottom_edge.height = fgeom.bottom_height; + bottom_edge.y = fgeom->height - fgeom->bottom_height; + bottom_edge.width = fgeom->width; + bottom_edge.height = fgeom->bottom_height; + + if (title_layout) + pango_layout_get_pixel_extents (title_layout, + NULL, &extents); + + draw_info.mini_icon = mini_icon; + draw_info.icon = icon; + draw_info.title_layout = title_layout; + draw_info.title_layout_width = title_layout ? extents.width : 0; + draw_info.title_layout_height = title_layout ? extents.height : 0; + draw_info.fgeom = fgeom; /* The enum is in the order the pieces should be rendered. */ i = 0; @@ -2328,18 +3223,17 @@ meta_frame_style_draw (MetaFrameStyle *style, { GdkRectangle rect; GdkRectangle combined_clip; - gboolean draw_title_text = FALSE; - + switch ((MetaFramePiece) i) { case META_FRAME_PIECE_ENTIRE_BACKGROUND: rect.x = 0; rect.y = 0; - rect.width = fgeom.width; - rect.height = fgeom.height; + rect.width = fgeom->width; + rect.height = fgeom->height; break; - case META_FRAME_PIECE_TITLEBAR_BACKGROUND: + case META_FRAME_PIECE_TITLEBAR: rect = titlebar_rect; break; @@ -2366,14 +3260,11 @@ meta_frame_style_draw (MetaFrameStyle *style, right_titlebar_edge.width; rect.height = titlebar_rect.height - top_titlebar_edge.height - bottom_titlebar_edge.height; break; - - case META_FRAME_PIECE_TITLE_BACKGROUND: - rect = fgeom.title_rect; - /* Trigger drawing the title itself */ - draw_title_text = TRUE; + case META_FRAME_PIECE_TITLE: + rect = fgeom->title_rect; break; - + case META_FRAME_PIECE_LEFT_EDGE: rect = left_edge; break; @@ -2389,8 +3280,8 @@ meta_frame_style_draw (MetaFrameStyle *style, case META_FRAME_PIECE_OVERLAY: rect.x = 0; rect.y = 0; - rect.width = fgeom.width; - rect.height = fgeom.height; + rect.width = fgeom->width; + rect.height = fgeom->height; break; case META_FRAME_PIECE_LAST: @@ -2400,7 +3291,7 @@ meta_frame_style_draw (MetaFrameStyle *style, rect.x += x_offset; rect.y += y_offset; - + if (clip == NULL) combined_clip = rect; else @@ -2417,8 +3308,8 @@ meta_frame_style_draw (MetaFrameStyle *style, op_list = NULL; while (parent && op_list == NULL) { - op_list = style->pieces[i]; - parent = style->parent; + op_list = parent->pieces[i]; + parent = parent->parent; } if (op_list) @@ -2426,80 +3317,25 @@ meta_frame_style_draw (MetaFrameStyle *style, widget, drawable, &combined_clip, + &draw_info, rect.x, rect.y, rect.width, rect.height); } - if (draw_title_text) - { - /* FIXME */ - - /* Deal with whole icon-in-the-titlebar issue; there should - * probably be "this window's icon" and "this window's mini-icon" - * textures, and a way to stick textures on either end of - * the title text as well as behind the text. - * Most likely the text_rect should extend a configurable - * distance from either end of the text. - */ - } - ++i; } - /* Now we draw button backgrounds */ + /* Now we draw buttons */ i = 0; while (i < META_BUTTON_TYPE_LAST) { GdkRectangle rect; GdkRectangle combined_clip; - button_rect (i, &fgeom, &rect); + button_rect (i, fgeom, &rect); rect.x += x_offset; rect.y += y_offset; - - if (clip == NULL) - combined_clip = rect; - else - gdk_rectangle_intersect ((GdkRectangle*) clip, /* const cast */ - &rect, - &combined_clip); - if (combined_clip.width > 0 && combined_clip.height > 0) - { - MetaDrawOpList *op_list; - MetaFrameStyle *parent; - - parent = style; - op_list = NULL; - while (parent && op_list == NULL) - { - op_list = style->button_backgrounds[i][button_states[i]]; - parent = style->parent; - } - - if (op_list) - meta_draw_op_list_draw (op_list, - widget, - drawable, - &combined_clip, - rect.x, rect.y, rect.width, rect.height); - } - - ++i; - } - - /* And button icons */ - i = 0; - while (i < META_BUTTON_TYPE_LAST) - { - GdkRectangle rect; - GdkRectangle combined_clip; - - button_rect (i, &fgeom, &rect); - - rect.x += x_offset; - rect.y += y_offset; - if (clip == NULL) combined_clip = rect; else @@ -2510,24 +3346,18 @@ meta_frame_style_draw (MetaFrameStyle *style, if (combined_clip.width > 0 && combined_clip.height > 0) { MetaDrawOpList *op_list; - MetaFrameStyle *parent; - - parent = style; - op_list = NULL; - while (parent && op_list == NULL) - { - op_list = style->button_icons[i][button_states[i]]; - parent = style->parent; - } + + op_list = get_button (style, i, button_states[i]); if (op_list) meta_draw_op_list_draw (op_list, widget, drawable, &combined_clip, + &draw_info, rect.x, rect.y, rect.width, rect.height); } - + ++i; } } @@ -2542,6 +3372,8 @@ meta_frame_style_set_new (MetaFrameStyleSet *parent) style_set->parent = parent; if (parent) meta_frame_style_set_ref (parent); + + style_set->refcount = 1; return style_set; } @@ -2565,7 +3397,7 @@ void meta_frame_style_set_ref (MetaFrameStyleSet *style_set) { g_return_if_fail (style_set != NULL); - + style_set->refcount += 1; } @@ -2585,21 +3417,192 @@ meta_frame_style_set_unref (MetaFrameStyleSet *style_set) while (i < META_FRAME_RESIZE_LAST) { free_focus_styles (style_set->normal_styles[i]); - + ++i; } - + free_focus_styles (style_set->maximized_styles); free_focus_styles (style_set->shaded_styles); free_focus_styles (style_set->maximized_and_shaded_styles); if (style_set->parent) meta_frame_style_set_unref (style_set->parent); - + + DEBUG_FILL_STRUCT (style_set); g_free (style_set); } } + +static MetaFrameStyle* +get_style (MetaFrameStyleSet *style_set, + MetaFrameState state, + MetaFrameResize resize, + MetaFrameFocus focus) +{ + MetaFrameStyle *style; + + style = NULL; + + if (state == META_FRAME_STATE_NORMAL) + { + style = style_set->normal_styles[resize][focus]; + + /* Try parent if we failed here */ + if (style == NULL && style_set->parent) + style = get_style (style_set->parent, state, resize, focus); + + /* Allow people to omit the vert/horz/none resize modes */ + if (style == NULL && + resize != META_FRAME_RESIZE_BOTH) + style = get_style (style_set, state, META_FRAME_RESIZE_BOTH, focus); + } + else + { + MetaFrameStyle **styles; + + styles = NULL; + + switch (state) + { + case META_FRAME_STATE_SHADED: + styles = style_set->shaded_styles; + break; + case META_FRAME_STATE_MAXIMIZED: + styles = style_set->maximized_styles; + break; + case META_FRAME_STATE_MAXIMIZED_AND_SHADED: + styles = style_set->maximized_and_shaded_styles; + break; + case META_FRAME_STATE_NORMAL: + case META_FRAME_STATE_LAST: + g_assert_not_reached (); + break; + } + + style = styles[focus]; + + /* Try parent if we failed here */ + if (style == NULL && style_set->parent) + style = get_style (style_set->parent, state, resize, focus); + } + + return style; +} + +static gboolean +check_state (MetaFrameStyleSet *style_set, + MetaFrameState state, + GError **error) +{ + int i; + + i = 0; + while (i < META_FRAME_FOCUS_LAST) + { + if (get_style (style_set, state, + META_FRAME_RESIZE_NONE, i) == NULL) + { + g_set_error (error, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Missing <frame state=\"%s\" resize=\"%s\" focus=\"%s\" style=\"whatever\"/>"), + meta_frame_state_to_string (state), + meta_frame_resize_to_string (META_FRAME_RESIZE_NONE), + meta_frame_focus_to_string (i)); + return FALSE; + } + + ++i; + } + + return TRUE; +} + +gboolean +meta_frame_style_set_validate (MetaFrameStyleSet *style_set, + GError **error) +{ + int i, j; + + g_return_val_if_fail (style_set != NULL, FALSE); + + i = 0; + while (i < META_FRAME_RESIZE_LAST) + { + j = 0; + while (j < META_FRAME_FOCUS_LAST) + { + if (get_style (style_set, META_FRAME_STATE_NORMAL, + i, j) == NULL) + { + g_set_error (error, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Missing <frame state=\"%s\" resize=\"%s\" focus=\"%s\" style=\"whatever\"/>"), + meta_frame_state_to_string (META_FRAME_STATE_NORMAL), + meta_frame_resize_to_string (i), + meta_frame_focus_to_string (j)); + return FALSE; + } + + ++j; + } + ++i; + } + + if (!check_state (style_set, META_FRAME_STATE_SHADED, error)) + return FALSE; + + if (!check_state (style_set, META_FRAME_STATE_MAXIMIZED, error)) + return FALSE; + + if (!check_state (style_set, META_FRAME_STATE_MAXIMIZED_AND_SHADED, error)) + return FALSE; + + return TRUE; +} + +static MetaTheme *meta_current_theme = NULL; + +MetaTheme* +meta_theme_get_current (void) +{ + return meta_current_theme; +} + +void +meta_theme_set_current (const char *name, + gboolean force_reload) +{ + MetaTheme *new_theme; + GError *err; + + meta_verbose ("Setting current theme to \"%s\"\n", name); + + if (!force_reload && + meta_current_theme && + strcmp (name, meta_current_theme->name) == 0) + return; + + err = NULL; + new_theme = meta_theme_load (name, &err); + + if (new_theme == NULL) + { + meta_warning (_("Failed to load theme \"%s\": %s"), + name, err->message); + g_error_free (err); + } + else + { + if (meta_current_theme) + meta_theme_free (meta_current_theme); + + meta_current_theme = new_theme; + + meta_verbose ("New theme is \"%s\"\n", meta_current_theme->name); + } +} + MetaTheme* meta_theme_new (void) { @@ -2607,30 +3610,80 @@ meta_theme_new (void) theme = g_new0 (MetaTheme, 1); - theme->styles_by_name = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - (GDestroyNotify) meta_frame_style_unref); - - theme->style_sets_by_name = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - (GDestroyNotify) meta_frame_style_set_unref); - + theme->images_by_filename = + g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) g_object_unref); + + theme->layouts_by_name = + g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) meta_frame_layout_unref); + + theme->draw_op_lists_by_name = + g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) meta_draw_op_list_unref); + + theme->styles_by_name = + g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) meta_frame_style_unref); + + theme->style_sets_by_name = + g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) meta_frame_style_set_unref); return theme; } + +static void +free_menu_ops (MetaDrawOpList *op_lists[META_BUTTON_TYPE_LAST][N_GTK_STATES]) +{ + int i, j; + + i = 0; + while (i < META_BUTTON_TYPE_LAST) + { + j = 0; + while (j < N_GTK_STATES) + { + if (op_lists[i][j]) + meta_draw_op_list_unref (op_lists[i][j]); + + ++j; + } + + ++i; + } +} + void meta_theme_free (MetaTheme *theme) { int i; - + g_return_if_fail (theme != NULL); g_free (theme->name); + g_free (theme->dirname); g_free (theme->filename); - + g_free (theme->readable_name); + g_free (theme->date); + g_free (theme->description); + g_free (theme->author); + g_free (theme->copyright); + + g_hash_table_destroy (theme->images_by_filename); + g_hash_table_destroy (theme->layouts_by_name); + g_hash_table_destroy (theme->draw_op_lists_by_name); g_hash_table_destroy (theme->styles_by_name); g_hash_table_destroy (theme->style_sets_by_name); @@ -2641,159 +3694,1087 @@ meta_theme_free (MetaTheme *theme) meta_frame_style_set_unref (theme->style_sets_by_type[i]); ++i; } + + free_menu_ops (theme->menu_icons); + DEBUG_FILL_STRUCT (theme); g_free (theme); } -static MetaDrawOp* -line_op (MetaGtkColorComponent component, - const char *x1, - const char *y1, - const char *x2, - const char *y2) +static MetaDrawOpList* +get_menu_icon (MetaTheme *theme, + MetaMenuIconType type, + GtkStateType state) { - MetaDrawOp *op; + MetaDrawOpList *op_list; + + op_list = theme->menu_icons[type][state]; + + /* We fall back to normal if other states aren't found */ + if (op_list == NULL && + state != GTK_STATE_NORMAL) + return get_menu_icon (theme, type, GTK_STATE_NORMAL); - op = meta_draw_op_new (META_DRAW_LINE); - op->data.line.color_spec = - meta_color_spec_new_gtk (component, GTK_STATE_NORMAL); - op->data.line.x1 = g_strdup (x1); - op->data.line.x2 = g_strdup (x2); - op->data.line.y1 = g_strdup (y1); - op->data.line.y2 = g_strdup (y2); - op->data.line.dash_on_length = 0; - op->data.line.dash_off_length = 0; - op->data.line.width = 0; + return op_list; +} - return op; +gboolean +meta_theme_validate (MetaTheme *theme, + GError **error) +{ + int i, j; + + g_return_val_if_fail (theme != NULL, FALSE); + + /* FIXME what else should be checked? */ + + g_assert (theme->name); + + if (theme->readable_name == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <name> set for theme \"%s\""), theme->name); + return FALSE; + } + + if (theme->author == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <author> set for theme \"%s\""), theme->name); + return FALSE; + } + + if (theme->date == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <date> set for theme \"%s\""), theme->name); + return FALSE; + } + + if (theme->description == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <description> set for theme \"%s\""), theme->name); + return FALSE; + } + + if (theme->copyright == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <copyright> set for theme \"%s\""), theme->name); + return FALSE; + } + + i = 0; + while (i < (int) META_FRAME_TYPE_LAST) + { + if (theme->style_sets_by_type[i] == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No frame style set for window type \"%s\" in theme \"%s\", add a <window type=\"%s\" style_set=\"whatever\"/> element"), + meta_frame_type_to_string (i), + theme->name, + meta_frame_type_to_string (i)); + + return FALSE; + } + + ++i; + } + + i = 0; + while (i < META_MENU_ICON_TYPE_LAST) + { + j = 0; + while (j < N_GTK_STATES) + { + + if (get_menu_icon (theme, i, j) == NULL) + { + g_set_error (error, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("<menu_icon function=\"%s\" state=\"%s\" draw_ops=\"whatever\"/> must be specified for this theme"), + meta_menu_icon_type_to_string (i), + meta_gtk_state_to_string (j)); + return FALSE; + } + + ++j; + } + + ++i; + } + + return TRUE; } -#define DEFAULT_INNER_BUTTON_BORDER 3 -MetaFrameStyle* -meta_frame_style_get_test (void) -{ - static MetaFrameStyle *style = NULL; - static GtkBorder default_title_border = { 3, 4, 4, 3 }; - static GtkBorder default_text_border = { 2, 2, 2, 2 }; - static GtkBorder default_button_border = { 0, 0, 1, 1 }; - static GtkBorder default_inner_button_border = { - DEFAULT_INNER_BUTTON_BORDER, - DEFAULT_INNER_BUTTON_BORDER, - DEFAULT_INNER_BUTTON_BORDER, - DEFAULT_INNER_BUTTON_BORDER - }; +GdkPixbuf* +meta_theme_load_image (MetaTheme *theme, + const char *filename, + GError **error) +{ + GdkPixbuf *pixbuf; + + pixbuf = g_hash_table_lookup (theme->images_by_filename, + filename); + + if (pixbuf == NULL) + { + char *full_path; + + full_path = g_build_filename (theme->dirname, filename, NULL); + + pixbuf = gdk_pixbuf_new_from_file (full_path, error); + if (pixbuf == NULL) + { + g_free (full_path); + return NULL; + } + + g_free (full_path); + + g_hash_table_replace (theme->images_by_filename, + g_strdup (filename), + pixbuf); + } + + g_assert (pixbuf); + + g_object_ref (G_OBJECT (pixbuf)); + + return pixbuf; +} + +static MetaFrameStyle* +theme_get_style (MetaTheme *theme, + MetaFrameType type, + MetaFrameFlags flags) +{ + MetaFrameState state; + MetaFrameResize resize; + MetaFrameFocus focus; + MetaFrameStyle *style; + MetaFrameStyleSet *style_set; + + style_set = theme->style_sets_by_type[type]; + + /* Right now the parser forces a style set for all types, + * but this fallback code is here in case I take that out. + */ + if (style_set == NULL) + style_set = theme->style_sets_by_type[META_FRAME_TYPE_NORMAL]; + if (style_set == NULL) + return NULL; + + switch (flags & (META_FRAME_MAXIMIZED | META_FRAME_SHADED)) + { + case 0: + state = META_FRAME_STATE_NORMAL; + break; + case META_FRAME_MAXIMIZED: + state = META_FRAME_STATE_MAXIMIZED; + break; + case META_FRAME_SHADED: + state = META_FRAME_STATE_SHADED; + break; + case (META_FRAME_MAXIMIZED | META_FRAME_SHADED): + state = META_FRAME_STATE_MAXIMIZED_AND_SHADED; + break; + default: + g_assert_not_reached (); + state = META_FRAME_STATE_LAST; /* compiler */ + break; + } + + switch (flags & (META_FRAME_ALLOWS_VERTICAL_RESIZE | META_FRAME_ALLOWS_HORIZONTAL_RESIZE)) + { + case 0: + resize = META_FRAME_RESIZE_NONE; + break; + case META_FRAME_ALLOWS_VERTICAL_RESIZE: + resize = META_FRAME_RESIZE_VERTICAL; + break; + case META_FRAME_ALLOWS_HORIZONTAL_RESIZE: + resize = META_FRAME_RESIZE_HORIZONTAL; + break; + case (META_FRAME_ALLOWS_VERTICAL_RESIZE | META_FRAME_ALLOWS_HORIZONTAL_RESIZE): + resize = META_FRAME_RESIZE_BOTH; + break; + default: + g_assert_not_reached (); + resize = META_FRAME_RESIZE_LAST; /* compiler */ + break; + } + + if (flags & META_FRAME_HAS_FOCUS) + focus = META_FRAME_FOCUS_YES; + else + focus = META_FRAME_FOCUS_NO; + + style = get_style (style_set, state, resize, focus); + + return style; +} + +void +meta_theme_draw_frame (MetaTheme *theme, + 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, + MetaButtonState button_states[META_BUTTON_TYPE_LAST], + GdkPixbuf *mini_icon, + GdkPixbuf *icon) +{ + MetaFrameGeometry fgeom; + MetaFrameStyle *style; + + g_return_if_fail (type < META_FRAME_TYPE_LAST); + + style = theme_get_style (theme, type, flags); + + /* Parser is not supposed to allow this currently */ + if (style == NULL) + return; + + meta_frame_layout_calc_geometry (style->layout, + text_height, + flags, + client_width, client_height, + &fgeom); + + meta_frame_style_draw (style, + widget, + drawable, + x_offset, y_offset, + clip, + &fgeom, + client_width, client_height, + title_layout, + text_height, + button_states, + mini_icon, icon); +} + +void +meta_theme_draw_menu_icon (MetaTheme *theme, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + int x_offset, + int y_offset, + int width, + int height, + MetaMenuIconType type) +{ + MetaDrawInfo info; MetaDrawOpList *op_list; - MetaDrawOp *op; - MetaGradientSpec *gradient; - if (style) - return style; + g_return_if_fail (type < META_BUTTON_TYPE_LAST); + + op_list = get_menu_icon (theme, type, + GTK_WIDGET_STATE (widget)); - style = meta_frame_style_new (NULL); + info.mini_icon = NULL; + info.icon = NULL; + info.title_layout = NULL; + info.title_layout_width = 0; + info.title_layout_height = 0; + info.fgeom = NULL; - style->layout = meta_frame_layout_new (); + meta_draw_op_list_draw (op_list, + widget, + drawable, + clip, + &info, + x_offset, y_offset, width, height); +} + +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) +{ + MetaFrameStyle *style; - style->layout->title_border = default_title_border; - style->layout->text_border = default_text_border; - style->layout->button_border = default_button_border; - style->layout->inner_button_border = default_inner_button_border; + g_return_if_fail (type < META_FRAME_TYPE_LAST); - style->layout->left_width = 6; - style->layout->right_width = 6; - style->layout->bottom_height = 7; - style->layout->spacer_padding = 3; - style->layout->spacer_width = 2; - style->layout->spacer_height = 11; - style->layout->right_inset = 6; - style->layout->left_inset = 6; - style->layout->button_width = 17; - style->layout->button_height = 17; - - op_list = meta_draw_op_list_new (3); + if (top_height) + *top_height = 0; + if (bottom_height) + *bottom_height = 0; + if (left_width) + *left_width = 0; + if (right_width) + *right_width = 0; - /* Gradient background */ - op = meta_draw_op_new (META_DRAW_GRADIENT); - gradient = meta_gradient_spec_new (META_GRADIENT_VERTICAL); - gradient->color_specs = - g_slist_prepend (gradient->color_specs, - meta_color_spec_new_gtk (META_GTK_COLOR_BG, - GTK_STATE_NORMAL)); - gradient->color_specs = - g_slist_prepend (gradient->color_specs, - meta_color_spec_new_gtk (META_GTK_COLOR_BG, - GTK_STATE_SELECTED)); - - op->data.gradient.gradient_spec = gradient; - op->data.gradient.alpha = 1.0; + style = theme_get_style (theme, type, flags); - op->data.gradient.x = g_strdup ("0"); - op->data.gradient.y = g_strdup ("0"); - op->data.gradient.width = g_strdup ("width"); - op->data.gradient.height = g_strdup ("height"); - - meta_draw_op_list_append (op_list, op); - - /* Put orange tint on parts of window */ - op = meta_draw_op_new (META_DRAW_TINT); - op->data.tint.color_spec = meta_color_spec_new_from_string ("orange", NULL); - op->data.tint.alpha = 0.3; - op->data.gradient.x = g_strdup ("0"); - op->data.gradient.y = g_strdup ("0"); - op->data.gradient.width = g_strdup ("width / 2"); - op->data.gradient.height = g_strdup ("height / 2"); - - meta_draw_op_list_append (op_list, op); - - op = meta_draw_op_new (META_DRAW_TINT); - op->data.tint.color_spec = meta_color_spec_new_from_string ("orange", NULL); - op->data.tint.alpha = 0.3; - op->data.gradient.x = g_strdup ("width / 2 + width % 2"); - op->data.gradient.y = g_strdup ("height / 2 + height % 2"); - op->data.gradient.width = g_strdup ("width / 2"); - op->data.gradient.height = g_strdup ("height / 2"); - - meta_draw_op_list_append (op_list, op); + /* Parser is not supposed to allow this currently */ + if (style == NULL) + return; + + meta_frame_layout_get_borders (style->layout, + text_height, + flags, + top_height, bottom_height, + left_width, right_width); +} + +void +meta_theme_calc_geometry (MetaTheme *theme, + MetaFrameType type, + int text_height, + MetaFrameFlags flags, + int client_width, + int client_height, + MetaFrameGeometry *fgeom) +{ + MetaFrameStyle *style; + + g_return_if_fail (type < META_FRAME_TYPE_LAST); - style->pieces[META_FRAME_PIECE_ENTIRE_BACKGROUND] = op_list; + style = theme_get_style (theme, type, flags); - op_list = meta_draw_op_list_new (5); - style->pieces[META_FRAME_PIECE_OVERLAY] = op_list; + /* Parser is not supposed to allow this currently */ + if (style == NULL) + return; - op = meta_draw_op_new (META_DRAW_RECTANGLE); - op->data.rectangle.color_spec = - meta_color_spec_new_from_string ("#000000", NULL); - op->data.rectangle.filled = FALSE; - op->data.rectangle.x = g_strdup ("0"); - op->data.rectangle.y = g_strdup ("0"); - op->data.rectangle.width = g_strdup ("width - 1"); - op->data.rectangle.height = g_strdup ("height - 1"); + meta_frame_layout_calc_geometry (style->layout, + text_height, + flags, + client_width, client_height, + fgeom); +} - meta_draw_op_list_append (op_list, op); +MetaFrameLayout* +meta_theme_lookup_layout (MetaTheme *theme, + const char *name) +{ + return g_hash_table_lookup (theme->layouts_by_name, name); +} - meta_draw_op_list_append (op_list, - line_op (META_GTK_COLOR_LIGHT, - "1", "1", "1", "height - 2")); +void +meta_theme_insert_layout (MetaTheme *theme, + const char *name, + MetaFrameLayout *layout) +{ + meta_frame_layout_ref (layout); + g_hash_table_replace (theme->layouts_by_name, g_strdup (name), layout); +} - meta_draw_op_list_append (op_list, - line_op (META_GTK_COLOR_LIGHT, - "1", "1", "width - 2", "1")); +MetaDrawOpList* +meta_theme_lookup_draw_op_list (MetaTheme *theme, + const char *name) +{ + return g_hash_table_lookup (theme->draw_op_lists_by_name, name); +} - meta_draw_op_list_append (op_list, - line_op (META_GTK_COLOR_DARK, - "width - 2", "1", "width - 2", "height - 2")); +void +meta_theme_insert_draw_op_list (MetaTheme *theme, + const char *name, + MetaDrawOpList *op_list) +{ + meta_draw_op_list_ref (op_list); + g_hash_table_replace (theme->draw_op_lists_by_name, g_strdup (name), op_list); +} - meta_draw_op_list_append (op_list, - line_op (META_GTK_COLOR_DARK, - "1", "height - 2", "width - 2", "height - 2")); +MetaFrameStyle* +meta_theme_lookup_style (MetaTheme *theme, + const char *name) +{ + return g_hash_table_lookup (theme->styles_by_name, name); +} + +void +meta_theme_insert_style (MetaTheme *theme, + const char *name, + MetaFrameStyle *style) +{ + meta_frame_style_ref (style); + g_hash_table_replace (theme->styles_by_name, g_strdup (name), style); +} + +MetaFrameStyleSet* +meta_theme_lookup_style_set (MetaTheme *theme, + const char *name) +{ + return g_hash_table_lookup (theme->style_sets_by_name, name); +} +void +meta_theme_insert_style_set (MetaTheme *theme, + const char *name, + MetaFrameStyleSet *style_set) +{ + meta_frame_style_set_ref (style_set); + g_hash_table_replace (theme->style_sets_by_name, g_strdup (name), style_set); +} + +static gboolean +first_uppercase (const char *str) +{ + return g_ascii_isupper (*str); +} + +gboolean +meta_theme_define_int_constant (MetaTheme *theme, + const char *name, + int value, + GError **error) +{ + if (theme->integer_constants == NULL) + theme->integer_constants = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); + + if (!first_uppercase (name)) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("User-defined constants must begin with a capital letter; \"%s\" does not"), + name); + return FALSE; + } + if (g_hash_table_lookup_extended (theme->integer_constants, name, NULL, NULL)) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("Constant \"%s\" has already been defined"), + name); + + return FALSE; + } + + g_hash_table_insert (theme->integer_constants, + g_strdup (name), + GINT_TO_POINTER (value)); +} + +gboolean +meta_theme_lookup_int_constant (MetaTheme *theme, + const char *name, + int *value) +{ + gpointer old_value; + + *value = 0; - return style; + if (theme->integer_constants == NULL) + return FALSE; + + if (g_hash_table_lookup_extended (theme->integer_constants, + name, NULL, &old_value)) + { + *value = GPOINTER_TO_INT (old_value); + return TRUE; + } + else + { + return FALSE; + } } +gboolean +meta_theme_define_float_constant (MetaTheme *theme, + const char *name, + double value, + GError **error) +{ + double *d; + + if (theme->float_constants == NULL) + theme->float_constants = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_free); + if (!first_uppercase (name)) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("User-defined constants must begin with a capital letter; \"%s\" does not"), + name); + return FALSE; + } + + if (g_hash_table_lookup_extended (theme->float_constants, name, NULL, NULL)) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("Constant \"%s\" has already been defined"), + name); + + return FALSE; + } + + d = g_new (double, 1); + *d = value; + + g_hash_table_insert (theme->float_constants, + g_strdup (name), d); +} + +gboolean +meta_theme_lookup_float_constant (MetaTheme *theme, + const char *name, + double *value) +{ + double *d; + + *value = 0.0; + + if (theme->float_constants == NULL) + return FALSE; + + d = g_hash_table_lookup (theme->float_constants, name); + + if (d) + { + *value = *d; + return TRUE; + } + else + { + return FALSE; + } +} + +int +meta_gtk_widget_get_text_height (GtkWidget *widget) +{ + PangoFontMetrics *metrics; + PangoFont *font; + PangoLanguage *lang; + int retval; + g_return_val_if_fail (GTK_WIDGET_REALIZED (widget), 0); + + font = pango_context_load_font (gtk_widget_get_pango_context (widget), + widget->style->font_desc); + lang = pango_context_get_language (gtk_widget_get_pango_context (widget)); + metrics = pango_font_get_metrics (font, lang); + + g_object_unref (G_OBJECT (font)); + + retval = PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) + + pango_font_metrics_get_descent (metrics)); + + pango_font_metrics_unref (metrics); + return retval; +} + +MetaGtkColorComponent +meta_color_component_from_string (const char *str) +{ + if (strcmp ("fg", str) == 0) + return META_GTK_COLOR_FG; + else if (strcmp ("bg", str) == 0) + return META_GTK_COLOR_BG; + else if (strcmp ("light", str) == 0) + return META_GTK_COLOR_LIGHT; + else if (strcmp ("dark", str) == 0) + return META_GTK_COLOR_DARK; + else if (strcmp ("mid", str) == 0) + return META_GTK_COLOR_MID; + else if (strcmp ("text", str) == 0) + return META_GTK_COLOR_TEXT; + else if (strcmp ("base", str) == 0) + return META_GTK_COLOR_BASE; + else if (strcmp ("text_aa", str) == 0) + return META_GTK_COLOR_TEXT_AA; + else + return META_GTK_COLOR_LAST; +} + +const char* +meta_color_component_to_string (MetaGtkColorComponent component) +{ + switch (component) + { + case META_GTK_COLOR_FG: + return "fg"; + case META_GTK_COLOR_BG: + return "bg"; + case META_GTK_COLOR_LIGHT: + return "light"; + case META_GTK_COLOR_DARK: + return "dark"; + case META_GTK_COLOR_MID: + return "mid"; + case META_GTK_COLOR_TEXT: + return "text"; + case META_GTK_COLOR_BASE: + return "base"; + case META_GTK_COLOR_TEXT_AA: + return "text_aa"; + case META_GTK_COLOR_LAST: + break; + } + + return "<unknown>"; +} + +MetaButtonState +meta_button_state_from_string (const char *str) +{ + if (strcmp ("normal", str) == 0) + return META_BUTTON_STATE_NORMAL; + else if (strcmp ("pressed", str) == 0) + return META_BUTTON_STATE_PRESSED; + else if (strcmp ("prelight", str) == 0) + return META_BUTTON_STATE_PRELIGHT; + else + return META_BUTTON_STATE_LAST; +} + +const char* +meta_button_state_to_string (MetaButtonState state) +{ + switch (state) + { + case META_BUTTON_STATE_NORMAL: + return "normal"; + case META_BUTTON_STATE_PRESSED: + return "pressed"; + case META_BUTTON_STATE_PRELIGHT: + return "prelight"; + case META_BUTTON_STATE_LAST: + break; + } + + return "<unknown>"; +} + +MetaButtonType +meta_button_type_from_string (const char *str) +{ + if (strcmp ("close", str) == 0) + return META_BUTTON_TYPE_CLOSE; + else if (strcmp ("maximize", str) == 0) + return META_BUTTON_TYPE_MAXIMIZE; + else if (strcmp ("minimize", str) == 0) + return META_BUTTON_TYPE_MINIMIZE; + else if (strcmp ("menu", str) == 0) + return META_BUTTON_TYPE_MENU; + else + return META_BUTTON_TYPE_LAST; +} + +const char* +meta_button_type_to_string (MetaButtonType type) +{ + switch (type) + { + case META_BUTTON_TYPE_CLOSE: + return "close"; + case META_BUTTON_TYPE_MAXIMIZE: + return "maximize"; + case META_BUTTON_TYPE_MINIMIZE: + return "minimize"; + case META_BUTTON_TYPE_MENU: + return "menu"; + case META_BUTTON_TYPE_LAST: + break; + } + + return "<unknown>"; +} + +MetaMenuIconType +meta_menu_icon_type_from_string (const char *str) +{ + if (strcmp ("close", str) == 0) + return META_MENU_ICON_TYPE_CLOSE; + else if (strcmp ("maximize", str) == 0) + return META_MENU_ICON_TYPE_MAXIMIZE; + else if (strcmp ("minimize", str) == 0) + return META_MENU_ICON_TYPE_MINIMIZE; + else if (strcmp ("unmaximize", str) == 0) + return META_MENU_ICON_TYPE_UNMAXIMIZE; + else + return META_MENU_ICON_TYPE_LAST; +} + +const char* +meta_menu_icon_type_to_string (MetaMenuIconType type) +{ + switch (type) + { + case META_MENU_ICON_TYPE_CLOSE: + return "close"; + case META_MENU_ICON_TYPE_MAXIMIZE: + return "maximize"; + case META_MENU_ICON_TYPE_MINIMIZE: + return "minimize"; + case META_MENU_ICON_TYPE_UNMAXIMIZE: + return "unmaximize"; + case META_MENU_ICON_TYPE_LAST: + break; + } + + return "<unknown>"; +} + +MetaFramePiece +meta_frame_piece_from_string (const char *str) +{ + if (strcmp ("entire_background", str) == 0) + return META_FRAME_PIECE_ENTIRE_BACKGROUND; + else if (strcmp ("titlebar", str) == 0) + return META_FRAME_PIECE_TITLEBAR; + else if (strcmp ("titlebar_middle", str) == 0) + return META_FRAME_PIECE_TITLEBAR_MIDDLE; + else if (strcmp ("left_titlebar_edge", str) == 0) + return META_FRAME_PIECE_LEFT_TITLEBAR_EDGE; + else if (strcmp ("right_titlebar_edge", str) == 0) + return META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE; + else if (strcmp ("top_titlebar_edge", str) == 0) + return META_FRAME_PIECE_TOP_TITLEBAR_EDGE; + else if (strcmp ("bottom_titlebar_edge", str) == 0) + return META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE; + else if (strcmp ("title", str) == 0) + return META_FRAME_PIECE_TITLE; + else if (strcmp ("left_edge", str) == 0) + return META_FRAME_PIECE_LEFT_EDGE; + else if (strcmp ("right_edge", str) == 0) + return META_FRAME_PIECE_RIGHT_EDGE; + else if (strcmp ("bottom_edge", str) == 0) + return META_FRAME_PIECE_BOTTOM_EDGE; + else if (strcmp ("overlay", str) == 0) + return META_FRAME_PIECE_OVERLAY; + else + return META_FRAME_PIECE_LAST; +} + +const char* +meta_frame_piece_to_string (MetaFramePiece piece) +{ + switch (piece) + { + case META_FRAME_PIECE_ENTIRE_BACKGROUND: + return "entire_background"; + case META_FRAME_PIECE_TITLEBAR: + return "titlebar"; + case META_FRAME_PIECE_TITLEBAR_MIDDLE: + return "titlebar_middle"; + case META_FRAME_PIECE_LEFT_TITLEBAR_EDGE: + return "left_titlebar_edge"; + case META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE: + return "right_titlebar_edge"; + case META_FRAME_PIECE_TOP_TITLEBAR_EDGE: + return "top_titlebar_edge"; + case META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE: + return "bottom_titlebar_edge"; + case META_FRAME_PIECE_TITLE: + return "title"; + case META_FRAME_PIECE_LEFT_EDGE: + return "left_edge"; + case META_FRAME_PIECE_RIGHT_EDGE: + return "right_edge"; + case META_FRAME_PIECE_BOTTOM_EDGE: + return "bottom_edge"; + case META_FRAME_PIECE_OVERLAY: + return "overlay"; + case META_FRAME_PIECE_LAST: + break; + } + + return "<unknown>"; +} + +MetaFrameState +meta_frame_state_from_string (const char *str) +{ + if (strcmp ("normal", str) == 0) + return META_FRAME_STATE_NORMAL; + else if (strcmp ("maximized", str) == 0) + return META_FRAME_STATE_MAXIMIZED; + else if (strcmp ("shaded", str) == 0) + return META_FRAME_STATE_SHADED; + else if (strcmp ("maximized_and_shaded", str) == 0) + return META_FRAME_STATE_MAXIMIZED_AND_SHADED; + else + return META_FRAME_STATE_LAST; +} + +const char* +meta_frame_state_to_string (MetaFrameState state) +{ + switch (state) + { + case META_FRAME_STATE_NORMAL: + return "normal"; + case META_FRAME_STATE_MAXIMIZED: + return "maximized"; + case META_FRAME_STATE_SHADED: + return "shaded"; + case META_FRAME_STATE_MAXIMIZED_AND_SHADED: + return "maximized_and_shaded"; + case META_FRAME_STATE_LAST: + break; + } + + return "<unknown>"; +} + +MetaFrameResize +meta_frame_resize_from_string (const char *str) +{ + if (strcmp ("none", str) == 0) + return META_FRAME_RESIZE_NONE; + else if (strcmp ("vertical", str) == 0) + return META_FRAME_RESIZE_VERTICAL; + else if (strcmp ("horizontal", str) == 0) + return META_FRAME_RESIZE_HORIZONTAL; + else if (strcmp ("both", str) == 0) + return META_FRAME_RESIZE_BOTH; + else + return META_FRAME_RESIZE_LAST; +} + +const char* +meta_frame_resize_to_string (MetaFrameResize resize) +{ + switch (resize) + { + case META_FRAME_RESIZE_NONE: + return "none"; + case META_FRAME_RESIZE_VERTICAL: + return "vertical"; + case META_FRAME_RESIZE_HORIZONTAL: + return "horizontal"; + case META_FRAME_RESIZE_BOTH: + return "both"; + case META_FRAME_RESIZE_LAST: + break; + } + + return "<unknown>"; +} + +MetaFrameFocus +meta_frame_focus_from_string (const char *str) +{ + if (strcmp ("no", str) == 0) + return META_FRAME_FOCUS_NO; + else if (strcmp ("yes", str) == 0) + return META_FRAME_FOCUS_YES; + else + return META_FRAME_FOCUS_LAST; +} + +const char* +meta_frame_focus_to_string (MetaFrameFocus focus) +{ + switch (focus) + { + case META_FRAME_FOCUS_NO: + return "no"; + case META_FRAME_FOCUS_YES: + return "yes"; + case META_FRAME_FOCUS_LAST: + break; + } + + return "<unknown>"; +} + +MetaFrameType +meta_frame_type_from_string (const char *str) +{ + if (strcmp ("normal", str) == 0) + return META_FRAME_TYPE_NORMAL; + else if (strcmp ("dialog", str) == 0) + return META_FRAME_TYPE_DIALOG; + else if (strcmp ("modal_dialog", str) == 0) + return META_FRAME_TYPE_MODAL_DIALOG; + else if (strcmp ("utility", str) == 0) + return META_FRAME_TYPE_UTILITY; + else if (strcmp ("menu", str) == 0) + return META_FRAME_TYPE_MENU; +#if 0 + else if (strcmp ("toolbar", str) == 0) + return META_FRAME_TYPE_TOOLBAR; +#endif + else + return META_FRAME_TYPE_LAST; +} + +const char* +meta_frame_type_to_string (MetaFrameType type) +{ + switch (type) + { + case META_FRAME_TYPE_NORMAL: + return "normal"; + case META_FRAME_TYPE_DIALOG: + return "dialog"; + case META_FRAME_TYPE_MODAL_DIALOG: + return "modal_dialog"; + case META_FRAME_TYPE_UTILITY: + return "utility"; + case META_FRAME_TYPE_MENU: + return "menu"; +#if 0 + case META_FRAME_TYPE_TOOLBAR: + return "toolbar"; +#endif + case META_FRAME_TYPE_LAST: + break; + } + + return "<unknown>"; +} + +MetaGradientType +meta_gradient_type_from_string (const char *str) +{ + if (strcmp ("vertical", str) == 0) + return META_GRADIENT_VERTICAL; + else if (strcmp ("horizontal", str) == 0) + return META_GRADIENT_HORIZONTAL; + else if (strcmp ("diagonal", str) == 0) + return META_GRADIENT_DIAGONAL; + else + return META_GRADIENT_LAST; +} + +const char* +meta_gradient_type_to_string (MetaGradientType type) +{ + switch (type) + { + case META_GRADIENT_VERTICAL: + return "vertical"; + case META_GRADIENT_HORIZONTAL: + return "horizontal"; + case META_GRADIENT_DIAGONAL: + return "diagonal"; + case META_GRADIENT_LAST: + break; + } + + return "<unknown>"; +} + +GtkStateType +meta_gtk_state_from_string (const char *str) +{ + if (strcmp ("normal", str) == 0 || strcmp ("NORMAL", str) == 0) + return GTK_STATE_NORMAL; + else if (strcmp ("prelight", str) == 0 || strcmp ("PRELIGHT", str) == 0) + return GTK_STATE_PRELIGHT; + else if (strcmp ("active", str) == 0 || strcmp ("ACTIVE", str) == 0) + return GTK_STATE_ACTIVE; + else if (strcmp ("selected", str) == 0 || strcmp ("SELECTED", str) == 0) + return GTK_STATE_SELECTED; + else if (strcmp ("insensitive", str) == 0 || strcmp ("INSENSITIVE", str) == 0) + return GTK_STATE_INSENSITIVE; + else + return -1; /* hack */ +} + +const char* +meta_gtk_state_to_string (GtkStateType state) +{ + switch (state) + { + case GTK_STATE_NORMAL: + return "NORMAL"; + case GTK_STATE_PRELIGHT: + return "PRELIGHT"; + case GTK_STATE_ACTIVE: + return "ACTIVE"; + case GTK_STATE_SELECTED: + return "SELECTED"; + case GTK_STATE_INSENSITIVE: + return "INSENSITIVE"; + } + + return "<unknown>"; +} + +GtkShadowType +meta_gtk_shadow_from_string (const char *str) +{ + if (strcmp ("none", str) == 0) + return GTK_SHADOW_NONE; + else if (strcmp ("in", str) == 0) + return GTK_SHADOW_IN; + else if (strcmp ("out", str) == 0) + return GTK_SHADOW_OUT; + else if (strcmp ("etched_in", str) == 0) + return GTK_SHADOW_ETCHED_IN; + else if (strcmp ("etched_out", str) == 0) + return GTK_SHADOW_ETCHED_OUT; + else + return -1; +} + +const char* +meta_gtk_shadow_to_string (GtkShadowType shadow) +{ + switch (shadow) + { + case GTK_SHADOW_NONE: + return "none"; + case GTK_SHADOW_IN: + return "in"; + case GTK_SHADOW_OUT: + return "out"; + case GTK_SHADOW_ETCHED_IN: + return "etched_in"; + case GTK_SHADOW_ETCHED_OUT: + return "etched_out"; + } + + return "<unknown>"; +} + +GtkArrowType +meta_gtk_arrow_from_string (const char *str) +{ + if (strcmp ("up", str) == 0) + return GTK_ARROW_UP; + else if (strcmp ("down", str) == 0) + return GTK_ARROW_DOWN; + else if (strcmp ("left", str) == 0) + return GTK_ARROW_LEFT; + else if (strcmp ("right", str) == 0) + return GTK_ARROW_RIGHT; + else + return -1; +} + +const char* +meta_gtk_arrow_to_string (GtkArrowType arrow) +{ + switch (arrow) + { + case GTK_ARROW_UP: + return "up"; + case GTK_ARROW_DOWN: + return "down"; + case GTK_ARROW_LEFT: + return "left"; + case GTK_ARROW_RIGHT: + return "right"; + } + + return "<unknown>"; +} #if 0 @@ -2817,15 +4798,15 @@ draw_bg_solid_composite (const MetaTextureSpec *bg, int height) { GdkColor bg_color; - + g_assert (bg->type == META_TEXTURE_SOLID); g_assert (fg->type != META_TEXTURE_COMPOSITE); g_assert (fg->type != META_TEXTURE_SHAPE_LIST); - + meta_color_spec_render (bg->data.solid.color_spec, widget, - &bg_color); - + &bg_color); + switch (fg->type) { case META_TEXTURE_SOLID: @@ -2853,13 +4834,13 @@ draw_bg_solid_composite (const MetaTextureSpec *bg, { GdkPixbuf *pixbuf; GdkPixbuf *composited; - + pixbuf = meta_texture_spec_render (fg, widget, mode, 255, width, height); if (pixbuf == NULL) return; - + composited = gdk_pixbuf_new (GDK_COLORSPACE_RGB, gdk_pixbuf_get_has_alpha (pixbuf), 8, gdk_pixbuf_get_width (pixbuf), @@ -2870,7 +4851,7 @@ draw_bg_solid_composite (const MetaTextureSpec *bg, g_object_unref (G_OBJECT (pixbuf)); return; } - + gdk_pixbuf_composite_color (pixbuf, composited, 0, 0, @@ -2890,17 +4871,17 @@ draw_bg_solid_composite (const MetaTextureSpec *bg, */ draw_color_rectangle (widget, drawable, &bg_color, clip, x, y, width, height); - + render_pixbuf_aligned (drawable, clip, composited, xalign, yalign, x, y, width, height); - + g_object_unref (G_OBJECT (pixbuf)); g_object_unref (G_OBJECT (composited)); } break; - case META_TEXTURE_BLANK: + case META_TEXTURE_BLANK: case META_TEXTURE_COMPOSITE: case META_TEXTURE_SHAPE_LIST: g_assert_not_reached (); @@ -2926,7 +4907,7 @@ draw_bg_gradient_composite (const MetaTextureSpec *bg, g_assert (bg->type == META_TEXTURE_GRADIENT); g_assert (fg->type != META_TEXTURE_COMPOSITE); g_assert (fg->type != META_TEXTURE_SHAPE_LIST); - + switch (fg->type) { case META_TEXTURE_SOLID: @@ -2937,7 +4918,7 @@ draw_bg_gradient_composite (const MetaTextureSpec *bg, GdkPixbuf *fg_pixbuf; GdkPixbuf *composited; int fg_width, fg_height; - + bg_pixbuf = meta_texture_spec_render (bg, widget, mode, 255, width, height); @@ -2949,14 +4930,14 @@ draw_bg_gradient_composite (const MetaTextureSpec *bg, if (fg_pixbuf == NULL) { - g_object_unref (G_OBJECT (bg_pixbuf)); + g_object_unref (G_OBJECT (bg_pixbuf)); return; } /* gradients always fill the entire target area */ g_assert (gdk_pixbuf_get_width (bg_pixbuf) == width); g_assert (gdk_pixbuf_get_height (bg_pixbuf) == height); - + composited = gdk_pixbuf_new (GDK_COLORSPACE_RGB, gdk_pixbuf_get_has_alpha (bg_pixbuf), 8, gdk_pixbuf_get_width (bg_pixbuf), @@ -2976,7 +4957,7 @@ draw_bg_gradient_composite (const MetaTextureSpec *bg, * offsets and try to composite only in the clip rectangle, * but I just don't care enough to figure it out. */ - + gdk_pixbuf_composite (fg_pixbuf, composited, x + (width - fg_width) * xalign, @@ -2987,9 +4968,9 @@ draw_bg_gradient_composite (const MetaTextureSpec *bg, 1.0, 1.0, /* scale */ GDK_INTERP_BILINEAR, 255 * alpha); - + render_pixbuf (drawable, clip, composited, x, y); - + g_object_unref (G_OBJECT (bg_pixbuf)); g_object_unref (G_OBJECT (fg_pixbuf)); g_object_unref (G_OBJECT (composited)); diff --git a/src/theme.h b/src/theme.h index 249cb558..ebb48a8b 100644 --- a/src/theme.h +++ b/src/theme.h @@ -36,18 +36,27 @@ typedef struct _MetaFrameLayout MetaFrameLayout; typedef struct _MetaFrameGeometry MetaFrameGeometry; typedef struct _MetaTheme MetaTheme; typedef struct _MetaPositionExprEnv MetaPositionExprEnv; +typedef struct _MetaDrawInfo MetaDrawInfo; + +#define META_THEME_ERROR (g_quark_from_static_string ("meta-theme-error")) typedef enum { - META_SCALE_NONE, - META_SCALE_VERTICALLY, - META_SCALE_HORIZONTALLY, - META_SCALE_BOTH -} MetaScaleMode; + META_THEME_ERROR_FRAME_GEOMETRY, + META_THEME_ERROR_BAD_CHARACTER, + META_THEME_ERROR_BAD_PARENS, + META_THEME_ERROR_UNKNOWN_VARIABLE, + META_THEME_ERROR_DIVIDE_BY_ZERO, + META_THEME_ERROR_MOD_ON_FLOAT, + META_THEME_ERROR_FAILED +} MetaThemeError; + /* Parameters used to calculate the geometry of the frame */ struct _MetaFrameLayout { + int refcount; + /* Size of left/right/bottom sides */ int left_width; int right_width; @@ -56,19 +65,12 @@ struct _MetaFrameLayout /* Border of blue title region */ GtkBorder title_border; - /* Border inside title region, around title */ - GtkBorder text_border; - - /* padding on either side of spacer */ - int spacer_padding; - - /* Size of spacer */ - int spacer_width; - int spacer_height; - + /* Extra height for inside of title region, above the font height */ + int title_vertical_pad; + /* indent of buttons from edges of frame */ - int right_inset; - int left_inset; + int right_titlebar_edge; + int left_titlebar_edge; /* Size of buttons */ int button_width; @@ -76,11 +78,6 @@ struct _MetaFrameLayout /* Space around buttons */ GtkBorder button_border; - - /* Space inside button which is clickable but doesn't draw the - * button icon - */ - GtkBorder inner_button_border; }; @@ -98,7 +95,6 @@ struct _MetaFrameGeometry GdkRectangle close_rect; GdkRectangle max_rect; GdkRectangle min_rect; - GdkRectangle spacer_rect; GdkRectangle menu_rect; GdkRectangle title_rect; @@ -125,7 +121,8 @@ typedef enum META_GTK_COLOR_MID, META_GTK_COLOR_TEXT, META_GTK_COLOR_BASE, - META_GTK_COLOR_TEXT_AA + META_GTK_COLOR_TEXT_AA, + META_GTK_COLOR_LAST } MetaGtkColorComponent; struct _MetaColorSpec @@ -154,6 +151,16 @@ struct _MetaGradientSpec GSList *color_specs; }; +struct _MetaDrawInfo +{ + GdkPixbuf *mini_icon; + GdkPixbuf *icon; + PangoLayout *title_layout; + int title_layout_width; + int title_layout_height; + const MetaFrameGeometry *fgeom; +}; + typedef enum { /* Basic drawing */ @@ -161,6 +168,9 @@ typedef enum META_DRAW_RECTANGLE, META_DRAW_ARC, + /* Clip to a rectangle */ + META_DRAW_CLIP, + /* Texture thingies */ META_DRAW_TINT, /* just a filled rectangle with alpha */ META_DRAW_GRADIENT, @@ -169,7 +179,14 @@ typedef enum /* GTK theme engine stuff */ META_DRAW_GTK_ARROW, META_DRAW_GTK_BOX, - META_DRAW_GTK_VLINE + META_DRAW_GTK_VLINE, + + /* App's window icon */ + META_DRAW_ICON, + /* App's window title */ + META_DRAW_TITLE, + /* a draw op list */ + META_DRAW_OP_LIST } MetaDrawType; struct _MetaDrawOp @@ -209,6 +226,13 @@ struct _MetaDrawOp double start_angle; double extent_angle; } arc; + + struct { + char *x; + char *y; + char *width; + char *height; + } clip; struct { MetaColorSpec *color_spec; @@ -231,7 +255,6 @@ struct _MetaDrawOp struct { GdkPixbuf *pixbuf; double alpha; - MetaScaleMode scale_mode; char *x; char *y; char *width; @@ -264,6 +287,28 @@ struct _MetaDrawOp char *y1; char *y2; } gtk_vline; + + struct { + double alpha; + char *x; + char *y; + char *width; + char *height; + } icon; + + struct { + MetaColorSpec *color_spec; + char *x; + char *y; + } title; + + struct { + MetaDrawOpList *op_list; + char *x; + char *y; + char *width; + char *height; + } op_list; } data; }; @@ -295,6 +340,15 @@ typedef enum typedef enum { + META_MENU_ICON_TYPE_CLOSE, + META_MENU_ICON_TYPE_MAXIMIZE, + META_MENU_ICON_TYPE_UNMAXIMIZE, + META_MENU_ICON_TYPE_MINIMIZE, + META_MENU_ICON_TYPE_LAST +} MetaMenuIconType; + +typedef enum +{ /* Listed in the order in which the textures are drawn. * (though this only matters for overlaps of course.) * Buttons are drawn after the frame textures. @@ -311,7 +365,7 @@ typedef enum /* entire frame */ META_FRAME_PIECE_ENTIRE_BACKGROUND, /* entire titlebar background */ - META_FRAME_PIECE_TITLEBAR_BACKGROUND, + META_FRAME_PIECE_TITLEBAR, /* portion of the titlebar background inside the titlebar * background edges */ @@ -325,7 +379,7 @@ typedef enum /* bottom edge of titlebar */ META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE, /* render over title background (text area) */ - META_FRAME_PIECE_TITLE_BACKGROUND, + META_FRAME_PIECE_TITLE, /* left edge of the frame */ META_FRAME_PIECE_LEFT_EDGE, /* right edge of the frame */ @@ -338,12 +392,12 @@ typedef enum META_FRAME_PIECE_LAST } MetaFramePiece; +#define N_GTK_STATES 5 struct _MetaFrameStyle { int refcount; MetaFrameStyle *parent; - MetaDrawOpList *button_icons[META_BUTTON_TYPE_LAST][META_BUTTON_STATE_LAST]; - MetaDrawOpList *button_backgrounds[META_BUTTON_TYPE_LAST][META_BUTTON_STATE_LAST]; + MetaDrawOpList *buttons[META_BUTTON_TYPE_LAST][META_BUTTON_STATE_LAST]; MetaDrawOpList *pieces[META_FRAME_PIECE_LAST]; MetaFrameLayout *layout; }; @@ -402,39 +456,54 @@ struct _MetaFrameStyleSet struct _MetaTheme { char *name; + char *dirname; char *filename; - + char *readable_name; + char *author; + char *copyright; + char *date; + char *description; + + GHashTable *integer_constants; + GHashTable *float_constants; + GHashTable *images_by_filename; + GHashTable *layouts_by_name; + GHashTable *draw_op_lists_by_name; GHashTable *styles_by_name; GHashTable *style_sets_by_name; MetaFrameStyleSet *style_sets_by_type[META_FRAME_TYPE_LAST]; + MetaDrawOpList *menu_icons[META_MENU_ICON_TYPE_LAST][N_GTK_STATES]; }; -#define META_POSITION_EXPR_ERROR (g_quark_from_static_string ("meta-position-expr-error")) -typedef enum -{ - META_POSITION_EXPR_ERROR_BAD_CHARACTER, - META_POSITION_EXPR_ERROR_BAD_PARENS, - META_POSITION_EXPR_ERROR_UNKNOWN_VARIABLE, - META_POSITION_EXPR_ERROR_DIVIDE_BY_ZERO, - META_POSITION_EXPR_ERROR_MOD_ON_FLOAT, - META_POSITION_EXPR_ERROR_FAILED -} MetaPositionExprError; - struct _MetaPositionExprEnv { int x; int y; int width; int height; - /* size of an image or whatever */ + /* size of an object being drawn, if it has a natural size */ int object_width; int object_height; + /* global object sizes, always available */ + int left_width; + int right_width; + int top_height; + int bottom_height; + int title_width; + int title_height; + int mini_icon_width; + int mini_icon_height; + int icon_width; + int icon_height; + /* Theme so we can look up constants */ + MetaTheme *theme; }; MetaFrameLayout* meta_frame_layout_new (void); -void meta_frame_layout_free (MetaFrameLayout *layout); +MetaFrameLayout* meta_frame_layout_copy (const MetaFrameLayout *src); +void meta_frame_layout_ref (MetaFrameLayout *layout); +void meta_frame_layout_unref (MetaFrameLayout *layout); void meta_frame_layout_get_borders (const MetaFrameLayout *layout, - GtkWidget *widget, int text_height, MetaFrameFlags flags, int *top_height, @@ -442,13 +511,15 @@ void meta_frame_layout_get_borders (const MetaFrameLayout *layout, int *left_width, int *right_width); void meta_frame_layout_calc_geometry (const MetaFrameLayout *layout, - GtkWidget *widget, int text_height, MetaFrameFlags flags, int client_width, int client_height, MetaFrameGeometry *fgeom); +gboolean meta_frame_layout_validate (const MetaFrameLayout *layout, + GError **error); + gboolean meta_parse_position_expression (const char *expr, const MetaPositionExprEnv *env, int *x_return, @@ -476,6 +547,7 @@ void meta_draw_op_draw (const MetaDrawOp *op, GtkWidget *widget, GdkDrawable *drawable, const GdkRectangle *clip, + const MetaDrawInfo *info, /* logical region being drawn */ int x, int y, @@ -490,12 +562,17 @@ void meta_draw_op_list_draw (const MetaDrawOpList *op_list, GtkWidget *widget, GdkDrawable *drawable, const GdkRectangle *clip, + const MetaDrawInfo *info, int x, int y, int width, int height); void meta_draw_op_list_append (MetaDrawOpList *op_list, MetaDrawOp *op); +gboolean meta_draw_op_list_validate (MetaDrawOpList *op_list, + GError **error); +gboolean meta_draw_op_list_contains (MetaDrawOpList *op_list, + MetaDrawOpList *child); MetaGradientSpec* meta_gradient_spec_new (MetaGradientType type); void meta_gradient_spec_free (MetaGradientSpec *desc); @@ -503,32 +580,164 @@ GdkPixbuf* meta_gradient_spec_render (const MetaGradientSpec *desc, GtkWidget *widget, int width, int height); +gboolean meta_gradient_spec_validate (MetaGradientSpec *spec, + GError **error); MetaFrameStyle* meta_frame_style_new (MetaFrameStyle *parent); void meta_frame_style_ref (MetaFrameStyle *style); void meta_frame_style_unref (MetaFrameStyle *style); -void meta_frame_style_draw (MetaFrameStyle *style, +void meta_frame_style_draw (MetaFrameStyle *style, + GtkWidget *widget, + GdkDrawable *drawable, + int x_offset, + int y_offset, + const GdkRectangle *clip, + const MetaFrameGeometry *fgeom, + int client_width, + int client_height, + PangoLayout *title_layout, + int text_height, + MetaButtonState button_states[META_BUTTON_TYPE_LAST], + GdkPixbuf *mini_icon, + GdkPixbuf *icon); + + +gboolean meta_frame_style_validate (MetaFrameStyle *style, + GError **error); + +MetaFrameStyleSet* meta_frame_style_set_new (MetaFrameStyleSet *parent); +void meta_frame_style_set_ref (MetaFrameStyleSet *style_set); +void meta_frame_style_set_unref (MetaFrameStyleSet *style_set); + +gboolean meta_frame_style_set_validate (MetaFrameStyleSet *style_set, + GError **error); + +MetaTheme* meta_theme_get_current (void); +void meta_theme_set_current (const char *name, + gboolean force_reload); + +MetaTheme* meta_theme_new (void); +void meta_theme_free (MetaTheme *theme); +gboolean meta_theme_validate (MetaTheme *theme, + GError **error); +GdkPixbuf* meta_theme_load_image (MetaTheme *theme, + const char *filename, + GError **error); + +void meta_theme_draw_frame (MetaTheme *theme, GtkWidget *widget, GdkDrawable *drawable, + const GdkRectangle *clip, int x_offset, int y_offset, - const GdkRectangle *clip, + MetaFrameType type, MetaFrameFlags flags, int client_width, int client_height, PangoLayout *title_layout, int text_height, - MetaButtonState button_states[META_BUTTON_TYPE_LAST]); - -MetaFrameStyleSet* meta_frame_style_set_new (MetaFrameStyleSet *parent); -void meta_frame_style_set_ref (MetaFrameStyleSet *style_set); -void meta_frame_style_set_unref (MetaFrameStyleSet *style_set); - -MetaTheme* meta_theme_new (void); -void meta_theme_free (MetaTheme *theme); - -MetaFrameStyle* meta_frame_style_get_test (void); + MetaButtonState button_states[META_BUTTON_TYPE_LAST], + GdkPixbuf *mini_icon, + GdkPixbuf *icon); + +void meta_theme_draw_menu_icon (MetaTheme *theme, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + int x_offset, + int y_offset, + int width, + int height, + MetaMenuIconType type); + +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); +void meta_theme_calc_geometry (MetaTheme *theme, + MetaFrameType type, + int text_height, + MetaFrameFlags flags, + int client_width, + int client_height, + MetaFrameGeometry *fgeom); + + +MetaFrameLayout* meta_theme_lookup_layout (MetaTheme *theme, + const char *name); +void meta_theme_insert_layout (MetaTheme *theme, + const char *name, + MetaFrameLayout *layout); +MetaDrawOpList* meta_theme_lookup_draw_op_list (MetaTheme *theme, + const char *name); +void meta_theme_insert_draw_op_list (MetaTheme *theme, + const char *name, + MetaDrawOpList *op_list); +MetaFrameStyle* meta_theme_lookup_style (MetaTheme *theme, + const char *name); +void meta_theme_insert_style (MetaTheme *theme, + const char *name, + MetaFrameStyle *style); +MetaFrameStyleSet* meta_theme_lookup_style_set (MetaTheme *theme, + const char *name); +void meta_theme_insert_style_set (MetaTheme *theme, + const char *name, + MetaFrameStyleSet *style_set); +gboolean meta_theme_define_int_constant (MetaTheme *theme, + const char *name, + int value, + GError **error); +gboolean meta_theme_lookup_int_constant (MetaTheme *theme, + const char *name, + int *value); +gboolean meta_theme_define_float_constant (MetaTheme *theme, + const char *name, + double value, + GError **error); +gboolean meta_theme_lookup_float_constant (MetaTheme *theme, + const char *name, + double *value); + +char* meta_theme_replace_constants (MetaTheme *theme, + const char *expr, + GError **err); + +/* random stuff */ + +int meta_gtk_widget_get_text_height (GtkWidget *widget); + +/* Enum converters */ +MetaGtkColorComponent meta_color_component_from_string (const char *str); +const char* meta_color_component_to_string (MetaGtkColorComponent component); +MetaButtonState meta_button_state_from_string (const char *str); +const char* meta_button_state_to_string (MetaButtonState state); +MetaButtonType meta_button_type_from_string (const char *str); +const char* meta_button_type_to_string (MetaButtonType type); +MetaMenuIconType meta_menu_icon_type_from_string (const char *str); +const char* meta_menu_icon_type_to_string (MetaMenuIconType type); +MetaFramePiece meta_frame_piece_from_string (const char *str); +const char* meta_frame_piece_to_string (MetaFramePiece piece); +MetaFrameState meta_frame_state_from_string (const char *str); +const char* meta_frame_state_to_string (MetaFrameState state); +MetaFrameResize meta_frame_resize_from_string (const char *str); +const char* meta_frame_resize_to_string (MetaFrameResize resize); +MetaFrameFocus meta_frame_focus_from_string (const char *str); +const char* meta_frame_focus_to_string (MetaFrameFocus focus); +MetaFrameType meta_frame_type_from_string (const char *str); +const char* meta_frame_type_to_string (MetaFrameType type); +MetaGradientType meta_gradient_type_from_string (const char *str); +const char* meta_gradient_type_to_string (MetaGradientType type); +GtkStateType meta_gtk_state_from_string (const char *str); +const char* meta_gtk_state_to_string (GtkStateType state); +GtkShadowType meta_gtk_shadow_from_string (const char *str); +const char* meta_gtk_shadow_to_string (GtkShadowType shadow); +GtkArrowType meta_gtk_arrow_from_string (const char *str); +const char* meta_gtk_arrow_to_string (GtkArrowType arrow); #endif diff --git a/src/themes/Atlanta/metacity-theme-1.xml b/src/themes/Atlanta/metacity-theme-1.xml new file mode 100644 index 00000000..dd767325 --- /dev/null +++ b/src/themes/Atlanta/metacity-theme-1.xml @@ -0,0 +1,239 @@ +<?xml version="1.0"?> +<metacity_theme> +<info> + <name>Default</name> + <author>Havoc Pennington <hp@redhat.com></author> + <copyright>Â Havoc Pennington, 2002</copyright> + <date>February 3, 2002</date> + <description>Simple low-overhead default theme that comes with Metacity.</description> +</info> + +<frame_geometry name="normal"> + <distance name="left_width" value="6"/> + <distance name="right_width" value="6"/> + <distance name="bottom_height" value="7"/> + <distance name="left_titlebar_edge" value="6"/> + <distance name="right_titlebar_edge" value="6"/> + <distance name="button_width" value="17"/> + <distance name="button_height" value="17"/> + <distance name="title_vertical_pad" value="3"/> + <border name="title_border" left="3" right="4" top="4" bottom="3"/> + <border name="button_border" left="0" right="0" top="1" bottom="1"/> +</frame_geometry> + +<!-- strip borders off the normal geometry --> +<frame_geometry name="normal_borderless" parent="normal"> + <distance name="left_width" value="0"/> + <distance name="right_width" value="0"/> + <distance name="bottom_height" value="0"/> + <distance name="left_titlebar_edge" value="0"/> + <distance name="right_titlebar_edge" value="0"/> +</frame_geometry> + +<!-- define constants --> +<constant name="ArrowWidth" value="7"/> +<constant name="ArrowHeight" value="5"/> +<constant name="ButtonIPad" value="3"/> +<constant name="ThickLineWidth" value="3"/> +<constant name="IconTitleSpacing" value="2"/> +<constant name="SpacerWidth" value="8"/> +<constant name="SpacerHeight" value="11"/> + +<!-- Buttons --> + +<draw_ops name="button_pressed_bg"> + <gtk_box state="active" shadow="in" x="0" y="0" width="width" height="height"/> +</draw_ops> + +<draw_ops name="menu_button"> + <gtk_arrow state="normal" shadow="out" arrow="down" + x="(width - ArrowWidth) / 2" + y="(height - ArrowHeight) / 2" + width="ArrowWidth" + height="ArrowHeight"/> +</draw_ops> + +<draw_ops name="menu_button_pressed"> + <include name="button_pressed_bg"/> + <include name="menu_button"/> +</draw_ops> + +<draw_ops name="minimize_button"> + <line color="gtk:fg[NORMAL]" + x1="ButtonIPad" + y1="height - ButtonIPad - ThickLineWidth + 1" + x2="width - ButtonIPad" + y2="height - ButtonIPad - ThickLineWidth + 1" + width="3"/> <!-- FIXME allow a constant here --> +</draw_ops> + +<draw_ops name="minimize_button_pressed"> + <include name="button_pressed_bg"/> + <include name="minimize_button"/> +</draw_ops> + +<draw_ops name="maximize_button"> + <rectangle color="gtk:fg[NORMAL]" filled="false" + x="ButtonIPad" y="ButtonIPad" width="width-ButtonIPad*2-1" height="height-ButtonIPad*2-1"/> + <line color="gtk:fg[NORMAL]" width="3" + x1="ButtonIPad" y1="ButtonIPad+1" x2="width-ButtonIPad" y2="ButtonIPad+1"/> +</draw_ops> + +<draw_ops name="maximize_button_pressed"> + <include name="button_pressed_bg"/> + <include name="maximize_button"/> +</draw_ops> + +<draw_ops name="mini_window_icon"> + <rectangle color="gtk:bg[NORMAL]" filled="true" + x="0" y="0" width="width-1" height="height-1"/> + <rectangle color="gtk:fg[NORMAL]" filled="false" + x="0" y="0" width="width-1" height="height-1"/> + <line color="gtk:fg[NORMAL]" width="2" + x1="0" y1="1" x2="width" y2="1"/> +</draw_ops> + +<draw_ops name="restore_button"> + <include name="mini_window_icon" + x="ButtonIPad" y="ButtonIPad" + width="width - 5 - ButtonIPad" + height="height - 5 - ButtonIPad"/> + <include name="mini_window_icon" + x="3 + ButtonIPad" y="3 + ButtonIPad" + width="width - 5 - ButtonIPad" + height="height - 5 - ButtonIPad"/> +</draw_ops> + +<draw_ops name="restore_button_pressed"> + <include name="button_pressed_bg"/> + <include name="restore_button"/> +</draw_ops> + +<draw_ops name="close_button"> + <line color="gtk:fg[NORMAL]" + x1="ButtonIPad" y1="ButtonIPad" + x2="width - ButtonIPad - 1" y2="height - ButtonIPad - 1"/> + <line color="gtk:fg[NORMAL]" + x1="ButtonIPad" y1="height - ButtonIPad - 1" + x2="width - ButtonIPad - 1" y2="ButtonIPad"/> +</draw_ops> + +<draw_ops name="close_button_pressed"> + <include name="button_pressed_bg"/> + <include name="close_button"/> +</draw_ops> + +<draw_ops name="outer_bevel"> + <rectangle color="#000000" + x="0" y="0" width="width-1" height="height-1"/> + <line color="gtk:light[NORMAL]" + x1="1" y1="1" x2="1" y2="height-2"/> + <line color="gtk:light[NORMAL]" + x1="1" y1="1" x2="width-2" y2="1"/> + <line color="gtk:dark[NORMAL]" + x1="width-2" y1="1" x2="width-2" y2="height-2"/> + <line color="gtk:dark[NORMAL]" + x1="1" y1="height-2" x2="width-2" y2="height-2"/> +</draw_ops> + +<draw_ops name="focus_background"> + <include name="outer_bevel"/> + <rectangle color="#000000" + x="left_width-1" y="top_height-1" + width="width-left_width-right_width+1" + height="height-top_height-bottom_height+1"/> +</draw_ops> + +<draw_ops name="title_gradient"> + <gradient type="diagonal" x="0" y="0" width="width-SpacerWidth" height="height"> + <color value="blend/gtk:bg[NORMAL]/gtk:bg[SELECTED]/0.6"/> + <color value="gtk:bg[SELECTED]"/> + </gradient> + <gtk_vline state="normal" x="width+1-SpacerWidth/2" + y1="(height-SpacerHeight)/2" + y2="height - (height-SpacerHeight)/2"/> +</draw_ops> + +<draw_ops name="title_text_focused"> + <clip x="0" y="0" width="width-SpacerWidth" height="height"/> + <title color="gtk:fg[SELECTED]" + x="(0 `max` (width-title_width-mini_icon_width-IconTitleSpacing)) / 2 + mini_icon_width + IconTitleSpacing" + y="((height - title_height) / 2) `max` 0"/> + <icon x="(0 `max` (width-title_width-mini_icon_width-IconTitleSpacing)) / 2" + y="(height-mini_icon_height) / 2" + width="mini_icon_width" height="mini_icon_height"/> +</draw_ops> + +<draw_ops name="title_text"> + <clip x="0" y="0" width="width-SpacerWidth" height="height"/> + <title color="gtk:fg[NORMAL]" + x="(0 `max` (width-title_width-mini_icon_width-IconTitleSpacing)) / 2 + mini_icon_width + IconTitleSpacing" + y="((height - title_height) / 2) `max` 0"/> + <icon x="(0 `max` (width-title_width-mini_icon_width-IconTitleSpacing)) / 2" + y="(height-mini_icon_height) / 2" + width="mini_icon_width" height="mini_icon_height"/> +</draw_ops> + +<draw_ops name="title_normal"> + <include name="title_text"/> +</draw_ops> + +<draw_ops name="title_focused"> + <include name="title_gradient"/> + <include name="title_text_focused"/> +</draw_ops> + +<frame_style name="normal_unfocused" geometry="normal"> + <piece position="entire_background" draw_ops="outer_bevel"/> + <piece position="title" draw_ops="title_normal"/> + + <!-- we don't specify for prelight, so normal is used --> + <button function="close" state="normal" draw_ops="close_button"/> + <button function="close" state="pressed" draw_ops="close_button_pressed"/> + <button function="minimize" state="normal" draw_ops="minimize_button"/> + <button function="minimize" state="pressed" draw_ops="minimize_button_pressed"/> + <button function="maximize" state="normal" draw_ops="maximize_button"/> + <button function="maximize" state="pressed" draw_ops="maximize_button_pressed"/> + <button function="menu" state="normal" draw_ops="menu_button"/> + <button function="menu" state="pressed" draw_ops="menu_button_pressed"/> +</frame_style> + +<frame_style name="normal_focused" geometry="normal" parent="normal_unfocused"> + <piece position="entire_background" draw_ops="focus_background"/> + <piece position="title" draw_ops="title_focused"/> +</frame_style> + +<frame_style name="maximized_unfocused" parent="normal_unfocused"> + <button function="maximize" state="normal" draw_ops="restore_button"/> + <button function="maximize" state="pressed" draw_ops="restore_button_pressed"/> +</frame_style> + +<frame_style name="maximized_focused" parent="normal_focused"> + <button function="maximize" state="normal" draw_ops="restore_button"/> + <button function="maximize" state="pressed" draw_ops="restore_button_pressed"/> +</frame_style> + + +<frame_style_set name="normal"> +<frame focus="yes" state="normal" resize="both" style="normal_focused"/> +<frame focus="no" state="normal" resize="both" style="normal_unfocused"/> +<frame focus="yes" state="maximized" style="maximized_focused"/> +<frame focus="no" state="maximized" style="maximized_unfocused"/> +<frame focus="yes" state="shaded" style="normal_focused"/> +<frame focus="no" state="shaded" style="normal_unfocused"/> +<frame focus="yes" state="maximized_and_shaded" style="maximized_focused"/> +<frame focus="no" state="maximized_and_shaded" style="maximized_unfocused"/> +</frame_style_set> + +<window type="normal" style_set="normal"/> +<window type="dialog" style_set="normal"/> +<window type="modal_dialog" style_set="normal"/> +<window type="menu" style_set="normal"/> +<window type="utility" style_set="normal"/> + +<menu_icon function="close" state="normal" draw_ops="close_button"/> +<menu_icon function="maximize" state="normal" draw_ops="maximize_button"/> +<menu_icon function="unmaximize" state="normal" draw_ops="restore_button"/> +<menu_icon function="minimize" state="normal" draw_ops="minimize_button"/> + +</metacity_theme> diff --git a/src/themes/Makefile.am b/src/themes/Makefile.am new file mode 100644 index 00000000..9edfd4b5 --- /dev/null +++ b/src/themes/Makefile.am @@ -0,0 +1,29 @@ +THEMES= \ + Atlanta \ + Crux + +THEME_DIR=$(pkgdatadir)/themes + +install-data-local: + $(mkinstalldirs) $(DESTDIR)$(THEME_DIR); \ + for THEME in $(THEMES); do \ + echo '-- Installing theme '$$THEME; \ + $(mkinstalldirs) $(DESTDIR)$(THEME_DIR)/$$THEME; \ + (installfiles=`find $(srcdir)/$$THEME -name "*.png" -o -name "*.xml"`; \ + for i in $$installfiles; do \ + echo '-- Installing '$$i ; \ + $(INSTALL_DATA) $$i $(DESTDIR)$(THEME_DIR)/$$THEME; \ + done) \ + done + +dist-hook: + mkdir $(distdir)/themes; \ + for THEME in $(THEMES); do \ + echo '-- Disting theme '$$THEME; \ + mkdir $(distdir)/$$THEME; \ + (installfiles=`find $(srcdir)/$$THEME -name "*.png" -o -name "*.xml"`; \ + for i in $$installfiles; do \ + echo '-- Disting '$$i ; \ + cp $$i $(distdir)/$$THEME; \ + done) \ + done diff --git a/src/themewidget.c b/src/themewidget.c new file mode 100644 index 00000000..b3092a56 --- /dev/null +++ b/src/themewidget.c @@ -0,0 +1,181 @@ +/* Metacity theme widget (displays themed draw operations) */ + +/* + * Copyright (C) 2002 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. + */ + +#include "themewidget.h" +#include <math.h> + +static void meta_area_class_init (MetaAreaClass *klass); +static void meta_area_init (MetaArea *area); +static void meta_area_size_request (GtkWidget *widget, + GtkRequisition *req); +static gint meta_area_expose (GtkWidget *widget, + GdkEventExpose *event); +static void meta_area_finalize (GObject *object); + + +static GtkMiscClass *parent_class; + +GtkType +meta_area_get_type (void) +{ + static GtkType area_type = 0; + + if (!area_type) + { + static const GtkTypeInfo area_info = + { + "MetaArea", + sizeof (MetaArea), + sizeof (MetaAreaClass), + (GtkClassInitFunc) meta_area_class_init, + (GtkObjectInitFunc) meta_area_init, + /* reserved_1 */ NULL, + /* reserved_2 */ NULL, + (GtkClassInitFunc) NULL, + }; + + area_type = gtk_type_unique (GTK_TYPE_MISC, &area_info); + } + + return area_type; +} + +static void +meta_area_class_init (MetaAreaClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + + object_class = (GtkObjectClass*) class; + widget_class = (GtkWidgetClass*) class; + parent_class = gtk_type_class (gtk_misc_get_type ()); + + gobject_class->finalize = meta_area_finalize; + + widget_class->expose_event = meta_area_expose; + widget_class->size_request = meta_area_size_request; +} + +static void +meta_area_init (MetaArea *area) +{ + GTK_WIDGET_SET_FLAGS (area, GTK_NO_WINDOW); +} + +GtkWidget* +meta_area_new (void) +{ + MetaArea *area; + + area = gtk_type_new (META_TYPE_AREA); + + return GTK_WIDGET (area); +} + +static void +meta_area_finalize (GObject *object) +{ + MetaArea *area; + + area = META_AREA (object); + + if (area->dnotify) + (* area->dnotify) (area->user_data); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gint +meta_area_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + MetaArea *area; + GtkMisc *misc; + gint x, y; + gfloat xalign; + + g_return_val_if_fail (META_IS_AREA (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + if (GTK_WIDGET_DRAWABLE (widget)) + { + area = META_AREA (widget); + misc = GTK_MISC (widget); + + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) + xalign = misc->xalign; + else + xalign = 1.0 - misc->xalign; + + x = floor (widget->allocation.x + misc->xpad + + ((widget->allocation.width - widget->requisition.width) * xalign) + + 0.5); + y = floor (widget->allocation.y + misc->ypad + + ((widget->allocation.height - widget->requisition.height) * misc->yalign) + + 0.5); + + if (area->expose_func) + { + (* area->expose_func) (area, event, x, y, + area->user_data); + } + } + + return FALSE; +} + +static void +meta_area_size_request (GtkWidget *widget, + GtkRequisition *req) +{ + MetaArea *area; + + area = META_AREA (widget); + + req->width = 0; + req->height = 0; + + if (area->size_func) + { + (* area->size_func) (area, &req->width, &req->height, + area->user_data); + } +} + +void +meta_area_setup (MetaArea *area, + MetaAreaSizeFunc size_func, + MetaAreaExposeFunc expose_func, + void *user_data, + GDestroyNotify dnotify) +{ + if (area->dnotify) + (* area->dnotify) (area->user_data); + + area->size_func = size_func; + area->expose_func = expose_func; + area->user_data = user_data; + area->dnotify = dnotify; + + gtk_widget_queue_resize (GTK_WIDGET (area)); +} + diff --git a/src/themewidget.h b/src/themewidget.h new file mode 100644 index 00000000..55161fbc --- /dev/null +++ b/src/themewidget.h @@ -0,0 +1,76 @@ +/* Metacity theme widget (displays themed draw operations) */ + +/* + * Copyright (C) 2002 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. + */ + +#include "theme.h" +#include <gtk/gtkmisc.h> + +#ifndef META_THEME_WIDGET_H +#define META_THEME_WIDGET_H + +#define META_TYPE_AREA (meta_area_get_type ()) +#define META_AREA(obj) (GTK_CHECK_CAST ((obj), META_TYPE_AREA, MetaArea)) +#define META_AREA_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), META_TYPE_AREA, MetaAreaClass)) +#define META_IS_AREA(obj) (GTK_CHECK_TYPE ((obj), META_TYPE_AREA)) +#define META_IS_AREA_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), META_TYPE_AREA)) +#define META_AREA_GET_CLASS(obj) (GTK_CHECK_GET_CLASS ((obj), META_TYPE_AREA, MetaAreaClass)) + +typedef struct _MetaArea MetaArea; +typedef struct _MetaAreaClass MetaAreaClass; + + +typedef void (* MetaAreaSizeFunc) (MetaArea *area, + int *width, + int *height, + void *user_data); + +typedef void (* MetaAreaExposeFunc) (MetaArea *area, + GdkEventExpose *event, + int x_offset, + int y_offset, + void *user_data); + +struct _MetaArea +{ + GtkMisc misc; + + MetaAreaSizeFunc size_func; + MetaAreaExposeFunc expose_func; + void *user_data; + GDestroyNotify dnotify; +}; + +struct _MetaAreaClass +{ + GtkMiscClass parent_class; +}; + + +GtkType meta_area_get_type (void) G_GNUC_CONST; +GtkWidget* meta_area_new (void); + +void meta_area_setup (MetaArea *area, + MetaAreaSizeFunc size_func, + MetaAreaExposeFunc expose_func, + void *user_data, + GDestroyNotify dnotify); + + +#endif diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am index 0db4a691..4b971512 100644 --- a/src/tools/Makefile.am +++ b/src/tools/Makefile.am @@ -1,9 +1,13 @@ -INCLUDES=@METACITY_RESTART_CFLAGS@ +INCLUDES=@METACITY_RESTART_CFLAGS@ @METACITY_RELOAD_THEME_CFLAGS@ metacity_restart_SOURCES= \ metacity-restart.c -bin_PROGRAMS=metacity-restart +metacity_reload_theme_SOURCES= \ + metacity-reload-theme.c + +bin_PROGRAMS=metacity-restart metacity-reload-theme metacity_restart_LDADD= @METACITY_RESTART_LIBS@ +metacity_reload_theme_LDADD= @METACITY_RELOAD_THEME_LIBS@ diff --git a/src/tools/metacity-reload-theme.c b/src/tools/metacity-reload-theme.c new file mode 100644 index 00000000..0ef19d55 --- /dev/null +++ b/src/tools/metacity-reload-theme.c @@ -0,0 +1,56 @@ +/* Metacity theme-reloader app */ + +/* + * Copyright (C) 2002 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. + */ + +#include <gtk/gtk.h> +#include <gdk/gdkx.h> + +int +main (int argc, char **argv) +{ + XEvent xev; + + gtk_init (&argc, &argv); + + xev.xclient.type = ClientMessage; + xev.xclient.serial = 0; + xev.xclient.send_event = True; + xev.xclient.display = gdk_display; + xev.xclient.window = gdk_x11_get_default_root_xwindow (); + xev.xclient.message_type = XInternAtom (gdk_display, + "_METACITY_RELOAD_THEME_MESSAGE", + False); + xev.xclient.format = 32; + xev.xclient.data.l[0] = 0; + xev.xclient.data.l[1] = 0; + xev.xclient.data.l[2] = 0; + + XSendEvent (gdk_display, + gdk_x11_get_default_root_xwindow (), + False, + SubstructureRedirectMask | SubstructureNotifyMask, + &xev); + + XFlush (gdk_display); + XSync (gdk_display, False); + + return 0; +} + @@ -25,6 +25,7 @@ #include "util.h" #include "menu.h" #include "core.h" +#include "theme.h" #include "inlinepixbufs.h" @@ -544,3 +545,16 @@ meta_text_property_to_utf8 (Display *xdisplay, return retval; } + +void +meta_ui_set_current_theme (const char *name, + gboolean force_reload) +{ + meta_theme_set_current (name, force_reload); +} + +gboolean +meta_ui_have_a_theme (void) +{ + return meta_theme_get_current () != NULL; +} @@ -141,6 +141,11 @@ gboolean meta_ui_window_should_not_cause_focus (Display *xdisplay, char* meta_text_property_to_utf8 (Display *xdisplay, const XTextProperty *prop); +void meta_ui_set_current_theme (const char *name, + gboolean force_reload); +gboolean meta_ui_have_a_theme (void); + + #include "tabpopup.h" #endif diff --git a/src/window.c b/src/window.c index 4d7699dc..3e663f84 100644 --- a/src/window.c +++ b/src/window.c @@ -459,7 +459,7 @@ meta_window_new (MetaDisplay *display, Window xwindow, if (window->initial_workspace_set) { - if (window->initial_workspace == 0xFFFFFFFF) + if (window->initial_workspace == (int) 0xFFFFFFFF) { meta_workspace_add_window (window->screen->active_workspace, window); window->on_all_workspaces = TRUE; @@ -487,17 +487,17 @@ meta_window_new (MetaDisplay *display, Window xwindow, if (parent) { - GList *tmp; + GList *tmp_list; if (parent->on_all_workspaces) window->on_all_workspaces = TRUE; - tmp = parent->workspaces; - while (tmp != NULL) + tmp_list = parent->workspaces; + while (tmp_list != NULL) { - meta_workspace_add_window (tmp->data, window); + meta_workspace_add_window (tmp_list->data, window); - tmp = tmp->next; + tmp_list = tmp_list->next; } } } @@ -2716,7 +2716,7 @@ meta_window_client_message (MetaWindow *window, meta_window_unstick (window); meta_window_change_workspace (window, workspace); } - else if (space == 0xFFFFFFFF) + else if (space == (int) 0xFFFFFFFF) { meta_window_stick (window); } @@ -4893,7 +4893,7 @@ recalc_do_not_cover_struts (MetaWindow *window) static void recalc_window_type (MetaWindow *window) { - int old_type; + MetaWindowType old_type; old_type = window->type; diff --git a/theme-format.txt b/theme-format.txt new file mode 100644 index 00000000..744f74a9 --- /dev/null +++ b/theme-format.txt @@ -0,0 +1,218 @@ +Docs on the theme format + +Themes are in a simple XML-subset format. + +<?xml version="1.0"?> +<metacity_theme> +<!-- Only one info section is allowed --> +<info> + <name>Foo</name> + <author>Foo P. Bar</author> + <copyright>whoever, 2002</copyright> + <date>Jan 31 2005</date> + <description>A sentence about the theme.</description> +</info> + +<!-- define a frame geometry to be referenced later --> +<frame_geometry name="normal"> + <distance name="left_width" value="6"/> + <distance name="right_width" value="6"/> + <distance name="bottom_height" value="7"/> + <distance name="left_titlebar_edge" value="6"/> + <distance name="right_titlebar_edge" value="6"/> + <distance name="button_width" value="17"/> + <distance name="button_height" value="17"/> + <distance name="title_vertical_pad" value="4"/> + <border name="title_border" left="3" right="12" top="4" bottom="3"/> + <border name="button_border" left="0" right="0" top="1" bottom="1"/> +</frame_geometry> + +<!-- inheritance is allowed; simply overwrites values from parent --> +<frame_geometry name="borderless" parent="normal"> + <distance name="left_width" value="0"/> + <distance name="right_width" value="0"/> + <distance name="bottom_height" value="0"/> + <distance name="left_titlebar_edge" value="0"/> + <distance name="right_titlebar_edge" value="0"/> +</frame_geometry> + +<!-- define a constant to use in positions/sizes of draw operations; + constant names must start with a capital letter. + --> +<constant name="LineOffset" value="3"/> + +<!-- define drawing operations to be referenced later; + these draw-op lists can also be placed inline. + + Positions/lengths are given as expressions. + Operators are: +,-,*,/,%,`max`,`min` + All operators are infix including `max` and `min`, + i.e. "2 `max` 5" + + Some variables are predefined, and constants can also + be used. Variables are: + + width - width of target area + height - height of target area + object_width - natural width of object being drawn + object_height - natural height of object being drawn + left_width - distance from left of frame to client window + right_width - distance from right of frame to client window + top_height - distance from top of frame to client window + bottom_height - distance from bottom of frame to client window + mini_icon_width - width of mini icon for window + mini_icon_height - height of mini icon + icon_width - width of large icon + icon_height - height of large icon + title_width - width of title text + title_height - height of title text + + All these are always defined, except object_width/object_height + which only exists for <image> right now. + + --> + +<draw_ops name="demo_all_ops"> + <line color="#00FF00" x1="LineOffset" y1="0" x2="0" y2="height"/> + <line color="gtk:fg[NORMAL]" + x1="width - 1" y1="0" x2="width - 1" y2="height" + width="3" dash_on_length="2" dash_off_length="3"/> + <rectangle color="blend/gtk:fg[NORMAL]/gtk:bg[NORMAL]/0.7" + x="0" y="0" width="width - 1" height="height - 1" filled="true"/> + <arc x="0" y="0" width="width - 1" height="height - 1" + filled="false" start_angle="30" extent_angle="180"/> + <tint color="orange" alpha="0.5" x1="0" y1="0" x2="0" y2="height"/> + <!-- may be vertical, horizontal, diagonal --> + <gradient type="diagonal" + x="10" y="30" width="width / 3" height="height / 4"> + <!-- any number of colors allowed here. A color can be + a color name like "blue" (look at gcolorsel), a hex color + as in HTML (#FFBB99), or a color from the gtk theme + given as "gtk:base[NORMAL]", "gtk:fg[ACTIVE]", etc. + --> + <color value="gtk:fg[SELECTED]"/> + <!-- color obtained by a 0.5 alpha composite of the second color onto the first --> + <color value="blend/gtk:bg[SELECTED]/gtk:fg[SELECTED]/0.5"/> + </gradient> + <image filename="foo.png" alpha="0.7" + x="10" y="30" width="width / 3" height="height / 4"/> + <gtk_arrow state="normal" shadow="in" arrow="up" + filled="true" + x="2" y="2" width="width - 4" height="height - 4"/> + <gtk_box state="normal" shadow="out" + x="2" y="2" width="width - 4" height="height - 4"/> + <gtk_vline state="normal" x="2" y1="0" y2="height"/> + <!-- window's icon --> + <icon alpha="0.7" + x="10" y="30" width="width / 3" height="height / 4"/> + <!-- window's title --> + <title color="gtk:text[NORMAL]" x="20" y="30"/> + <!-- include another draw ops list --> + <include name="some_other_draw_ops"/> +</draw_ops> + +<frame_style name="normal" geometry="normal"> + <!-- How to draw each piece of the frame. + For each piece, a draw_ops can be given inline or referenced + by name. If a piece is omitted, then nothing will be drawn + for that piece. + + For each piece, the "width" and "height" variables in + coordinate expressions refers to the dimensions of the piece, + the origin is at the top left of the piece. + + So <rectangle x="0" y="0" width="width-1" height="height-1"/> + will outline a piece. + --> + + <piece position="entire_background" draw_ops="demo_all_ops"/> + <piece position="left_titlebar_edge"> + <draw_ops> + <line color="#00FF00" x1="0" y1="0" x2="0" y2="height"/> + </draw_ops> + </piece> + + <!-- The complete list of frame pieces: + + entire_background: whole frame + titlebar: entire area above the app's window + titlebar_middle: area of titlebar_background not considered + part of an edge + left_titlebar_edge: left side of titlebar background + right_titlebar_edge: right side of titlebar background + top_titlebar_edge: top side of titlebar background + bottom_titlebar_edge: bottom side of titlebar background + title: the title area (doesn't include buttons) + left_edge: left edge of the frame + right_edge: right edge of the frame + bottom_edge: bottom edge of the frame + overlay: same area as entire_background, but drawn after + drawing all sub-pieces instead of before + + --> + + <!-- For buttons, drawing methods have to be provided for + each of three states: + normal, pressed, prelight + and the button name must be provided: + close, maximize, minimize, menu + So a working theme needs 3*4 = 12 button declarations + --> + + <button function="close" state="normal" draw_ops="previously_named"/> + <button function="menu" state="normal"> + <draw_ops> + <icon alpha="0.7" + x="0" y="0" width="object_width" height="object_height"/> + </draw_ops> + </button> + +</frame_style> + +<!-- styles can inherit from each other with the parent="" attribute. + In a subclass anything can be re-specified to override + the parent style. --> +<frame_style name="focused" parent="normal"> + <piece position="title"> + <draw_ops> + <rectangle color="gtk:bg[SELECTED]" + x="0" y="0" width="width-1" height="height-1"/> + <title color="gtk:fg[SELECTED]" x="(width - title_width) / 2" + y="(height - title_height) / 2"/> + </draw_ops> + </piece> +</frame_style> + +<!-- Maps styles to states of frame. + + Focus: yes (focused), no (not focused) + Window states: normal, maximized, shaded, maximized_and_shaded + Window resizability: none, vertical, horizontal, both + + Everything unspecified just does the same as + unfocused/normal/both. + + only state="normal" needs a resize="" attribute. + --> +<frame_style_set name="normal"> +<frame focus="yes" state="normal" resize="both" style="focused"/> +<frame focus="no" state="normal" resize="both" style="normal"/> +</frame_style_set> + +<!-- Each window type needs a style set + Types: normal, dialog, modal_dialog, toolbar, menu, utility + --> +<window type="normal" style_set="normal"/> + + +<!-- For menu icons, drawing methods are needed for the same + four types as the buttons, and GTK states + (insensitive,prelight,normal,etc.) + --> + +<menu_icon function="close" state="normal" draw_ops="previously_named"/> + + +</metacity_theme> + + |