diff options
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/draw-workspace.c | 229 | ||||
-rw-r--r-- | src/ui/draw-workspace.h | 61 | ||||
-rw-r--r-- | src/ui/fixedtip.c | 133 | ||||
-rw-r--r-- | src/ui/fixedtip.h | 69 | ||||
-rw-r--r-- | src/ui/frames.c | 2940 | ||||
-rw-r--r-- | src/ui/frames.h | 163 | ||||
-rw-r--r-- | src/ui/gradient.c | 842 | ||||
-rw-r--r-- | src/ui/gradient.h | 65 | ||||
-rw-r--r-- | src/ui/menu.c | 509 | ||||
-rw-r--r-- | src/ui/menu.h | 51 | ||||
-rw-r--r-- | src/ui/metaaccellabel.c | 456 | ||||
-rw-r--r-- | src/ui/metaaccellabel.h | 106 | ||||
-rw-r--r-- | src/ui/preview-widget.c | 601 | ||||
-rw-r--r-- | src/ui/preview-widget.h | 87 | ||||
-rw-r--r-- | src/ui/resizepopup.c | 217 | ||||
-rw-r--r-- | src/ui/tabpopup.c | 967 | ||||
-rw-r--r-- | src/ui/testgradient.c | 336 | ||||
-rw-r--r-- | src/ui/theme-parser.c | 4212 | ||||
-rw-r--r-- | src/ui/theme-parser.h | 32 | ||||
-rw-r--r-- | src/ui/theme-viewer.c | 1338 | ||||
-rw-r--r-- | src/ui/theme.c | 6652 | ||||
-rw-r--r-- | src/ui/theme.h | 1190 | ||||
-rw-r--r-- | src/ui/themewidget.c | 183 | ||||
-rw-r--r-- | src/ui/themewidget.h | 78 | ||||
-rw-r--r-- | src/ui/ui.c | 1117 |
25 files changed, 22634 insertions, 0 deletions
diff --git a/src/ui/draw-workspace.c b/src/ui/draw-workspace.c new file mode 100644 index 00000000..b1a7c733 --- /dev/null +++ b/src/ui/draw-workspace.c @@ -0,0 +1,229 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Draw a workspace */ + +/* This file should not be modified to depend on other files in + * libwnck or marco, since it's used in both of them + */ + +/* + * Copyright (C) 2002 Red Hat Inc. + * + * 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 "draw-workspace.h" + + +static void +get_window_rect (const WnckWindowDisplayInfo *win, + int screen_width, + int screen_height, + const GdkRectangle *workspace_rect, + GdkRectangle *rect) +{ + double width_ratio, height_ratio; + int x, y, width, height; + + width_ratio = (double) workspace_rect->width / (double) screen_width; + height_ratio = (double) workspace_rect->height / (double) screen_height; + + x = win->x; + y = win->y; + width = win->width; + height = win->height; + + x *= width_ratio; + y *= height_ratio; + width *= width_ratio; + height *= height_ratio; + + x += workspace_rect->x; + y += workspace_rect->y; + + if (width < 3) + width = 3; + if (height < 3) + height = 3; + + rect->x = x; + rect->y = y; + rect->width = width; + rect->height = height; +} + +static void +draw_window (GtkWidget *widget, + GdkDrawable *drawable, + const WnckWindowDisplayInfo *win, + const GdkRectangle *winrect, + GtkStateType state) +{ + cairo_t *cr; + GdkPixbuf *icon; + int icon_x, icon_y, icon_w, icon_h; + gboolean is_active; + GdkColor *color; + GtkStyle *style; + + is_active = win->is_active; + + cr = gdk_cairo_create (drawable); + cairo_rectangle (cr, winrect->x, winrect->y, winrect->width, winrect->height); + cairo_clip (cr); + + style = gtk_widget_get_style (widget); + if (is_active) + color = &style->light[state]; + else + color = &style->bg[state]; + cairo_set_source_rgb (cr, + color->red / 65535., + color->green / 65535., + color->blue / 65535.); + + cairo_rectangle (cr, + winrect->x + 1, winrect->y + 1, + MAX (0, winrect->width - 2), MAX (0, winrect->height - 2)); + cairo_fill (cr); + + + icon = win->icon; + + icon_w = icon_h = 0; + + if (icon) + { + icon_w = gdk_pixbuf_get_width (icon); + icon_h = gdk_pixbuf_get_height (icon); + + /* If the icon is too big, fall back to mini icon. + * We don't arbitrarily scale the icon, because it's + * just too slow on my Athlon 850. + */ + if (icon_w > (winrect->width - 2) || + icon_h > (winrect->height - 2)) + { + icon = win->mini_icon; + if (icon) + { + icon_w = gdk_pixbuf_get_width (icon); + icon_h = gdk_pixbuf_get_height (icon); + + /* Give up. */ + if (icon_w > (winrect->width - 2) || + icon_h > (winrect->height - 2)) + icon = NULL; + } + } + } + + if (icon) + { + icon_x = winrect->x + (winrect->width - icon_w) / 2; + icon_y = winrect->y + (winrect->height - icon_h) / 2; + + cairo_save (cr); + gdk_cairo_set_source_pixbuf (cr, icon, icon_x, icon_y); + cairo_rectangle (cr, icon_x, icon_y, icon_w, icon_h); + cairo_clip (cr); + cairo_paint (cr); + cairo_restore (cr); + } + + if (is_active) + color = &style->fg[state]; + else + color = &style->fg[state]; + + cairo_set_source_rgb (cr, + color->red / 65535., + color->green / 65535., + color->blue / 65535.); + cairo_set_line_width (cr, 1.0); + cairo_rectangle (cr, + winrect->x + 0.5, winrect->y + 0.5, + MAX (0, winrect->width - 1), MAX (0, winrect->height - 1)); + cairo_stroke (cr); + + cairo_destroy (cr); +} + +void +wnck_draw_workspace (GtkWidget *widget, + GdkDrawable *drawable, + int x, + int y, + int width, + int height, + int screen_width, + int screen_height, + GdkPixbuf *workspace_background, + gboolean is_active, + const WnckWindowDisplayInfo *windows, + int n_windows) +{ + int i; + GdkRectangle workspace_rect; + GtkStateType state; + cairo_t *cr; + + workspace_rect.x = x; + workspace_rect.y = y; + workspace_rect.width = width; + workspace_rect.height = height; + + if (is_active) + state = GTK_STATE_SELECTED; + else if (workspace_background) + state = GTK_STATE_PRELIGHT; + else + state = GTK_STATE_NORMAL; + + cr = gdk_cairo_create (drawable); + + if (workspace_background) + { + gdk_cairo_set_source_pixbuf (cr, workspace_background, x, y); + cairo_paint (cr); + } + else + { + gdk_cairo_set_source_color (cr, >k_widget_get_style (widget)->dark[state]); + cairo_rectangle (cr, x, y, width, height); + cairo_fill (cr); + } + + cairo_destroy (cr); + + i = 0; + while (i < n_windows) + { + const WnckWindowDisplayInfo *win = &windows[i]; + GdkRectangle winrect; + + get_window_rect (win, screen_width, + screen_height, &workspace_rect, &winrect); + + draw_window (widget, + drawable, + win, + &winrect, + state); + + ++i; + } +} diff --git a/src/ui/draw-workspace.h b/src/ui/draw-workspace.h new file mode 100644 index 00000000..a8b77cab --- /dev/null +++ b/src/ui/draw-workspace.h @@ -0,0 +1,61 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Draw a workspace */ + +/* This file should not be modified to depend on other files in + * libwnck or marco, since it's used in both of them + */ + +/* + * Copyright (C) 2002 Red Hat Inc. + * + * 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. + */ + +#ifndef WNCK_DRAW_WORKSPACE_H +#define WNCK_DRAW_WORKSPACE_H + +#include <gdk/gdk.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gtk/gtk.h> + +typedef struct +{ + GdkPixbuf *icon; + GdkPixbuf *mini_icon; + int x; + int y; + int width; + int height; + + guint is_active : 1; + +} WnckWindowDisplayInfo; + +void wnck_draw_workspace (GtkWidget *widget, + GdkDrawable *drawable, + int x, + int y, + int width, + int height, + int screen_width, + int screen_height, + GdkPixbuf *workspace_background, + gboolean is_active, + const WnckWindowDisplayInfo *windows, + int n_windows); + +#endif diff --git a/src/ui/fixedtip.c b/src/ui/fixedtip.c new file mode 100644 index 00000000..93370636 --- /dev/null +++ b/src/ui/fixedtip.c @@ -0,0 +1,133 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco fixed tooltip routine */ + +/* + * 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 "fixedtip.h" +#include "ui.h" + +/** + * The floating rectangle. This is a GtkWindow, and it contains + * the "label" widget, below. + */ +static GtkWidget *tip = NULL; + +/** + * The actual text that gets displayed. + */ +static GtkWidget *label = NULL; +/* + * X coordinate of the right-hand edge of the screen. + * + * \bug This appears to be a bug; screen_right_edge is calculated only when + * the window is redrawn. Actually we should never cache it because + * different windows are different sizes. + */ +static int screen_right_edge = 0; +/* + * Y coordinate of the bottom edge of the screen. + * + * \bug As with screen_right_edge. + */ +static int screen_bottom_edge = 0; + +static gint +expose_handler (GtkTooltips *tooltips) +{ + gtk_paint_flat_box (gtk_widget_get_style (tip), gtk_widget_get_window (tip), + GTK_STATE_NORMAL, GTK_SHADOW_OUT, + NULL, tip, "tooltip", + 0, 0, -1, -1); + + return FALSE; +} + +void +meta_fixed_tip_show (Display *xdisplay, int screen_number, + int root_x, int root_y, + const char *markup_text) +{ + int w, h; + + if (tip == NULL) + { + tip = gtk_window_new (GTK_WINDOW_POPUP); + gtk_window_set_type_hint (GTK_WINDOW(tip), GDK_WINDOW_TYPE_HINT_TOOLTIP); + + { + GdkScreen *gdk_screen; + GdkRectangle monitor; + gint mon_num; + + gdk_screen = gdk_display_get_screen (gdk_display_get_default (), + screen_number); + gtk_window_set_screen (GTK_WINDOW (tip), + gdk_screen); + mon_num = gdk_screen_get_monitor_at_point (gdk_screen, root_x, root_y); + gdk_screen_get_monitor_geometry (gdk_screen, mon_num, &monitor); + screen_right_edge = monitor.x + monitor.width; + screen_bottom_edge = monitor.y + monitor.height; + } + + gtk_widget_set_app_paintable (tip, TRUE); + gtk_window_set_resizable (GTK_WINDOW (tip), FALSE); + gtk_widget_set_name (tip, "gtk-tooltips"); + gtk_container_set_border_width (GTK_CONTAINER (tip), 4); + + g_signal_connect_swapped (tip, "expose_event", + G_CALLBACK (expose_handler), NULL); + + label = gtk_label_new (NULL); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0.5, 0.5); + gtk_widget_show (label); + + gtk_container_add (GTK_CONTAINER (tip), label); + + g_signal_connect (tip, "destroy", + G_CALLBACK (gtk_widget_destroyed), &tip); + } + + gtk_label_set_markup (GTK_LABEL (label), markup_text); + + gtk_window_get_size (GTK_WINDOW (tip), &w, &h); + + if (meta_ui_get_direction() == META_UI_DIRECTION_RTL) + root_x = MAX(0, root_x - w); + + if ((root_x + w) > screen_right_edge) + root_x -= (root_x + w) - screen_right_edge; + + gtk_window_move (GTK_WINDOW (tip), root_x, root_y); + + gtk_widget_show (tip); +} + +void +meta_fixed_tip_hide (void) +{ + if (tip) + { + gtk_widget_destroy (tip); + tip = NULL; + } +} diff --git a/src/ui/fixedtip.h b/src/ui/fixedtip.h new file mode 100644 index 00000000..28a618d9 --- /dev/null +++ b/src/ui/fixedtip.h @@ -0,0 +1,69 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * Copyright (C) 2001 Havoc Pennington + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +/** + * \file fixedtip.h Marco fixed tooltip routine + * + * Sometimes we want to display a small floating rectangle with helpful + * text near the pointer. For example, if the user holds the mouse over + * the maximise button, we can display a tooltip saying "Maximize". + * The text is localised, of course. + * + * This file contains the functions to create and delete these tooltips. + * + * \todo Since we now consider MetaDisplay a singleton, there can be + * only one tooltip per display; this might quite simply live in + * display.c. Alternatively, it could move to frames.c, which + * is the only place this business is called anyway. + * + * \todo Apparently some UI needs changing (check bugzilla) + */ + +#ifndef META_FIXED_TIP_H +#define META_FIXED_TIP_H + +#include <gtk/gtk.h> +#include <gdk/gdkx.h> + +/** + * Displays a tooltip. There can be only one across the entire system. + * This function behaves identically whether or not a tooltip is already + * displayed, but if it is the window will be reused rather than destroyed + * and recreated. + * + * \param xdisplay An X display. + * \param screen_number The number of the screen. + * \param root_x The X coordinate where the tooltip should appear + * \param root_y The Y coordinate where the tooltip should appear + * \param markup_text Text to display in the tooltip; can contain markup + */ +void meta_fixed_tip_show (Display *xdisplay, int screen_number, + int root_x, int root_y, + const char *markup_text); + +/** + * Removes the tooltip that was created by meta_fixed_tip_show(). If there + * is no tooltip currently visible, this is a no-op. + */ +void meta_fixed_tip_hide (void); + + +#endif diff --git a/src/ui/frames.c b/src/ui/frames.c new file mode 100644 index 00000000..bb4c863c --- /dev/null +++ b/src/ui/frames.c @@ -0,0 +1,2940 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco window frame manager widget */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2005, 2006 Elijah Newren + * + * 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 <math.h> +#include "boxes.h" +#include "frames.h" +#include "util.h" +#include "core.h" +#include "menu.h" +#include "fixedtip.h" +#include "theme.h" +#include "prefs.h" +#include "ui.h" + +#ifdef HAVE_SHAPE +#include <X11/extensions/shape.h> +#endif + +#define DEFAULT_INNER_BUTTON_BORDER 3 + +static void meta_frames_class_init (MetaFramesClass *klass); +static void meta_frames_init (MetaFrames *frames); +static void meta_frames_destroy (GtkObject *object); +static void meta_frames_finalize (GObject *object); +static void meta_frames_style_set (GtkWidget *widget, + GtkStyle *prev_style); +static void meta_frames_realize (GtkWidget *widget); +static void meta_frames_unrealize (GtkWidget *widget); + +static void meta_frames_update_prelit_control (MetaFrames *frames, + MetaUIFrame *frame, + MetaFrameControl control); +static gboolean meta_frames_button_press_event (GtkWidget *widget, + GdkEventButton *event); +static gboolean meta_frames_button_release_event (GtkWidget *widget, + GdkEventButton *event); +static gboolean meta_frames_motion_notify_event (GtkWidget *widget, + GdkEventMotion *event); +static gboolean meta_frames_destroy_event (GtkWidget *widget, + GdkEventAny *event); +static gboolean meta_frames_expose_event (GtkWidget *widget, + GdkEventExpose *event); +static gboolean meta_frames_enter_notify_event (GtkWidget *widget, + GdkEventCrossing *event); +static gboolean meta_frames_leave_notify_event (GtkWidget *widget, + GdkEventCrossing *event); + +static void meta_frames_attach_style (MetaFrames *frames, + MetaUIFrame *frame); + +static void meta_frames_paint_to_drawable (MetaFrames *frames, + MetaUIFrame *frame, + GdkDrawable *drawable, + GdkRegion *region, + int x_offset, + int y_offset); + +static void meta_frames_set_window_background (MetaFrames *frames, + MetaUIFrame *frame); + +static void meta_frames_calc_geometry (MetaFrames *frames, + MetaUIFrame *frame, + MetaFrameGeometry *fgeom); + +static void meta_frames_ensure_layout (MetaFrames *frames, + MetaUIFrame *frame); + +static MetaUIFrame* meta_frames_lookup_window (MetaFrames *frames, + Window xwindow); + +static void meta_frames_font_changed (MetaFrames *frames); +static void meta_frames_button_layout_changed (MetaFrames *frames); + + +static GdkRectangle* control_rect (MetaFrameControl control, + MetaFrameGeometry *fgeom); +static MetaFrameControl get_control (MetaFrames *frames, + MetaUIFrame *frame, + int x, + int y); +static void clear_tip (MetaFrames *frames); +static void invalidate_all_caches (MetaFrames *frames); +static void invalidate_whole_window (MetaFrames *frames, + MetaUIFrame *frame); + +static GtkWidgetClass *parent_class = NULL; + +GType +meta_frames_get_type (void) +{ + static GType frames_type = 0; + + if (!frames_type) + { + static const GtkTypeInfo frames_info = + { + "MetaFrames", + sizeof (MetaFrames), + sizeof (MetaFramesClass), + (GtkClassInitFunc) meta_frames_class_init, + (GtkObjectInitFunc) meta_frames_init, + /* reserved_1 */ NULL, + /* reserved_2 */ NULL, + (GtkClassInitFunc) NULL, + }; + + frames_type = gtk_type_unique (GTK_TYPE_WINDOW, &frames_info); + } + + return frames_type; +} + +static void +meta_frames_class_init (MetaFramesClass *class) +{ + GObjectClass *gobject_class; + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + + gobject_class = G_OBJECT_CLASS (class); + object_class = (GtkObjectClass*) class; + widget_class = (GtkWidgetClass*) class; + + parent_class = g_type_class_peek_parent (class); + + gobject_class->finalize = meta_frames_finalize; + object_class->destroy = meta_frames_destroy; + + widget_class->style_set = meta_frames_style_set; + + widget_class->realize = meta_frames_realize; + widget_class->unrealize = meta_frames_unrealize; + + widget_class->expose_event = meta_frames_expose_event; + widget_class->destroy_event = meta_frames_destroy_event; + widget_class->button_press_event = meta_frames_button_press_event; + widget_class->button_release_event = meta_frames_button_release_event; + widget_class->motion_notify_event = meta_frames_motion_notify_event; + widget_class->enter_notify_event = meta_frames_enter_notify_event; + widget_class->leave_notify_event = meta_frames_leave_notify_event; +} + +static gint +unsigned_long_equal (gconstpointer v1, + gconstpointer v2) +{ + return *((const gulong*) v1) == *((const gulong*) v2); +} + +static guint +unsigned_long_hash (gconstpointer v) +{ + gulong val = * (const gulong *) v; + + /* I'm not sure this works so well. */ +#if GLIB_SIZEOF_LONG > 4 + return (guint) (val ^ (val >> 32)); +#else + return val; +#endif +} + +static void +prefs_changed_callback (MetaPreference pref, + void *data) +{ + switch (pref) + { + case META_PREF_TITLEBAR_FONT: + meta_frames_font_changed (META_FRAMES (data)); + break; + case META_PREF_BUTTON_LAYOUT: + meta_frames_button_layout_changed (META_FRAMES (data)); + break; + default: + break; + } +} + +static void +meta_frames_init (MetaFrames *frames) +{ + GTK_WINDOW (frames)->type = GTK_WINDOW_POPUP; + + frames->text_heights = g_hash_table_new (NULL, NULL); + + frames->frames = g_hash_table_new (unsigned_long_hash, unsigned_long_equal); + + frames->tooltip_timeout = 0; + + frames->expose_delay_count = 0; + + frames->invalidate_cache_timeout_id = 0; + frames->invalidate_frames = NULL; + frames->cache = g_hash_table_new (g_direct_hash, g_direct_equal); + + gtk_widget_set_double_buffered (GTK_WIDGET (frames), FALSE); + + meta_prefs_add_listener (prefs_changed_callback, frames); +} + +static void +listify_func (gpointer key, gpointer value, gpointer data) +{ + GSList **listp; + + listp = data; + *listp = g_slist_prepend (*listp, value); +} + +static void +meta_frames_destroy (GtkObject *object) +{ + GSList *winlist; + GSList *tmp; + MetaFrames *frames; + + frames = META_FRAMES (object); + + clear_tip (frames); + + winlist = NULL; + g_hash_table_foreach (frames->frames, listify_func, &winlist); + + /* Unmanage all frames */ + for (tmp = winlist; tmp != NULL; tmp = tmp->next) + { + MetaUIFrame *frame; + + frame = tmp->data; + + meta_frames_unmanage_window (frames, frame->xwindow); + } + g_slist_free (winlist); + + GTK_OBJECT_CLASS (parent_class)->destroy (object); +} + +static void +meta_frames_finalize (GObject *object) +{ + MetaFrames *frames; + + frames = META_FRAMES (object); + + meta_prefs_remove_listener (prefs_changed_callback, frames); + + g_hash_table_destroy (frames->text_heights); + + invalidate_all_caches (frames); + if (frames->invalidate_cache_timeout_id) + g_source_remove (frames->invalidate_cache_timeout_id); + + g_assert (g_hash_table_size (frames->frames) == 0); + g_hash_table_destroy (frames->frames); + g_hash_table_destroy (frames->cache); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +typedef struct +{ + MetaRectangle rect; + GdkPixmap *pixmap; +} CachedFramePiece; + +typedef struct +{ + /* Caches of the four rendered sides in a MetaFrame. + * Order: top (titlebar), left, right, bottom. + */ + CachedFramePiece piece[4]; +} CachedPixels; + +static CachedPixels * +get_cache (MetaFrames *frames, + MetaUIFrame *frame) +{ + CachedPixels *pixels; + + pixels = g_hash_table_lookup (frames->cache, frame); + + if (!pixels) + { + pixels = g_new0 (CachedPixels, 1); + g_hash_table_insert (frames->cache, frame, pixels); + } + + return pixels; +} + +static void +invalidate_cache (MetaFrames *frames, + MetaUIFrame *frame) +{ + CachedPixels *pixels = get_cache (frames, frame); + int i; + + for (i = 0; i < 4; i++) + if (pixels->piece[i].pixmap) + g_object_unref (pixels->piece[i].pixmap); + + g_free (pixels); + g_hash_table_remove (frames->cache, frame); +} + +static void +invalidate_all_caches (MetaFrames *frames) +{ + GList *l; + + for (l = frames->invalidate_frames; l; l = l->next) + { + MetaUIFrame *frame = l->data; + + invalidate_cache (frames, frame); + } + + g_list_free (frames->invalidate_frames); + frames->invalidate_frames = NULL; +} + +static gboolean +invalidate_cache_timeout (gpointer data) +{ + MetaFrames *frames = data; + + invalidate_all_caches (frames); + frames->invalidate_cache_timeout_id = 0; + return FALSE; +} + +static void +queue_recalc_func (gpointer key, gpointer value, gpointer data) +{ + MetaUIFrame *frame; + MetaFrames *frames; + + frames = META_FRAMES (data); + frame = value; + + /* If a resize occurs it will cause a redraw, but the + * resize may not actually be needed so we always redraw + * in case of color change. + */ + meta_frames_set_window_background (frames, frame); + + invalidate_whole_window (frames, frame); + meta_core_queue_frame_resize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow); + if (frame->layout) + { + /* save title to recreate layout */ + g_free (frame->title); + + frame->title = g_strdup (pango_layout_get_text (frame->layout)); + + g_object_unref (G_OBJECT (frame->layout)); + frame->layout = NULL; + } +} + +static void +meta_frames_font_changed (MetaFrames *frames) +{ + if (g_hash_table_size (frames->text_heights) > 0) + { + g_hash_table_destroy (frames->text_heights); + frames->text_heights = g_hash_table_new (NULL, NULL); + } + + /* Queue a draw/resize on all frames */ + g_hash_table_foreach (frames->frames, + queue_recalc_func, frames); + +} + +static void +queue_draw_func (gpointer key, gpointer value, gpointer data) +{ + MetaUIFrame *frame; + MetaFrames *frames; + + frames = META_FRAMES (data); + frame = value; + + /* If a resize occurs it will cause a redraw, but the + * resize may not actually be needed so we always redraw + * in case of color change. + */ + meta_frames_set_window_background (frames, frame); + + invalidate_whole_window (frames, frame); +} + +static void +meta_frames_button_layout_changed (MetaFrames *frames) +{ + g_hash_table_foreach (frames->frames, + queue_draw_func, frames); +} + +static void +reattach_style_func (gpointer key, gpointer value, gpointer data) +{ + MetaUIFrame *frame; + MetaFrames *frames; + + frames = META_FRAMES (data); + frame = value; + + meta_frames_attach_style (frames, frame); +} + +static void +meta_frames_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + MetaFrames *frames; + + frames = META_FRAMES (widget); + + meta_frames_font_changed (frames); + + g_hash_table_foreach (frames->frames, + reattach_style_func, frames); + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); +} + +static void +meta_frames_ensure_layout (MetaFrames *frames, + MetaUIFrame *frame) +{ + GtkWidget *widget; + MetaFrameFlags flags; + MetaFrameType type; + MetaFrameStyle *style; + + g_return_if_fail (GTK_WIDGET_REALIZED (frames)); + + widget = GTK_WIDGET (frames); + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_FRAME_TYPE, &type, + META_CORE_GET_END); + + style = meta_theme_get_frame_style (meta_theme_get_current (), + type, flags); + + if (style != frame->cache_style) + { + if (frame->layout) + { + /* save title to recreate layout */ + g_free (frame->title); + + frame->title = g_strdup (pango_layout_get_text (frame->layout)); + + g_object_unref (G_OBJECT (frame->layout)); + frame->layout = NULL; + } + } + + frame->cache_style = style; + + if (frame->layout == NULL) + { + gpointer key, value; + PangoFontDescription *font_desc; + double scale; + int size; + + scale = meta_theme_get_title_scale (meta_theme_get_current (), + type, + flags); + + frame->layout = gtk_widget_create_pango_layout (widget, frame->title); + + pango_layout_set_auto_dir (frame->layout, FALSE); + + font_desc = meta_gtk_widget_get_font_desc (widget, scale, + meta_prefs_get_titlebar_font ()); + + size = pango_font_description_get_size (font_desc); + + if (g_hash_table_lookup_extended (frames->text_heights, + GINT_TO_POINTER (size), + &key, &value)) + { + frame->text_height = GPOINTER_TO_INT (value); + } + else + { + frame->text_height = + meta_pango_font_desc_get_text_height (font_desc, + gtk_widget_get_pango_context (widget)); + + g_hash_table_replace (frames->text_heights, + GINT_TO_POINTER (size), + GINT_TO_POINTER (frame->text_height)); + } + + pango_layout_set_font_description (frame->layout, + font_desc); + + pango_font_description_free (font_desc); + + /* Save some RAM */ + g_free (frame->title); + frame->title = NULL; + } +} + +static void +meta_frames_calc_geometry (MetaFrames *frames, + MetaUIFrame *frame, + MetaFrameGeometry *fgeom) +{ + int width, height; + MetaFrameFlags flags; + MetaFrameType type; + MetaButtonLayout button_layout; + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_CLIENT_WIDTH, &width, + META_CORE_GET_CLIENT_HEIGHT, &height, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_FRAME_TYPE, &type, + META_CORE_GET_END); + + meta_frames_ensure_layout (frames, frame); + + meta_prefs_get_button_layout (&button_layout); + + meta_theme_calc_geometry (meta_theme_get_current (), + type, + frame->text_height, + flags, + width, height, + &button_layout, + fgeom); +} + +MetaFrames* +meta_frames_new (int screen_number) +{ + GdkScreen *screen; + + screen = gdk_display_get_screen (gdk_display_get_default (), + screen_number); + + return g_object_new (META_TYPE_FRAMES, + "screen", screen, + NULL); +} + +/* In order to use a style with a window it has to be attached to that + * window. Actually, the colormaps just have to match, but since GTK+ + * already takes care of making sure that its cheap to attach a style + * to multiple windows with the same colormap, we can just go ahead + * and attach separately for each window. + */ +static void +meta_frames_attach_style (MetaFrames *frames, + MetaUIFrame *frame) +{ + if (frame->style != NULL) + gtk_style_detach (frame->style); + + /* Weirdly, gtk_style_attach() steals a reference count from the style passed in */ + g_object_ref (GTK_WIDGET (frames)->style); + frame->style = gtk_style_attach (GTK_WIDGET (frames)->style, frame->window); +} + +void +meta_frames_manage_window (MetaFrames *frames, + Window xwindow, + GdkWindow *window) +{ + MetaUIFrame *frame; + + g_assert (window); + + frame = g_new (MetaUIFrame, 1); + + frame->window = window; + + gdk_window_set_user_data (frame->window, frames); + + frame->style = NULL; + meta_frames_attach_style (frames, frame); + + /* Don't set event mask here, it's in frame.c */ + + frame->xwindow = xwindow; + frame->cache_style = NULL; + frame->layout = NULL; + frame->text_height = -1; + frame->title = NULL; + frame->expose_delayed = FALSE; + frame->shape_applied = FALSE; + frame->prelit_control = META_FRAME_CONTROL_NONE; + + /* Don't set the window background yet; we need frame->xwindow to be + * registered with its MetaWindow, which happens after this function + * and meta_ui_create_frame_window() return to meta_window_ensure_frame(). + */ + + meta_core_grab_buttons (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + + g_hash_table_replace (frames->frames, &frame->xwindow, frame); +} + +void +meta_frames_unmanage_window (MetaFrames *frames, + Window xwindow) +{ + MetaUIFrame *frame; + + clear_tip (frames); + + frame = g_hash_table_lookup (frames->frames, &xwindow); + + if (frame) + { + /* invalidating all caches ensures the frame + * is not actually referenced anymore + */ + invalidate_all_caches (frames); + + /* restore the cursor */ + meta_core_set_screen_cursor (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + META_CURSOR_DEFAULT); + + gdk_window_set_user_data (frame->window, NULL); + + if (frames->last_motion_frame == frame) + frames->last_motion_frame = NULL; + + g_hash_table_remove (frames->frames, &frame->xwindow); + + gtk_style_detach (frame->style); + + gdk_window_destroy (frame->window); + + if (frame->layout) + g_object_unref (G_OBJECT (frame->layout)); + + if (frame->title) + g_free (frame->title); + + g_free (frame); + } + else + meta_warning ("Frame 0x%lx not managed, can't unmanage\n", xwindow); +} + +static void +meta_frames_realize (GtkWidget *widget) +{ + if (GTK_WIDGET_CLASS (parent_class)->realize) + GTK_WIDGET_CLASS (parent_class)->realize (widget); +} + +static void +meta_frames_unrealize (GtkWidget *widget) +{ + if (GTK_WIDGET_CLASS (parent_class)->unrealize) + GTK_WIDGET_CLASS (parent_class)->unrealize (widget); +} + +static MetaUIFrame* +meta_frames_lookup_window (MetaFrames *frames, + Window xwindow) +{ + MetaUIFrame *frame; + + frame = g_hash_table_lookup (frames->frames, &xwindow); + + return frame; +} + +void +meta_frames_get_geometry (MetaFrames *frames, + Window xwindow, + int *top_height, int *bottom_height, + int *left_width, int *right_width) +{ + 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); + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_FRAME_TYPE, &type, + META_CORE_GET_END); + + g_return_if_fail (type < META_FRAME_TYPE_LAST); + + meta_frames_ensure_layout (frames, frame); + + /* 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_theme_get_frame_borders (meta_theme_get_current (), + type, + frame->text_height, + flags, + top_height, bottom_height, + left_width, right_width); +} + +void +meta_frames_reset_bg (MetaFrames *frames, + Window xwindow) +{ + MetaUIFrame *frame; + + frame = meta_frames_lookup_window (frames, xwindow); + + meta_frames_set_window_background (frames, frame); +} + +static void +set_background_none (Display *xdisplay, + Window xwindow) +{ + XSetWindowAttributes attrs; + + attrs.background_pixmap = None; + XChangeWindowAttributes (xdisplay, xwindow, + CWBackPixmap, &attrs); +} + +void +meta_frames_unflicker_bg (MetaFrames *frames, + Window xwindow, + int target_width, + int target_height) +{ + MetaUIFrame *frame; + + frame = meta_frames_lookup_window (frames, xwindow); + g_return_if_fail (frame != NULL); + +#if 0 + pixmap = gdk_pixmap_new (frame->window, + width, height, + -1); + + /* Oops, no way to get the background here */ + + meta_frames_paint_to_drawable (frames, frame, pixmap); +#endif + + set_background_none (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); +} + +void +meta_frames_apply_shapes (MetaFrames *frames, + Window xwindow, + int new_window_width, + int new_window_height, + gboolean window_has_shape) +{ +#ifdef HAVE_SHAPE + /* Apply shapes as if window had new_window_width, new_window_height */ + MetaUIFrame *frame; + MetaFrameGeometry fgeom; + XRectangle xrect; + Region corners_xregion; + Region window_xregion; + + frame = meta_frames_lookup_window (frames, xwindow); + g_return_if_fail (frame != NULL); + + meta_frames_calc_geometry (frames, frame, &fgeom); + + if (!(fgeom.top_left_corner_rounded_radius != 0 || + fgeom.top_right_corner_rounded_radius != 0 || + fgeom.bottom_left_corner_rounded_radius != 0 || + fgeom.bottom_right_corner_rounded_radius != 0 || + window_has_shape)) + { + if (frame->shape_applied) + { + meta_topic (META_DEBUG_SHAPES, + "Unsetting shape mask on frame 0x%lx\n", + frame->xwindow); + + XShapeCombineMask (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + ShapeBounding, 0, 0, None, ShapeSet); + frame->shape_applied = FALSE; + } + else + { + meta_topic (META_DEBUG_SHAPES, + "Frame 0x%lx still doesn't need a shape mask\n", + frame->xwindow); + } + + return; /* nothing to do */ + } + + corners_xregion = XCreateRegion (); + + if (fgeom.top_left_corner_rounded_radius != 0) + { + const int corner = fgeom.top_left_corner_rounded_radius; + const float radius = sqrt(corner) + corner; + int i; + + for (i=0; i<corner; i++) + { + const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5)))); + xrect.x = 0; + xrect.y = i; + xrect.width = width; + xrect.height = 1; + + XUnionRectWithRegion (&xrect, corners_xregion, corners_xregion); + } + } + + if (fgeom.top_right_corner_rounded_radius != 0) + { + const int corner = fgeom.top_right_corner_rounded_radius; + const float radius = sqrt(corner) + corner; + int i; + + for (i=0; i<corner; i++) + { + const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5)))); + xrect.x = new_window_width - width; + xrect.y = i; + xrect.width = width; + xrect.height = 1; + + XUnionRectWithRegion (&xrect, corners_xregion, corners_xregion); + } + } + + if (fgeom.bottom_left_corner_rounded_radius != 0) + { + const int corner = fgeom.bottom_left_corner_rounded_radius; + const float radius = sqrt(corner) + corner; + int i; + + for (i=0; i<corner; i++) + { + const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5)))); + xrect.x = 0; + xrect.y = new_window_height - i - 1; + xrect.width = width; + xrect.height = 1; + + XUnionRectWithRegion (&xrect, corners_xregion, corners_xregion); + } + } + + if (fgeom.bottom_right_corner_rounded_radius != 0) + { + const int corner = fgeom.bottom_right_corner_rounded_radius; + const float radius = sqrt(corner) + corner; + int i; + + for (i=0; i<corner; i++) + { + const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5)))); + xrect.x = new_window_width - width; + xrect.y = new_window_height - i - 1; + xrect.width = width; + xrect.height = 1; + + XUnionRectWithRegion (&xrect, corners_xregion, corners_xregion); + } + } + + window_xregion = XCreateRegion (); + + xrect.x = 0; + xrect.y = 0; + xrect.width = new_window_width; + xrect.height = new_window_height; + + XUnionRectWithRegion (&xrect, window_xregion, window_xregion); + + XSubtractRegion (window_xregion, corners_xregion, window_xregion); + + XDestroyRegion (corners_xregion); + + if (window_has_shape) + { + /* The client window is oclock or something and has a shape + * mask. To avoid a round trip to get its shape region, we + * create a fake window that's never mapped, build up our shape + * on that, then combine. Wasting the window is assumed cheaper + * than a round trip, but who really knows for sure. + */ + XSetWindowAttributes attrs; + Window shape_window; + Window client_window; + Region client_xregion; + GdkScreen *screen; + int screen_number; + + meta_topic (META_DEBUG_SHAPES, + "Frame 0x%lx needs to incorporate client shape\n", + frame->xwindow); + + screen = gtk_widget_get_screen (GTK_WIDGET (frames)); + screen_number = gdk_x11_screen_get_screen_number (screen); + + attrs.override_redirect = True; + + shape_window = XCreateWindow (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + RootWindow (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), screen_number), + -5000, -5000, + new_window_width, + new_window_height, + 0, + CopyFromParent, + CopyFromParent, + (Visual *)CopyFromParent, + CWOverrideRedirect, + &attrs); + + /* Copy the client's shape to the temporary shape_window */ + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_CLIENT_XWINDOW, &client_window, + META_CORE_GET_END); + + XShapeCombineShape (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), shape_window, ShapeBounding, + fgeom.left_width, + fgeom.top_height, + client_window, + ShapeBounding, + ShapeSet); + + /* Punch the client area out of the normal frame shape, + * then union it with the shape_window's existing shape + */ + client_xregion = XCreateRegion (); + + xrect.x = fgeom.left_width; + xrect.y = fgeom.top_height; + xrect.width = new_window_width - fgeom.right_width - xrect.x; + xrect.height = new_window_height - fgeom.bottom_height - xrect.y; + + XUnionRectWithRegion (&xrect, client_xregion, client_xregion); + + XSubtractRegion (window_xregion, client_xregion, window_xregion); + + XDestroyRegion (client_xregion); + + XShapeCombineRegion (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), shape_window, + ShapeBounding, 0, 0, window_xregion, ShapeUnion); + + /* Now copy shape_window shape to the real frame */ + XShapeCombineShape (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, ShapeBounding, + 0, 0, + shape_window, + ShapeBounding, + ShapeSet); + + XDestroyWindow (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), shape_window); + } + else + { + /* No shape on the client, so just do simple stuff */ + + meta_topic (META_DEBUG_SHAPES, + "Frame 0x%lx has shaped corners\n", + frame->xwindow); + + XShapeCombineRegion (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + ShapeBounding, 0, 0, window_xregion, ShapeSet); + } + + frame->shape_applied = TRUE; + + XDestroyRegion (window_xregion); +#endif /* HAVE_SHAPE */ +} + +void +meta_frames_move_resize_frame (MetaFrames *frames, + Window xwindow, + int x, + int y, + int width, + int height) +{ + MetaUIFrame *frame = meta_frames_lookup_window (frames, xwindow); + int old_x, old_y, old_width, old_height; + + #if GTK_CHECK_VERSION(3, 0, 0) + old_width = gdk_window_get_width(GDK_WINDOW(frame->window)); + old_height = gdk_window_get_height(GDK_WINDOW(frame->window)); + #else + gdk_drawable_get_size(frame->window, &old_width, &old_height); + #endif + + gdk_window_get_position (frame->window, &old_x, &old_y); + + gdk_window_move_resize (frame->window, x, y, width, height); + + if (old_width != width || old_height != height) + invalidate_whole_window (frames, frame); +} + +void +meta_frames_queue_draw (MetaFrames *frames, + Window xwindow) +{ + MetaUIFrame *frame; + + frame = meta_frames_lookup_window (frames, xwindow); + + invalidate_whole_window (frames, frame); +} + +void +meta_frames_set_title (MetaFrames *frames, + Window xwindow, + const char *title) +{ + MetaUIFrame *frame; + + frame = meta_frames_lookup_window (frames, xwindow); + + g_assert (frame); + + g_free (frame->title); + frame->title = g_strdup (title); + + if (frame->layout) + { + g_object_unref (frame->layout); + frame->layout = NULL; + } + + invalidate_whole_window (frames, frame); +} + +void +meta_frames_repaint_frame (MetaFrames *frames, + Window xwindow) +{ + MetaUIFrame *frame; + + frame = meta_frames_lookup_window (frames, xwindow); + + g_assert (frame); + + /* repaint everything, so the other frame don't + * lag behind if they are exposed + */ + gdk_window_process_all_updates (); +} + +static void +show_tip_now (MetaFrames *frames) +{ + const char *tiptext; + MetaUIFrame *frame; + int x, y, root_x, root_y; + Window root, child; + guint mask; + MetaFrameControl control; + + frame = frames->last_motion_frame; + if (frame == NULL) + return; + + XQueryPointer (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + &root, &child, + &root_x, &root_y, + &x, &y, + &mask); + + control = get_control (frames, frame, x, y); + + tiptext = NULL; + switch (control) + { + case META_FRAME_CONTROL_TITLE: + break; + case META_FRAME_CONTROL_DELETE: + tiptext = _("Close Window"); + break; + case META_FRAME_CONTROL_MENU: + tiptext = _("Window Menu"); + break; + case META_FRAME_CONTROL_MINIMIZE: + tiptext = _("Minimize Window"); + break; + case META_FRAME_CONTROL_MAXIMIZE: + tiptext = _("Maximize Window"); + break; + case META_FRAME_CONTROL_UNMAXIMIZE: + tiptext = _("Restore Window"); + break; + case META_FRAME_CONTROL_SHADE: + tiptext = _("Roll Up Window"); + break; + case META_FRAME_CONTROL_UNSHADE: + tiptext = _("Unroll Window"); + break; + case META_FRAME_CONTROL_ABOVE: + tiptext = _("Keep Window On Top"); + break; + case META_FRAME_CONTROL_UNABOVE: + tiptext = _("Remove Window From Top"); + break; + case META_FRAME_CONTROL_STICK: + tiptext = _("Always On Visible Workspace"); + break; + case META_FRAME_CONTROL_UNSTICK: + tiptext = _("Put Window On Only One Workspace"); + break; + case META_FRAME_CONTROL_RESIZE_SE: + break; + case META_FRAME_CONTROL_RESIZE_S: + break; + case META_FRAME_CONTROL_RESIZE_SW: + break; + case META_FRAME_CONTROL_RESIZE_N: + break; + case META_FRAME_CONTROL_RESIZE_NE: + break; + case META_FRAME_CONTROL_RESIZE_NW: + break; + case META_FRAME_CONTROL_RESIZE_W: + break; + case META_FRAME_CONTROL_RESIZE_E: + break; + case META_FRAME_CONTROL_NONE: + break; + case META_FRAME_CONTROL_CLIENT_AREA: + break; + } + + if (tiptext) + { + MetaFrameGeometry fgeom; + GdkRectangle *rect; + int dx, dy; + int screen_number; + + meta_frames_calc_geometry (frames, frame, &fgeom); + + rect = control_rect (control, &fgeom); + + /* get conversion delta for root-to-frame coords */ + dx = root_x - x; + dy = root_y - y; + + /* Align the tooltip to the button right end if RTL */ + if (meta_ui_get_direction() == META_UI_DIRECTION_RTL) + dx += rect->width; + + screen_number = gdk_screen_get_number (gtk_widget_get_screen (GTK_WIDGET (frames))); + + meta_fixed_tip_show (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + screen_number, + rect->x + dx, + rect->y + rect->height + 2 + dy, + tiptext); + } +} + +static gboolean +tip_timeout_func (gpointer data) +{ + MetaFrames *frames; + + frames = data; + + show_tip_now (frames); + + return FALSE; +} + +#define TIP_DELAY 450 +static void +queue_tip (MetaFrames *frames) +{ + clear_tip (frames); + + frames->tooltip_timeout = g_timeout_add (TIP_DELAY, + tip_timeout_func, + frames); +} + +static void +clear_tip (MetaFrames *frames) +{ + if (frames->tooltip_timeout) + { + g_source_remove (frames->tooltip_timeout); + frames->tooltip_timeout = 0; + } + meta_fixed_tip_hide (); +} + +static void +redraw_control (MetaFrames *frames, + MetaUIFrame *frame, + MetaFrameControl control) +{ + MetaFrameGeometry fgeom; + GdkRectangle *rect; + + meta_frames_calc_geometry (frames, frame, &fgeom); + + rect = control_rect (control, &fgeom); + + gdk_window_invalidate_rect (frame->window, rect, FALSE); + invalidate_cache (frames, frame); +} + +static gboolean +meta_frame_titlebar_event (MetaUIFrame *frame, + GdkEventButton *event, + int action) +{ + MetaFrameFlags flags; + + switch (action) + { + case META_ACTION_TITLEBAR_TOGGLE_SHADE: + { + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_END); + + if (flags & META_FRAME_ALLOWS_SHADE) + { + if (flags & META_FRAME_SHADED) + meta_core_unshade (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + event->time); + else + meta_core_shade (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + event->time); + } + } + break; + + case META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE: + { + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_END); + + if (flags & META_FRAME_ALLOWS_MAXIMIZE) + { + meta_core_toggle_maximize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + } + } + break; + + case META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE_HORIZONTALLY: + { + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_END); + + if (flags & META_FRAME_ALLOWS_MAXIMIZE) + { + meta_core_toggle_maximize_horizontally (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + } + } + break; + + case META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE_VERTICALLY: + { + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_END); + + if (flags & META_FRAME_ALLOWS_MAXIMIZE) + { + meta_core_toggle_maximize_vertically (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + } + } + break; + + case META_ACTION_TITLEBAR_MINIMIZE: + { + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_END); + + if (flags & META_FRAME_ALLOWS_MINIMIZE) + { + meta_core_minimize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + } + } + break; + + case META_ACTION_TITLEBAR_NONE: + /* Yaay, a sane user that doesn't use that other weird crap! */ + break; + + case META_ACTION_TITLEBAR_LOWER: + meta_core_user_lower_and_unfocus (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + event->time); + break; + + case META_ACTION_TITLEBAR_MENU: + meta_core_show_window_menu (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + event->x_root, + event->y_root, + event->button, + event->time); + break; + + case META_ACTION_TITLEBAR_LAST: + break; + } + + return TRUE; +} + +static gboolean +meta_frame_double_click_event (MetaUIFrame *frame, + GdkEventButton *event) +{ + int action = meta_prefs_get_action_double_click_titlebar (); + + return meta_frame_titlebar_event (frame, event, action); +} + +static gboolean +meta_frame_middle_click_event (MetaUIFrame *frame, + GdkEventButton *event) +{ + int action = meta_prefs_get_action_middle_click_titlebar(); + + return meta_frame_titlebar_event (frame, event, action); +} + +static gboolean +meta_frame_right_click_event(MetaUIFrame *frame, + GdkEventButton *event) +{ + int action = meta_prefs_get_action_right_click_titlebar(); + + return meta_frame_titlebar_event (frame, event, action); +} + +static gboolean +meta_frames_button_press_event (GtkWidget *widget, + GdkEventButton *event) +{ + MetaUIFrame *frame; + MetaFrames *frames; + MetaFrameControl control; + + frames = META_FRAMES (widget); + + /* Remember that the display may have already done something with this event. + * If so there's probably a GrabOp in effect. + */ + + frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window)); + if (frame == NULL) + return FALSE; + + clear_tip (frames); + + control = get_control (frames, frame, event->x, event->y); + + /* focus on click, even if click was on client area */ + if (event->button == 1 && + !(control == META_FRAME_CONTROL_MINIMIZE || + control == META_FRAME_CONTROL_DELETE || + control == META_FRAME_CONTROL_MAXIMIZE)) + { + meta_topic (META_DEBUG_FOCUS, + "Focusing window with frame 0x%lx due to button 1 press\n", + frame->xwindow); + meta_core_user_focus (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + event->time); + } + + /* don't do the rest of this if on client area */ + if (control == META_FRAME_CONTROL_CLIENT_AREA) + return FALSE; /* not on the frame, just passed through from client */ + + /* We want to shade even if we have a GrabOp, since we'll have a move grab + * if we double click the titlebar. + */ + if (control == META_FRAME_CONTROL_TITLE && + event->button == 1 && + event->type == GDK_2BUTTON_PRESS) + { + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + return meta_frame_double_click_event (frame, event); + } + + if (meta_core_get_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())) != + META_GRAB_OP_NONE) + return FALSE; /* already up to something */ + + if (event->button == 1 && + (control == META_FRAME_CONTROL_MAXIMIZE || + control == META_FRAME_CONTROL_UNMAXIMIZE || + control == META_FRAME_CONTROL_MINIMIZE || + control == META_FRAME_CONTROL_DELETE || + control == META_FRAME_CONTROL_SHADE || + control == META_FRAME_CONTROL_UNSHADE || + control == META_FRAME_CONTROL_ABOVE || + control == META_FRAME_CONTROL_UNABOVE || + control == META_FRAME_CONTROL_STICK || + control == META_FRAME_CONTROL_UNSTICK || + control == META_FRAME_CONTROL_MENU)) + { + MetaGrabOp op = META_GRAB_OP_NONE; + + switch (control) + { + case META_FRAME_CONTROL_MINIMIZE: + op = META_GRAB_OP_CLICKING_MINIMIZE; + break; + case META_FRAME_CONTROL_MAXIMIZE: + op = META_GRAB_OP_CLICKING_MAXIMIZE; + break; + case META_FRAME_CONTROL_UNMAXIMIZE: + op = META_GRAB_OP_CLICKING_UNMAXIMIZE; + break; + case META_FRAME_CONTROL_DELETE: + op = META_GRAB_OP_CLICKING_DELETE; + break; + case META_FRAME_CONTROL_MENU: + op = META_GRAB_OP_CLICKING_MENU; + break; + case META_FRAME_CONTROL_SHADE: + op = META_GRAB_OP_CLICKING_SHADE; + break; + case META_FRAME_CONTROL_UNSHADE: + op = META_GRAB_OP_CLICKING_UNSHADE; + break; + case META_FRAME_CONTROL_ABOVE: + op = META_GRAB_OP_CLICKING_ABOVE; + break; + case META_FRAME_CONTROL_UNABOVE: + op = META_GRAB_OP_CLICKING_UNABOVE; + break; + case META_FRAME_CONTROL_STICK: + op = META_GRAB_OP_CLICKING_STICK; + break; + case META_FRAME_CONTROL_UNSTICK: + op = META_GRAB_OP_CLICKING_UNSTICK; + break; + default: + g_assert_not_reached (); + break; + } + + meta_core_begin_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + op, + TRUE, + TRUE, + event->button, + 0, + event->time, + event->x_root, + event->y_root); + + frame->prelit_control = control; + redraw_control (frames, frame, control); + + if (op == META_GRAB_OP_CLICKING_MENU) + { + MetaFrameGeometry fgeom; + GdkRectangle *rect; + int dx, dy; + + meta_frames_calc_geometry (frames, frame, &fgeom); + + rect = control_rect (META_FRAME_CONTROL_MENU, &fgeom); + + /* get delta to convert to root coords */ + dx = event->x_root - event->x; + dy = event->y_root - event->y; + + /* Align to the right end of the menu rectangle if RTL */ + if (meta_ui_get_direction() == META_UI_DIRECTION_RTL) + dx += rect->width; + + meta_core_show_window_menu (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + rect->x + dx, + rect->y + rect->height + dy, + event->button, + event->time); + } + } + else if (event->button == 1 && + (control == META_FRAME_CONTROL_RESIZE_SE || + control == META_FRAME_CONTROL_RESIZE_S || + control == META_FRAME_CONTROL_RESIZE_SW || + control == META_FRAME_CONTROL_RESIZE_NE || + control == META_FRAME_CONTROL_RESIZE_N || + control == META_FRAME_CONTROL_RESIZE_NW || + control == META_FRAME_CONTROL_RESIZE_E || + control == META_FRAME_CONTROL_RESIZE_W)) + { + MetaGrabOp op; + gboolean titlebar_is_onscreen; + + op = META_GRAB_OP_NONE; + + switch (control) + { + case META_FRAME_CONTROL_RESIZE_SE: + op = META_GRAB_OP_RESIZING_SE; + break; + case META_FRAME_CONTROL_RESIZE_S: + op = META_GRAB_OP_RESIZING_S; + break; + case META_FRAME_CONTROL_RESIZE_SW: + op = META_GRAB_OP_RESIZING_SW; + break; + case META_FRAME_CONTROL_RESIZE_NE: + op = META_GRAB_OP_RESIZING_NE; + break; + case META_FRAME_CONTROL_RESIZE_N: + op = META_GRAB_OP_RESIZING_N; + break; + case META_FRAME_CONTROL_RESIZE_NW: + op = META_GRAB_OP_RESIZING_NW; + break; + case META_FRAME_CONTROL_RESIZE_E: + op = META_GRAB_OP_RESIZING_E; + break; + case META_FRAME_CONTROL_RESIZE_W: + op = META_GRAB_OP_RESIZING_W; + break; + default: + g_assert_not_reached (); + break; + } + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_IS_TITLEBAR_ONSCREEN, &titlebar_is_onscreen, + META_CORE_GET_END); + + if (!titlebar_is_onscreen) + meta_core_show_window_menu (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + event->x_root, + event->y_root, + event->button, + event->time); + else + meta_core_begin_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + op, + TRUE, + TRUE, + event->button, + 0, + event->time, + event->x_root, + event->y_root); + } + else if (control == META_FRAME_CONTROL_TITLE && + event->button == 1) + { + MetaFrameFlags flags; + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_END); + + if (flags & META_FRAME_ALLOWS_MOVE) + { + meta_core_begin_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + META_GRAB_OP_MOVING, + TRUE, + TRUE, + event->button, + 0, + event->time, + event->x_root, + event->y_root); + } + } + else if (event->button == 2) + { + return meta_frame_middle_click_event (frame, event); + } + else if (event->button == 3) + { + return meta_frame_right_click_event (frame, event); + } + + return TRUE; +} + +void +meta_frames_notify_menu_hide (MetaFrames *frames) +{ + if (meta_core_get_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())) == + META_GRAB_OP_CLICKING_MENU) + { + Window grab_frame; + + grab_frame = meta_core_get_grab_frame (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + + if (grab_frame != None) + { + MetaUIFrame *frame; + + frame = meta_frames_lookup_window (frames, grab_frame); + + if (frame) + { + redraw_control (frames, frame, + META_FRAME_CONTROL_MENU); + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), CurrentTime); + } + } + } +} + +static gboolean +meta_frames_button_release_event (GtkWidget *widget, + GdkEventButton *event) +{ + MetaUIFrame *frame; + MetaFrames *frames; + MetaGrabOp op; + + frames = META_FRAMES (widget); + + frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window)); + if (frame == NULL) + return FALSE; + + clear_tip (frames); + + op = meta_core_get_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + + if (op == META_GRAB_OP_NONE) + return FALSE; + + /* We only handle the releases we handled the presses for (things + * involving frame controls). Window ops that don't require a + * frame are handled in the Xlib part of the code, display.c/window.c + */ + if (frame->xwindow == meta_core_get_grab_frame (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())) && + ((int) event->button) == meta_core_get_grab_button (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()))) + { + MetaFrameControl control; + + control = get_control (frames, frame, event->x, event->y); + + switch (op) + { + case META_GRAB_OP_CLICKING_MINIMIZE: + if (control == META_FRAME_CONTROL_MINIMIZE) + meta_core_minimize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_MAXIMIZE: + if (control == META_FRAME_CONTROL_MAXIMIZE) + { + /* Focus the window on the maximize */ + meta_core_user_focus (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + event->time); + meta_core_maximize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + } + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_UNMAXIMIZE: + if (control == META_FRAME_CONTROL_UNMAXIMIZE) + meta_core_unmaximize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_DELETE: + if (control == META_FRAME_CONTROL_DELETE) + meta_core_delete (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, event->time); + + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_MENU: + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_SHADE: + if (control == META_FRAME_CONTROL_SHADE) + meta_core_shade (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, event->time); + + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_UNSHADE: + if (control == META_FRAME_CONTROL_UNSHADE) + meta_core_unshade (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, event->time); + + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_ABOVE: + if (control == META_FRAME_CONTROL_ABOVE) + meta_core_make_above (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_UNABOVE: + if (control == META_FRAME_CONTROL_UNABOVE) + meta_core_unmake_above (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_STICK: + if (control == META_FRAME_CONTROL_STICK) + meta_core_stick (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_UNSTICK: + if (control == META_FRAME_CONTROL_UNSTICK) + meta_core_unstick (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + default: + break; + } + + /* Update the prelit control regardless of what button the mouse + * was released over; needed so that the new button can become + * prelit so to let the user know that it can now be pressed. + * :) + */ + meta_frames_update_prelit_control (frames, frame, control); + } + + return TRUE; +} + +static void +meta_frames_update_prelit_control (MetaFrames *frames, + MetaUIFrame *frame, + MetaFrameControl control) +{ + MetaFrameControl old_control; + MetaCursor cursor; + + + meta_verbose ("Updating prelit control from %u to %u\n", + frame->prelit_control, control); + + cursor = META_CURSOR_DEFAULT; + + switch (control) + { + case META_FRAME_CONTROL_CLIENT_AREA: + break; + case META_FRAME_CONTROL_NONE: + break; + case META_FRAME_CONTROL_TITLE: + break; + case META_FRAME_CONTROL_DELETE: + break; + case META_FRAME_CONTROL_MENU: + break; + case META_FRAME_CONTROL_MINIMIZE: + break; + case META_FRAME_CONTROL_MAXIMIZE: + break; + case META_FRAME_CONTROL_UNMAXIMIZE: + break; + case META_FRAME_CONTROL_SHADE: + break; + case META_FRAME_CONTROL_UNSHADE: + break; + case META_FRAME_CONTROL_ABOVE: + break; + case META_FRAME_CONTROL_UNABOVE: + break; + case META_FRAME_CONTROL_STICK: + break; + case META_FRAME_CONTROL_UNSTICK: + break; + case META_FRAME_CONTROL_RESIZE_SE: + cursor = META_CURSOR_SE_RESIZE; + break; + case META_FRAME_CONTROL_RESIZE_S: + cursor = META_CURSOR_SOUTH_RESIZE; + break; + case META_FRAME_CONTROL_RESIZE_SW: + cursor = META_CURSOR_SW_RESIZE; + break; + case META_FRAME_CONTROL_RESIZE_N: + cursor = META_CURSOR_NORTH_RESIZE; + break; + case META_FRAME_CONTROL_RESIZE_NE: + cursor = META_CURSOR_NE_RESIZE; + break; + case META_FRAME_CONTROL_RESIZE_NW: + cursor = META_CURSOR_NW_RESIZE; + break; + case META_FRAME_CONTROL_RESIZE_W: + cursor = META_CURSOR_WEST_RESIZE; + break; + case META_FRAME_CONTROL_RESIZE_E: + cursor = META_CURSOR_EAST_RESIZE; + break; + } + + /* set/unset the prelight cursor */ + meta_core_set_screen_cursor (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + cursor); + + switch (control) + { + case META_FRAME_CONTROL_MENU: + case META_FRAME_CONTROL_MINIMIZE: + case META_FRAME_CONTROL_MAXIMIZE: + case META_FRAME_CONTROL_DELETE: + case META_FRAME_CONTROL_SHADE: + case META_FRAME_CONTROL_UNSHADE: + case META_FRAME_CONTROL_ABOVE: + case META_FRAME_CONTROL_UNABOVE: + case META_FRAME_CONTROL_STICK: + case META_FRAME_CONTROL_UNSTICK: + case META_FRAME_CONTROL_UNMAXIMIZE: + /* leave control set */ + break; + default: + /* Only prelight buttons */ + control = META_FRAME_CONTROL_NONE; + break; + } + + if (control == frame->prelit_control) + return; + + /* Save the old control so we can unprelight it */ + old_control = frame->prelit_control; + + frame->prelit_control = control; + + redraw_control (frames, frame, old_control); + redraw_control (frames, frame, control); +} + +static gboolean +meta_frames_motion_notify_event (GtkWidget *widget, + GdkEventMotion *event) +{ + MetaUIFrame *frame; + MetaFrames *frames; + MetaGrabOp grab_op; + + frames = META_FRAMES (widget); + + frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window)); + if (frame == NULL) + return FALSE; + + clear_tip (frames); + + frames->last_motion_frame = frame; + + grab_op = meta_core_get_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + + switch (grab_op) + { + case META_GRAB_OP_CLICKING_MENU: + case META_GRAB_OP_CLICKING_DELETE: + case META_GRAB_OP_CLICKING_MINIMIZE: + case META_GRAB_OP_CLICKING_MAXIMIZE: + case META_GRAB_OP_CLICKING_UNMAXIMIZE: + case META_GRAB_OP_CLICKING_SHADE: + case META_GRAB_OP_CLICKING_UNSHADE: + case META_GRAB_OP_CLICKING_ABOVE: + case META_GRAB_OP_CLICKING_UNABOVE: + case META_GRAB_OP_CLICKING_STICK: + case META_GRAB_OP_CLICKING_UNSTICK: + { + MetaFrameControl control; + int x, y; + + gdk_window_get_pointer (frame->window, &x, &y, NULL); + + /* Control is set to none unless it matches + * the current grab + */ + control = get_control (frames, frame, x, y); + if (! ((control == META_FRAME_CONTROL_MENU && + grab_op == META_GRAB_OP_CLICKING_MENU) || + (control == META_FRAME_CONTROL_DELETE && + grab_op == META_GRAB_OP_CLICKING_DELETE) || + (control == META_FRAME_CONTROL_MINIMIZE && + grab_op == META_GRAB_OP_CLICKING_MINIMIZE) || + ((control == META_FRAME_CONTROL_MAXIMIZE || + control == META_FRAME_CONTROL_UNMAXIMIZE) && + (grab_op == META_GRAB_OP_CLICKING_MAXIMIZE || + grab_op == META_GRAB_OP_CLICKING_UNMAXIMIZE)) || + (control == META_FRAME_CONTROL_SHADE && + grab_op == META_GRAB_OP_CLICKING_SHADE) || + (control == META_FRAME_CONTROL_UNSHADE && + grab_op == META_GRAB_OP_CLICKING_UNSHADE) || + (control == META_FRAME_CONTROL_ABOVE && + grab_op == META_GRAB_OP_CLICKING_ABOVE) || + (control == META_FRAME_CONTROL_UNABOVE && + grab_op == META_GRAB_OP_CLICKING_UNABOVE) || + (control == META_FRAME_CONTROL_STICK && + grab_op == META_GRAB_OP_CLICKING_STICK) || + (control == META_FRAME_CONTROL_UNSTICK && + grab_op == META_GRAB_OP_CLICKING_UNSTICK))) + control = META_FRAME_CONTROL_NONE; + + /* Update prelit control and cursor */ + meta_frames_update_prelit_control (frames, frame, control); + + /* No tooltip while in the process of clicking */ + } + break; + case META_GRAB_OP_NONE: + { + MetaFrameControl control; + int x, y; + + gdk_window_get_pointer (frame->window, &x, &y, NULL); + + control = get_control (frames, frame, x, y); + + /* Update prelit control and cursor */ + meta_frames_update_prelit_control (frames, frame, control); + + queue_tip (frames); + } + break; + + default: + break; + } + + return TRUE; +} + +static gboolean +meta_frames_destroy_event (GtkWidget *widget, + GdkEventAny *event) +{ + MetaUIFrame *frame; + MetaFrames *frames; + + frames = META_FRAMES (widget); + + frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window)); + if (frame == NULL) + return FALSE; + + return TRUE; +} + +#if !GTK_CHECK_VERSION(2,21,6) +/* Copied from GDK */ +static cairo_surface_t * +_gdk_drawable_ref_cairo_surface (GdkDrawable *drawable) +{ + g_return_val_if_fail (GDK_IS_DRAWABLE (drawable), NULL); + + return GDK_DRAWABLE_GET_CLASS (drawable)->ref_cairo_surface (drawable); +} + +static cairo_pattern_t * +gdk_window_get_background_pattern (GdkWindow *window) +{ + GdkWindowObject *private = (GdkWindowObject *) window; + cairo_pattern_t *pattern; + + g_return_val_if_fail (GDK_IS_WINDOW (window), NULL); + + if (private->bg_pixmap == GDK_PARENT_RELATIVE_BG) + pattern = NULL; + else if (private->bg_pixmap != GDK_NO_BG && + private->bg_pixmap != NULL) + { + static cairo_user_data_key_t key; + cairo_surface_t *surface; + + surface = _gdk_drawable_ref_cairo_surface (private->bg_pixmap); + pattern = cairo_pattern_create_for_surface (surface); + cairo_surface_destroy (surface); + + cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT); + cairo_pattern_set_user_data (pattern, + &key, + g_object_ref (private->bg_pixmap), + g_object_unref); + } + else + { + pattern = + cairo_pattern_create_rgb (private->bg_color.red / 65535., + private->bg_color.green / 65535., + private->bg_color.blue / 65535.); + } + + return pattern; +} +#endif + +static void +setup_bg_cr (cairo_t *cr, GdkWindow *window, int x_offset, int y_offset) +{ + GdkWindow *parent = gdk_window_get_parent (window); + cairo_pattern_t *bg_pattern; + + bg_pattern = gdk_window_get_background_pattern (window); + if (bg_pattern == NULL && parent) + { + gint window_x, window_y; + + gdk_window_get_position (window, &window_x, &window_y); + setup_bg_cr (cr, parent, x_offset + window_x, y_offset + window_y); + } + else if (bg_pattern) + { + cairo_translate (cr, - x_offset, - y_offset); + cairo_set_source (cr, bg_pattern); + cairo_translate (cr, x_offset, y_offset); + } +} + +static void +clear_backing (GdkPixmap *pixmap, + GdkWindow *window, + int xoffset, int yoffset) +{ + int width, height; + cairo_t *cr = gdk_cairo_create (pixmap); + + setup_bg_cr (cr, window, xoffset, yoffset); + + #if GTK_CHECK_VERSION(3, 0, 0) + width = gdk_window_get_width(GDK_WINDOW(pixmap)); + height = gdk_window_get_height(GDK_WINDOW(pixmap)); + #else + gdk_drawable_get_size(GDK_DRAWABLE(pixmap), &width, &height); + #endif + + cairo_rectangle (cr, 0, 0, width, height); + cairo_fill (cr); + + cairo_destroy (cr); +} + +/* Returns a pixmap with a piece of the windows frame painted on it. +*/ + +static GdkPixmap * +generate_pixmap (MetaFrames *frames, + MetaUIFrame *frame, + MetaRectangle rect) +{ + GdkRectangle rectangle; + GdkRegion *region; + GdkPixmap *result; + + rectangle.x = rect.x; + rectangle.y = rect.y; + rectangle.width = MAX (rect.width, 1); + rectangle.height = MAX (rect.height, 1); + + result = gdk_pixmap_new (frame->window, + rectangle.width, rectangle.height, -1); + + clear_backing (result, frame->window, rectangle.x, rectangle.y); + + region = gdk_region_rectangle (&rectangle); + + meta_frames_paint_to_drawable (frames, frame, result, region, + -rectangle.x, -rectangle.y); + + gdk_region_destroy (region); + + return result; +} + + +static void +populate_cache (MetaFrames *frames, + MetaUIFrame *frame) +{ + int top, bottom, left, right; + int width, height; + int frame_width, frame_height, screen_width, screen_height; + CachedPixels *pixels; + MetaFrameType frame_type; + MetaFrameFlags frame_flags; + int i; + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_WIDTH, &frame_width, + META_CORE_GET_FRAME_HEIGHT, &frame_height, + META_CORE_GET_SCREEN_WIDTH, &screen_width, + META_CORE_GET_SCREEN_HEIGHT, &screen_height, + META_CORE_GET_CLIENT_WIDTH, &width, + META_CORE_GET_CLIENT_HEIGHT, &height, + META_CORE_GET_FRAME_TYPE, &frame_type, + META_CORE_GET_FRAME_FLAGS, &frame_flags, + META_CORE_GET_END); + + /* don't cache extremely large windows */ + if (frame_width > 2 * screen_width || + frame_height > 2 * screen_height) + { + return; + } + + meta_theme_get_frame_borders (meta_theme_get_current (), + frame_type, + frame->text_height, + frame_flags, + &top, &bottom, &left, &right); + + pixels = get_cache (frames, frame); + + /* Setup the rectangles for the four frame borders. First top, then + left, right and bottom. */ + pixels->piece[0].rect.x = 0; + pixels->piece[0].rect.y = 0; + pixels->piece[0].rect.width = left + width + right; + pixels->piece[0].rect.height = top; + + pixels->piece[1].rect.x = 0; + pixels->piece[1].rect.y = top; + pixels->piece[1].rect.width = left; + pixels->piece[1].rect.height = height; + + pixels->piece[2].rect.x = left + width; + pixels->piece[2].rect.y = top; + pixels->piece[2].rect.width = right; + pixels->piece[2].rect.height = height; + + pixels->piece[3].rect.x = 0; + pixels->piece[3].rect.y = top + height; + pixels->piece[3].rect.width = left + width + right; + pixels->piece[3].rect.height = bottom; + + for (i = 0; i < 4; i++) + { + CachedFramePiece *piece = &pixels->piece[i]; + if (!piece->pixmap) + piece->pixmap = generate_pixmap (frames, frame, piece->rect); + } + + if (frames->invalidate_cache_timeout_id) + g_source_remove (frames->invalidate_cache_timeout_id); + + frames->invalidate_cache_timeout_id = g_timeout_add (1000, invalidate_cache_timeout, frames); + + if (!g_list_find (frames->invalidate_frames, frame)) + frames->invalidate_frames = + g_list_prepend (frames->invalidate_frames, frame); +} + +static void +clip_to_screen (GdkRegion *region, MetaUIFrame *frame) +{ + GdkRectangle frame_area; + GdkRectangle screen_area = { 0, 0, 0, 0 }; + GdkRegion *tmp_region; + + /* Chop off stuff outside the screen; this optimization + * is crucial to handle huge client windows, + * like "xterm -geometry 1000x1000" + */ + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_X, &frame_area.x, + META_CORE_GET_FRAME_Y, &frame_area.y, + META_CORE_GET_FRAME_WIDTH, &frame_area.width, + META_CORE_GET_FRAME_HEIGHT, &frame_area.height, + META_CORE_GET_SCREEN_WIDTH, &screen_area.height, + META_CORE_GET_SCREEN_HEIGHT, &screen_area.height, + META_CORE_GET_END); + + gdk_region_offset (region, frame_area.x, frame_area.y); + + tmp_region = gdk_region_rectangle (&frame_area); + gdk_region_intersect (region, tmp_region); + gdk_region_destroy (tmp_region); + + gdk_region_offset (region, - frame_area.x, - frame_area.y); +} + +static void +subtract_from_region (GdkRegion *region, GdkDrawable *drawable, + gint x, gint y) +{ + GdkRectangle rect; + GdkRegion *reg_rect; + + #if GTK_CHECK_VERSION(3, 0, 0) + rect.width = gdk_window_get_width(GDK_WINDOW(drawable)); + rect.height = gdk_window_get_height(GDK_WINDOW(drawable)); + #else + gdk_drawable_get_size (drawable, &rect.width, &rect.height); + #endif + + rect.x = x; + rect.y = y; + + reg_rect = gdk_region_rectangle (&rect); + gdk_region_subtract (region, reg_rect); + gdk_region_destroy (reg_rect); +} + +static void +cached_pixels_draw (CachedPixels *pixels, + GdkWindow *window, + GdkRegion *region) +{ + cairo_t *cr; + int i; + + cr = gdk_cairo_create (window); + + for (i = 0; i < 4; i++) + { + CachedFramePiece *piece; + piece = &pixels->piece[i]; + + if (piece->pixmap) + { + gdk_cairo_set_source_pixmap (cr, piece->pixmap, + piece->rect.x, piece->rect.y); + cairo_paint (cr); + subtract_from_region (region, piece->pixmap, + piece->rect.x, piece->rect.y); + } + } + + cairo_destroy (cr); +} + +static gboolean +meta_frames_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + MetaUIFrame *frame; + MetaFrames *frames; + GdkRegion *region; + CachedPixels *pixels; + + frames = META_FRAMES (widget); + + frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window)); + if (frame == NULL) + return FALSE; + + if (frames->expose_delay_count > 0) + { + /* Redraw this entire frame later */ + frame->expose_delayed = TRUE; + return TRUE; + } + + populate_cache (frames, frame); + + region = gdk_region_copy (event->region); + + pixels = get_cache (frames, frame); + + cached_pixels_draw (pixels, frame->window, region); + + clip_to_screen (region, frame); + meta_frames_paint_to_drawable (frames, frame, frame->window, region, 0, 0); + + gdk_region_destroy (region); + + return TRUE; +} + +/* How far off the screen edge the window decorations should + * be drawn. Used only in meta_frames_paint_to_drawable, below. + */ +#define DECORATING_BORDER 100 + +static void +meta_frames_paint_to_drawable (MetaFrames *frames, + MetaUIFrame *frame, + GdkDrawable *drawable, + GdkRegion *region, + int x_offset, + int y_offset) +{ + GtkWidget *widget; + MetaFrameFlags flags; + MetaFrameType type; + GdkPixbuf *mini_icon; + GdkPixbuf *icon; + int w, h; + MetaButtonState button_states[META_BUTTON_TYPE_LAST]; + Window grab_frame; + int i; + MetaButtonLayout button_layout; + MetaGrabOp grab_op; + + widget = GTK_WIDGET (frames); + + for (i = 0; i < META_BUTTON_TYPE_LAST; i++) + button_states[i] = META_BUTTON_STATE_NORMAL; + + grab_frame = meta_core_get_grab_frame (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + grab_op = meta_core_get_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + if (grab_frame != frame->xwindow) + grab_op = META_GRAB_OP_NONE; + + /* Set prelight state */ + switch (frame->prelit_control) + { + case META_FRAME_CONTROL_MENU: + if (grab_op == META_GRAB_OP_CLICKING_MENU) + button_states[META_BUTTON_TYPE_MENU] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_MENU] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_MINIMIZE: + if (grab_op == META_GRAB_OP_CLICKING_MINIMIZE) + button_states[META_BUTTON_TYPE_MINIMIZE] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_MINIMIZE] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_MAXIMIZE: + if (grab_op == META_GRAB_OP_CLICKING_MAXIMIZE) + button_states[META_BUTTON_TYPE_MAXIMIZE] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_MAXIMIZE] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_UNMAXIMIZE: + if (grab_op == META_GRAB_OP_CLICKING_UNMAXIMIZE) + button_states[META_BUTTON_TYPE_MAXIMIZE] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_MAXIMIZE] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_SHADE: + if (grab_op == META_GRAB_OP_CLICKING_SHADE) + button_states[META_BUTTON_TYPE_SHADE] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_SHADE] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_UNSHADE: + if (grab_op == META_GRAB_OP_CLICKING_UNSHADE) + button_states[META_BUTTON_TYPE_UNSHADE] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_UNSHADE] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_ABOVE: + if (grab_op == META_GRAB_OP_CLICKING_ABOVE) + button_states[META_BUTTON_TYPE_ABOVE] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_ABOVE] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_UNABOVE: + if (grab_op == META_GRAB_OP_CLICKING_UNABOVE) + button_states[META_BUTTON_TYPE_UNABOVE] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_UNABOVE] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_STICK: + if (grab_op == META_GRAB_OP_CLICKING_STICK) + button_states[META_BUTTON_TYPE_STICK] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_STICK] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_UNSTICK: + if (grab_op == META_GRAB_OP_CLICKING_UNSTICK) + button_states[META_BUTTON_TYPE_UNSTICK] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_UNSTICK] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_DELETE: + if (grab_op == META_GRAB_OP_CLICKING_DELETE) + button_states[META_BUTTON_TYPE_CLOSE] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_CLOSE] = META_BUTTON_STATE_PRELIGHT; + break; + default: + break; + } + + /* Map button function states to button position states */ + button_states[META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND] = + button_states[META_BUTTON_TYPE_MENU]; + button_states[META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND] = + META_BUTTON_STATE_NORMAL; + button_states[META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND] = + META_BUTTON_STATE_NORMAL; + button_states[META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND] = + button_states[META_BUTTON_TYPE_MINIMIZE]; + button_states[META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND] = + button_states[META_BUTTON_TYPE_MAXIMIZE]; + button_states[META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND] = + button_states[META_BUTTON_TYPE_CLOSE]; + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_FRAME_TYPE, &type, + META_CORE_GET_MINI_ICON, &mini_icon, + META_CORE_GET_ICON, &icon, + META_CORE_GET_CLIENT_WIDTH, &w, + META_CORE_GET_CLIENT_HEIGHT, &h, + META_CORE_GET_END); + + meta_frames_ensure_layout (frames, frame); + + meta_prefs_get_button_layout (&button_layout); + + if (G_LIKELY (GDK_IS_WINDOW (drawable))) + { + /* A window; happens about 2/3 of the time */ + + GdkRectangle area, *areas; + int n_areas; + int screen_width, screen_height; + GdkRegion *edges, *tmp_region; + int top, bottom, left, right; + + /* Repaint each side of the frame */ + + meta_theme_get_frame_borders (meta_theme_get_current (), + type, frame->text_height, flags, + &top, &bottom, &left, &right); + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_SCREEN_WIDTH, &screen_width, + META_CORE_GET_SCREEN_HEIGHT, &screen_height, + META_CORE_GET_END); + + edges = gdk_region_copy (region); + + /* Punch out the client area */ + + area.x = left; + area.y = top; + area.width = w; + area.height = h; + tmp_region = gdk_region_rectangle (&area); + gdk_region_subtract (edges, tmp_region); + gdk_region_destroy (tmp_region); + + /* Now draw remaining portion of region */ + + gdk_region_get_rectangles (edges, &areas, &n_areas); + + for (i = 0; i < n_areas; i++) + { + /* Bug 399529: clamp areas[i] so that it doesn't go too far + * off the edge of the screen. This works around a GDK bug + * which makes gdk_window_begin_paint_rect cause an X error + * if the window is insanely huge. If the client is a GDK program + * and does this, it will still probably cause an X error in that + * program, but the last thing we want is for Marco to crash + * because it attempted to decorate the silly window. + */ + + areas[i].x = MAX (areas[i].x, -DECORATING_BORDER); + areas[i].y = MAX (areas[i].y, -DECORATING_BORDER); + if (areas[i].x+areas[i].width > screen_width + DECORATING_BORDER) + areas[i].width = MIN (0, screen_width - areas[i].x); + if (areas[i].y+areas[i].height > screen_height + DECORATING_BORDER) + areas[i].height = MIN (0, screen_height - areas[i].y); + + /* Okay, so let's start painting. */ + + gdk_window_begin_paint_rect (drawable, &areas[i]); + + meta_theme_draw_frame_with_style (meta_theme_get_current (), + frame->style, + widget, + drawable, + NULL, /* &areas[i], */ + x_offset, y_offset, + type, + flags, + w, h, + frame->layout, + frame->text_height, + &button_layout, + button_states, + mini_icon, icon); + + gdk_window_end_paint (drawable); + } + + g_free (areas); + gdk_region_destroy (edges); + + } + else + { + /* Not a window; happens about 1/3 of the time */ + + meta_theme_draw_frame_with_style (meta_theme_get_current (), + frame->style, + widget, + drawable, + NULL, + x_offset, y_offset, + type, + flags, + w, h, + frame->layout, + frame->text_height, + &button_layout, + button_states, + mini_icon, icon); + } + +} + +static void +meta_frames_set_window_background (MetaFrames *frames, + MetaUIFrame *frame) +{ + MetaFrameFlags flags; + MetaFrameType type; + MetaFrameStyle *style; + gboolean frame_exists; + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_WINDOW_HAS_FRAME, &frame_exists, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_FRAME_TYPE, &type, + META_CORE_GET_END); + + if (frame_exists) + { + style = meta_theme_get_frame_style (meta_theme_get_current (), + type, flags); + } + + if (frame_exists && style->window_background_color != NULL) + { + GdkColor color; + GdkVisual *visual; + + meta_color_spec_render (style->window_background_color, + GTK_WIDGET (frames), + &color); + + /* Set A in ARGB to window_background_alpha, if we have ARGB */ + + visual = gtk_widget_get_visual (GTK_WIDGET (frames)); + if (visual->depth == 32) /* we have ARGB */ + { + color.pixel = (color.pixel & 0xffffff) & + style->window_background_alpha << 24; + } + + gdk_window_set_background (frame->window, &color); + } + else + { + gtk_style_set_background (frame->style, + frame->window, GTK_STATE_NORMAL); + } + } + +static gboolean +meta_frames_enter_notify_event (GtkWidget *widget, + GdkEventCrossing *event) +{ + MetaUIFrame *frame; + MetaFrames *frames; + MetaFrameControl control; + + frames = META_FRAMES (widget); + + frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window)); + if (frame == NULL) + return FALSE; + + control = get_control (frames, frame, event->x, event->y); + meta_frames_update_prelit_control (frames, frame, control); + + return TRUE; +} + +static gboolean +meta_frames_leave_notify_event (GtkWidget *widget, + GdkEventCrossing *event) +{ + MetaUIFrame *frame; + MetaFrames *frames; + + frames = META_FRAMES (widget); + + frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window)); + if (frame == NULL) + return FALSE; + + meta_frames_update_prelit_control (frames, frame, META_FRAME_CONTROL_NONE); + + clear_tip (frames); + + return TRUE; +} + +static GdkRectangle* +control_rect (MetaFrameControl control, + MetaFrameGeometry *fgeom) +{ + GdkRectangle *rect; + + rect = NULL; + switch (control) + { + case META_FRAME_CONTROL_TITLE: + rect = &fgeom->title_rect; + break; + case META_FRAME_CONTROL_DELETE: + rect = &fgeom->close_rect.visible; + break; + case META_FRAME_CONTROL_MENU: + rect = &fgeom->menu_rect.visible; + break; + case META_FRAME_CONTROL_MINIMIZE: + rect = &fgeom->min_rect.visible; + break; + case META_FRAME_CONTROL_MAXIMIZE: + case META_FRAME_CONTROL_UNMAXIMIZE: + rect = &fgeom->max_rect.visible; + break; + case META_FRAME_CONTROL_SHADE: + rect = &fgeom->shade_rect.visible; + break; + case META_FRAME_CONTROL_UNSHADE: + rect = &fgeom->unshade_rect.visible; + break; + case META_FRAME_CONTROL_ABOVE: + rect = &fgeom->above_rect.visible; + break; + case META_FRAME_CONTROL_UNABOVE: + rect = &fgeom->unabove_rect.visible; + break; + case META_FRAME_CONTROL_STICK: + rect = &fgeom->stick_rect.visible; + break; + case META_FRAME_CONTROL_UNSTICK: + rect = &fgeom->unstick_rect.visible; + break; + case META_FRAME_CONTROL_RESIZE_SE: + break; + case META_FRAME_CONTROL_RESIZE_S: + break; + case META_FRAME_CONTROL_RESIZE_SW: + break; + case META_FRAME_CONTROL_RESIZE_N: + break; + case META_FRAME_CONTROL_RESIZE_NE: + break; + case META_FRAME_CONTROL_RESIZE_NW: + break; + case META_FRAME_CONTROL_RESIZE_W: + break; + case META_FRAME_CONTROL_RESIZE_E: + break; + case META_FRAME_CONTROL_NONE: + break; + case META_FRAME_CONTROL_CLIENT_AREA: + break; + } + + return rect; +} + +#define RESIZE_EXTENDS 15 +#define TOP_RESIZE_HEIGHT 2 +static MetaFrameControl +get_control (MetaFrames *frames, + MetaUIFrame *frame, + int x, int y) +{ + MetaFrameGeometry fgeom; + MetaFrameFlags flags; + gboolean has_vert, has_horiz; + GdkRectangle client; + + meta_frames_calc_geometry (frames, frame, &fgeom); + + client.x = fgeom.left_width; + client.y = fgeom.top_height; + client.width = fgeom.width - fgeom.left_width - fgeom.right_width; + client.height = fgeom.height - fgeom.top_height - fgeom.bottom_height; + + if (POINT_IN_RECT (x, y, client)) + return META_FRAME_CONTROL_CLIENT_AREA; + + if (POINT_IN_RECT (x, y, fgeom.close_rect.clickable)) + return META_FRAME_CONTROL_DELETE; + + if (POINT_IN_RECT (x, y, fgeom.min_rect.clickable)) + return META_FRAME_CONTROL_MINIMIZE; + + if (POINT_IN_RECT (x, y, fgeom.menu_rect.clickable)) + return META_FRAME_CONTROL_MENU; + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_END); + + has_vert = (flags & META_FRAME_ALLOWS_VERTICAL_RESIZE) != 0; + has_horiz = (flags & META_FRAME_ALLOWS_HORIZONTAL_RESIZE) != 0; + + if (POINT_IN_RECT (x, y, fgeom.title_rect)) + { + if (has_vert && y <= TOP_RESIZE_HEIGHT) + return META_FRAME_CONTROL_RESIZE_N; + else + return META_FRAME_CONTROL_TITLE; + } + + if (POINT_IN_RECT (x, y, fgeom.max_rect.clickable)) + { + if (flags & META_FRAME_MAXIMIZED) + return META_FRAME_CONTROL_UNMAXIMIZE; + else + return META_FRAME_CONTROL_MAXIMIZE; + } + + if (POINT_IN_RECT (x, y, fgeom.shade_rect.clickable)) + { + return META_FRAME_CONTROL_SHADE; + } + + if (POINT_IN_RECT (x, y, fgeom.unshade_rect.clickable)) + { + return META_FRAME_CONTROL_UNSHADE; + } + + if (POINT_IN_RECT (x, y, fgeom.above_rect.clickable)) + { + return META_FRAME_CONTROL_ABOVE; + } + + if (POINT_IN_RECT (x, y, fgeom.unabove_rect.clickable)) + { + return META_FRAME_CONTROL_UNABOVE; + } + + if (POINT_IN_RECT (x, y, fgeom.stick_rect.clickable)) + { + return META_FRAME_CONTROL_STICK; + } + + if (POINT_IN_RECT (x, y, fgeom.unstick_rect.clickable)) + { + return META_FRAME_CONTROL_UNSTICK; + } + + /* South resize always has priority over north resize, + * in case of overlap. + */ + + if (y >= (fgeom.height - fgeom.bottom_height - RESIZE_EXTENDS) && + x >= (fgeom.width - fgeom.right_width - RESIZE_EXTENDS)) + { + if (has_vert && has_horiz) + return META_FRAME_CONTROL_RESIZE_SE; + else if (has_vert) + return META_FRAME_CONTROL_RESIZE_S; + else if (has_horiz) + return META_FRAME_CONTROL_RESIZE_E; + } + else if (y >= (fgeom.height - fgeom.bottom_height - RESIZE_EXTENDS) && + x <= (fgeom.left_width + RESIZE_EXTENDS)) + { + if (has_vert && has_horiz) + return META_FRAME_CONTROL_RESIZE_SW; + else if (has_vert) + return META_FRAME_CONTROL_RESIZE_S; + else if (has_horiz) + return META_FRAME_CONTROL_RESIZE_W; + } + else if (y < (fgeom.top_height + RESIZE_EXTENDS) && + x < RESIZE_EXTENDS) + { + if (has_vert && has_horiz) + return META_FRAME_CONTROL_RESIZE_NW; + else if (has_vert) + return META_FRAME_CONTROL_RESIZE_N; + else if (has_horiz) + return META_FRAME_CONTROL_RESIZE_W; + } + else if (y < (fgeom.top_height + RESIZE_EXTENDS) && + x >= (fgeom.width - RESIZE_EXTENDS)) + { + if (has_vert && has_horiz) + return META_FRAME_CONTROL_RESIZE_NE; + else if (has_vert) + return META_FRAME_CONTROL_RESIZE_N; + else if (has_horiz) + return META_FRAME_CONTROL_RESIZE_E; + } + else if (y >= (fgeom.height - fgeom.bottom_height - RESIZE_EXTENDS)) + { + if (has_vert) + return META_FRAME_CONTROL_RESIZE_S; + } + else if (y <= TOP_RESIZE_HEIGHT) + { + if (has_vert) + return META_FRAME_CONTROL_RESIZE_N; + else if (has_horiz) + return META_FRAME_CONTROL_TITLE; + } + else if (x <= fgeom.left_width) + { + if (has_horiz) + return META_FRAME_CONTROL_RESIZE_W; + } + else if (x >= (fgeom.width - fgeom.right_width)) + { + if (has_horiz) + return META_FRAME_CONTROL_RESIZE_E; + } + + if (y >= fgeom.top_height) + return META_FRAME_CONTROL_NONE; + else + return META_FRAME_CONTROL_TITLE; +} + +void +meta_frames_push_delay_exposes (MetaFrames *frames) +{ + if (frames->expose_delay_count == 0) + { + /* Make sure we've repainted things */ + gdk_window_process_all_updates (); + XFlush (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + } + + frames->expose_delay_count += 1; +} + +static void +queue_pending_exposes_func (gpointer key, gpointer value, gpointer data) +{ + MetaUIFrame *frame; + MetaFrames *frames; + + frames = META_FRAMES (data); + frame = value; + + if (frame->expose_delayed) + { + invalidate_whole_window (frames, frame); + frame->expose_delayed = FALSE; + } +} + +void +meta_frames_pop_delay_exposes (MetaFrames *frames) +{ + g_return_if_fail (frames->expose_delay_count > 0); + + frames->expose_delay_count -= 1; + + if (frames->expose_delay_count == 0) + { + g_hash_table_foreach (frames->frames, + queue_pending_exposes_func, + frames); + } +} + +static void +invalidate_whole_window (MetaFrames *frames, + MetaUIFrame *frame) +{ + gdk_window_invalidate_rect (frame->window, NULL, FALSE); + invalidate_cache (frames, frame); +} diff --git a/src/ui/frames.h b/src/ui/frames.h new file mode 100644 index 00000000..e9da4714 --- /dev/null +++ b/src/ui/frames.h @@ -0,0 +1,163 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco window frame manager widget */ + +/* + * 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. + */ + +#ifndef META_FRAMES_H +#define META_FRAMES_H + +#include <gtk/gtk.h> +#include <gdk/gdkx.h> +#include "common.h" +#include "theme.h" + +typedef enum +{ + META_FRAME_CONTROL_NONE, + META_FRAME_CONTROL_TITLE, + META_FRAME_CONTROL_DELETE, + META_FRAME_CONTROL_MENU, + META_FRAME_CONTROL_MINIMIZE, + META_FRAME_CONTROL_MAXIMIZE, + META_FRAME_CONTROL_UNMAXIMIZE, + META_FRAME_CONTROL_SHADE, + META_FRAME_CONTROL_UNSHADE, + META_FRAME_CONTROL_ABOVE, + META_FRAME_CONTROL_UNABOVE, + META_FRAME_CONTROL_STICK, + META_FRAME_CONTROL_UNSTICK, + META_FRAME_CONTROL_RESIZE_SE, + META_FRAME_CONTROL_RESIZE_S, + META_FRAME_CONTROL_RESIZE_SW, + META_FRAME_CONTROL_RESIZE_N, + META_FRAME_CONTROL_RESIZE_NE, + META_FRAME_CONTROL_RESIZE_NW, + META_FRAME_CONTROL_RESIZE_W, + META_FRAME_CONTROL_RESIZE_E, + META_FRAME_CONTROL_CLIENT_AREA +} MetaFrameControl; + +/* This is one widget that manages all the window frames + * as subwindows. + */ + +#define META_TYPE_FRAMES (meta_frames_get_type ()) +#define META_FRAMES(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_FRAMES, MetaFrames)) +#define META_FRAMES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), META_TYPE_FRAMES, MetaFramesClass)) +#define META_IS_FRAMES(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), META_TYPE_FRAMES)) +#define META_IS_FRAMES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), META_TYPE_FRAMES)) +#define META_FRAMES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), META_TYPE_FRAMES, MetaFramesClass)) + +typedef struct _MetaFrames MetaFrames; +typedef struct _MetaFramesClass MetaFramesClass; + +typedef struct _MetaUIFrame MetaUIFrame; + +struct _MetaUIFrame +{ + Window xwindow; + GdkWindow *window; + GtkStyle *style; + MetaFrameStyle *cache_style; + PangoLayout *layout; + int text_height; + char *title; /* NULL once we have a layout */ + guint expose_delayed : 1; + guint shape_applied : 1; + + /* FIXME get rid of this, it can just be in the MetaFrames struct */ + MetaFrameControl prelit_control; +}; + +struct _MetaFrames +{ + GtkWindow parent_instance; + + GHashTable *text_heights; + + GHashTable *frames; + + guint tooltip_timeout; + MetaUIFrame *last_motion_frame; + + int expose_delay_count; + + int invalidate_cache_timeout_id; + GList *invalidate_frames; + GHashTable *cache; +}; + +struct _MetaFramesClass +{ + GtkWindowClass parent_class; + +}; + +GType meta_frames_get_type (void) G_GNUC_CONST; + +MetaFrames *meta_frames_new (int screen_number); + +void meta_frames_manage_window (MetaFrames *frames, + Window xwindow, + GdkWindow *window); +void meta_frames_unmanage_window (MetaFrames *frames, + Window xwindow); +void meta_frames_set_title (MetaFrames *frames, + Window xwindow, + const char *title); + +void meta_frames_repaint_frame (MetaFrames *frames, + Window xwindow); + +void meta_frames_get_geometry (MetaFrames *frames, + Window xwindow, + int *top_height, int *bottom_height, + int *left_width, int *right_width); + +void meta_frames_reset_bg (MetaFrames *frames, + Window xwindow); +void meta_frames_unflicker_bg (MetaFrames *frames, + Window xwindow, + int target_width, + int target_height); + +void meta_frames_apply_shapes (MetaFrames *frames, + Window xwindow, + int new_window_width, + int new_window_height, + gboolean window_has_shape); +void meta_frames_move_resize_frame (MetaFrames *frames, + Window xwindow, + int x, + int y, + int width, + int height); +void meta_frames_queue_draw (MetaFrames *frames, + Window xwindow); + +void meta_frames_notify_menu_hide (MetaFrames *frames); + +Window meta_frames_get_moving_frame (MetaFrames *frames); + +void meta_frames_push_delay_exposes (MetaFrames *frames); +void meta_frames_pop_delay_exposes (MetaFrames *frames); + +#endif diff --git a/src/ui/gradient.c b/src/ui/gradient.c new file mode 100644 index 00000000..0e739817 --- /dev/null +++ b/src/ui/gradient.c @@ -0,0 +1,842 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco gradient rendering */ + +/* + * Copyright (C) 2001 Havoc Pennington, 99% copied from wrlib in + * WindowMaker, Copyright (C) 1997-2000 Dan Pascu and Alfredo Kojima + * Copyright (C) 2005 Elijah Newren + * + * 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 "gradient.h" +#include "util.h" +#include <string.h> + +/* This is all Alfredo's and Dan's usual very nice WindowMaker code, + * slightly GTK-ized + */ +static GdkPixbuf* meta_gradient_create_horizontal (int width, + int height, + const GdkColor *from, + const GdkColor *to); +static GdkPixbuf* meta_gradient_create_vertical (int width, + int height, + const GdkColor *from, + const GdkColor *to); +static GdkPixbuf* meta_gradient_create_diagonal (int width, + int height, + const GdkColor *from, + const GdkColor *to); +static GdkPixbuf* meta_gradient_create_multi_horizontal (int width, + int height, + const GdkColor *colors, + int count); +static GdkPixbuf* meta_gradient_create_multi_vertical (int width, + int height, + const GdkColor *colors, + int count); +static GdkPixbuf* meta_gradient_create_multi_diagonal (int width, + int height, + const GdkColor *colors, + int count); + + +/* Used as the destroy notification function for gdk_pixbuf_new() */ +static void +free_buffer (guchar *pixels, gpointer data) +{ + g_free (pixels); +} + +static GdkPixbuf* +blank_pixbuf (int width, int height, gboolean no_padding) +{ + guchar *buf; + int rowstride; + + g_return_val_if_fail (width > 0, NULL); + g_return_val_if_fail (height > 0, NULL); + + if (no_padding) + rowstride = width * 3; + else + /* Always align rows to 32-bit boundaries */ + rowstride = 4 * ((3 * width + 3) / 4); + + buf = g_try_malloc (height * rowstride); + if (!buf) + return NULL; + + return gdk_pixbuf_new_from_data (buf, GDK_COLORSPACE_RGB, + FALSE, 8, + width, height, rowstride, + free_buffer, NULL); +} + +GdkPixbuf* +meta_gradient_create_simple (int width, + int height, + const GdkColor *from, + const GdkColor *to, + MetaGradientType style) +{ + switch (style) + { + case META_GRADIENT_HORIZONTAL: + return meta_gradient_create_horizontal (width, height, + from, to); + case META_GRADIENT_VERTICAL: + return meta_gradient_create_vertical (width, height, + from, to); + + case META_GRADIENT_DIAGONAL: + return meta_gradient_create_diagonal (width, height, + from, to); + case META_GRADIENT_LAST: + break; + } + g_assert_not_reached (); + return NULL; +} + +GdkPixbuf* +meta_gradient_create_multi (int width, + int height, + const GdkColor *colors, + int n_colors, + MetaGradientType style) +{ + + if (n_colors > 2) + { + switch (style) + { + case META_GRADIENT_HORIZONTAL: + return meta_gradient_create_multi_horizontal (width, height, colors, n_colors); + case META_GRADIENT_VERTICAL: + 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) + { + return meta_gradient_create_simple (width, height, &colors[0], &colors[1], + style); + } + else if (n_colors > 0) + { + return meta_gradient_create_simple (width, height, &colors[0], &colors[0], + style); + } + g_assert_not_reached (); + return NULL; +} + +/* Interwoven essentially means we have two vertical gradients, + * cut into horizontal strips of the given thickness, and then the strips + * are alternated. I'm not sure what it's good for, just copied since + * WindowMaker had it. + */ +GdkPixbuf* +meta_gradient_create_interwoven (int width, + int height, + const GdkColor colors1[2], + int thickness1, + const GdkColor colors2[2], + int thickness2) +{ + + int i, j, k, l, ll; + long r1, g1, b1, dr1, dg1, db1; + long r2, g2, b2, dr2, dg2, db2; + GdkPixbuf *pixbuf; + unsigned char *ptr; + unsigned char *pixels; + int rowstride; + + pixbuf = blank_pixbuf (width, height, FALSE); + if (pixbuf == NULL) + return NULL; + + pixels = gdk_pixbuf_get_pixels (pixbuf); + rowstride = gdk_pixbuf_get_rowstride (pixbuf); + + r1 = colors1[0].red<<8; + g1 = colors1[0].green<<8; + b1 = colors1[0].blue<<8; + + r2 = colors2[0].red<<8; + g2 = colors2[0].green<<8; + b2 = colors2[0].blue<<8; + + dr1 = ((colors1[1].red-colors1[0].red)<<8)/(int)height; + dg1 = ((colors1[1].green-colors1[0].green)<<8)/(int)height; + db1 = ((colors1[1].blue-colors1[0].blue)<<8)/(int)height; + + dr2 = ((colors2[1].red-colors2[0].red)<<8)/(int)height; + dg2 = ((colors2[1].green-colors2[0].green)<<8)/(int)height; + db2 = ((colors2[1].blue-colors2[0].blue)<<8)/(int)height; + + for (i=0,k=0,l=0,ll=thickness1; i<height; i++) + { + ptr = pixels + i * rowstride; + + if (k == 0) + { + ptr[0] = (unsigned char) (r1>>16); + ptr[1] = (unsigned char) (g1>>16); + ptr[2] = (unsigned char) (b1>>16); + } + else + { + ptr[0] = (unsigned char) (r2>>16); + ptr[1] = (unsigned char) (g2>>16); + ptr[2] = (unsigned char) (b2>>16); + } + + for (j=1; j <= width/2; j *= 2) + memcpy (&(ptr[j*3]), ptr, j*3); + memcpy (&(ptr[j*3]), ptr, (width - j)*3); + + if (++l == ll) + { + if (k == 0) + { + k = 1; + ll = thickness2; + } + else + { + k = 0; + ll = thickness1; + } + l = 0; + } + r1+=dr1; + g1+=dg1; + b1+=db1; + + r2+=dr2; + g2+=dg2; + b2+=db2; + } + + return pixbuf; +} + +/* + *---------------------------------------------------------------------- + * meta_gradient_create_horizontal-- + * Renders a horizontal linear gradient of the specified size in the + * GdkPixbuf format with a border of the specified type. + * + * Returns: + * A 24bit GdkPixbuf with the gradient (no alpha channel). + * + * Side effects: + * None + *---------------------------------------------------------------------- + */ +static GdkPixbuf* +meta_gradient_create_horizontal (int width, int height, + const GdkColor *from, + const GdkColor *to) +{ + int i; + long r, g, b, dr, dg, db; + GdkPixbuf *pixbuf; + unsigned char *ptr; + unsigned char *pixels; + int r0, g0, b0; + int rf, gf, bf; + int rowstride; + + pixbuf = blank_pixbuf (width, height, FALSE); + if (pixbuf == NULL) + return NULL; + + pixels = gdk_pixbuf_get_pixels (pixbuf); + ptr = pixels; + rowstride = gdk_pixbuf_get_rowstride (pixbuf); + + r0 = (guchar) (from->red / 256.0); + g0 = (guchar) (from->green / 256.0); + b0 = (guchar) (from->blue / 256.0); + rf = (guchar) (to->red / 256.0); + gf = (guchar) (to->green / 256.0); + bf = (guchar) (to->blue / 256.0); + + r = r0 << 16; + g = g0 << 16; + b = b0 << 16; + + dr = ((rf-r0)<<16)/(int)width; + dg = ((gf-g0)<<16)/(int)width; + db = ((bf-b0)<<16)/(int)width; + /* render the first line */ + for (i=0; i<width; i++) + { + *(ptr++) = (unsigned char)(r>>16); + *(ptr++) = (unsigned char)(g>>16); + *(ptr++) = (unsigned char)(b>>16); + r += dr; + g += dg; + b += db; + } + + /* copy the first line to the other lines */ + for (i=1; i<height; i++) + { + memcpy (&(pixels[i*rowstride]), pixels, rowstride); + } + return pixbuf; +} + +/* + *---------------------------------------------------------------------- + * meta_gradient_create_vertical-- + * Renders a vertical linear gradient of the specified size in the + * GdkPixbuf format with a border of the specified type. + * + * Returns: + * A 24bit GdkPixbuf with the gradient (no alpha channel). + * + * Side effects: + * None + *---------------------------------------------------------------------- + */ +static GdkPixbuf* +meta_gradient_create_vertical (int width, int height, + const GdkColor *from, + const GdkColor *to) +{ + int i, j; + long r, g, b, dr, dg, db; + GdkPixbuf *pixbuf; + unsigned char *ptr; + int r0, g0, b0; + int rf, gf, bf; + int rowstride; + unsigned char *pixels; + + pixbuf = blank_pixbuf (width, height, FALSE); + if (pixbuf == NULL) + return NULL; + + pixels = gdk_pixbuf_get_pixels (pixbuf); + rowstride = gdk_pixbuf_get_rowstride (pixbuf); + + r0 = (guchar) (from->red / 256.0); + g0 = (guchar) (from->green / 256.0); + b0 = (guchar) (from->blue / 256.0); + rf = (guchar) (to->red / 256.0); + gf = (guchar) (to->green / 256.0); + bf = (guchar) (to->blue / 256.0); + + r = r0<<16; + g = g0<<16; + b = b0<<16; + + dr = ((rf-r0)<<16)/(int)height; + dg = ((gf-g0)<<16)/(int)height; + db = ((bf-b0)<<16)/(int)height; + + for (i=0; i<height; i++) + { + ptr = pixels + i * rowstride; + + ptr[0] = (unsigned char)(r>>16); + ptr[1] = (unsigned char)(g>>16); + ptr[2] = (unsigned char)(b>>16); + + for (j=1; j <= width/2; j *= 2) + memcpy (&(ptr[j*3]), ptr, j*3); + memcpy (&(ptr[j*3]), ptr, (width - j)*3); + + r+=dr; + g+=dg; + b+=db; + } + return pixbuf; +} + + +/* + *---------------------------------------------------------------------- + * meta_gradient_create_diagonal-- + * Renders a diagonal linear gradient of the specified size in the + * GdkPixbuf format with a border of the specified type. + * + * Returns: + * A 24bit GdkPixbuf with the gradient (no alpha channel). + * + * Side effects: + * None + *---------------------------------------------------------------------- + */ + + +static GdkPixbuf* +meta_gradient_create_diagonal (int width, int height, + const GdkColor *from, + const GdkColor *to) +{ + GdkPixbuf *pixbuf, *tmp; + int j; + float a, offset; + unsigned char *ptr; + unsigned char *pixels; + int rowstride; + + if (width == 1) + return meta_gradient_create_vertical (width, height, from, to); + else if (height == 1) + return meta_gradient_create_horizontal (width, height, from, to); + + pixbuf = blank_pixbuf (width, height, FALSE); + if (pixbuf == NULL) + return NULL; + + pixels = gdk_pixbuf_get_pixels (pixbuf); + rowstride = gdk_pixbuf_get_rowstride (pixbuf); + + tmp = meta_gradient_create_horizontal (2*width-1, 1, from, to); + if (!tmp) + { + g_object_unref (G_OBJECT (pixbuf)); + return NULL; + } + + ptr = gdk_pixbuf_get_pixels (tmp); + + a = ((float)(width - 1))/((float)(height - 1)); + width = width * 3; + + /* copy the first line to the other lines with corresponding offset */ + for (j=0, offset=0.0; j<rowstride*height; j += rowstride) + { + memcpy (&(pixels[j]), &ptr[3*(int)offset], width); + offset += a; + } + + g_object_unref (G_OBJECT (tmp)); + return pixbuf; +} + + +static GdkPixbuf* +meta_gradient_create_multi_horizontal (int width, int height, + const GdkColor *colors, + int count) +{ + int i, j, k; + long r, g, b, dr, dg, db; + GdkPixbuf *pixbuf; + unsigned char *ptr; + unsigned char *pixels; + int width2; + int rowstride; + + g_return_val_if_fail (count > 2, NULL); + + pixbuf = blank_pixbuf (width, height, FALSE); + if (pixbuf == NULL) + return NULL; + + pixels = gdk_pixbuf_get_pixels (pixbuf); + rowstride = gdk_pixbuf_get_rowstride (pixbuf); + ptr = pixels; + + if (count > width) + count = width; + + if (count > 1) + width2 = width/(count-1); + else + width2 = width; + + k = 0; + + r = colors[0].red << 8; + g = colors[0].green << 8; + b = colors[0].blue << 8; + + /* render the first line */ + for (i=1; i<count; i++) + { + dr = ((int)(colors[i].red - colors[i-1].red) <<8)/(int)width2; + dg = ((int)(colors[i].green - colors[i-1].green)<<8)/(int)width2; + db = ((int)(colors[i].blue - colors[i-1].blue) <<8)/(int)width2; + for (j=0; j<width2; j++) + { + *ptr++ = (unsigned char)(r>>16); + *ptr++ = (unsigned char)(g>>16); + *ptr++ = (unsigned char)(b>>16); + r += dr; + g += dg; + b += db; + k++; + } + r = colors[i].red << 8; + g = colors[i].green << 8; + b = colors[i].blue << 8; + } + for (j=k; j<width; j++) + { + *ptr++ = (unsigned char)(r>>16); + *ptr++ = (unsigned char)(g>>16); + *ptr++ = (unsigned char)(b>>16); + } + + /* copy the first line to the other lines */ + for (i=1; i<height; i++) + { + memcpy (&(pixels[i*rowstride]), pixels, rowstride); + } + return pixbuf; +} + +static GdkPixbuf* +meta_gradient_create_multi_vertical (int width, int height, + const GdkColor *colors, + int count) +{ + int i, j, k; + long r, g, b, dr, dg, db; + GdkPixbuf *pixbuf; + unsigned char *ptr, *tmp, *pixels; + int height2; + int x; + int rowstride; + + g_return_val_if_fail (count > 2, NULL); + + pixbuf = blank_pixbuf (width, height, FALSE); + if (pixbuf == NULL) + return NULL; + + pixels = gdk_pixbuf_get_pixels (pixbuf); + rowstride = gdk_pixbuf_get_rowstride (pixbuf); + ptr = pixels; + + if (count > height) + count = height; + + if (count > 1) + height2 = height/(count-1); + else + height2 = height; + + k = 0; + + r = colors[0].red << 8; + g = colors[0].green << 8; + b = colors[0].blue << 8; + + for (i=1; i<count; i++) + { + dr = ((int)(colors[i].red - colors[i-1].red) <<8)/(int)height2; + dg = ((int)(colors[i].green - colors[i-1].green)<<8)/(int)height2; + db = ((int)(colors[i].blue - colors[i-1].blue) <<8)/(int)height2; + + for (j=0; j<height2; j++) + { + ptr[0] = (unsigned char)(r>>16); + ptr[1] = (unsigned char)(g>>16); + ptr[2] = (unsigned char)(b>>16); + + for (x=1; x <= width/2; x *= 2) + memcpy (&(ptr[x*3]), ptr, x*3); + memcpy (&(ptr[x*3]), ptr, (width - x)*3); + + ptr += rowstride; + + r += dr; + g += dg; + b += db; + k++; + } + r = colors[i].red << 8; + g = colors[i].green << 8; + b = colors[i].blue << 8; + } + + if (k<height) + { + tmp = ptr; + + ptr[0] = (unsigned char) (r>>16); + ptr[1] = (unsigned char) (g>>16); + ptr[2] = (unsigned char) (b>>16); + + for (x=1; x <= width/2; x *= 2) + memcpy (&(ptr[x*3]), ptr, x*3); + memcpy (&(ptr[x*3]), ptr, (width - x)*3); + + ptr += rowstride; + + for (j=k+1; j<height; j++) + { + memcpy (ptr, tmp, rowstride); + ptr += rowstride; + } + } + + return pixbuf; +} + + +static GdkPixbuf* +meta_gradient_create_multi_diagonal (int width, int height, + const GdkColor *colors, + int count) +{ + GdkPixbuf *pixbuf, *tmp; + float a, offset; + int j; + unsigned char *ptr; + unsigned char *pixels; + int rowstride; + + g_return_val_if_fail (count > 2, NULL); + + if (width == 1) + return meta_gradient_create_multi_vertical (width, height, colors, count); + else if (height == 1) + return meta_gradient_create_multi_horizontal (width, height, colors, count); + + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, + width, height); + if (pixbuf == NULL) + return NULL; + + pixels = gdk_pixbuf_get_pixels (pixbuf); + rowstride = gdk_pixbuf_get_rowstride (pixbuf); + + if (count > width) + count = width; + if (count > height) + count = height; + + if (count > 2) + tmp = meta_gradient_create_multi_horizontal (2*width-1, 1, colors, count); + else + /* wrlib multiplies these colors by 256 before passing them in, but + * I think it's a bug in wrlib, so changed here. I could be wrong + * though, if we notice two-color multi diagonals not working. + */ + tmp = meta_gradient_create_horizontal (2*width-1, 1, + &colors[0], &colors[1]); + + if (!tmp) + { + g_object_unref (G_OBJECT (pixbuf)); + return NULL; + } + ptr = gdk_pixbuf_get_pixels (tmp); + + a = ((float)(width - 1))/((float)(height - 1)); + width = width * 3; + + /* copy the first line to the other lines with corresponding offset */ + for (j=0, offset=0; j<rowstride*height; j += rowstride) + { + memcpy (&(pixels[j]), &ptr[3*(int)offset], width); + offset += a; + } + + g_object_unref (G_OBJECT (tmp)); + return pixbuf; +} + +static void +simple_multiply_alpha (GdkPixbuf *pixbuf, + guchar alpha) +{ + guchar *pixels; + int rowstride; + int height; + int row; + + g_return_if_fail (GDK_IS_PIXBUF (pixbuf)); + + if (alpha == 255) + return; + + g_assert (gdk_pixbuf_get_has_alpha (pixbuf)); + + pixels = gdk_pixbuf_get_pixels (pixbuf); + rowstride = gdk_pixbuf_get_rowstride (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + + row = 0; + while (row < height) + { + guchar *p; + guchar *end; + + p = pixels + row * rowstride; + end = p + rowstride; + + while (p != end) + { + p += 3; /* skip RGB */ + + /* multiply the two alpha channels. not sure this is right. + * but some end cases are that if the pixbuf contains 255, + * then it should be modified to contain "alpha"; if the + * pixbuf contains 0, it should remain 0. + */ + /* ((*p / 255.0) * (alpha / 255.0)) * 255; */ + *p = (guchar) (((int) *p * (int) alpha) / (int) 255); + + ++p; /* skip A */ + } + + ++row; + } +} + +static void +meta_gradient_add_alpha_horizontal (GdkPixbuf *pixbuf, + const unsigned char *alphas, + int n_alphas) +{ + int i, j; + long a, da; + unsigned char *p; + unsigned char *pixels; + int width2; + int rowstride; + int width, height; + unsigned char *gradient; + unsigned char *gradient_p; + unsigned char *gradient_end; + + g_return_if_fail (n_alphas > 0); + + if (n_alphas == 1) + { + /* Optimize this */ + simple_multiply_alpha (pixbuf, alphas[0]); + return; + } + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + + gradient = g_new (unsigned char, width); + gradient_end = gradient + width; + + if (n_alphas > width) + n_alphas = width; + + if (n_alphas > 1) + width2 = width / (n_alphas - 1); + else + width2 = width; + + a = alphas[0] << 8; + gradient_p = gradient; + + /* render the gradient into an array */ + for (i = 1; i < n_alphas; i++) + { + da = (((int)(alphas[i] - (int) alphas[i-1])) << 8) / (int) width2; + + for (j = 0; j < width2; j++) + { + *gradient_p++ = (a >> 8); + + a += da; + } + + a = alphas[i] << 8; + } + + /* get leftover pixels */ + while (gradient_p != gradient_end) + { + *gradient_p++ = a >> 8; + } + + /* Now for each line of the pixbuf, fill in with the gradient */ + pixels = gdk_pixbuf_get_pixels (pixbuf); + rowstride = gdk_pixbuf_get_rowstride (pixbuf); + + p = pixels; + i = 0; + while (i < height) + { + unsigned char *row_end = p + rowstride; + gradient_p = gradient; + + p += 3; + while (gradient_p != gradient_end) + { + /* multiply the two alpha channels. not sure this is right. + * but some end cases are that if the pixbuf contains 255, + * then it should be modified to contain "alpha"; if the + * pixbuf contains 0, it should remain 0. + */ + /* ((*p / 255.0) * (alpha / 255.0)) * 255; */ + *p = (guchar) (((int) *p * (int) *gradient_p) / (int) 255); + + p += 4; + ++gradient_p; + } + + p = row_end; + ++i; + } + + g_free (gradient); +} + +void +meta_gradient_add_alpha (GdkPixbuf *pixbuf, + const guchar *alphas, + int n_alphas, + MetaGradientType type) +{ + g_return_if_fail (GDK_IS_PIXBUF (pixbuf)); + g_return_if_fail (gdk_pixbuf_get_has_alpha (pixbuf)); + g_return_if_fail (n_alphas > 0); + + switch (type) + { + case META_GRADIENT_HORIZONTAL: + meta_gradient_add_alpha_horizontal (pixbuf, alphas, n_alphas); + break; + + case META_GRADIENT_VERTICAL: + g_printerr ("marco: vertical alpha channel gradient not implemented yet\n"); + break; + + case META_GRADIENT_DIAGONAL: + g_printerr ("marco: diagonal alpha channel gradient not implemented yet\n"); + break; + + case META_GRADIENT_LAST: + g_assert_not_reached (); + break; + } +} diff --git a/src/ui/gradient.h b/src/ui/gradient.h new file mode 100644 index 00000000..196fd6a0 --- /dev/null +++ b/src/ui/gradient.h @@ -0,0 +1,65 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco gradient rendering */ + +/* + * Copyright (C) 2001 Havoc Pennington, 99% copied from wrlib in + * WindowMaker, Copyright (C) 1997-2000 Dan Pascu and Alfredo Kojima + * + * 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. */ + +#ifndef META_GRADIENT_H +#define META_GRADIENT_H + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gdk/gdk.h> + +typedef enum +{ + META_GRADIENT_VERTICAL, + META_GRADIENT_HORIZONTAL, + META_GRADIENT_DIAGONAL, + META_GRADIENT_LAST +} MetaGradientType; + +GdkPixbuf* meta_gradient_create_simple (int width, + int height, + const GdkColor *from, + const GdkColor *to, + MetaGradientType style); +GdkPixbuf* meta_gradient_create_multi (int width, + int height, + const GdkColor *colors, + int n_colors, + MetaGradientType style); +GdkPixbuf* meta_gradient_create_interwoven (int width, + int height, + const GdkColor colors1[2], + int thickness1, + const GdkColor colors2[2], + int thickness2); + + +/* Generate an alpha gradient and multiply it with the existing alpha + * channel of the given pixbuf + */ +void meta_gradient_add_alpha (GdkPixbuf *pixbuf, + const guchar *alphas, + int n_alphas, + MetaGradientType type); + + +#endif diff --git a/src/ui/menu.c b/src/ui/menu.c new file mode 100644 index 00000000..5207876f --- /dev/null +++ b/src/ui/menu.c @@ -0,0 +1,509 @@ +/* Marco window menu */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2004 Rob Adams + * Copyright (C) 2005 Elijah Newren + * + * 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 <stdio.h> +#include <string.h> +#include "menu.h" +#include "main.h" +#include "util.h" +#include "core.h" +#include "themewidget.h" +#include "metaaccellabel.h" +#include "ui.h" + +typedef struct _MenuItem MenuItem; +typedef struct _MenuData MenuData; + +typedef enum { + MENU_ITEM_SEPARATOR = 0, + MENU_ITEM_NORMAL, + MENU_ITEM_IMAGE, + MENU_ITEM_CHECKBOX, + MENU_ITEM_RADIOBUTTON, + MENU_ITEM_WORKSPACE_LIST, +} MetaMenuItemType; + +struct _MenuItem { + MetaMenuOp op; + MetaMenuItemType type; + const char* stock_id; + const gboolean checked; + const char* label; +}; + + +struct _MenuData { + MetaWindowMenu* menu; + MetaMenuOp op; +}; + +static void activate_cb(GtkWidget* menuitem, gpointer data); + +static MenuItem menuitems[] = { + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_MINIMIZE, MENU_ITEM_IMAGE, MARCO_STOCK_MINIMIZE, FALSE, N_("Mi_nimize")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_MAXIMIZE, MENU_ITEM_IMAGE, MARCO_STOCK_MAXIMIZE, FALSE, N_("Ma_ximize")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_UNMAXIMIZE, MENU_ITEM_IMAGE, MARCO_STOCK_RESTORE, FALSE, N_("Unma_ximize")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_SHADE, MENU_ITEM_NORMAL, NULL, FALSE, N_("Roll _Up")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_UNSHADE, MENU_ITEM_NORMAL, NULL, FALSE, N_("_Unroll")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_MOVE, MENU_ITEM_NORMAL, NULL, FALSE, N_("_Move") }, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_RESIZE, MENU_ITEM_NORMAL, NULL, FALSE, N_("_Resize")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_RECOVER, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move Titlebar On_screen")}, + {META_MENU_OP_WORKSPACES, MENU_ITEM_SEPARATOR, NULL, FALSE, NULL}, /* separator */ + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_ABOVE, MENU_ITEM_CHECKBOX, NULL, FALSE, N_("Always on _Top")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_UNABOVE, MENU_ITEM_CHECKBOX, NULL, TRUE, N_("Always on _Top")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_STICK, MENU_ITEM_RADIOBUTTON, NULL, FALSE, N_("_Always on Visible Workspace")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_UNSTICK, MENU_ITEM_RADIOBUTTON, NULL, FALSE, N_("_Only on This Workspace")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_MOVE_LEFT, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move to Workspace _Left")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_MOVE_RIGHT, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move to Workspace R_ight")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_MOVE_UP, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move to Workspace _Up")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_MOVE_DOWN, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move to Workspace _Down")}, + {0, MENU_ITEM_WORKSPACE_LIST, NULL, FALSE, NULL}, + {0, MENU_ITEM_SEPARATOR, NULL, FALSE, NULL}, /* separator */ + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_DELETE, MENU_ITEM_IMAGE, MARCO_STOCK_DELETE, FALSE, N_("_Close")} +}; + +static void popup_position_func(GtkMenu* menu, gint* x, gint* y, gboolean* push_in, gpointer user_data) +{ + GtkRequisition req; + GdkPoint* pos; + + pos = user_data; + + gtk_widget_size_request(GTK_WIDGET(menu), &req); + + *x = pos->x; + *y = pos->y; + + if (meta_ui_get_direction() == META_UI_DIRECTION_RTL) + { + *x = MAX (0, *x - req.width); + } + + /* Ensure onscreen */ + *x = CLAMP (*x, 0, MAX(0, gdk_screen_width() - req.width)); + *y = CLAMP (*y, 0, MAX(0, gdk_screen_height() - req.height)); +} + +static void menu_closed(GtkMenu* widget, gpointer data) +{ + MetaWindowMenu *menu; + + menu = data; + + meta_frames_notify_menu_hide (menu->frames); + + (*menu->func)( + menu, + GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), + menu->client_xwindow, + gtk_get_current_event_time (), + 0, 0, + menu->data); + + /* menu may now be freed */ +} + +static void activate_cb(GtkWidget* menuitem, gpointer data) +{ + MenuData* md; + + g_return_if_fail (GTK_IS_WIDGET (menuitem)); + + md = data; + + meta_frames_notify_menu_hide(md->menu->frames); + + (*md->menu->func)( + md->menu, + GDK_DISPLAY_XDISPLAY (gdk_display_get_default()), + md->menu->client_xwindow, + gtk_get_current_event_time(), + md->op, + GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem), "workspace")), + md->menu->data); + + /* menu may now be freed */ +} + +/* + * Given a Display and an index, get the workspace name and add any + * accelerators. At the moment this means adding a _ if the name is of + * the form "Workspace n" where n is less than 10, and escaping any + * other '_'s so they do not create inadvertant accelerators. + * + * The calling code owns the string, and is reponsible to free the + * memory after use. + * + * See also http://mail.gnome.org/archives/mate-i18n/2008-March/msg00380.html + * which discusses possible i18n concerns. + */ +static char* +get_workspace_name_with_accel (Display *display, + Window xroot, + int index) +{ + const char *name; + int number; + int charcount=0; + + name = meta_core_get_workspace_name_with_index (display, xroot, index); + + g_assert (name != NULL); + + /* + * If the name is of the form "Workspace x" where x is an unsigned + * integer, insert a '_' before the number if it is less than 10 and + * return it + */ + number = 0; + if (sscanf (name, _("Workspace %d%n"), &number, &charcount) != 0 && + *(name + charcount)=='\0') + { + char *new_name; + + /* + * Above name is a pointer into the Workspace struct. Here we make + * a copy copy so we can have our wicked way with it. + */ + if (number == 10) + new_name = g_strdup_printf (_("Workspace 1_0")); + else + new_name = g_strdup_printf (_("Workspace %s%d"), + number < 10 ? "_" : "", + number); + return new_name; + } + else + { + /* + * Otherwise this is just a normal name. Escape any _ characters so that + * the user's workspace names do not get mangled. If the number is less + * than 10 we provide an accelerator. + */ + char *new_name; + const char *source; + char *dest; + + /* + * Assume the worst case, that every character is a _. We also + * provide memory for " (_#)" + */ + new_name = g_malloc0 (strlen (name) * 2 + 6 + 1); + + /* + * Now iterate down the strings, adding '_' to escape as we go + */ + dest = new_name; + source = name; + while (*source != '\0') + { + if (*source == '_') + *dest++ = '_'; + *dest++ = *source++; + } + + /* People don't start at workspace 0, but workspace 1 */ + if (index < 9) + { + g_snprintf (dest, 6, " (_%d)", index + 1); + } + else if (index == 9) + { + g_snprintf (dest, 6, " (_0)"); + } + + return new_name; + } +} + +static GtkWidget* menu_item_new(MenuItem* menuitem, int workspace_id) +{ + unsigned int key; + MetaVirtualModifier mods; + const char* i18n_label; + GtkWidget* mi; + GtkWidget* accel_label; + + if (menuitem->type == MENU_ITEM_NORMAL) + { + mi = gtk_menu_item_new (); + } + else if (menuitem->type == MENU_ITEM_IMAGE) + { + GtkWidget* image = gtk_image_new_from_icon_name(menuitem->stock_id, GTK_ICON_SIZE_MENU); + + mi = gtk_image_menu_item_new(); + + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), image); + gtk_widget_show(image); + } + else if (menuitem->type == MENU_ITEM_CHECKBOX) + { + mi = gtk_check_menu_item_new (); + + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(mi), menuitem->checked); + } + else if (menuitem->type == MENU_ITEM_RADIOBUTTON) + { + mi = gtk_check_menu_item_new (); + + gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (mi), TRUE); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mi), menuitem->checked); + } + else if (menuitem->type == MENU_ITEM_WORKSPACE_LIST) + { + return NULL; + } + else + { + return gtk_separator_menu_item_new(); + } + + i18n_label = _(menuitem->label); + meta_core_get_menu_accelerator (menuitem->op, workspace_id, &key, &mods); + + accel_label = meta_accel_label_new_with_mnemonic (i18n_label); + gtk_misc_set_alignment (GTK_MISC (accel_label), 0.0, 0.5); + + gtk_container_add (GTK_CONTAINER (mi), accel_label); + gtk_widget_show (accel_label); + + meta_accel_label_set_accelerator (META_ACCEL_LABEL (accel_label), key, mods); + + return mi; +} + +MetaWindowMenu* +meta_window_menu_new (MetaFrames *frames, + MetaMenuOp ops, + MetaMenuOp insensitive, + Window client_xwindow, + unsigned long active_workspace, + int n_workspaces, + MetaWindowMenuFunc func, + gpointer data) +{ + int i; + MetaWindowMenu *menu; + + /* FIXME: Modifications to 'ops' should happen in meta_window_show_menu */ + if (n_workspaces < 2) + ops &= ~(META_MENU_OP_STICK | META_MENU_OP_UNSTICK | META_MENU_OP_WORKSPACES); + else if (n_workspaces == 2) + /* #151183: If we only have two workspaces, disable the menu listing them. */ + ops &= ~(META_MENU_OP_WORKSPACES); + + menu = g_new (MetaWindowMenu, 1); + menu->frames = frames; + menu->client_xwindow = client_xwindow; + menu->func = func; + menu->data = data; + menu->ops = ops; + menu->insensitive = insensitive; + + menu->menu = gtk_menu_new (); + + gtk_menu_set_screen (GTK_MENU (menu->menu), + gtk_widget_get_screen (GTK_WIDGET (frames))); + + for (i = 0; i < (int) G_N_ELEMENTS (menuitems); i++) + { + MenuItem menuitem = menuitems[i]; + if (ops & menuitem.op || menuitem.op == 0) + { + GtkWidget *mi; + MenuData *md; + unsigned int key; + MetaVirtualModifier mods; + + mi = menu_item_new (&menuitem, -1); + + /* Set the activeness of radiobuttons. */ + switch (menuitem.op) + { + case META_MENU_OP_STICK: + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mi), + active_workspace == 0xFFFFFFFF); + break; + case META_MENU_OP_UNSTICK: + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mi), + active_workspace != 0xFFFFFFFF); + break; + default: + break; + } + + if (menuitem.type == MENU_ITEM_WORKSPACE_LIST) + { + if (ops & META_MENU_OP_WORKSPACES) + { + Display *display; + Window xroot; + GdkScreen *screen; + GtkWidget *submenu; + int j; + + MenuItem to_another_workspace = { + 0, MENU_ITEM_NORMAL, + NULL, FALSE, + N_("Move to Another _Workspace") + }; + + meta_verbose ("Creating %d-workspace menu current space %lu\n", + n_workspaces, active_workspace); + + display = gdk_x11_drawable_get_xdisplay (GTK_WIDGET (frames)->window); + + screen = gdk_drawable_get_screen (GTK_WIDGET (frames)->window); + xroot = GDK_DRAWABLE_XID (gdk_screen_get_root_window (screen)); + + submenu = gtk_menu_new (); + + g_assert (mi==NULL); + mi = menu_item_new (&to_another_workspace, -1); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), submenu); + + for (j = 0; j < n_workspaces; j++) + { + char *label; + MenuData *md; + unsigned int key; + MetaVirtualModifier mods; + MenuItem moveitem; + GtkWidget *submi; + + meta_core_get_menu_accelerator (META_MENU_OP_WORKSPACES, + j + 1, + &key, &mods); + + label = get_workspace_name_with_accel (display, xroot, j); + + moveitem.type = MENU_ITEM_NORMAL; + moveitem.op = META_MENU_OP_WORKSPACES; + moveitem.label = label; + submi = menu_item_new (&moveitem, j + 1); + + g_free (label); + + if ((active_workspace == (unsigned)j) && (ops & META_MENU_OP_UNSTICK)) + gtk_widget_set_sensitive (submi, FALSE); + + md = g_new (MenuData, 1); + + md->menu = menu; + md->op = META_MENU_OP_WORKSPACES; + + g_object_set_data (G_OBJECT (submi), + "workspace", + GINT_TO_POINTER (j)); + + gtk_signal_connect_full (GTK_OBJECT (submi), + "activate", + G_CALLBACK (activate_cb), + NULL, + md, + g_free, FALSE, FALSE); + + gtk_menu_shell_append (GTK_MENU_SHELL (submenu), submi); + + gtk_widget_show (submi); + } + } + else + meta_verbose ("not creating workspace menu\n"); + } + else if (menuitem.type != MENU_ITEM_SEPARATOR) + { + meta_core_get_menu_accelerator (menuitems[i].op, -1, + &key, &mods); + + if (insensitive & menuitem.op) + gtk_widget_set_sensitive (mi, FALSE); + + md = g_new (MenuData, 1); + + md->menu = menu; + md->op = menuitem.op; + + gtk_signal_connect_full (GTK_OBJECT (mi), + "activate", + G_CALLBACK (activate_cb), + NULL, + md, + g_free, FALSE, FALSE); + } + + if (mi) + { + gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), mi); + + gtk_widget_show (mi); + } + } + } + + g_signal_connect (menu->menu, "selection_done", G_CALLBACK(menu_closed), menu); + + return menu; +} + +void meta_window_menu_popup(MetaWindowMenu* menu, int root_x, int root_y, int button, guint32 timestamp) +{ + GdkPoint* pt = g_new(GdkPoint, 1); + + g_object_set_data_full(G_OBJECT(menu->menu), "destroy-point", pt, g_free); + + pt->x = root_x; + pt->y = root_y; + + gtk_menu_popup(GTK_MENU (menu->menu), NULL, NULL, popup_position_func, pt, button, timestamp); + + if (!GTK_MENU_SHELL(menu->menu)->have_xgrab) + { + meta_warning("GtkMenu failed to grab the pointer\n"); + } +} + +void meta_window_menu_free(MetaWindowMenu* menu) +{ + gtk_widget_destroy(menu->menu); + g_free(menu); +} diff --git a/src/ui/menu.h b/src/ui/menu.h new file mode 100644 index 00000000..29e85a5b --- /dev/null +++ b/src/ui/menu.h @@ -0,0 +1,51 @@ +/* Marco window menu */ + +/* + * 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. + */ + +#ifndef META_MENU_H +#define META_MENU_H + +#include <gtk/gtk.h> +#include "frames.h" + +/* Stock icons */ +#define MARCO_STOCK_DELETE "window-close" +#define MARCO_STOCK_RESTORE "view-restore" +#define MARCO_STOCK_MINIMIZE "go-down" +#define MARCO_STOCK_MAXIMIZE "view-fullscreen" + + + +struct _MetaWindowMenu { + MetaFrames* frames; + Window client_xwindow; + GtkWidget* menu; + MetaWindowMenuFunc func; + gpointer data; + MetaMenuOp ops; + MetaMenuOp insensitive; +}; + +MetaWindowMenu* meta_window_menu_new(MetaFrames* frames, MetaMenuOp ops, MetaMenuOp insensitive, Window client_xwindow, unsigned long active_workspace, int n_workspaces, MetaWindowMenuFunc func, gpointer data); +void meta_window_menu_popup(MetaWindowMenu* menu, int root_x, int root_y, int button, guint32 timestamp); +void meta_window_menu_free(MetaWindowMenu* menu); + + +#endif diff --git a/src/ui/metaaccellabel.c b/src/ui/metaaccellabel.c new file mode 100644 index 00000000..5efd7d2e --- /dev/null +++ b/src/ui/metaaccellabel.c @@ -0,0 +1,456 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco hacked-up GtkAccelLabel */ +/* Copyright (C) 2002 Red Hat, Inc. */ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * MetaAccelLabel: GtkLabel with accelerator monitoring facilities. + * Copyright (C) 1998 Tim Janik + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * Modified by the GTK+ Team and others 1997-2001. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +#include <config.h> +#include "metaaccellabel.h" +#include <gtk/gtk.h> +#include <string.h> +#include "util.h" + +static void meta_accel_label_class_init (MetaAccelLabelClass *klass); +static void meta_accel_label_init (MetaAccelLabel *accel_label); +static void meta_accel_label_destroy (GtkObject *object); +static void meta_accel_label_finalize (GObject *object); +static void meta_accel_label_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static gboolean meta_accel_label_expose_event (GtkWidget *widget, + GdkEventExpose *event); + +static void meta_accel_label_update (MetaAccelLabel *accel_label); +static int meta_accel_label_get_accel_width (MetaAccelLabel *accel_label); + + +static GtkLabelClass *parent_class = NULL; + + +GType +meta_accel_label_get_type (void) +{ + static GType accel_label_type = 0; + + if (!accel_label_type) + { + static const GtkTypeInfo accel_label_info = + { + "MetaAccelLabel", + sizeof (MetaAccelLabel), + sizeof (MetaAccelLabelClass), + (GtkClassInitFunc) meta_accel_label_class_init, + (GtkObjectInitFunc) meta_accel_label_init, + /* reserved_1 */ NULL, + /* reserved_2 */ NULL, + (GtkClassInitFunc) NULL, + }; + + accel_label_type = gtk_type_unique (GTK_TYPE_LABEL, &accel_label_info); + } + + return accel_label_type; +} + +static void +meta_accel_label_class_init (MetaAccelLabelClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + GtkObjectClass *object_class = GTK_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + parent_class = g_type_class_peek_parent (class); + + gobject_class->finalize = meta_accel_label_finalize; + + object_class->destroy = meta_accel_label_destroy; + + widget_class->size_request = meta_accel_label_size_request; + widget_class->expose_event = meta_accel_label_expose_event; + + class->signal_quote1 = g_strdup ("<:"); + class->signal_quote2 = g_strdup (":>"); + /* This is the text that should appear next to menu accelerators + * that use the shift key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_shift = g_strdup (_("Shift")); + /* This is the text that should appear next to menu accelerators + * that use the control key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_control = g_strdup (_("Ctrl")); + /* This is the text that should appear next to menu accelerators + * that use the alt key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_alt = g_strdup (_("Alt")); + /* This is the text that should appear next to menu accelerators + * that use the meta key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_meta = g_strdup (_("Meta")); + /* This is the text that should appear next to menu accelerators + * that use the super key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_super = g_strdup (_("Super")); + /* This is the text that should appear next to menu accelerators + * that use the hyper key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_hyper = g_strdup (_("Hyper")); + /* This is the text that should appear next to menu accelerators + * that use the mod2 key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_mod2 = g_strdup (_("Mod2")); + /* This is the text that should appear next to menu accelerators + * that use the mod3 key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_mod3 = g_strdup (_("Mod3")); + /* This is the text that should appear next to menu accelerators + * that use the mod4 key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_mod4 = g_strdup (_("Mod4")); + /* This is the text that should appear next to menu accelerators + * that use the mod5 key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_mod5 = g_strdup (_("Mod5")); + + class->mod_separator = g_strdup ("+"); + class->accel_seperator = g_strdup (" / "); + class->latin1_to_char = TRUE; +} + +static void +meta_accel_label_init (MetaAccelLabel *accel_label) +{ + accel_label->accel_padding = 3; + accel_label->accel_string = NULL; + + meta_accel_label_update (accel_label); +} + +GtkWidget* +meta_accel_label_new_with_mnemonic (const gchar *string) +{ + MetaAccelLabel *accel_label; + + g_return_val_if_fail (string != NULL, NULL); + + accel_label = g_object_new (META_TYPE_ACCEL_LABEL, NULL); + + gtk_label_set_text_with_mnemonic (GTK_LABEL (accel_label), string); + + return GTK_WIDGET (accel_label); +} + +static void +meta_accel_label_destroy (GtkObject *object) +{ + MetaAccelLabel *accel_label = META_ACCEL_LABEL (object); + + + g_free (accel_label->accel_string); + accel_label->accel_string = NULL; + + accel_label->accel_mods = 0; + accel_label->accel_key = 0; + + GTK_OBJECT_CLASS (parent_class)->destroy (object); +} + +static void +meta_accel_label_finalize (GObject *object) +{ + MetaAccelLabel *accel_label = META_ACCEL_LABEL (object); + + g_free (accel_label->accel_string); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +void +meta_accel_label_set_accelerator (MetaAccelLabel *accel_label, + guint accelerator_key, + MetaVirtualModifier accelerator_mods) +{ + g_return_if_fail (META_IS_ACCEL_LABEL (accel_label)); + + if (accelerator_key != accel_label->accel_key || + accelerator_mods != accel_label->accel_mods) + { + accel_label->accel_mods = accelerator_mods; + accel_label->accel_key = accelerator_key; + + meta_accel_label_update (accel_label); + } +} + +static int +meta_accel_label_get_accel_width (MetaAccelLabel *accel_label) +{ + g_return_val_if_fail (META_IS_ACCEL_LABEL (accel_label), 0); + + return (accel_label->accel_string_width + + (accel_label->accel_string_width ? accel_label->accel_padding : 0)); +} + +static void +meta_accel_label_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + MetaAccelLabel *accel_label = META_ACCEL_LABEL (widget); + PangoLayout *layout; + gint width; + + if (GTK_WIDGET_CLASS (parent_class)->size_request) + GTK_WIDGET_CLASS (parent_class)->size_request (widget, requisition); + + layout = gtk_widget_create_pango_layout (widget, accel_label->accel_string); + pango_layout_get_pixel_size (layout, &width, NULL); + accel_label->accel_string_width = width; + + g_object_unref (G_OBJECT (layout)); +} + +static gboolean +meta_accel_label_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + MetaAccelLabel *accel_label = META_ACCEL_LABEL (widget); + GtkMisc *misc = GTK_MISC (accel_label); + PangoLayout *layout; + + if (GTK_WIDGET_DRAWABLE (accel_label)) + { + int ac_width; + + ac_width = meta_accel_label_get_accel_width (accel_label); + + if (widget->allocation.width >= widget->requisition.width + ac_width) + { + GtkTextDirection direction = gtk_widget_get_direction (widget); + gint x; + gint y; + + if (direction == GTK_TEXT_DIR_RTL) + { + widget->allocation.x += ac_width; + } + widget->allocation.width -= ac_width; + + if (GTK_WIDGET_CLASS (parent_class)->expose_event) + GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); + + if (direction == GTK_TEXT_DIR_RTL) + { + widget->allocation.x -= ac_width; + } + widget->allocation.width += ac_width; + + if (direction == GTK_TEXT_DIR_RTL) + { + x = widget->allocation.x + misc->xpad; + } + else + { + x = widget->allocation.x + widget->allocation.width - misc->xpad - ac_width; + } + + y = (widget->allocation.y * (1.0 - misc->yalign) + + (widget->allocation.y + widget->allocation.height - + (widget->requisition.height - misc->ypad * 2)) * + misc->yalign) + 1.5; + + layout = gtk_widget_create_pango_layout (widget, accel_label->accel_string); + + gtk_paint_layout (widget->style, + widget->window, + GTK_WIDGET_STATE (widget), + FALSE, + &event->area, + widget, + "accellabel", + x, y, + layout); + + g_object_unref (G_OBJECT (layout)); + } + else + { + if (GTK_WIDGET_CLASS (parent_class)->expose_event) + GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); + } + } + + return FALSE; +} + +static void +meta_accel_label_update (MetaAccelLabel *accel_label) +{ + MetaAccelLabelClass *class; + GString *gstring; + gboolean seen_mod = FALSE; + gunichar ch; + + g_return_if_fail (META_IS_ACCEL_LABEL (accel_label)); + + class = META_ACCEL_LABEL_GET_CLASS (accel_label); + + g_free (accel_label->accel_string); + accel_label->accel_string = NULL; + + gstring = g_string_new (accel_label->accel_string); + g_string_append (gstring, gstring->len ? class->accel_seperator : " "); + + if (accel_label->accel_mods & META_VIRTUAL_SHIFT_MASK) + { + g_string_append (gstring, class->mod_name_shift); + seen_mod = TRUE; + } + if (accel_label->accel_mods & META_VIRTUAL_CONTROL_MASK) + { + if (seen_mod) + g_string_append (gstring, class->mod_separator); + g_string_append (gstring, class->mod_name_control); + seen_mod = TRUE; + } + if (accel_label->accel_mods & META_VIRTUAL_ALT_MASK) + { + if (seen_mod) + g_string_append (gstring, class->mod_separator); + g_string_append (gstring, class->mod_name_alt); + seen_mod = TRUE; + } + if (accel_label->accel_mods & META_VIRTUAL_META_MASK) + { + if (seen_mod) + g_string_append (gstring, class->mod_separator); + g_string_append (gstring, class->mod_name_meta); + seen_mod = TRUE; + } + if (accel_label->accel_mods & META_VIRTUAL_SUPER_MASK) + { + if (seen_mod) + g_string_append (gstring, class->mod_separator); + g_string_append (gstring, class->mod_name_super); + seen_mod = TRUE; + } + if (accel_label->accel_mods & META_VIRTUAL_HYPER_MASK) + { + if (seen_mod) + g_string_append (gstring, class->mod_separator); + g_string_append (gstring, class->mod_name_hyper); + seen_mod = TRUE; + } + if (accel_label->accel_mods & META_VIRTUAL_MOD2_MASK) + { + if (seen_mod) + g_string_append (gstring, class->mod_separator); + g_string_append (gstring, class->mod_name_mod2); + seen_mod = TRUE; + } + if (accel_label->accel_mods & META_VIRTUAL_MOD3_MASK) + { + if (seen_mod) + g_string_append (gstring, class->mod_separator); + g_string_append (gstring, class->mod_name_mod3); + seen_mod = TRUE; + } + if (accel_label->accel_mods & META_VIRTUAL_MOD4_MASK) + { + if (seen_mod) + g_string_append (gstring, class->mod_separator); + g_string_append (gstring, class->mod_name_mod4); + seen_mod = TRUE; + } + if (accel_label->accel_mods & META_VIRTUAL_MOD5_MASK) + { + if (seen_mod) + g_string_append (gstring, class->mod_separator); + g_string_append (gstring, class->mod_name_mod5); + seen_mod = TRUE; + } + + if (seen_mod) + g_string_append (gstring, class->mod_separator); + + ch = gdk_keyval_to_unicode (accel_label->accel_key); + if (ch && (g_unichar_isgraph (ch) || ch == ' ') && + (ch < 0x80 || class->latin1_to_char)) + { + switch (ch) + { + case ' ': + g_string_append (gstring, "Space"); + break; + case '\\': + g_string_append (gstring, "Backslash"); + break; + default: + g_string_append_unichar (gstring, g_unichar_toupper (ch)); + break; + } + } + else + { + gchar *tmp; + + tmp = gtk_accelerator_name (accel_label->accel_key, 0); + if (tmp[0] != 0 && tmp[1] == 0) + tmp[0] = g_ascii_toupper (tmp[0]); + g_string_append (gstring, tmp); + g_free (tmp); + } + + g_free (accel_label->accel_string); + accel_label->accel_string = gstring->str; + g_string_free (gstring, FALSE); + + g_assert (accel_label->accel_string); + /* accel_label->accel_string = g_strdup ("-/-"); */ + + gtk_widget_queue_resize (GTK_WIDGET (accel_label)); +} diff --git a/src/ui/metaaccellabel.h b/src/ui/metaaccellabel.h new file mode 100644 index 00000000..34914c92 --- /dev/null +++ b/src/ui/metaaccellabel.h @@ -0,0 +1,106 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco hacked-up GtkAccelLabel */ +/* Copyright (C) 2002 Red Hat, Inc. */ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * MetaAccelLabel: GtkLabel with accelerator monitoring facilities. + * Copyright (C) 1998 Tim Janik + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * Modified by the GTK+ Team and others 1997-2001. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __META_ACCEL_LABEL_H__ +#define __META_ACCEL_LABEL_H__ + +#include <gtk/gtk.h> +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#define META_TYPE_ACCEL_LABEL (meta_accel_label_get_type ()) +#define META_ACCEL_LABEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_ACCEL_LABEL, MetaAccelLabel)) +#define META_ACCEL_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), META_TYPE_ACCEL_LABEL, MetaAccelLabelClass)) +#define META_IS_ACCEL_LABEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), META_TYPE_ACCEL_LABEL)) +#define META_IS_ACCEL_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), META_TYPE_ACCEL_LABEL)) +#define META_ACCEL_LABEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), META_TYPE_ACCEL_LABEL, MetaAccelLabelClass)) + + +typedef struct _MetaAccelLabel MetaAccelLabel; +typedef struct _MetaAccelLabelClass MetaAccelLabelClass; + +struct _MetaAccelLabel +{ + GtkLabel label; + + MetaVirtualModifier accel_mods; + guint accel_key; + guint accel_padding; + gchar *accel_string; + guint16 accel_string_width; +}; + +struct _MetaAccelLabelClass +{ + GtkLabelClass parent_class; + + gchar *signal_quote1; + gchar *signal_quote2; + gchar *mod_name_shift; + gchar *mod_name_control; + gchar *mod_name_alt; + gchar *mod_name_meta; + gchar *mod_name_super; + gchar *mod_name_hyper; + gchar *mod_name_mod2; + gchar *mod_name_mod3; + gchar *mod_name_mod4; + gchar *mod_name_mod5; + gchar *mod_separator; + gchar *accel_seperator; + guint latin1_to_char : 1; + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); +}; + +GType meta_accel_label_get_type (void) G_GNUC_CONST; +GtkWidget* meta_accel_label_new_with_mnemonic (const gchar *string); +void meta_accel_label_set_accelerator (MetaAccelLabel *accel_label, + guint accelerator_key, + MetaVirtualModifier accelerator_mods); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __META_ACCEL_LABEL_H__ */ diff --git a/src/ui/preview-widget.c b/src/ui/preview-widget.c new file mode 100644 index 00000000..8fcd2517 --- /dev/null +++ b/src/ui/preview-widget.c @@ -0,0 +1,601 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco theme preview widget */ + +/* + * 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. + */ + +#define _GNU_SOURCE +#define _XOPEN_SOURCE 600 /* for the maths routines over floats */ + +#include <math.h> +#include <gtk/gtk.h> +#include "preview-widget.h" + +static void meta_preview_class_init (MetaPreviewClass *klass); +static void meta_preview_init (MetaPreview *preview); +static void meta_preview_size_request (GtkWidget *widget, + GtkRequisition *req); +static void meta_preview_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static gboolean meta_preview_expose (GtkWidget *widget, + GdkEventExpose *event); +static void meta_preview_finalize (GObject *object); + +static GtkWidgetClass *parent_class; + +GType +meta_preview_get_type (void) +{ + static GType preview_type = 0; + + if (!preview_type) + { + static const GtkTypeInfo preview_info = + { + "MetaPreview", + sizeof (MetaPreview), + sizeof (MetaPreviewClass), + (GtkClassInitFunc) meta_preview_class_init, + (GtkObjectInitFunc) meta_preview_init, + /* reserved_1 */ NULL, + /* reserved_2 */ NULL, + (GtkClassInitFunc) NULL, + }; + + preview_type = gtk_type_unique (GTK_TYPE_BIN, &preview_info); + } + + return preview_type; +} + +static void +meta_preview_class_init (MetaPreviewClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class; + + widget_class = (GtkWidgetClass*) class; + parent_class = g_type_class_peek (GTK_TYPE_BIN); + + gobject_class->finalize = meta_preview_finalize; + + widget_class->expose_event = meta_preview_expose; + widget_class->size_request = meta_preview_size_request; + widget_class->size_allocate = meta_preview_size_allocate; +} + +static void +meta_preview_init (MetaPreview *preview) +{ + int i; + + gtk_widget_set_has_window (GTK_WIDGET (preview), FALSE); + + i = 0; + while (i < MAX_BUTTONS_PER_CORNER) + { + preview->button_layout.left_buttons[i] = META_BUTTON_FUNCTION_LAST; + preview->button_layout.right_buttons[i] = META_BUTTON_FUNCTION_LAST; + ++i; + } + + preview->button_layout.left_buttons[0] = META_BUTTON_FUNCTION_MENU; + + preview->button_layout.right_buttons[0] = META_BUTTON_FUNCTION_MINIMIZE; + preview->button_layout.right_buttons[1] = META_BUTTON_FUNCTION_MAXIMIZE; + preview->button_layout.right_buttons[2] = META_BUTTON_FUNCTION_CLOSE; + + preview->type = META_FRAME_TYPE_NORMAL; + preview->flags = + 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; + + preview->left_width = -1; + preview->right_width = -1; + preview->top_height = -1; + preview->bottom_height = -1; +} + +GtkWidget* +meta_preview_new (void) +{ + MetaPreview *preview; + + preview = gtk_type_new (META_TYPE_PREVIEW); + + return GTK_WIDGET (preview); +} + +static void +meta_preview_finalize (GObject *object) +{ + MetaPreview *preview; + + preview = META_PREVIEW (object); + + g_free (preview->title); + preview->title = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +ensure_info (MetaPreview *preview) +{ + GtkWidget *widget; + + widget = GTK_WIDGET (preview); + + if (preview->layout == NULL) + { + PangoFontDescription *font_desc; + double scale; + PangoAttrList *attrs; + PangoAttribute *attr; + + if (preview->theme) + scale = meta_theme_get_title_scale (preview->theme, + preview->type, + preview->flags); + else + scale = 1.0; + + preview->layout = gtk_widget_create_pango_layout (widget, + preview->title); + + font_desc = meta_gtk_widget_get_font_desc (widget, scale, NULL); + + preview->text_height = + meta_pango_font_desc_get_text_height (font_desc, + gtk_widget_get_pango_context (widget)); + + attrs = pango_attr_list_new (); + + attr = pango_attr_size_new (pango_font_description_get_size (font_desc)); + attr->start_index = 0; + attr->end_index = G_MAXINT; + + pango_attr_list_insert (attrs, attr); + + pango_layout_set_attributes (preview->layout, attrs); + + pango_attr_list_unref (attrs); + + pango_font_description_free (font_desc); + } + + if (preview->top_height < 0) + { + if (preview->theme) + { + meta_theme_get_frame_borders (preview->theme, + preview->type, + preview->text_height, + preview->flags, + &preview->top_height, + &preview->bottom_height, + &preview->left_width, + &preview->right_width); + } + else + { + preview->top_height = 0; + preview->bottom_height = 0; + preview->left_width = 0; + preview->right_width = 0; + } + } +} + +static gboolean +meta_preview_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + MetaPreview *preview; + GtkAllocation allocation; + int border_width; + int client_width; + int client_height; + MetaButtonState button_states[META_BUTTON_TYPE_LAST] = + { + META_BUTTON_STATE_NORMAL, + META_BUTTON_STATE_NORMAL, + META_BUTTON_STATE_NORMAL, + META_BUTTON_STATE_NORMAL + }; + + g_return_val_if_fail (META_IS_PREVIEW (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + preview = META_PREVIEW (widget); + + ensure_info (preview); + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + + gtk_widget_get_allocation (widget, &allocation); + client_width = allocation.width - preview->left_width - preview->right_width - border_width * 2; + client_height = allocation.height - preview->top_height - preview->bottom_height - border_width * 2; + + if (client_width < 0) + client_width = 1; + if (client_height < 0) + client_height = 1; + + if (preview->theme) + { + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + + meta_theme_draw_frame (preview->theme, + widget, + gtk_widget_get_window (widget), + &event->area, + allocation.x + border_width, + allocation.y + border_width, + preview->type, + preview->flags, + client_width, client_height, + preview->layout, + preview->text_height, + &preview->button_layout, + button_states, + meta_preview_get_mini_icon (), + meta_preview_get_icon ()); + } + + /* draw child */ + return GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); +} + +static void +meta_preview_size_request (GtkWidget *widget, + GtkRequisition *req) +{ + MetaPreview *preview; + GtkWidget *child; + guint border_width; + + preview = META_PREVIEW (widget); + + ensure_info (preview); + + req->width = preview->left_width + preview->right_width; + req->height = preview->top_height + preview->bottom_height; + + child = gtk_bin_get_child (GTK_BIN (preview)); + if (child && + gtk_widget_get_visible (child)) + { + GtkRequisition child_requisition; + + gtk_widget_size_request (child, &child_requisition); + + req->width += child_requisition.width; + req->height += child_requisition.height; + } + else + { +#define NO_CHILD_WIDTH 80 +#define NO_CHILD_HEIGHT 20 + req->width += NO_CHILD_WIDTH; + req->height += NO_CHILD_HEIGHT; + } + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + req->width += border_width * 2; + req->height += border_width * 2; +} + +static void +meta_preview_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + MetaPreview *preview; + int border_width; + GtkAllocation child_allocation; + GtkWidget *child; + + preview = META_PREVIEW (widget); + + ensure_info (preview); + + widget->allocation = *allocation; + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + + child = gtk_bin_get_child (GTK_BIN (widget)); + if (child && + gtk_widget_get_visible (child)) + { + child_allocation.x = widget->allocation.x + border_width + preview->left_width; + child_allocation.y = widget->allocation.y + border_width + preview->top_height; + + child_allocation.width = MAX (1, widget->allocation.width - border_width * 2 - preview->left_width - preview->right_width); + child_allocation.height = MAX (1, widget->allocation.height - border_width * 2 - preview->top_height - preview->bottom_height); + + gtk_widget_size_allocate (gtk_bin_get_child (GTK_BIN (widget)), &child_allocation); + } +} + +static void +clear_cache (MetaPreview *preview) +{ + if (preview->layout) + { + g_object_unref (G_OBJECT (preview->layout)); + preview->layout = NULL; + } + + preview->left_width = -1; + preview->right_width = -1; + preview->top_height = -1; + preview->bottom_height = -1; +} + +void +meta_preview_set_theme (MetaPreview *preview, + MetaTheme *theme) +{ + g_return_if_fail (META_IS_PREVIEW (preview)); + + preview->theme = theme; + + clear_cache (preview); + + gtk_widget_queue_resize (GTK_WIDGET (preview)); +} + +void +meta_preview_set_title (MetaPreview *preview, + const char *title) +{ + g_return_if_fail (META_IS_PREVIEW (preview)); + + g_free (preview->title); + preview->title = g_strdup (title); + + clear_cache (preview); + + gtk_widget_queue_resize (GTK_WIDGET (preview)); +} + +void +meta_preview_set_frame_type (MetaPreview *preview, + MetaFrameType type) +{ + g_return_if_fail (META_IS_PREVIEW (preview)); + + preview->type = type; + + clear_cache (preview); + + gtk_widget_queue_resize (GTK_WIDGET (preview)); +} + +void +meta_preview_set_frame_flags (MetaPreview *preview, + MetaFrameFlags flags) +{ + g_return_if_fail (META_IS_PREVIEW (preview)); + + preview->flags = flags; + + clear_cache (preview); + + gtk_widget_queue_resize (GTK_WIDGET (preview)); +} + +void +meta_preview_set_button_layout (MetaPreview *preview, + const MetaButtonLayout *button_layout) +{ + g_return_if_fail (META_IS_PREVIEW (preview)); + + preview->button_layout = *button_layout; + + gtk_widget_queue_draw (GTK_WIDGET (preview)); +} + +GdkPixbuf* +meta_preview_get_icon (void) +{ + static GdkPixbuf *default_icon = NULL; + + if (default_icon == NULL) + { + GtkIconTheme *theme; + gboolean icon_exists; + + theme = gtk_icon_theme_get_default (); + + icon_exists = gtk_icon_theme_has_icon (theme, META_DEFAULT_ICON_NAME); + + if (icon_exists) + default_icon = gtk_icon_theme_load_icon (theme, + META_DEFAULT_ICON_NAME, + META_ICON_WIDTH, + 0, + NULL); + else + default_icon = gtk_icon_theme_load_icon (theme, + "gtk-missing-image", + META_ICON_WIDTH, + 0, + NULL); + + g_assert (default_icon); + } + + return default_icon; +} + +GdkPixbuf* +meta_preview_get_mini_icon (void) +{ + static GdkPixbuf *default_icon = NULL; + + if (default_icon == NULL) + { + GtkIconTheme *theme; + gboolean icon_exists; + + theme = gtk_icon_theme_get_default (); + + icon_exists = gtk_icon_theme_has_icon (theme, META_DEFAULT_ICON_NAME); + + if (icon_exists) + default_icon = gtk_icon_theme_load_icon (theme, + META_DEFAULT_ICON_NAME, + META_MINI_ICON_WIDTH, + 0, + NULL); + else + default_icon = gtk_icon_theme_load_icon (theme, + "gtk-missing-image", + META_MINI_ICON_WIDTH, + 0, + NULL); + + g_assert (default_icon); + } + + return default_icon; +} + +GdkRegion * +meta_preview_get_clip_region (MetaPreview *preview, gint new_window_width, gint new_window_height) +{ + GdkRectangle xrect; + GdkRegion *corners_xregion, *window_xregion; + gint flags; + MetaFrameLayout *fgeom; + MetaFrameStyle *frame_style; + + g_return_val_if_fail (META_IS_PREVIEW (preview), NULL); + + flags = (META_PREVIEW (preview)->flags); + + window_xregion = gdk_region_new (); + + xrect.x = 0; + xrect.y = 0; + xrect.width = new_window_width; + xrect.height = new_window_height; + + gdk_region_union_with_rect (window_xregion, &xrect); + + if (preview->theme == NULL) + return window_xregion; + + /* Otherwise, we do have a theme, so calculate the corners */ + frame_style = meta_theme_get_frame_style (preview->theme, + META_FRAME_TYPE_NORMAL, flags); + + fgeom = frame_style->layout; + + corners_xregion = gdk_region_new (); + + if (fgeom->top_left_corner_rounded_radius != 0) + { + const int corner = fgeom->top_left_corner_rounded_radius; + const float radius = sqrt(corner) + corner; + int i; + + for (i=0; i<corner; i++) + { + + const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5)))); + xrect.x = 0; + xrect.y = i; + xrect.width = width; + xrect.height = 1; + + gdk_region_union_with_rect (corners_xregion, &xrect); + } + } + + if (fgeom->top_right_corner_rounded_radius != 0) + { + const int corner = fgeom->top_right_corner_rounded_radius; + const float radius = sqrt(corner) + corner; + int i; + + for (i=0; i<corner; i++) + { + const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5)))); + xrect.x = new_window_width - width; + xrect.y = i; + xrect.width = width; + xrect.height = 1; + + gdk_region_union_with_rect (corners_xregion, &xrect); + } + } + + if (fgeom->bottom_left_corner_rounded_radius != 0) + { + const int corner = fgeom->bottom_left_corner_rounded_radius; + const float radius = sqrt(corner) + corner; + int i; + + for (i=0; i<corner; i++) + { + const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5)))); + xrect.x = 0; + xrect.y = new_window_height - i - 1; + xrect.width = width; + xrect.height = 1; + + gdk_region_union_with_rect (corners_xregion, &xrect); + } + } + + if (fgeom->bottom_right_corner_rounded_radius != 0) + { + const int corner = fgeom->bottom_right_corner_rounded_radius; + const float radius = sqrt(corner) + corner; + int i; + + for (i=0; i<corner; i++) + { + const int width = floor(0.5 + radius - sqrt(radius*radius - (radius-(i+0.5))*(radius-(i+0.5)))); + xrect.x = new_window_width - width; + xrect.y = new_window_height - i - 1; + xrect.width = width; + xrect.height = 1; + + gdk_region_union_with_rect (corners_xregion, &xrect); + } + } + + gdk_region_subtract (window_xregion, corners_xregion); + gdk_region_destroy (corners_xregion); + + return window_xregion; +} + + diff --git a/src/ui/preview-widget.h b/src/ui/preview-widget.h new file mode 100644 index 00000000..4b213714 --- /dev/null +++ b/src/ui/preview-widget.h @@ -0,0 +1,87 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco theme preview widget */ + +/* + * 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/gtk.h> + +#ifndef META_PREVIEW_WIDGET_H +#define META_PREVIEW_WIDGET_H + +#define META_TYPE_PREVIEW (meta_preview_get_type ()) +#define META_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_PREVIEW, MetaPreview)) +#define META_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), META_TYPE_PREVIEW, MetaPreviewClass)) +#define META_IS_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), META_TYPE_PREVIEW)) +#define META_IS_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), META_TYPE_PREVIEW)) +#define META_PREVIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), META_TYPE_PREVIEW, MetaPreviewClass)) + +typedef struct _MetaPreview MetaPreview; +typedef struct _MetaPreviewClass MetaPreviewClass; + +struct _MetaPreview +{ + GtkBin bin; + + MetaTheme *theme; + char *title; + MetaFrameType type; + MetaFrameFlags flags; + + PangoLayout *layout; + int text_height; + + int left_width; + int right_width; + int top_height; + int bottom_height; + + MetaButtonLayout button_layout; +}; + +struct _MetaPreviewClass +{ + GtkBinClass parent_class; +}; + + +GType meta_preview_get_type (void) G_GNUC_CONST; +GtkWidget* meta_preview_new (void); + +void meta_preview_set_theme (MetaPreview *preview, + MetaTheme *theme); +void meta_preview_set_title (MetaPreview *preview, + const char *title); +void meta_preview_set_frame_type (MetaPreview *preview, + MetaFrameType type); +void meta_preview_set_frame_flags (MetaPreview *preview, + MetaFrameFlags flags); +void meta_preview_set_button_layout (MetaPreview *preview, + const MetaButtonLayout *button_layout); + +GdkRegion * meta_preview_get_clip_region (MetaPreview *preview, + gint new_window_width, + gint new_window_height); + +GdkPixbuf* meta_preview_get_icon (void); +GdkPixbuf* meta_preview_get_mini_icon (void); + +#endif diff --git a/src/ui/resizepopup.c b/src/ui/resizepopup.c new file mode 100644 index 00000000..67f39505 --- /dev/null +++ b/src/ui/resizepopup.c @@ -0,0 +1,217 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco resizing-terminal-window feedback */ + +/* + * 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 "resizepopup.h" +#include "util.h" +#include <gtk/gtk.h> +#include <gdk/gdkx.h> + +struct _MetaResizePopup +{ + GtkWidget *size_window; + GtkWidget *size_label; + Display *display; + int screen_number; + + int vertical_size; + int horizontal_size; + + gboolean showing; + + MetaRectangle rect; +}; + +MetaResizePopup* +meta_ui_resize_popup_new (Display *display, + int screen_number) +{ + MetaResizePopup *popup; + + popup = g_new0 (MetaResizePopup, 1); + + popup->display = display; + popup->screen_number = screen_number; + + return popup; +} + +void +meta_ui_resize_popup_free (MetaResizePopup *popup) +{ + g_return_if_fail (popup != NULL); + + if (popup->size_window) + gtk_widget_destroy (popup->size_window); + + g_free (popup); +} + +static void +ensure_size_window (MetaResizePopup *popup) +{ + GtkWidget *frame; + + if (popup->size_window) + return; + + popup->size_window = gtk_window_new (GTK_WINDOW_POPUP); + + gtk_window_set_screen (GTK_WINDOW (popup->size_window), + gdk_display_get_screen (gdk_x11_lookup_xdisplay (popup->display), + popup->screen_number)); + + /* never shrink the size window */ + gtk_window_set_resizable (GTK_WINDOW (popup->size_window), + TRUE); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + + gtk_container_add (GTK_CONTAINER (popup->size_window), frame); + + popup->size_label = gtk_label_new (""); + gtk_misc_set_padding (GTK_MISC (popup->size_label), 3, 3); + + gtk_container_add (GTK_CONTAINER (frame), popup->size_label); + + gtk_widget_show_all (frame); +} + +static void +update_size_window (MetaResizePopup *popup) +{ + char *str; + int x, y; + int width, height; + + g_return_if_fail (popup->size_window != NULL); + + /* Translators: This represents the size of a window. The first number is + * the width of the window and the second is the height. + */ + str = g_strdup_printf (_("%d x %d"), + popup->horizontal_size, + popup->vertical_size); + + gtk_label_set_text (GTK_LABEL (popup->size_label), str); + + g_free (str); + + gtk_window_get_size (GTK_WINDOW (popup->size_window), &width, &height); + + x = popup->rect.x + (popup->rect.width - width) / 2; + y = popup->rect.y + (popup->rect.height - height) / 2; + + if (GTK_WIDGET_REALIZED (popup->size_window)) + { + /* using move_resize to avoid jumpiness */ + gdk_window_move_resize (popup->size_window->window, + x, y, + width, height); + } + else + { + gtk_window_move (GTK_WINDOW (popup->size_window), + x, y); + } +} + +static void +sync_showing (MetaResizePopup *popup) +{ + if (popup->showing) + { + if (popup->size_window) + gtk_widget_show (popup->size_window); + + if (popup->size_window && GTK_WIDGET_REALIZED (popup->size_window)) + gdk_window_raise (popup->size_window->window); + } + else + { + if (popup->size_window) + gtk_widget_hide (popup->size_window); + } +} + +void +meta_ui_resize_popup_set (MetaResizePopup *popup, + MetaRectangle rect, + int base_width, + int base_height, + int width_inc, + int height_inc) +{ + gboolean need_update_size; + int display_w, display_h; + + g_return_if_fail (popup != NULL); + + need_update_size = FALSE; + + display_w = rect.width - base_width; + if (width_inc > 0) + display_w /= width_inc; + + display_h = rect.height - base_height; + if (height_inc > 0) + display_h /= height_inc; + + if (!meta_rectangle_equal(&popup->rect, &rect) || + display_w != popup->horizontal_size || + display_h != popup->vertical_size) + need_update_size = TRUE; + + popup->rect = rect; + popup->vertical_size = display_h; + popup->horizontal_size = display_w; + + if (need_update_size) + { + ensure_size_window (popup); + update_size_window (popup); + } + + sync_showing (popup); +} + +void +meta_ui_resize_popup_set_showing (MetaResizePopup *popup, + gboolean showing) +{ + g_return_if_fail (popup != NULL); + + if (showing == popup->showing) + return; + + popup->showing = !!showing; + + if (popup->showing) + { + ensure_size_window (popup); + update_size_window (popup); + } + + sync_showing (popup); +} diff --git a/src/ui/tabpopup.c b/src/ui/tabpopup.c new file mode 100644 index 00000000..e0a55447 --- /dev/null +++ b/src/ui/tabpopup.c @@ -0,0 +1,967 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco popup window thing showing windows you can tab to */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2002 Red Hat, Inc. + * Copyright (C) 2005 Elijah Newren + * + * 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 "util.h" +#include "core.h" +#include "tabpopup.h" +/* FIXME these two includes are 100% broken ... + */ +#include "../core/workspace.h" +#include "../core/frame-private.h" +#include "draw-workspace.h" +#include <gtk/gtk.h> +#include <math.h> + +#define OUTSIDE_SELECT_RECT 2 +#define INSIDE_SELECT_RECT 2 + +typedef struct _TabEntry TabEntry; + +struct _TabEntry +{ + MetaTabEntryKey key; + char *title; + GdkPixbuf *icon, *dimmed_icon; + GtkWidget *widget; + GdkRectangle rect; + GdkRectangle inner_rect; + guint blank : 1; +}; + +struct _MetaTabPopup +{ + GtkWidget *window; + GtkWidget *label; + GList *current; + GList *entries; + TabEntry *current_selected_entry; + GtkWidget *outline_window; + gboolean outline; +}; + +static GtkWidget* selectable_image_new (GdkPixbuf *pixbuf); +static void select_image (GtkWidget *widget); +static void unselect_image (GtkWidget *widget); + +static GtkWidget* selectable_workspace_new (MetaWorkspace *workspace); +static void select_workspace (GtkWidget *widget); +static void unselect_workspace (GtkWidget *widget); + +static gboolean +outline_window_expose (GtkWidget *widget, + GdkEventExpose *event, + gpointer data) +{ + MetaTabPopup *popup; + TabEntry *te; + GtkStyle *style; + GdkWindow *window; + cairo_t *cr; + + popup = data; + + if (!popup->outline || popup->current_selected_entry == NULL) + return FALSE; + + te = popup->current_selected_entry; + window = gtk_widget_get_window (widget); + style = gtk_widget_get_style (widget); + cr = gdk_cairo_create (window); + + cairo_set_line_width (cr, 1.0); + gdk_cairo_set_source_color (cr, &style->white); + + cairo_rectangle (cr, + 0.5, 0.5, + te->rect.width - 1, + te->rect.height - 1); + cairo_stroke (cr); + + cairo_rectangle (cr, + te->inner_rect.x - 0.5, te->inner_rect.y - 0.5, + te->inner_rect.width + 1, + te->inner_rect.height + 1); + cairo_stroke (cr); + + cairo_destroy (cr); + + return FALSE; +} + +static GdkPixbuf* +dimm_icon (GdkPixbuf *pixbuf) +{ + int x, y, pixel_stride, row_stride; + guchar *row, *pixels; + int w, h; + GdkPixbuf *dimmed_pixbuf; + + if (gdk_pixbuf_get_has_alpha (pixbuf)) + { + dimmed_pixbuf = gdk_pixbuf_copy (pixbuf); + } + else + { + dimmed_pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0); + } + + w = gdk_pixbuf_get_width (dimmed_pixbuf); + h = gdk_pixbuf_get_height (dimmed_pixbuf); + + pixel_stride = 4; + + row = gdk_pixbuf_get_pixels (dimmed_pixbuf); + row_stride = gdk_pixbuf_get_rowstride (dimmed_pixbuf); + + for (y = 0; y < h; y++) + { + pixels = row; + for (x = 0; x < w; x++) + { + pixels[3] /= 2; + pixels += pixel_stride; + } + row += row_stride; + } + return dimmed_pixbuf; +} + +static TabEntry* +tab_entry_new (const MetaTabEntry *entry, + gint screen_width, + gboolean outline) +{ + TabEntry *te; + + te = g_new (TabEntry, 1); + te->key = entry->key; + te->title = NULL; + if (entry->title) + { + gchar *str; + gchar *tmp; + gchar *formatter = "%s"; + + str = meta_g_utf8_strndup (entry->title, 4096); + + if (entry->hidden) + { + formatter = "[%s]"; + } + + tmp = g_markup_printf_escaped (formatter, str); + g_free (str); + str = tmp; + + if (entry->demands_attention) + { + /* Escape the whole line of text then markup the text and + * copy it back into the original buffer. + */ + tmp = g_strdup_printf ("<b>%s</b>", str); + g_free (str); + str = tmp; + } + + te->title=g_strdup(str); + + g_free (str); + } + te->widget = NULL; + te->icon = entry->icon; + te->blank = entry->blank; + te->dimmed_icon = NULL; + if (te->icon) + { + g_object_ref (G_OBJECT (te->icon)); + if (entry->hidden) + te->dimmed_icon = dimm_icon (entry->icon); + } + + if (outline) + { + te->rect.x = entry->rect.x; + te->rect.y = entry->rect.y; + te->rect.width = entry->rect.width; + te->rect.height = entry->rect.height; + + te->inner_rect.x = entry->inner_rect.x; + te->inner_rect.y = entry->inner_rect.y; + te->inner_rect.width = entry->inner_rect.width; + te->inner_rect.height = entry->inner_rect.height; + } + return te; +} + +MetaTabPopup* +meta_ui_tab_popup_new (const MetaTabEntry *entries, + int screen_number, + int entry_count, + int width, + gboolean outline) +{ + MetaTabPopup *popup; + int i, left, right, top, bottom; + int height; + GtkWidget *table; + GtkWidget *vbox; + GtkWidget *align; + GList *tmp; + GtkWidget *frame; + int max_label_width; /* the actual max width of the labels we create */ + AtkObject *obj; + GdkScreen *screen; + int screen_width; + + popup = g_new (MetaTabPopup, 1); + + popup->outline_window = gtk_window_new (GTK_WINDOW_POPUP); + + screen = gdk_display_get_screen (gdk_display_get_default (), + screen_number); + gtk_window_set_screen (GTK_WINDOW (popup->outline_window), + screen); + + gtk_widget_set_app_paintable (popup->outline_window, TRUE); + gtk_widget_realize (popup->outline_window); + + g_signal_connect (G_OBJECT (popup->outline_window), "expose_event", + G_CALLBACK (outline_window_expose), popup); + + popup->window = gtk_window_new (GTK_WINDOW_POPUP); + + gtk_window_set_screen (GTK_WINDOW (popup->window), + screen); + + gtk_window_set_position (GTK_WINDOW (popup->window), + GTK_WIN_POS_CENTER_ALWAYS); + /* enable resizing, to get never-shrink behavior */ + gtk_window_set_resizable (GTK_WINDOW (popup->window), + TRUE); + popup->current = NULL; + popup->entries = NULL; + popup->current_selected_entry = NULL; + popup->outline = outline; + + screen_width = gdk_screen_get_width (screen); + for (i = 0; i < entry_count; ++i) + { + TabEntry* new_entry = tab_entry_new (&entries[i], screen_width, outline); + popup->entries = g_list_prepend (popup->entries, new_entry); + } + + popup->entries = g_list_reverse (popup->entries); + + g_assert (width > 0); + height = i / width; + if (i % width) + height += 1; + + table = gtk_table_new (height, width, FALSE); + vbox = gtk_vbox_new (FALSE, 0); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + gtk_container_set_border_width (GTK_CONTAINER (table), 1); + gtk_container_add (GTK_CONTAINER (popup->window), + frame); + gtk_container_add (GTK_CONTAINER (frame), + vbox); + + align = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + + gtk_box_pack_start (GTK_BOX (vbox), align, TRUE, TRUE, 0); + + gtk_container_add (GTK_CONTAINER (align), + table); + + popup->label = gtk_label_new (""); + + /* Set the accessible role of the label to a status bar so it + * will emit name changed events that can be used by screen + * readers. + */ + obj = gtk_widget_get_accessible (popup->label); + atk_object_set_role (obj, ATK_ROLE_STATUSBAR); + + gtk_misc_set_padding (GTK_MISC (popup->label), 3, 3); + + gtk_box_pack_end (GTK_BOX (vbox), popup->label, FALSE, FALSE, 0); + + max_label_width = 0; + top = 0; + bottom = 1; + tmp = popup->entries; + + while (tmp && top < height) + { + left = 0; + right = 1; + + while (tmp && left < width) + { + GtkWidget *image; + GtkRequisition req; + + TabEntry *te; + + te = tmp->data; + + if (te->blank) + { + /* just stick a widget here to avoid special cases */ + image = gtk_alignment_new (0.0, 0.0, 0.0, 0.0); + } + else if (outline) + { + if (te->dimmed_icon) + { + image = selectable_image_new (te->dimmed_icon); + } + else + { + image = selectable_image_new (te->icon); + } + + gtk_misc_set_padding (GTK_MISC (image), + INSIDE_SELECT_RECT + OUTSIDE_SELECT_RECT + 1, + INSIDE_SELECT_RECT + OUTSIDE_SELECT_RECT + 1); + gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.5); + } + else + { + image = selectable_workspace_new ((MetaWorkspace *) te->key); + } + + te->widget = image; + + gtk_table_attach (GTK_TABLE (table), + te->widget, + left, right, top, bottom, + 0, 0, + 0, 0); + + /* Efficiency rules! */ + gtk_label_set_markup (GTK_LABEL (popup->label), + te->title); + gtk_widget_size_request (popup->label, &req); + max_label_width = MAX (max_label_width, req.width); + + tmp = tmp->next; + + ++left; + ++right; + } + + ++top; + ++bottom; + } + + /* remove all the temporary text */ + gtk_label_set_text (GTK_LABEL (popup->label), ""); + /* Make it so that we ellipsize if the text is too long */ + gtk_label_set_ellipsize (GTK_LABEL (popup->label), PANGO_ELLIPSIZE_END); + + /* Limit the window size to no bigger than screen_width/4 */ + if (max_label_width>(screen_width/4)) + { + max_label_width = screen_width/4; + } + + max_label_width += 20; /* add random padding */ + + gtk_window_set_default_size (GTK_WINDOW (popup->window), + max_label_width, + -1); + + return popup; +} + +static void +free_tab_entry (gpointer data, gpointer user_data) +{ + TabEntry *te; + + te = data; + + g_free (te->title); + if (te->icon) + g_object_unref (G_OBJECT (te->icon)); + if (te->dimmed_icon) + g_object_unref (G_OBJECT (te->dimmed_icon)); + + g_free (te); +} + +void +meta_ui_tab_popup_free (MetaTabPopup *popup) +{ + meta_verbose ("Destroying tab popup window\n"); + + gtk_widget_destroy (popup->outline_window); + gtk_widget_destroy (popup->window); + + g_list_foreach (popup->entries, free_tab_entry, NULL); + + g_list_free (popup->entries); + + g_free (popup); +} + +void +meta_ui_tab_popup_set_showing (MetaTabPopup *popup, + gboolean showing) +{ + if (showing) + { + gtk_widget_show_all (popup->window); + } + else + { + if (GTK_WIDGET_VISIBLE (popup->window)) + { + meta_verbose ("Hiding tab popup window\n"); + gtk_widget_hide (popup->window); + meta_core_increment_event_serial (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + } + } +} + +static void +display_entry (MetaTabPopup *popup, + TabEntry *te) +{ + GdkRectangle rect; + GdkRegion *region; + GdkRegion *inner_region; + + + if (popup->current_selected_entry) + { + if (popup->outline) + unselect_image (popup->current_selected_entry->widget); + else + unselect_workspace (popup->current_selected_entry->widget); + } + + gtk_label_set_markup (GTK_LABEL (popup->label), te->title); + + if (popup->outline) + select_image (te->widget); + else + select_workspace (te->widget); + + if (popup->outline) + { + /* Do stuff behind gtk's back */ + gdk_window_hide (popup->outline_window->window); + meta_core_increment_event_serial (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + + rect = te->rect; + rect.x = 0; + rect.y = 0; + + gdk_window_move_resize (popup->outline_window->window, + te->rect.x, te->rect.y, + te->rect.width, te->rect.height); + + gdk_window_set_background (popup->outline_window->window, + &popup->outline_window->style->black); + + region = gdk_region_rectangle (&rect); + inner_region = gdk_region_rectangle (&te->inner_rect); + gdk_region_subtract (region, inner_region); + gdk_region_destroy (inner_region); + + gdk_window_shape_combine_region (popup->outline_window->window, + region, + 0, 0); + + gdk_region_destroy (region); + + /* This should piss off gtk a bit, but we don't want to raise + * above the tab popup. So, instead of calling gtk_widget_show, + * we manually set the window as mapped and then manually map it + * with gdk functions. + */ + GTK_WIDGET_SET_FLAGS (popup->outline_window, GTK_MAPPED); + gdk_window_show_unraised (popup->outline_window->window); + } + + /* Must be before we handle an expose for the outline window */ + popup->current_selected_entry = te; +} + +void +meta_ui_tab_popup_forward (MetaTabPopup *popup) +{ + if (popup->current != NULL) + popup->current = popup->current->next; + + if (popup->current == NULL) + popup->current = popup->entries; + + if (popup->current != NULL) + { + TabEntry *te; + + te = popup->current->data; + + display_entry (popup, te); + } +} + +void +meta_ui_tab_popup_backward (MetaTabPopup *popup) +{ + if (popup->current != NULL) + popup->current = popup->current->prev; + + if (popup->current == NULL) + popup->current = g_list_last (popup->entries); + + if (popup->current != NULL) + { + TabEntry *te; + + te = popup->current->data; + + display_entry (popup, te); + } +} + +MetaTabEntryKey +meta_ui_tab_popup_get_selected (MetaTabPopup *popup) +{ + if (popup->current) + { + TabEntry *te; + + te = popup->current->data; + + return te->key; + } + else + return (MetaTabEntryKey)None; +} + +void +meta_ui_tab_popup_select (MetaTabPopup *popup, + MetaTabEntryKey key) +{ + GList *tmp; + + /* Note, "key" may not be in the list of entries; other code assumes + * it's OK to pass in a key that isn't. + */ + + tmp = popup->entries; + while (tmp != NULL) + { + TabEntry *te; + + te = tmp->data; + + if (te->key == key) + { + popup->current = tmp; + + display_entry (popup, te); + + return; + } + + tmp = tmp->next; + } +} + +#define META_TYPE_SELECT_IMAGE (meta_select_image_get_type ()) +#define META_SELECT_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_SELECT_IMAGE, MetaSelectImage)) + +typedef struct _MetaSelectImage MetaSelectImage; +typedef struct _MetaSelectImageClass MetaSelectImageClass; + +struct _MetaSelectImage +{ + GtkImage parent_instance; + guint selected : 1; +}; + +struct _MetaSelectImageClass +{ + GtkImageClass parent_class; +}; + + +static GType meta_select_image_get_type (void) G_GNUC_CONST; + +static GtkWidget* +selectable_image_new (GdkPixbuf *pixbuf) +{ + GtkWidget *w; + + w = g_object_new (meta_select_image_get_type (), NULL); + gtk_image_set_from_pixbuf (GTK_IMAGE (w), pixbuf); + + return w; +} + +static void +select_image (GtkWidget *widget) +{ + META_SELECT_IMAGE (widget)->selected = TRUE; + gtk_widget_queue_draw (widget); +} + +static void +unselect_image (GtkWidget *widget) +{ + META_SELECT_IMAGE (widget)->selected = FALSE; + gtk_widget_queue_draw (widget); +} + +static void meta_select_image_class_init (MetaSelectImageClass *klass); +static gboolean meta_select_image_expose_event (GtkWidget *widget, + GdkEventExpose *event); + +static GtkImageClass *parent_class; + +GType +meta_select_image_get_type (void) +{ + static GType image_type = 0; + + if (!image_type) + { + static const GTypeInfo image_info = + { + sizeof (MetaSelectImageClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) meta_select_image_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (MetaSelectImage), + 16, /* n_preallocs */ + (GInstanceInitFunc) NULL, + }; + + image_type = g_type_register_static (GTK_TYPE_IMAGE, "MetaSelectImage", &image_info, 0); + } + + return image_type; +} + +static void +meta_select_image_class_init (MetaSelectImageClass *klass) +{ + GtkWidgetClass *widget_class; + + parent_class = g_type_class_peek (gtk_image_get_type ()); + + widget_class = GTK_WIDGET_CLASS (klass); + + widget_class->expose_event = meta_select_image_expose_event; +} + +static gboolean +meta_select_image_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + if (META_SELECT_IMAGE (widget)->selected) + { + int x, y, w, h; + GtkMisc *misc; + GtkStyle *style; + GtkStateType state; + cairo_t *cr; + + misc = GTK_MISC (widget); + + x = (widget->allocation.x * (1.0 - misc->xalign) + + (widget->allocation.x + widget->allocation.width + - (widget->requisition.width - misc->xpad * 2)) * + misc->xalign) + 0.5; + y = (widget->allocation.y * (1.0 - misc->yalign) + + (widget->allocation.y + widget->allocation.height + - (widget->requisition.height - misc->ypad * 2)) * + misc->yalign) + 0.5; + + x -= INSIDE_SELECT_RECT + 1; + y -= INSIDE_SELECT_RECT + 1; + + w = widget->requisition.width - OUTSIDE_SELECT_RECT * 2 - 1; + h = widget->requisition.height - OUTSIDE_SELECT_RECT * 2 - 1; + + style = gtk_widget_get_style (widget); + state = gtk_widget_get_state (widget); + cr = gdk_cairo_create (widget->window); + + cairo_set_line_width (cr, 2.0); + gdk_cairo_set_source_color (cr, &style->fg[state]); + + cairo_rectangle (cr, x, y, w + 1, h + 1); + cairo_stroke (cr); + + cairo_set_line_width (cr, 1.0); +#if 0 + gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_SELECTED]); + cairo_rectangle (cr, x, y, w, h); + cairo_fill (cr); +#endif + +#if 0 + gtk_paint_focus (widget->style, widget->window, + &event->area, widget, "meta-tab-image", + x, y, w, h); +#endif + + cairo_destroy (cr); + } + + return GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); +} + +#define META_TYPE_SELECT_WORKSPACE (meta_select_workspace_get_type ()) +#define META_SELECT_WORKSPACE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_SELECT_WORKSPACE, MetaSelectWorkspace)) + +typedef struct _MetaSelectWorkspace MetaSelectWorkspace; +typedef struct _MetaSelectWorkspaceClass MetaSelectWorkspaceClass; + +struct _MetaSelectWorkspace +{ + GtkDrawingArea parent_instance; + MetaWorkspace *workspace; + guint selected : 1; +}; + +struct _MetaSelectWorkspaceClass +{ + GtkDrawingAreaClass parent_class; +}; + + +static GType meta_select_workspace_get_type (void) G_GNUC_CONST; + +#define SELECT_OUTLINE_WIDTH 2 +#define MINI_WORKSPACE_WIDTH 48 + +static GtkWidget* +selectable_workspace_new (MetaWorkspace *workspace) +{ + GtkWidget *widget; + double screen_aspect; + + widget = g_object_new (meta_select_workspace_get_type (), NULL); + + screen_aspect = (double) workspace->screen->rect.height / + (double) workspace->screen->rect.width; + + /* account for select rect */ + gtk_widget_set_size_request (widget, + MINI_WORKSPACE_WIDTH + SELECT_OUTLINE_WIDTH * 2, + MINI_WORKSPACE_WIDTH * screen_aspect + SELECT_OUTLINE_WIDTH * 2); + + META_SELECT_WORKSPACE (widget)->workspace = workspace; + + return widget; +} + +static void +select_workspace (GtkWidget *widget) +{ + META_SELECT_WORKSPACE(widget)->selected = TRUE; + gtk_widget_queue_draw (widget); +} + +static void +unselect_workspace (GtkWidget *widget) +{ + META_SELECT_WORKSPACE (widget)->selected = FALSE; + gtk_widget_queue_draw (widget); +} + +static void meta_select_workspace_class_init (MetaSelectWorkspaceClass *klass); + +static gboolean meta_select_workspace_expose_event (GtkWidget *widget, + GdkEventExpose *event); + +GType +meta_select_workspace_get_type (void) +{ + static GType workspace_type = 0; + + if (!workspace_type) + { + static const GTypeInfo workspace_info = + { + sizeof (MetaSelectWorkspaceClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) meta_select_workspace_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (MetaSelectWorkspace), + 16, /* n_preallocs */ + (GInstanceInitFunc) NULL, + }; + + workspace_type = g_type_register_static (GTK_TYPE_DRAWING_AREA, + "MetaSelectWorkspace", + &workspace_info, + 0); + } + + return workspace_type; +} + +static void +meta_select_workspace_class_init (MetaSelectWorkspaceClass *klass) +{ + GtkWidgetClass *widget_class; + + widget_class = GTK_WIDGET_CLASS (klass); + + widget_class->expose_event = meta_select_workspace_expose_event; +} + +/** + * meta_convert_meta_to_wnck() converts a MetaWindow to a + * WnckWindowDisplayInfo window that is used to build a thumbnail of a + * workspace. + **/ +static WnckWindowDisplayInfo +meta_convert_meta_to_wnck (MetaWindow *window, MetaScreen *screen) +{ + WnckWindowDisplayInfo wnck_window; + wnck_window.icon = window->icon; + wnck_window.mini_icon = window->mini_icon; + + wnck_window.is_active = FALSE; + if (window == window->display->expected_focus_window) + wnck_window.is_active = TRUE; + + if (window->frame) + { + wnck_window.x = window->frame->rect.x; + wnck_window.y = window->frame->rect.y; + wnck_window.width = window->frame->rect.width; + wnck_window.height = window->frame->rect.height; + } + else + { + wnck_window.x = window->rect.x; + wnck_window.y = window->rect.y; + wnck_window.width = window->rect.width; + wnck_window.height = window->rect.height; + } + return wnck_window; +} + + +static gboolean +meta_select_workspace_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + MetaWorkspace *workspace; + WnckWindowDisplayInfo *windows; + GtkStyle *style; + cairo_t *cr; + int i, n_windows; + GList *tmp, *list; + + workspace = META_SELECT_WORKSPACE (widget)->workspace; + + list = meta_stack_list_windows (workspace->screen->stack, workspace); + n_windows = g_list_length (list); + windows = g_new (WnckWindowDisplayInfo, n_windows); + + tmp = list; + i = 0; + while (tmp != NULL) + { + MetaWindow *window; + gboolean ignoreable_sticky; + + window = tmp->data; + + ignoreable_sticky = window->on_all_workspaces && + workspace != workspace->screen->active_workspace; + + if (window->skip_pager || + !meta_window_showing_on_its_workspace (window) || + window->unmaps_pending || + ignoreable_sticky) + { + --n_windows; + } + else + { + windows[i] = meta_convert_meta_to_wnck (window, workspace->screen); + i++; + } + tmp = tmp->next; + } + + g_list_free (list); + + wnck_draw_workspace (widget, + widget->window, + SELECT_OUTLINE_WIDTH, + SELECT_OUTLINE_WIDTH, + widget->allocation.width - SELECT_OUTLINE_WIDTH * 2, + widget->allocation.height - SELECT_OUTLINE_WIDTH * 2, + workspace->screen->rect.width, + workspace->screen->rect.height, + NULL, + (workspace->screen->active_workspace == workspace), + windows, + n_windows); + + g_free (windows); + + if (META_SELECT_WORKSPACE (widget)->selected) + { + style = gtk_widget_get_style (widget); + cr = gdk_cairo_create (widget->window); + + gdk_cairo_set_source_color (cr, + &style->fg[gtk_widget_get_state (widget)]); + cairo_set_line_width (cr, SELECT_OUTLINE_WIDTH); + + cairo_rectangle (cr, + SELECT_OUTLINE_WIDTH / 2.0, SELECT_OUTLINE_WIDTH / 2.0, + widget->allocation.width - SELECT_OUTLINE_WIDTH, + widget->allocation.height - SELECT_OUTLINE_WIDTH); + cairo_stroke (cr); + + cairo_destroy (cr); + } + + return TRUE; +} + diff --git a/src/ui/testgradient.c b/src/ui/testgradient.c new file mode 100644 index 00000000..fd1b09c7 --- /dev/null +++ b/src/ui/testgradient.c @@ -0,0 +1,336 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco gradient test program */ + +/* + * 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 "gradient.h" +#include <gtk/gtk.h> + +typedef void (* RenderGradientFunc) (GdkDrawable *drawable, + cairo_t *cr, + int width, + int height); + +static void +draw_checkerboard (GdkDrawable *drawable, + int width, + int height) +{ + gint i, j, xcount, ycount; + GdkColor color1, color2; + cairo_t *cr; + +#define CHECK_SIZE 10 +#define SPACING 2 + + color1.red = 30000; + color1.green = 30000; + color1.blue = 30000; + + color2.red = 50000; + color2.green = 50000; + color2.blue = 50000; + + cr = gdk_cairo_create (drawable); + + xcount = 0; + i = SPACING; + while (i < width) + { + j = SPACING; + ycount = xcount % 2; /* start with even/odd depending on row */ + while (j < height) + { + if (ycount % 2) + gdk_cairo_set_source_color (cr, &color1); + else + gdk_cairo_set_source_color (cr, &color2); + + /* If we're outside event->area, this will do nothing. + * It might be mildly more efficient if we handled + * the clipping ourselves, but again we're feeling lazy. + */ + cairo_rectangle (cr, i, j, CHECK_SIZE, CHECK_SIZE); + cairo_fill (cr); + + j += CHECK_SIZE + SPACING; + ++ycount; + } + + i += CHECK_SIZE + SPACING; + ++xcount; + } + + cairo_destroy (cr); +} + +static void +render_simple (GdkDrawable *drawable, + cairo_t *cr, + int width, int height, + MetaGradientType type, + gboolean with_alpha) +{ + GdkPixbuf *pixbuf; + GdkColor from, to; + + gdk_color_parse ("blue", &from); + gdk_color_parse ("green", &to); + + pixbuf = meta_gradient_create_simple (width, height, + &from, &to, + type); + + if (with_alpha) + { + const unsigned char alphas[] = { 0xff, 0xaa, 0x2f, 0x0, 0xcc, 0xff, 0xff }; + + if (!gdk_pixbuf_get_has_alpha (pixbuf)) + { + GdkPixbuf *new_pixbuf; + + new_pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0); + g_object_unref (G_OBJECT (pixbuf)); + pixbuf = new_pixbuf; + } + + meta_gradient_add_alpha (pixbuf, + alphas, G_N_ELEMENTS (alphas), + META_GRADIENT_HORIZONTAL); + + draw_checkerboard (drawable, width, height); + } + + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + cairo_rectangle (cr, 0, 0, width, height); + cairo_fill (cr); + + g_object_unref (G_OBJECT (pixbuf)); +} + +static void +render_vertical_func (GdkDrawable *drawable, + cairo_t *cr, + int width, int height) +{ + render_simple (drawable, cr, width, height, META_GRADIENT_VERTICAL, FALSE); +} + +static void +render_horizontal_func (GdkDrawable *drawable, + cairo_t *cr, + int width, int height) +{ + render_simple (drawable, cr, width, height, META_GRADIENT_HORIZONTAL, FALSE); +} + +static void +render_diagonal_func (GdkDrawable *drawable, + cairo_t *cr, + int width, int height) +{ + render_simple (drawable, cr, width, height, META_GRADIENT_DIAGONAL, FALSE); +} + +static void +render_diagonal_alpha_func (GdkDrawable *drawable, + cairo_t *cr, + int width, int height) +{ + render_simple (drawable, cr, width, height, META_GRADIENT_DIAGONAL, TRUE); +} + +static void +render_multi (GdkDrawable *drawable, + cairo_t *cr, + int width, int height, + MetaGradientType type) +{ + GdkPixbuf *pixbuf; +#define N_COLORS 5 + GdkColor colors[N_COLORS]; + + gdk_color_parse ("red", &colors[0]); + gdk_color_parse ("blue", &colors[1]); + gdk_color_parse ("orange", &colors[2]); + gdk_color_parse ("pink", &colors[3]); + gdk_color_parse ("green", &colors[4]); + + pixbuf = meta_gradient_create_multi (width, height, + colors, N_COLORS, + type); + + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + cairo_rectangle (cr, 0, 0, width, height); + cairo_fill (cr); + + g_object_unref (G_OBJECT (pixbuf)); +#undef N_COLORS +} + +static void +render_vertical_multi_func (GdkDrawable *drawable, + cairo_t *cr, + int width, int height) +{ + render_multi (drawable, cr, width, height, META_GRADIENT_VERTICAL); +} + +static void +render_horizontal_multi_func (GdkDrawable *drawable, + cairo_t *cr, + int width, int height) +{ + render_multi (drawable, cr, width, height, META_GRADIENT_HORIZONTAL); +} + +static void +render_diagonal_multi_func (GdkDrawable *drawable, + cairo_t *cr, + int width, int height) +{ + render_multi (drawable, cr, width, height, META_GRADIENT_DIAGONAL); +} + +static void +render_interwoven_func (GdkDrawable *drawable, + cairo_t *cr, + int width, int height) +{ + GdkPixbuf *pixbuf; +#define N_COLORS 4 + GdkColor colors[N_COLORS]; + + gdk_color_parse ("red", &colors[0]); + gdk_color_parse ("blue", &colors[1]); + gdk_color_parse ("pink", &colors[2]); + gdk_color_parse ("green", &colors[3]); + + pixbuf = meta_gradient_create_interwoven (width, height, + colors, height / 10, + colors + 2, height / 14); + + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + cairo_rectangle (cr, 0, 0, width, height); + cairo_fill (cr); + + g_object_unref (G_OBJECT (pixbuf)); +} + +static gboolean +expose_callback (GtkWidget *widget, + GdkEventExpose *event, + gpointer data) +{ + RenderGradientFunc func = data; + GdkWindow *window; + GtkAllocation allocation; + GtkStyle *style; + cairo_t *cr; + + style = gtk_widget_get_style (widget); + gtk_widget_get_allocation (widget, &allocation); + + window = gtk_widget_get_window (widget); + cr = gdk_cairo_create (window); + gdk_cairo_set_source_color (cr, &style->fg[gtk_widget_get_state (widget)]); + + (* func) (window, + cr, + allocation.width, + allocation.height); + + cairo_destroy (cr); + + return TRUE; +} + +static GtkWidget* +create_gradient_window (const char *title, + RenderGradientFunc func) +{ + GtkWidget *window; + GtkWidget *drawing_area; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_title (GTK_WINDOW (window), title); + + drawing_area = gtk_drawing_area_new (); + + gtk_widget_set_size_request (drawing_area, 1, 1); + + gtk_window_set_default_size (GTK_WINDOW (window), 175, 175); + + g_signal_connect (G_OBJECT (drawing_area), + "expose_event", + G_CALLBACK (expose_callback), + func); + + gtk_container_add (GTK_CONTAINER (window), drawing_area); + + gtk_widget_show_all (window); + + return window; +} + +static void +meta_gradient_test (void) +{ + GtkWidget *window; + + window = create_gradient_window ("Simple vertical", + render_vertical_func); + + window = create_gradient_window ("Simple horizontal", + render_horizontal_func); + + window = create_gradient_window ("Simple diagonal", + render_diagonal_func); + + window = create_gradient_window ("Multi vertical", + render_vertical_multi_func); + + window = create_gradient_window ("Multi horizontal", + render_horizontal_multi_func); + + window = create_gradient_window ("Multi diagonal", + render_diagonal_multi_func); + + window = create_gradient_window ("Interwoven", + render_interwoven_func); + + window = create_gradient_window ("Simple diagonal with horizontal multi alpha", + render_diagonal_alpha_func); + +} + +int +main (int argc, char **argv) +{ + gtk_init (&argc, &argv); + + meta_gradient_test (); + + gtk_main (); + + return 0; +} + diff --git a/src/ui/theme-parser.c b/src/ui/theme-parser.c new file mode 100644 index 00000000..12f01061 --- /dev/null +++ b/src/ui/theme-parser.c @@ -0,0 +1,4212 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco 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> + +/* This is a hack for Ubuntu's metacity themes + * Its safe for unable or not. + * I am using this define to know what code was added */ +#define USE_UBUNTU_CODE 1 + +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, + STATE_ASPECT_RATIO, + /* 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 */ + STATE_TILE, /* tile another draw op list */ + /* sub-parts of gradient */ + STATE_COLOR, + /* frame style */ + STATE_FRAME_STYLE, + STATE_PIECE, + STATE_BUTTON, +#ifdef USE_UBUNTU_CODE + STATE_SHADOW, + STATE_PADDING, +#endif + /* style set */ + STATE_FRAME_STYLE_SET, + STATE_FRAME, + /* assigning style sets to windows */ + STATE_WINDOW, + /* things we don't use any more but we can still parse: */ + STATE_MENU_ICON, + STATE_FALLBACK +} 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 */ + guint format_version; /* version of format of theme file */ + 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 */ +} 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); + +#ifdef USE_UBUNTU_CODE +static void parse_shadow_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); + +static void parse_padding_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +#endif +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); + +/* Translators: This means that an attribute which should have been found + * on an XML element was not in fact found. + */ +#define ATTRIBUTE_NOT_FOUND _("No \"%s\" attribute on element <%s>") + +static GMarkupParser marco_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; + gboolean required; +} LocateAttr; + +/* Attribute names can have a leading '!' to indicate that they are + * required. + */ +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; + + /* FIXME: duplicated code; refactor loop */ + n_attrs = 1; + attrs[0].name = first_attribute_name; + attrs[0].retloc = first_attribute_retloc; + attrs[0].required = attrs[0].name[0]=='!'; + if (attrs[0].required) + attrs[0].name++; /* skip past it */ + *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); + + g_assert (n_attrs < MAX_ATTRS); + + attrs[n_attrs].name = name; + attrs[n_attrs].retloc = retloc; + attrs[n_attrs].required = attrs[n_attrs].name[0]=='!'; + if (attrs[n_attrs].required) + attrs[n_attrs].name++; /* skip past it */ + + n_attrs += 1; + *retloc = NULL; + + name = va_arg (args, const char*); + retloc = va_arg (args, const char**); + } + + va_end (args); + + 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 out; + } + + *retloc = attribute_values[i]; + found = TRUE; + } + + ++j; + } + + if (!found) + { + j = 0; + while (j < n_attrs) + { + g_warning ("It could have been %s.\n", attrs[j++].name); + } + + 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 out; + } + + ++i; + } + + /* Did we catch them all? */ + i = 0; + while (i < n_attrs) + { + if (attrs[i].required && *(attrs[i].retloc)==NULL) + { + set_error (error, context, + G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + ATTRIBUTE_NOT_FOUND, + attrs[i].name, element_name); + retval = FALSE; + goto out; + } + + ++i; + } + + out: + 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, + MetaTheme *theme, + GError **error) +{ + char *end; + long l; + int j; + + *val = 0; + + end = NULL; + + /* Is str a constant? */ + + if (META_THEME_ALLOWS (theme, META_THEME_UBIQUITOUS_CONSTANTS) && + meta_theme_lookup_int_constant (theme, str, &j)) + { + /* Yes. */ + l = j; + } + else + { + /* No. Let's try parsing it instead. */ + + 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_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 gboolean +parse_rounding (const char *str, + guint *val, + GMarkupParseContext *context, + MetaTheme *theme, + GError **error) +{ + if (strcmp ("true", str) == 0) + *val = 5; /* historical "true" value */ + else if (strcmp ("false", str) == 0) + *val = 0; + else + { + int tmp; + gboolean result; + if (!META_THEME_ALLOWS (theme, META_THEME_VARIED_ROUND_CORNERS)) + { + /* Not known in this version, so bail. */ + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Boolean values must be \"true\" or \"false\" not \"%s\""), + str); + return FALSE; + } + + result = parse_positive_integer (str, &tmp, context, theme, error); + + *val = tmp; + + return result; + } + + 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, + MetaAlphaGradientSpec **spec_ret, + GMarkupParseContext *context, + GError **error) +{ + char **split; + int i; + int n_alphas; + MetaAlphaGradientSpec *spec; + + *spec_ret = NULL; + + split = g_strsplit (str, ":", -1); + + i = 0; + while (split[i]) + ++i; + + if (i == 0) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Could not parse \"%s\" as a floating point number"), + str); + + g_strfreev (split); + + return FALSE; + } + + n_alphas = i; + + /* FIXME allow specifying horizontal/vertical/diagonal in theme format, + * once we implement vertical/diagonal in gradient.c + */ + spec = meta_alpha_gradient_spec_new (META_GRADIENT_HORIZONTAL, + n_alphas); + + i = 0; + while (i < n_alphas) + { + double v; + + if (!parse_double (split[i], &v, context, error)) + { + /* clear up, but don't set error: it was set by parse_double */ + g_strfreev (split); + meta_alpha_gradient_spec_free (spec); + + return FALSE; + } + + if (v < (0.0 - 1e-6) || v > (1.0 + 1e-6)) + { + 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"), + v); + + g_strfreev (split); + meta_alpha_gradient_spec_free (spec); + + return FALSE; + } + + spec->alphas[i] = (unsigned char) (v * 255); + + ++i; + } + + g_strfreev (split); + + *spec_ret = spec; + + return TRUE; +} + +static MetaColorSpec* +parse_color (MetaTheme *theme, + const char *str, + GError **err) +{ + char* referent; + + if (META_THEME_ALLOWS (theme, META_THEME_COLOR_CONSTANTS) && + meta_theme_lookup_color_constant (theme, str, &referent)) + { + if (referent) + return meta_color_spec_new_from_string (referent, err); + + /* no need to free referent: it's a pointer into the actual hash table */ + } + + return meta_color_spec_new_from_string (str, err); +} + +static gboolean +parse_title_scale (const char *str, + double *val, + GMarkupParseContext *context, + GError **error) +{ + double factor; + + if (strcmp (str, "xx-small") == 0) + factor = PANGO_SCALE_XX_SMALL; + else if (strcmp (str, "x-small") == 0) + factor = PANGO_SCALE_X_SMALL; + else if (strcmp (str, "small") == 0) + factor = PANGO_SCALE_SMALL; + else if (strcmp (str, "medium") == 0) + factor = PANGO_SCALE_MEDIUM; + else if (strcmp (str, "large") == 0) + factor = PANGO_SCALE_LARGE; + else if (strcmp (str, "x-large") == 0) + factor = PANGO_SCALE_X_LARGE; + else if (strcmp (str, "xx-large") == 0) + factor = PANGO_SCALE_XX_LARGE; + else + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Invalid title scale \"%s\" (must be one of xx-small,x-small,small,medium,large,x-large,xx-large)\n"), + str); + return FALSE; + } + + *val = factor; + + 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 = 0; + double dval = 0.0; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, "!value", &value, + NULL)) + return; + + /* We don't know how a a constant is going to be used, so we have guess its + * type from its contents: + * + * - Starts like a number and contains a '.': float constant + * - Starts like a number and doesn't contain a '.': int constant + * - Starts with anything else: a color constant. + * (colors always start with # or a letter) + */ + if (value[0] == '.' || value[0] == '+' || value[0] == '-' || (value[0] >= '0' && value[0] <= '9')) + { + if (strchr (value, '.')) + { + 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 + { + if (!parse_positive_integer (value, &ival, context, info->theme, error)) + return; + + if (!meta_theme_define_int_constant (info->theme, + name, + ival, + error)) + { + add_context_to_error (error, context); + return; + } + } + } + else + { + if (!meta_theme_define_color_constant (info->theme, + name, + value, + 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; + const char *has_title = NULL; + const char *title_scale = NULL; + const char *rounded_top_left = NULL; + const char *rounded_top_right = NULL; + const char *rounded_bottom_left = NULL; + const char *rounded_bottom_right = NULL; + const char *hide_buttons = NULL; + gboolean has_title_val; + guint rounded_top_left_val; + guint rounded_top_right_val; + guint rounded_bottom_left_val; + guint rounded_bottom_right_val; + gboolean hide_buttons_val; + double title_scale_val; + MetaFrameLayout *parent_layout; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, "parent", &parent, + "has_title", &has_title, "title_scale", &title_scale, + "rounded_top_left", &rounded_top_left, + "rounded_top_right", &rounded_top_right, + "rounded_bottom_left", &rounded_bottom_left, + "rounded_bottom_right", &rounded_bottom_right, + "hide_buttons", &hide_buttons, + NULL)) + return; + + has_title_val = TRUE; + if (has_title && !parse_boolean (has_title, &has_title_val, context, error)) + return; + + hide_buttons_val = FALSE; + if (hide_buttons && !parse_boolean (hide_buttons, &hide_buttons_val, context, error)) + return; + + rounded_top_left_val = 0; + rounded_top_right_val = 0; + rounded_bottom_left_val = 0; + rounded_bottom_right_val = 0; + + if (rounded_top_left && !parse_rounding (rounded_top_left, &rounded_top_left_val, context, info->theme, error)) + return; + if (rounded_top_right && !parse_rounding (rounded_top_right, &rounded_top_right_val, context, info->theme, error)) + return; + if (rounded_bottom_left && !parse_rounding (rounded_bottom_left, &rounded_bottom_left_val, context, info->theme, error)) + return; + if (rounded_bottom_right && !parse_rounding (rounded_bottom_right, &rounded_bottom_right_val, context, info->theme, error)) + return; + + title_scale_val = 1.0; + if (title_scale && !parse_title_scale (title_scale, &title_scale_val, context, error)) + 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 (); + + if (has_title) /* only if explicit, otherwise inherit */ + info->layout->has_title = has_title_val; + + if (META_THEME_ALLOWS (info->theme, META_THEME_HIDDEN_BUTTONS) && hide_buttons_val) + info->layout->hide_buttons = hide_buttons_val; + + if (title_scale) + info->layout->title_scale = title_scale_val; + + if (rounded_top_left) + info->layout->top_left_corner_rounded_radius = rounded_top_left_val; + + if (rounded_top_right) + info->layout->top_right_corner_rounded_radius = rounded_top_right_val; + + if (rounded_bottom_left) + info->layout->bottom_left_corner_rounded_radius = rounded_bottom_left_val; + + if (rounded_bottom_right) + info->layout->bottom_right_corner_rounded_radius = rounded_bottom_right_val; + + 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 (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; + const char *background = NULL; + const char *alpha = NULL; + MetaFrameStyle *parent_style; + MetaFrameLayout *layout; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, "parent", &parent, + "geometry", &geometry, + "background", &background, + "alpha", &alpha, + NULL)) + 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; + + if (background != NULL && META_THEME_ALLOWS (info->theme, META_THEME_FRAME_BACKGROUNDS)) + { + info->style->window_background_color = meta_color_spec_new_from_string (background, error); + if (!info->style->window_background_color) + return; + + if (alpha != NULL) + { + + gboolean success; + MetaAlphaGradientSpec *alpha_vector; + + g_clear_error (error); + /* fortunately, we already have a routine to parse alpha values, + * though it produces a vector of them, which is a superset of + * what we want. + */ + success = parse_alpha (alpha, &alpha_vector, context, error); + if (!success) + return; + + /* alpha_vector->alphas must contain at least one element */ + info->style->window_background_alpha = alpha_vector->alphas[0]; + + meta_alpha_gradient_spec_free (alpha_vector); + } + } + else if (alpha != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("You must specify a background for an alpha value to be meaningful")); + return; + } + + 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 (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; + + 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")) + { + /* Not supported any more, but we have to parse it if they include it, + * for backwards compatibility. + */ + g_assert (info->op_list == NULL); + + push_state (info, STATE_MENU_ICON); + } + else if (ELEMENT_IS ("fallback")) + { + /* Not supported any more, but we have to parse it if they include it, + * for backwards compatibility. + */ + push_state (info, STATE_FALLBACK); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "metacity_theme"); + } +} + +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 <%s>"), + element_name, "info"); + } +} + +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; + + val = 0; + if (!parse_positive_integer (value, &val, context, info->theme, 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; + + if (!(info->layout->button_sizing == META_BUTTON_SIZING_LAST || + info->layout->button_sizing == META_BUTTON_SIZING_FIXED)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Cannot specify both \"button_width\"/\"button_height\" and \"aspect_ratio\" for buttons")); + return; + } + + info->layout->button_sizing = META_BUTTON_SIZING_FIXED; + } + else if (strcmp (name, "button_height") == 0) + { + info->layout->button_height = val; + + if (!(info->layout->button_sizing == META_BUTTON_SIZING_LAST || + info->layout->button_sizing == META_BUTTON_SIZING_FIXED)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Cannot specify both \"button_width\"/\"button_height\" and \"aspect_ratio\" for buttons")); + return; + } + + info->layout->button_sizing = META_BUTTON_SIZING_FIXED; + } + else + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Distance \"%s\" is unknown"), name); + return; + } +} + +static void +parse_aspect_ratio (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + const char *name; + const char *value; + double val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, "!value", &value, + NULL)) + return; + + val = 0; + if (!parse_double (value, &val, context, error)) + return; + + g_assert (info->layout); + + if (strcmp (name, "button") == 0) + { + info->layout->button_aspect = val; + + if (info->layout->button_sizing != META_BUTTON_SIZING_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Cannot specify both \"button_width\"/\"button_height\" and \"aspect_ratio\" for buttons")); + return; + } + + info->layout->button_sizing = META_BUTTON_SIZING_ASPECT; + } + else + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Aspect ratio \"%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; + + top_val = 0; + if (!parse_positive_integer (top, &top_val, context, info->theme, error)) + return; + + bottom_val = 0; + if (!parse_positive_integer (bottom, &bottom_val, context, info->theme, error)) + return; + + left_val = 0; + if (!parse_positive_integer (left, &left_val, context, info->theme, error)) + return; + + right_val = 0; + if (!parse_positive_integer (right, &right_val, context, info->theme, 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 if (ELEMENT_IS ("aspect_ratio")) + { + parse_aspect_ratio (context, element_name, + attribute_names, attribute_values, + info, error); + + push_state (info, STATE_ASPECT_RATIO); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "frame_geometry"); + } +} + +#if 0 +static gboolean +check_expression (PosToken *tokens, + int n_tokens, + 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.rect = meta_rect (0, 0, 0, 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 (tokens, n_tokens, + &env, + &x, &y, + error)) + { + add_context_to_error (error, context); + return FALSE; + } + + return TRUE; +} +#endif + +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 0 + 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; +#endif + + dash_on_val = 0; + if (dash_on_length && + !parse_positive_integer (dash_on_length, &dash_on_val, context, info->theme, error)) + return; + + dash_off_val = 0; + if (dash_off_length && + !parse_positive_integer (dash_off_length, &dash_off_val, context, info->theme, error)) + return; + + width_val = 0; + if (width && + !parse_positive_integer (width, &width_val, context, info->theme, error)) + return; + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = parse_color (info->theme, 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 = meta_draw_spec_new (info->theme, x1, NULL); + op->data.line.y1 = meta_draw_spec_new (info->theme, y1, NULL); + + if (strcmp(x1, x2)==0) + op->data.line.x2 = NULL; + else + op->data.line.x2 = meta_draw_spec_new (info->theme, x2, NULL); + + if (strcmp(y1, y2)==0) + op->data.line.y2 = NULL; + else + op->data.line.y2 = meta_draw_spec_new (info->theme, y2, NULL); + + 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 0 + 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; +#endif + + 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 = parse_color (info->theme, 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 = meta_draw_spec_new (info->theme, x, NULL); + op->data.rectangle.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.rectangle.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.rectangle.height = meta_draw_spec_new (info->theme, + height, NULL); + + 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; + const char *from; + const char *to; + 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, + "from", &from, + "to", &to, + NULL)) + return; + + if (META_THEME_ALLOWS (info->theme, META_THEME_DEGREES_IN_ARCS) ) + { + if (start_angle == NULL && from == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"start_angle\" or \"from\" attribute on element <%s>"), element_name); + return; + } + + if (extent_angle == NULL && to == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"extent_angle\" or \"to\" attribute on element <%s>"), element_name); + return; + } + } + else + { + if (start_angle == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + ATTRIBUTE_NOT_FOUND, "start_angle", element_name); + return; + } + + if (extent_angle == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + ATTRIBUTE_NOT_FOUND, "extent_angle", element_name); + return; + } + } + +#if 0 + 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; +#endif + + if (start_angle == NULL) + { + if (!parse_angle (from, &start_angle_val, context, error)) + return; + + start_angle_val = (180-start_angle_val)/360.0; + } + else + { + if (!parse_angle (start_angle, &start_angle_val, context, error)) + return; + } + + if (extent_angle == NULL) + { + if (!parse_angle (to, &extent_angle_val, context, error)) + return; + + extent_angle_val = ((180-extent_angle_val)/360.0) - start_angle_val; + } + else + { + 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 = parse_color (info->theme, 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 = meta_draw_spec_new (info->theme, x, NULL); + op->data.arc.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.arc.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.arc.height = meta_draw_spec_new (info->theme, height, NULL); + + 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 0 + 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; +#endif + op = meta_draw_op_new (META_DRAW_CLIP); + + op->data.clip.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.clip.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.clip.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.clip.height = meta_draw_spec_new (info->theme, height, NULL); + + 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; + MetaAlphaGradientSpec *alpha_spec; + 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 0 + 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; +#endif + alpha_spec = NULL; + if (!parse_alpha (alpha, &alpha_spec, context, error)) + return; + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = parse_color (info->theme, color, error); + if (color_spec == NULL) + { + if (alpha_spec) + meta_alpha_gradient_spec_free (alpha_spec); + + 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.alpha_spec = alpha_spec; + + op->data.tint.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.tint.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.tint.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.tint.height = meta_draw_spec_new (info->theme, height, NULL); + + 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; + MetaAlphaGradientSpec *alpha_spec; + 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 0 + 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; +#endif + + 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; + } + + alpha_spec = NULL; + if (alpha && !parse_alpha (alpha, &alpha_spec, context, error)) + return; + + g_assert (info->op == NULL); + info->op = meta_draw_op_new (META_DRAW_GRADIENT); + + info->op->data.gradient.x = meta_draw_spec_new (info->theme, x, NULL); + info->op->data.gradient.y = meta_draw_spec_new (info->theme, y, NULL); + info->op->data.gradient.width = meta_draw_spec_new (info->theme, + width, NULL); + info->op->data.gradient.height = meta_draw_spec_new (info->theme, + height, NULL); + + info->op->data.gradient.gradient_spec = meta_gradient_spec_new (type_val); + + info->op->data.gradient.alpha_spec = alpha_spec; + + 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; + const char *colorize; + const char *fill_type; + MetaAlphaGradientSpec *alpha_spec; + GdkPixbuf *pixbuf; + MetaColorSpec *colorize_spec = NULL; + MetaImageFillType fill_type_val; + int h, w, c; + int pixbuf_width, pixbuf_height, pixbuf_n_channels, pixbuf_rowstride; + guchar *pixbuf_pixels; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + "alpha", &alpha, "!filename", &filename, + "colorize", &colorize, + "fill_type", &fill_type, + NULL)) + return; + +#if 0 + 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; +#endif + fill_type_val = META_IMAGE_FILL_SCALE; + if (fill_type) + { + fill_type_val = meta_image_fill_type_from_string (fill_type); + + if (((int) fill_type_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand fill type \"%s\" for <%s> element"), + fill_type, element_name); + } + } + + /* Check last so we don't have to free it when other + * stuff fails. + * + * If it's a theme image, ask for it at 64px, which is + * the largest possible. We scale it anyway. + */ + pixbuf = meta_theme_load_image (info->theme, filename, 64, error); + + if (pixbuf == NULL) + { + add_context_to_error (error, context); + return; + } + + if (colorize) + { + colorize_spec = parse_color (info->theme, colorize, error); + + if (colorize_spec == NULL) + { + add_context_to_error (error, context); + g_object_unref (G_OBJECT (pixbuf)); + return; + } + } + + alpha_spec = NULL; + if (alpha && !parse_alpha (alpha, &alpha_spec, context, error)) + { + g_object_unref (G_OBJECT (pixbuf)); + return; + } + + op = meta_draw_op_new (META_DRAW_IMAGE); + + op->data.image.pixbuf = pixbuf; + op->data.image.colorize_spec = colorize_spec; + + op->data.image.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.image.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.image.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.image.height = meta_draw_spec_new (info->theme, height, NULL); + + op->data.image.alpha_spec = alpha_spec; + op->data.image.fill_type = fill_type_val; + + /* Check for vertical & horizontal stripes */ + pixbuf_n_channels = gdk_pixbuf_get_n_channels(pixbuf); + pixbuf_width = gdk_pixbuf_get_width(pixbuf); + pixbuf_height = gdk_pixbuf_get_height(pixbuf); + pixbuf_rowstride = gdk_pixbuf_get_rowstride(pixbuf); + pixbuf_pixels = gdk_pixbuf_get_pixels(pixbuf); + + /* Check for horizontal stripes */ + for (h = 0; h < pixbuf_height; h++) + { + for (w = 1; w < pixbuf_width; w++) + { + for (c = 0; c < pixbuf_n_channels; c++) + { + if (pixbuf_pixels[(h * pixbuf_rowstride) + c] != + pixbuf_pixels[(h * pixbuf_rowstride) + w + c]) + break; + } + if (c < pixbuf_n_channels) + break; + } + if (w < pixbuf_width) + break; + } + + if (h >= pixbuf_height) + { + op->data.image.horizontal_stripes = TRUE; + } + else + { + op->data.image.horizontal_stripes = FALSE; + } + + /* Check for vertical stripes */ + for (w = 0; w < pixbuf_width; w++) + { + for (h = 1; h < pixbuf_height; h++) + { + for (c = 0; c < pixbuf_n_channels; c++) + { + if (pixbuf_pixels[w + c] != + pixbuf_pixels[(h * pixbuf_rowstride) + w + c]) + break; + } + if (c < pixbuf_n_channels) + break; + } + if (h < pixbuf_height) + break; + } + + if (w >= pixbuf_width) + { + op->data.image.vertical_stripes = TRUE; + } + else + { + op->data.image.vertical_stripes = FALSE; + } + + 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 0 + 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; +#endif + 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 = meta_draw_spec_new (info->theme, x, NULL); + op->data.gtk_arrow.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.gtk_arrow.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.gtk_arrow.height = meta_draw_spec_new (info->theme, + height, NULL); + + 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 0 + 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; +#endif + 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 = meta_draw_spec_new (info->theme, x, NULL); + op->data.gtk_box.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.gtk_box.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.gtk_box.height = meta_draw_spec_new (info->theme, height, NULL); + + 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 0 + 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; +#endif + + 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 = meta_draw_spec_new (info->theme, x, NULL); + op->data.gtk_vline.y1 = meta_draw_spec_new (info->theme, y1, NULL); + op->data.gtk_vline.y2 = meta_draw_spec_new (info->theme, y2, NULL); + + 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; + const char *fill_type; + MetaAlphaGradientSpec *alpha_spec; + MetaImageFillType fill_type_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + "alpha", &alpha, + "fill_type", &fill_type, + NULL)) + return; + +#if 0 + 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; +#endif + fill_type_val = META_IMAGE_FILL_SCALE; + if (fill_type) + { + fill_type_val = meta_image_fill_type_from_string (fill_type); + + if (((int) fill_type_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand fill type \"%s\" for <%s> element"), + fill_type, element_name); + } + } + + alpha_spec = NULL; + if (alpha && !parse_alpha (alpha, &alpha_spec, context, error)) + return; + + op = meta_draw_op_new (META_DRAW_ICON); + + op->data.icon.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.icon.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.icon.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.icon.height = meta_draw_spec_new (info->theme, height, NULL); + + op->data.icon.alpha_spec = alpha_spec; + op->data.icon.fill_type = fill_type_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 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; +#endif + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = parse_color (info->theme, 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 = meta_draw_spec_new (info->theme, x, NULL); + op->data.title.y = meta_draw_spec_new (info->theme, y, NULL); + + 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; + + /* x/y/width/height default to 0,0,width,height - should + * probably do this for all the draw ops + */ +#if 0 + 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; +#endif + + 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 = meta_draw_spec_new (info->theme, x ? x : "0", NULL); + op->data.op_list.y = meta_draw_spec_new (info->theme, y ? y : "0", NULL); + op->data.op_list.width = meta_draw_spec_new (info->theme, + width ? width : "width", + NULL); + op->data.op_list.height = meta_draw_spec_new (info->theme, + height ? height : "height", + NULL); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_INCLUDE); + } + else if (ELEMENT_IS ("tile")) + { + MetaDrawOp *op; + const char *name; + const char *x; + const char *y; + const char *width; + const char *height; + const char *tile_xoffset; + const char *tile_yoffset; + const char *tile_width; + const char *tile_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, + "tile_xoffset", &tile_xoffset, + "tile_yoffset", &tile_yoffset, + "!tile_width", &tile_width, + "!tile_height", &tile_height, + NULL)) + return; + + /* These default to 0 */ +#if 0 + if (tile_xoffset && !check_expression (tile_xoffset, FALSE, info->theme, context, error)) + return; + + if (tile_yoffset && !check_expression (tile_yoffset, FALSE, info->theme, context, error)) + 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; + + if (!check_expression (tile_width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (tile_height, FALSE, info->theme, context, error)) + return; +#endif + 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_TILE); + + meta_draw_op_list_ref (op_list); + + op->data.tile.x = meta_draw_spec_new (info->theme, x ? x : "0", NULL); + op->data.tile.y = meta_draw_spec_new (info->theme, y ? y : "0", NULL); + op->data.tile.width = meta_draw_spec_new (info->theme, + width ? width : "width", + NULL); + op->data.tile.height = meta_draw_spec_new (info->theme, + height ? height : "height", + NULL); + op->data.tile.tile_xoffset = meta_draw_spec_new (info->theme, + tile_xoffset ? tile_xoffset : "0", + NULL); + op->data.tile.tile_yoffset = meta_draw_spec_new (info->theme, + tile_yoffset ? tile_yoffset : "0", + NULL); + op->data.tile.tile_width = meta_draw_spec_new (info->theme, tile_width, NULL); + op->data.tile.tile_height = meta_draw_spec_new (info->theme, tile_height, NULL); + + op->data.tile.op_list = op_list; + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_TILE); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "draw_ops"); + } +} + +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; + + color_spec = parse_color (info->theme, 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 <%s>"), + element_name, "gradient"); + } +} + +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; + + 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; + + info->button_type = meta_button_type_from_string (function, info->theme); + 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; + } + + if (meta_theme_earliest_version_with_button (info->button_type) > + info->theme->format_version) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Button function \"%s\" does not exist in this version (%d, need %d)"), + function, + info->theme->format_version, + meta_theme_earliest_version_with_button (info->button_type) + ); + 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); + } +#ifdef USE_UBUNTU_CODE + else if (ELEMENT_IS ("shadow")) + { + push_state (info, STATE_SHADOW); + } + else if (ELEMENT_IS ("padding")) + { + push_state (info, STATE_PADDING); + } +#endif + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "frame_style"); + } +} + +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; + + 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; + } + + switch (frame_state) + { + case META_FRAME_STATE_NORMAL: + if (resize == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + ATTRIBUTE_NOT_FOUND, + "resize", 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; + } + + break; + + case META_FRAME_STATE_SHADED: + if (META_THEME_ALLOWS (info->theme, META_THEME_UNRESIZABLE_SHADED_STYLES)) + { + if (resize == NULL) + /* In state="normal" we would complain here. But instead we accept + * not having a resize attribute and default to resize="both", since + * that most closely mimics what we did in v1, and thus people can + * upgrade a theme to v2 without as much hassle. + */ + frame_resize = META_FRAME_RESIZE_BOTH; + else + { + 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 /* v1 theme */ + { + 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; + } + + /* resize="both" is equivalent to the old behaviour */ + frame_resize = META_FRAME_RESIZE_BOTH; + } + break; + + default: + if (resize != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Should not have \"resize\" attribute on <%s> element for maximized 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_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->shaded_styles[frame_resize][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 <%s>"), + element_name, "frame_style_set"); + } +} + +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 <%s>"), + element_name, "piece"); + } +} + +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 <%s>"), + element_name, "button"); + } +} + +#ifdef USE_UBUNTU_CODE +static void +parse_shadow_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_SHADOW); + + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "shadow"); +} + +static void +parse_padding_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_PADDING); + + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "padding"); +} +#endif + +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 <%s>"), + element_name, "menu_icon"); + } +} + + +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); + info->theme->format_version = info->format_version; + + 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: + case STATE_ASPECT_RATIO: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed inside a distance/border/aspect_ratio 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: + case STATE_TILE: + 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 <%s> element"), + element_name, "color"); + 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; +#ifdef USE_UBUNTU_CODE + case STATE_SHADOW: + parse_shadow_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; + case STATE_PADDING: + parse_padding_element (context, element_name, + attribute_names, attribute_values, + info, error); + break; +#endif + 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 <%s> element"), + element_name, "frame"); + break; + case STATE_WINDOW: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed inside a <%s> element"), + element_name, "window"); + break; + case STATE_FALLBACK: + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed inside a <%s> element"), + element_name, "fallback"); + 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_ASPECT_RATIO: + 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_TILE: + 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, + info->theme->format_version, + 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; +#ifdef USE_UBUNTU_CODE + case STATE_SHADOW: + g_assert (info->style); + pop_state (info); + break; + case STATE_PADDING: + g_assert (info->style); + pop_state (info); + break; +#endif + case STATE_MENU_ICON: + g_assert (info->theme); + if (info->op_list != NULL) + { + meta_draw_op_list_unref (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; + case STATE_FALLBACK: + 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, + _("<%s> specified twice for this theme"), + "name"); + 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, + _("<%s> specified twice for this theme"), + "author"); + 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, + _("<%s> specified twice for this theme"), + "copyright"); + 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, + _("<%s> specified twice for this theme"), + "date"); + 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, + _("<%s> specified twice for this theme"), + "description"); + 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_ASPECT_RATIO: + NO_TEXT ("aspect_ratio"); + 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_TILE: + NO_TEXT ("tile"); + 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; +#ifdef USE_UBUNTU_CODE + case STATE_SHADOW: + NO_TEXT ("shadow"); + break; + case STATE_PADDING: + NO_TEXT ("padding"); + break; +#endif + 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; + case STATE_FALLBACK: + NO_TEXT ("fallback"); + break; + } +} + +/* We were intending to put the version number + * in the subdirectory name, but we ended up + * using the filename instead. The "-1" survives + * as a fossil. + */ +#define THEME_SUBDIR "metacity-1" + +/* Highest version of the theme format to + * look out for. + */ +#define THEME_VERSION 2 + +#define MARCO_THEME_FILENAME_FORMAT "metacity-theme-%d.xml" + +MetaTheme* +meta_theme_load (const char *theme_name, + GError **err) +{ + GMarkupParseContext *context; + GError *error; + ParseInfo info; + char *text; + gsize length; + char *theme_file; + char *theme_dir; + MetaTheme *retval; + guint version; + const gchar* const* xdg_data_dirs; + int i; + + text = NULL; + length = 0; + retval = NULL; + context = NULL; + + theme_dir = NULL; + theme_file = NULL; + + if (meta_is_debugging ()) + { + gchar *theme_filename = g_strdup_printf (MARCO_THEME_FILENAME_FORMAT, + THEME_VERSION); + + /* Try in themes in our source tree */ + 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_topic (META_DEBUG_THEMES, "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; + } + version = THEME_VERSION; + + g_free (theme_filename); + } + + /* We try all supported versions from current to oldest */ + for (version = THEME_VERSION; (version > 0) && (text == NULL); version--) + { + gchar *theme_filename = g_strdup_printf (MARCO_THEME_FILENAME_FORMAT, + version); + + /* We try first in home dir, XDG_DATA_DIRS, then system dir for themes */ + + /* Try home dir for themes */ + theme_dir = g_build_filename (g_get_home_dir (), + ".themes", + theme_name, + THEME_SUBDIR, + NULL); + + theme_file = g_build_filename (theme_dir, + theme_filename, + NULL); + + error = NULL; + if (!g_file_get_contents (theme_file, + &text, + &length, + &error)) + { + meta_topic (META_DEBUG_THEMES, "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; + } + + /* Try each XDG_DATA_DIRS for theme */ + xdg_data_dirs = g_get_system_data_dirs(); + for(i = 0; xdg_data_dirs[i] != NULL; i++) + { + if (text == NULL) + { + theme_dir = g_build_filename (xdg_data_dirs[i], + "themes", + theme_name, + THEME_SUBDIR, + NULL); + + theme_file = g_build_filename (theme_dir, + theme_filename, + NULL); + + error = NULL; + if (!g_file_get_contents (theme_file, + &text, + &length, + &error)) + { + meta_topic (META_DEBUG_THEMES, "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; + } + else + { + break; + } + } + } + + /* Look for themes in MARCO_DATADIR */ + if (text == NULL) + { + theme_dir = g_build_filename (MARCO_DATADIR, + "themes", + theme_name, + THEME_SUBDIR, + NULL); + + theme_file = g_build_filename (theme_dir, + theme_filename, + NULL); + + error = NULL; + if (!g_file_get_contents (theme_file, + &text, + &length, + &error)) + { + meta_topic (META_DEBUG_THEMES, "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; + } + } + + g_free (theme_filename); + } + + if (text == NULL) + { + g_set_error (err, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("Failed to find a valid file for theme %s\n"), + theme_name); + + return NULL; /* all fallbacks failed */ + } + + meta_topic (META_DEBUG_THEMES, "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; + + info.format_version = version + 1; + + context = g_markup_parse_context_new (&marco_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; + + goto out; + + out: + + if (context) + g_markup_parse_context_free (context); + g_free (text); + + if (info.theme) + info.theme->format_version = info.format_version; + + 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/ui/theme-parser.h b/src/ui/theme-parser.h new file mode 100644 index 00000000..ff090ee9 --- /dev/null +++ b/src/ui/theme-parser.h @@ -0,0 +1,32 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco 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/ui/theme-viewer.c b/src/ui/theme-viewer.c new file mode 100644 index 00000000..c4a1fcd6 --- /dev/null +++ b/src/ui/theme-viewer.c @@ -0,0 +1,1338 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco 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 + * 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 "util.h" +#include "theme.h" +#include "theme-parser.h" +#include "preview-widget.h" +#include <gtk/gtk.h> +#include <time.h> +#include <stdlib.h> +#include <string.h> + +#include <libintl.h> +#define _(x) dgettext (GETTEXT_PACKAGE, x) +#define N_(x) x + +/* We need to compute all different button arrangements + * in terms of button location. We don't care about + * different arrangements in terms of button function. + * + * So if dups are allowed, from 0-4 buttons on the left, from 0-4 on + * the right, 5x5=25 combinations. + * + * If no dups, 0-4 on left determines the number on the right plus + * we have a special case for the "no buttons on either side" case. + */ +#ifndef ALLOW_DUPLICATE_BUTTONS +#define BUTTON_LAYOUT_COMBINATIONS (MAX_BUTTONS_PER_CORNER + 1 + 1) +#else +#define BUTTON_LAYOUT_COMBINATIONS ((MAX_BUTTONS_PER_CORNER+1)*(MAX_BUTTONS_PER_CORNER+1)) +#endif + +enum +{ + FONT_SIZE_SMALL, + FONT_SIZE_NORMAL, + FONT_SIZE_LARGE, + FONT_SIZE_LAST +}; + +static MetaTheme *global_theme = NULL; +static GtkWidget *previews[META_FRAME_TYPE_LAST*FONT_SIZE_LAST + BUTTON_LAYOUT_COMBINATIONS] = { NULL, }; +static double milliseconds_to_draw_frame = 0.0; + +static void run_position_expression_tests (void); +#if 0 +static void run_position_expression_timings (void); +#endif +static void run_theme_benchmark (void); + + +static GtkItemFactoryEntry menu_items[] = +{ + { N_("/_Windows"), NULL, NULL, 0, "<Branch>" }, + { N_("/Windows/tearoff"), NULL, NULL, 0, "<Tearoff>" }, + { N_("/Windows/_Dialog"), "<control>d", NULL, 0, NULL }, + { N_("/Windows/_Modal dialog"), NULL, NULL, 0, NULL }, + { N_("/Windows/_Utility"), "<control>u", NULL, 0, NULL }, + { N_("/Windows/_Splashscreen"), "<control>s", NULL, 0, NULL }, + { N_("/Windows/_Top dock"), NULL, NULL, 0, NULL }, + { N_("/Windows/_Bottom dock"), NULL, NULL, 0, NULL }, + { N_("/Windows/_Left dock"), NULL, NULL, 0, NULL }, + { N_("/Windows/_Right dock"), NULL, NULL, 0, NULL }, + { N_("/Windows/_All docks"), NULL, NULL, 0, NULL }, + { N_("/Windows/Des_ktop"), NULL, NULL, 0, NULL } +}; + +static GtkWidget * +normal_contents (void) +{ + GtkWidget *table; + GtkWidget *toolbar; + GtkWidget *handlebox; + GtkWidget *statusbar; + GtkWidget *contents; + GtkWidget *sw; + GtkItemFactory *item_factory; + + table = gtk_table_new (1, 4, FALSE); + + /* Create the menubar + */ + + item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", NULL); + + gtk_item_factory_set_translate_func(item_factory, + (GtkTranslateFunc)gettext, NULL, NULL); + + /* Set up item factory to go away */ + g_object_ref (item_factory); + g_object_ref_sink (item_factory); + g_object_unref (item_factory); + g_object_set_data_full (G_OBJECT (table), + "<main>", + item_factory, + (GDestroyNotify) g_object_unref); + + /* create menu items */ + gtk_item_factory_create_items (item_factory, G_N_ELEMENTS (menu_items), + menu_items, NULL); + + gtk_table_attach (GTK_TABLE (table), + gtk_item_factory_get_widget (item_factory, "<main>"), + /* X direction */ /* Y direction */ + 0, 1, 0, 1, + GTK_EXPAND | GTK_FILL, 0, + 0, 0); + + /* Create the toolbar + */ + toolbar = gtk_toolbar_new (); + + GtkToolItem *newButton = gtk_tool_button_new_from_stock(GTK_STOCK_NEW); + gtk_tool_item_set_tooltip_text(newButton, + "Open another one of these windows"); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), + newButton, + -1); /*-1 means append to end of toolbar*/ + + GtkToolItem *openButton = gtk_tool_button_new_from_stock(GTK_STOCK_OPEN); + gtk_tool_item_set_tooltip_text(openButton, + "This is a demo button with an \'open\' icon"); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), + openButton, + -1); /*-1 means append to end of toolbar*/ + + GtkToolItem *quitButton = gtk_tool_button_new_from_stock(GTK_STOCK_QUIT); + gtk_tool_item_set_tooltip_text(quitButton, + "This is a demo button with a \'quit\' icon"); + gtk_toolbar_insert(GTK_TOOLBAR(toolbar), + quitButton, + -1); /*-1 means append to end of toolbar*/ + + + handlebox = gtk_handle_box_new (); + + gtk_container_add (GTK_CONTAINER (handlebox), toolbar); + + gtk_table_attach (GTK_TABLE (table), + handlebox, + /* X direction */ /* Y direction */ + 0, 1, 1, 2, + GTK_EXPAND | GTK_FILL, 0, + 0, 0); + + /* Create document + */ + + sw = gtk_scrolled_window_new (NULL, NULL); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), + GTK_SHADOW_IN); + + gtk_table_attach (GTK_TABLE (table), + sw, + /* X direction */ /* Y direction */ + 0, 1, 2, 3, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, + 0, 0); + + contents = gtk_text_view_new (); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (contents), + PANGO_WRAP_WORD); + + gtk_container_add (GTK_CONTAINER (sw), + contents); + + /* Create statusbar */ + + statusbar = gtk_statusbar_new (); + gtk_table_attach (GTK_TABLE (table), + statusbar, + /* X direction */ /* Y direction */ + 0, 1, 3, 4, + GTK_EXPAND | GTK_FILL, 0, + 0, 0); + + gtk_widget_show_all (table); + + return table; +} + +static void +update_spacings (GtkWidget *vbox, + GtkWidget *action_area) +{ + gtk_container_set_border_width (GTK_CONTAINER (vbox), 2); + gtk_box_set_spacing (GTK_BOX (action_area), 10); + gtk_container_set_border_width (GTK_CONTAINER (action_area), 5); +} + +static GtkWidget* +dialog_contents (void) +{ + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *action_area; + GtkWidget *label; + GtkWidget *image; + GtkWidget *button; + + vbox = gtk_vbox_new (FALSE, 0); + + action_area = gtk_hbutton_box_new (); + + gtk_button_box_set_layout (GTK_BUTTON_BOX (action_area), + GTK_BUTTONBOX_END); + + button = gtk_button_new_from_stock (GTK_STOCK_OK); + gtk_box_pack_end (GTK_BOX (action_area), + button, + FALSE, TRUE, 0); + + gtk_box_pack_end (GTK_BOX (vbox), action_area, + FALSE, TRUE, 0); + + update_spacings (vbox, action_area); + + label = gtk_label_new (_("This is a sample message in a sample dialog")); + image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_INFO, + GTK_ICON_SIZE_DIALOG); + gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0); + + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_label_set_selectable (GTK_LABEL (label), TRUE); + + hbox = gtk_hbox_new (FALSE, 6); + + gtk_box_pack_start (GTK_BOX (hbox), image, + FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (hbox), label, + TRUE, TRUE, 0); + + gtk_box_pack_start (GTK_BOX (vbox), + hbox, + FALSE, FALSE, 0); + + gtk_widget_show_all (vbox); + + return vbox; +} + +static GtkWidget* +utility_contents (void) +{ + GtkWidget *table; + GtkWidget *button; + int i, j; + + table = gtk_table_new (3, 4, FALSE); + + i = 0; + while (i < 3) + { + j = 0; + while (j < 4) + { + char *str; + + str = g_strdup_printf ("_%c", (char) ('A' + 4*i + j)); + + button = gtk_button_new_with_mnemonic (str); + + g_free (str); + + gtk_table_attach (GTK_TABLE (table), + button, + /* X direction */ /* Y direction */ + i, i+1, j, j+1, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, + 0, 0); + + ++j; + } + + ++i; + } + + gtk_widget_show_all (table); + + return table; +} + +static GtkWidget* +menu_contents (void) +{ + GtkWidget *vbox; + GtkWidget *mi; + int i; + GtkWidget *frame; + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), + GTK_SHADOW_OUT); + + vbox = gtk_vbox_new (FALSE, 0); + + i = 0; + while (i < 10) + { + char *str = g_strdup_printf (_("Fake menu item %d\n"), i + 1); + mi = gtk_label_new (str); + gtk_misc_set_alignment (GTK_MISC (mi), 0.0, 0.5); + g_free (str); + gtk_box_pack_start (GTK_BOX (vbox), mi, FALSE, FALSE, 0); + + ++i; + } + + gtk_container_add (GTK_CONTAINER (frame), vbox); + + gtk_widget_show_all (frame); + + return frame; +} + +static GtkWidget* +border_only_contents (void) +{ + GtkWidget *event_box; + GtkWidget *vbox; + GtkWidget *w; + GdkColor color; + + event_box = gtk_event_box_new (); + + color.red = 40000; + color.green = 0; + color.blue = 40000; + gtk_widget_modify_bg (event_box, GTK_STATE_NORMAL, &color); + + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 3); + + w = gtk_label_new (_("Border-only window")); + gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 0); + w = gtk_button_new_with_label (_("Bar")); + gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 0); + + gtk_container_add (GTK_CONTAINER (event_box), vbox); + + gtk_widget_show_all (event_box); + + return event_box; +} + +static GtkWidget* +get_window_contents (MetaFrameType type, + const char **title) +{ + switch (type) + { + case META_FRAME_TYPE_NORMAL: + *title = _("Normal Application Window"); + return normal_contents (); + + case META_FRAME_TYPE_DIALOG: + *title = _("Dialog Box"); + return dialog_contents (); + + case META_FRAME_TYPE_MODAL_DIALOG: + *title = _("Modal Dialog Box"); + return dialog_contents (); + + case META_FRAME_TYPE_UTILITY: + *title = _("Utility Palette"); + return utility_contents (); + + case META_FRAME_TYPE_MENU: + *title = _("Torn-off Menu"); + return menu_contents (); + + case META_FRAME_TYPE_BORDER: + *title = _("Border"); + return border_only_contents (); + + case META_FRAME_TYPE_LAST: + g_assert_not_reached (); + break; + } + + return NULL; +} + +static MetaFrameFlags +get_window_flags (MetaFrameType type) +{ + MetaFrameFlags flags; + + flags = 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; + + switch (type) + { + case META_FRAME_TYPE_NORMAL: + break; + + case META_FRAME_TYPE_DIALOG: + case META_FRAME_TYPE_MODAL_DIALOG: + flags &= ~(META_FRAME_ALLOWS_MINIMIZE | + META_FRAME_ALLOWS_MAXIMIZE); + break; + + case META_FRAME_TYPE_UTILITY: + flags &= ~(META_FRAME_ALLOWS_MINIMIZE | + META_FRAME_ALLOWS_MAXIMIZE); + break; + + case META_FRAME_TYPE_MENU: + flags &= ~(META_FRAME_ALLOWS_MINIMIZE | + META_FRAME_ALLOWS_MAXIMIZE); + break; + + case META_FRAME_TYPE_BORDER: + break; + + case META_FRAME_TYPE_LAST: + g_assert_not_reached (); + break; + } + + return flags; +} + +static GtkWidget* +preview_collection (int font_size, + PangoFontDescription *base_desc) +{ + GtkWidget *box; + GtkWidget *sw; + GdkColor desktop_color; + int i; + GtkWidget *eventbox; + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + box = gtk_vbox_new (FALSE, 0); + gtk_box_set_spacing (GTK_BOX (box), 20); + gtk_container_set_border_width (GTK_CONTAINER (box), 20); + + eventbox = gtk_event_box_new (); + gtk_container_add (GTK_CONTAINER (eventbox), box); + + gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), eventbox); + + desktop_color.red = 0x5144; + desktop_color.green = 0x75D6; + desktop_color.blue = 0xA699; + + gtk_widget_modify_bg (eventbox, GTK_STATE_NORMAL, &desktop_color); + + i = 0; + while (i < META_FRAME_TYPE_LAST) + { + const char *title = NULL; + GtkWidget *contents; + GtkWidget *align; + double xalign, yalign; + GtkWidget *eventbox2; + GtkWidget *preview; + PangoFontDescription *font_desc; + double scale; + + eventbox2 = gtk_event_box_new (); + + preview = meta_preview_new (); + + gtk_container_add (GTK_CONTAINER (eventbox2), preview); + + meta_preview_set_frame_type (META_PREVIEW (preview), i); + meta_preview_set_frame_flags (META_PREVIEW (preview), + get_window_flags (i)); + + meta_preview_set_theme (META_PREVIEW (preview), global_theme); + + contents = get_window_contents (i, &title); + + meta_preview_set_title (META_PREVIEW (preview), title); + + gtk_container_add (GTK_CONTAINER (preview), contents); + + if (i == META_FRAME_TYPE_MENU) + { + xalign = 0.0; + yalign = 0.0; + } + else + { + xalign = 0.5; + yalign = 0.5; + } + + align = gtk_alignment_new (0.0, 0.0, xalign, yalign); + gtk_container_add (GTK_CONTAINER (align), eventbox2); + + gtk_box_pack_start (GTK_BOX (box), align, TRUE, TRUE, 0); + + switch (font_size) + { + case FONT_SIZE_SMALL: + scale = PANGO_SCALE_XX_SMALL; + break; + case FONT_SIZE_LARGE: + scale = PANGO_SCALE_XX_LARGE; + break; + default: + scale = 1.0; + break; + } + + if (scale != 1.0) + { + font_desc = pango_font_description_new (); + + pango_font_description_set_size (font_desc, + MAX (pango_font_description_get_size (base_desc) * scale, 1)); + + gtk_widget_modify_font (preview, font_desc); + + pango_font_description_free (font_desc); + } + + previews[font_size*META_FRAME_TYPE_LAST + i] = preview; + + ++i; + } + + return sw; +} + +static MetaButtonLayout different_layouts[BUTTON_LAYOUT_COMBINATIONS]; + +static void +init_layouts (void) +{ + int i; + + /* Blank out all the layouts */ + i = 0; + while (i < (int) G_N_ELEMENTS (different_layouts)) + { + int j; + + j = 0; + while (j < MAX_BUTTONS_PER_CORNER) + { + different_layouts[i].left_buttons[j] = META_BUTTON_FUNCTION_LAST; + different_layouts[i].right_buttons[j] = META_BUTTON_FUNCTION_LAST; + ++j; + } + ++i; + } + +#ifndef ALLOW_DUPLICATE_BUTTONS + i = 0; + while (i <= MAX_BUTTONS_PER_CORNER) + { + int j; + + j = 0; + while (j < i) + { + different_layouts[i].right_buttons[j] = (MetaButtonFunction) j; + ++j; + } + while (j < MAX_BUTTONS_PER_CORNER) + { + different_layouts[i].left_buttons[j-i] = (MetaButtonFunction) j; + ++j; + } + + ++i; + } + + /* Special extra case for no buttons on either side */ + different_layouts[i].left_buttons[0] = META_BUTTON_FUNCTION_LAST; + different_layouts[i].right_buttons[0] = META_BUTTON_FUNCTION_LAST; + +#else + /* FIXME this code is if we allow duplicate buttons, + * which we currently do not + */ + int left; + int i; + + left = 0; + i = 0; + + while (left < MAX_BUTTONS_PER_CORNER) + { + int right; + + right = 0; + + while (right < MAX_BUTTONS_PER_CORNER) + { + int j; + + static MetaButtonFunction left_functions[MAX_BUTTONS_PER_CORNER] = { + META_BUTTON_FUNCTION_MENU, + META_BUTTON_FUNCTION_MINIMIZE, + META_BUTTON_FUNCTION_MAXIMIZE, + META_BUTTON_FUNCTION_CLOSE + }; + static MetaButtonFunction right_functions[MAX_BUTTONS_PER_CORNER] = { + META_BUTTON_FUNCTION_MINIMIZE, + META_BUTTON_FUNCTION_MAXIMIZE, + META_BUTTON_FUNCTION_CLOSE, + META_BUTTON_FUNCTION_MENU + }; + + g_assert (i < BUTTON_LAYOUT_COMBINATIONS); + + j = 0; + while (j <= left) + { + different_layouts[i].left_buttons[j] = left_functions[j]; + ++j; + } + + j = 0; + while (j <= right) + { + different_layouts[i].right_buttons[j] = right_functions[j]; + ++j; + } + + ++i; + + ++right; + } + + ++left; + } +#endif +} + + +static GtkWidget* +previews_of_button_layouts (void) +{ + static gboolean initted = FALSE; + GtkWidget *box; + GtkWidget *sw; + GdkColor desktop_color; + int i; + GtkWidget *eventbox; + + if (!initted) + { + init_layouts (); + initted = TRUE; + } + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + box = gtk_vbox_new (FALSE, 0); + gtk_box_set_spacing (GTK_BOX (box), 20); + gtk_container_set_border_width (GTK_CONTAINER (box), 20); + + eventbox = gtk_event_box_new (); + gtk_container_add (GTK_CONTAINER (eventbox), box); + + gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), eventbox); + + desktop_color.red = 0x5144; + desktop_color.green = 0x75D6; + desktop_color.blue = 0xA699; + + gtk_widget_modify_bg (eventbox, GTK_STATE_NORMAL, &desktop_color); + + i = 0; + while (i < BUTTON_LAYOUT_COMBINATIONS) + { + GtkWidget *align; + double xalign, yalign; + GtkWidget *eventbox2; + GtkWidget *preview; + char *title; + + eventbox2 = gtk_event_box_new (); + + preview = meta_preview_new (); + + gtk_container_add (GTK_CONTAINER (eventbox2), preview); + + meta_preview_set_theme (META_PREVIEW (preview), global_theme); + + title = g_strdup_printf (_("Button layout test %d"), i+1); + meta_preview_set_title (META_PREVIEW (preview), title); + g_free (title); + + meta_preview_set_button_layout (META_PREVIEW (preview), + &different_layouts[i]); + + xalign = 0.5; + yalign = 0.5; + + align = gtk_alignment_new (0.0, 0.0, xalign, yalign); + gtk_container_add (GTK_CONTAINER (align), eventbox2); + + gtk_box_pack_start (GTK_BOX (box), align, TRUE, TRUE, 0); + + previews[META_FRAME_TYPE_LAST*FONT_SIZE_LAST + i] = preview; + + ++i; + } + + return sw; +} + +static GtkWidget* +benchmark_summary (void) +{ + char *msg; + GtkWidget *label; + + msg = g_strdup_printf (_("%g milliseconds to draw one window frame"), + milliseconds_to_draw_frame); + label = gtk_label_new (msg); + g_free (msg); + + return label; +} + +int +main (int argc, char **argv) +{ + GtkWidget *window; + GtkWidget *collection; + GError *err; + clock_t start, end; + GtkWidget *notebook; + int i; + + bindtextdomain (GETTEXT_PACKAGE, MARCO_LOCALEDIR); + textdomain(GETTEXT_PACKAGE); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + + run_position_expression_tests (); +#if 0 + run_position_expression_timings (); +#endif + + gtk_init (&argc, &argv); + + if (g_getenv ("MARCO_DEBUG") != NULL) + { + meta_set_debugging (TRUE); + meta_set_verbose (TRUE); + } + + start = clock (); + err = NULL; + if (argc == 1) + global_theme = meta_theme_load ("ClearlooksRe", &err); + else if (argc == 2) + global_theme = meta_theme_load (argv[1], &err); + else + { + g_printerr (_("Usage: marco-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); + + run_theme_benchmark (); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size (GTK_WINDOW (window), 350, 350); + + if (strcmp (global_theme->name, global_theme->readable_name)==0) + gtk_window_set_title (GTK_WINDOW (window), + global_theme->readable_name); + else + { + /* The theme directory name is different from the name the theme + * gives itself within its file. Display both, directory name first. + */ + gchar *title = g_strconcat (global_theme->name, " - ", + global_theme->readable_name, + NULL); + + gtk_window_set_title (GTK_WINDOW (window), + title); + + g_free (title); + } + + g_signal_connect (G_OBJECT (window), "destroy", + G_CALLBACK (gtk_main_quit), NULL); + + gtk_widget_realize (window); + g_assert (window->style); + g_assert (window->style->font_desc); + + notebook = gtk_notebook_new (); + gtk_container_add (GTK_CONTAINER (window), notebook); + + collection = preview_collection (FONT_SIZE_NORMAL, + window->style->font_desc); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), + collection, + gtk_label_new (_("Normal Title Font"))); + + collection = preview_collection (FONT_SIZE_SMALL, + window->style->font_desc); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), + collection, + gtk_label_new (_("Small Title Font"))); + + collection = preview_collection (FONT_SIZE_LARGE, + window->style->font_desc); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), + collection, + gtk_label_new (_("Large Title Font"))); + + collection = previews_of_button_layouts (); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), + collection, + gtk_label_new (_("Button Layouts"))); + + collection = benchmark_summary (); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), + collection, + gtk_label_new (_("Benchmark"))); + + i = 0; + while (i < (int) G_N_ELEMENTS (previews)) + { + /* preview widget likes to be realized before its size request. + * it's lame that way. + */ + gtk_widget_realize (previews[i]); + + ++i; + } + + gtk_widget_show_all (window); + + gtk_main (); + + return 0; +} + + +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_pango_font_desc_get_text_height (widget->style->font_desc, + gtk_widget_get_pango_context (widget)); +} + +static PangoLayout* +create_title_layout (GtkWidget *widget) +{ + PangoLayout *layout; + + layout = gtk_widget_create_pango_layout (widget, _("Window Title Goes Here")); + + return layout; +} + +static void +run_theme_benchmark (void) +{ + GtkWidget* widget; + GdkPixmap *pixmap; + int top_height, bottom_height, left_width, right_width; + MetaButtonState button_states[META_BUTTON_TYPE_LAST] = + { + META_BUTTON_STATE_NORMAL, + META_BUTTON_STATE_NORMAL, + META_BUTTON_STATE_NORMAL, + META_BUTTON_STATE_NORMAL + }; + PangoLayout *layout; + clock_t start; + clock_t end; + GTimer *timer; + int i; + MetaButtonLayout button_layout; +#define ITERATIONS 100 + int client_width; + int client_height; + int inc; + + widget = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_realize (widget); + + 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); + + layout = create_title_layout (widget); + + i = 0; + while (i < MAX_BUTTONS_PER_CORNER) + { + button_layout.left_buttons[i] = META_BUTTON_FUNCTION_LAST; + button_layout.right_buttons[i] = META_BUTTON_FUNCTION_LAST; + ++i; + } + + button_layout.left_buttons[0] = META_BUTTON_FUNCTION_MENU; + + button_layout.right_buttons[0] = META_BUTTON_FUNCTION_MINIMIZE; + button_layout.right_buttons[1] = META_BUTTON_FUNCTION_MAXIMIZE; + button_layout.right_buttons[2] = META_BUTTON_FUNCTION_CLOSE; + + timer = g_timer_new (); + start = clock (); + + client_width = 50; + client_height = 50; + inc = 1000 / ITERATIONS; /* Increment to grow width/height, + * eliminates caching effects. + */ + + i = 0; + while (i < ITERATIONS) + { + /* Creating the pixmap in the loop is right, since + * GDK does the same with its double buffering. + */ + pixmap = gdk_pixmap_new (widget->window, + client_width + left_width + right_width, + client_height + top_height + bottom_height, + -1); + + meta_theme_draw_frame (global_theme, + widget, + pixmap, + NULL, + 0, 0, + META_FRAME_TYPE_NORMAL, + get_flags (widget), + client_width, client_height, + layout, + get_text_height (widget), + &button_layout, + button_states, + meta_preview_get_mini_icon (), + meta_preview_get_icon ()); + + g_object_unref (G_OBJECT (pixmap)); + + ++i; + client_width += inc; + client_height += inc; + } + + end = clock (); + g_timer_stop (timer); + + milliseconds_to_draw_frame = (g_timer_elapsed (timer, NULL) / (double) ITERATIONS) * 1000; + + g_print (_("Drew %d frames in %g client-side seconds (%g milliseconds per frame) and %g seconds wall clock time including X server resources (%g milliseconds per frame)\n"), + ITERATIONS, + ((double)end - (double)start) / CLOCKS_PER_SEC, + (((double)end - (double)start) / CLOCKS_PER_SEC / (double) ITERATIONS) * 1000, + g_timer_elapsed (timer, NULL), + milliseconds_to_draw_frame); + + g_timer_destroy (timer); + g_object_unref (G_OBJECT (layout)); + gtk_widget_destroy (widget); + +#undef ITERATIONS +} + +typedef struct +{ + GdkRectangle rect; + const char *expr; + int expected_x; + int expected_y; + MetaThemeError expected_error; +} PositionExpressionTest; + +#define NO_ERROR -1 + +static const PositionExpressionTest position_expression_tests[] = { + /* Just numbers */ + { { 10, 20, 40, 50 }, + "10", 20, 30, NO_ERROR }, + { { 10, 20, 40, 50 }, + "14.37", 24, 34, NO_ERROR }, + /* Binary expressions with 2 ints */ + { { 10, 20, 40, 50 }, + "14 * 10", 150, 160, NO_ERROR }, + { { 10, 20, 40, 50 }, + "14 + 10", 34, 44, NO_ERROR }, + { { 10, 20, 40, 50 }, + "14 - 10", 14, 24, NO_ERROR }, + { { 10, 20, 40, 50 }, + "8 / 2", 14, 24, NO_ERROR }, + { { 10, 20, 40, 50 }, + "8 % 3", 12, 22, NO_ERROR }, + /* Binary expressions with floats and mixed float/ints */ + { { 10, 20, 40, 50 }, + "7.0 / 3.5", 12, 22, NO_ERROR }, + { { 10, 20, 40, 50 }, + "12.1 / 3", 14, 24, NO_ERROR }, + { { 10, 20, 40, 50 }, + "12 / 2.95", 14, 24, NO_ERROR }, + /* Binary expressions without whitespace after first number */ + { { 10, 20, 40, 50 }, + "14* 10", 150, 160, NO_ERROR }, + { { 10, 20, 40, 50 }, + "14+ 10", 34, 44, NO_ERROR }, + { { 10, 20, 40, 50 }, + "14- 10", 14, 24, NO_ERROR }, + { { 10, 20, 40, 50 }, + "8/ 2", 14, 24, NO_ERROR }, + { { 10, 20, 40, 50 }, + "7.0/ 3.5", 12, 22, NO_ERROR }, + { { 10, 20, 40, 50 }, + "12.1/ 3", 14, 24, NO_ERROR }, + { { 10, 20, 40, 50 }, + "12/ 2.95", 14, 24, NO_ERROR }, + /* Binary expressions without whitespace before second number */ + { { 10, 20, 40, 50 }, + "14 *10", 150, 160, NO_ERROR }, + { { 10, 20, 40, 50 }, + "14 +10", 34, 44, NO_ERROR }, + { { 10, 20, 40, 50 }, + "14 -10", 14, 24, NO_ERROR }, + { { 10, 20, 40, 50 }, + "8 /2", 14, 24, NO_ERROR }, + { { 10, 20, 40, 50 }, + "7.0 /3.5", 12, 22, NO_ERROR }, + { { 10, 20, 40, 50 }, + "12.1 /3", 14, 24, NO_ERROR }, + { { 10, 20, 40, 50 }, + "12 /2.95", 14, 24, NO_ERROR }, + /* Binary expressions without any whitespace */ + { { 10, 20, 40, 50 }, + "14*10", 150, 160, NO_ERROR }, + { { 10, 20, 40, 50 }, + "14+10", 34, 44, NO_ERROR }, + { { 10, 20, 40, 50 }, + "14-10", 14, 24, NO_ERROR }, + { { 10, 20, 40, 50 }, + "8/2", 14, 24, NO_ERROR }, + { { 10, 20, 40, 50 }, + "7.0/3.5", 12, 22, NO_ERROR }, + { { 10, 20, 40, 50 }, + "12.1/3", 14, 24, NO_ERROR }, + { { 10, 20, 40, 50 }, + "12/2.95", 14, 24, NO_ERROR }, + /* Binary expressions with parentheses */ + { { 10, 20, 40, 50 }, + "(14) * (10)", 150, 160, NO_ERROR }, + { { 10, 20, 40, 50 }, + "(14) + (10)", 34, 44, NO_ERROR }, + { { 10, 20, 40, 50 }, + "(14) - (10)", 14, 24, NO_ERROR }, + { { 10, 20, 40, 50 }, + "(8) / (2)", 14, 24, NO_ERROR }, + { { 10, 20, 40, 50 }, + "(7.0) / (3.5)", 12, 22, NO_ERROR }, + { { 10, 20, 40, 50 }, + "(12.1) / (3)", 14, 24, NO_ERROR }, + { { 10, 20, 40, 50 }, + "(12) / (2.95)", 14, 24, NO_ERROR }, + /* Lots of extra parentheses */ + { { 10, 20, 40, 50 }, + "(((14)) * ((10)))", 150, 160, NO_ERROR }, + { { 10, 20, 40, 50 }, + "((((14)))) + ((((((((10))))))))", 34, 44, NO_ERROR }, + { { 10, 20, 40, 50 }, + "((((((((((14 - 10))))))))))", 14, 24, NO_ERROR }, + /* Binary expressions with variables */ + { { 10, 20, 40, 50 }, + "2 * width", 90, 100, NO_ERROR }, + { { 10, 20, 40, 50 }, + "2 * height", 110, 120, NO_ERROR }, + { { 10, 20, 40, 50 }, + "width - 10", 40, 50, NO_ERROR }, + { { 10, 20, 40, 50 }, + "height / 2", 35, 45, NO_ERROR }, + /* More than two operands */ + { { 10, 20, 40, 50 }, + "8 / 2 + 5", 19, 29, NO_ERROR }, + { { 10, 20, 40, 50 }, + "8 * 2 + 5", 31, 41, NO_ERROR }, + { { 10, 20, 40, 50 }, + "8 + 2 * 5", 28, 38, NO_ERROR }, + { { 10, 20, 40, 50 }, + "8 + 8 / 2", 22, 32, NO_ERROR }, + { { 10, 20, 40, 50 }, + "14 / (2 + 5)", 12, 22, NO_ERROR }, + { { 10, 20, 40, 50 }, + "8 * (2 + 5)", 66, 76, NO_ERROR }, + { { 10, 20, 40, 50 }, + "(8 + 2) * 5", 60, 70, NO_ERROR }, + { { 10, 20, 40, 50 }, + "(8 + 8) / 2", 18, 28, NO_ERROR }, + /* Errors */ + { { 10, 20, 40, 50 }, + "2 * foo", 0, 0, META_THEME_ERROR_UNKNOWN_VARIABLE }, + { { 10, 20, 40, 50 }, + "2 *", 0, 0, META_THEME_ERROR_FAILED }, + { { 10, 20, 40, 50 }, + "- width", 0, 0, META_THEME_ERROR_FAILED }, + { { 10, 20, 40, 50 }, + "5 % 1.0", 0, 0, META_THEME_ERROR_MOD_ON_FLOAT }, + { { 10, 20, 40, 50 }, + "1.0 % 5", 0, 0, META_THEME_ERROR_MOD_ON_FLOAT }, + { { 10, 20, 40, 50 }, + "! * 2", 0, 0, META_THEME_ERROR_BAD_CHARACTER }, + { { 10, 20, 40, 50 }, + " ", 0, 0, META_THEME_ERROR_FAILED }, + { { 10, 20, 40, 50 }, + "() () (( ) ()) ((()))", 0, 0, META_THEME_ERROR_FAILED }, + { { 10, 20, 40, 50 }, + "(*) () ((/) ()) ((()))", 0, 0, META_THEME_ERROR_FAILED }, + { { 10, 20, 40, 50 }, + "2 * 5 /", 0, 0, META_THEME_ERROR_FAILED }, + { { 10, 20, 40, 50 }, + "+ 2 * 5", 0, 0, META_THEME_ERROR_FAILED }, + { { 10, 20, 40, 50 }, + "+ 2 * 5", 0, 0, META_THEME_ERROR_FAILED } +}; + +static void +run_position_expression_tests (void) +{ +#if 0 + int i; + MetaPositionExprEnv env; + + i = 0; + while (i < (int) G_N_ELEMENTS (position_expression_tests)) + { + GError *err; + gboolean retval; + const PositionExpressionTest *test; + PosToken *tokens; + int n_tokens; + 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; + + env.rect = meta_rect (test->rect.x, test->rect.y, + test->rect.width, 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; + + if (err == NULL) + { + retval = meta_parse_position_expression (tokens, n_tokens, + &env, + &x, &y, + &err); + } + + if (retval && err) + 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 (((int) test->expected_error) != NO_ERROR) + { + if (err == NULL) + g_error (_("Error was expected but none given")); + if (err->code != (int) test->expected_error) + g_error (_("Error %d was expected but %d given"), + test->expected_error, err->code); + } + else + { + if (err) + g_error (_("Error not expected but one was returned: %s"), + err->message); + + if (x != test->expected_x) + g_error (_("x value was %d, %d was expected"), x, test->expected_x); + + if (y != test->expected_y) + g_error (_("y value was %d, %d was expected"), y, test->expected_y); + } + + if (err) + g_error_free (err); + + meta_pos_tokens_free (tokens, n_tokens); + ++i; + } +#endif +} + +#if 0 +static void +run_position_expression_timings (void) +{ + int i; + int iters; + 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; + env.y = test->rect.y; + env.width = test->rect.width; + 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); + + ++iters; + ++i; + if (i == G_N_ELEMENTS (position_expression_tests)) + i = 0; + } + + end = clock (); + + g_print (_("%d coordinate expressions parsed in %g seconds (%g seconds average)\n"), + ITERATIONS, + ((double)end - (double)start) / CLOCKS_PER_SEC, + ((double)end - (double)start) / CLOCKS_PER_SEC / (double) ITERATIONS); + +} +#endif diff --git a/src/ui/theme.c b/src/ui/theme.c new file mode 100644 index 00000000..63103960 --- /dev/null +++ b/src/ui/theme.c @@ -0,0 +1,6652 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco Theme Rendering */ + +/* + * Copyright (C) 2001 Havoc Pennington + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +/** + * \file theme.c Making Marco look pretty + * + * The window decorations drawn by Marco are described by files on disk + * known internally as "themes" (externally as "window border themes" on + * http://art.gnome.org/themes/marco/ or "Marco themes"). This file + * contains most of the code necessary to support themes; it does not + * contain the XML parser, which is in theme-parser.c. + * + * \bug This is a big file with lots of different subsystems, which might + * be better split out into separate files. + */ + +/** + * \defgroup tokenizer The theme expression tokenizer + * + * Themes can use a simple expression language to represent the values of + * things. This is the tokeniser used for that language. + * + * \bug We could remove almost all this code by using GScanner instead, + * but we would also have to find every expression in every existing theme + * we could and make sure the parse trees were the same. + */ + +/** + * \defgroup parser The theme expression parser + * + * Themes can use a simple expression language to represent the values of + * things. This is the parser used for that language. + */ + +#include <config.h> +#include "theme.h" +#include "theme-parser.h" +#include "util.h" +#include "gradient.h" +#include <gtk/gtk.h> +#include <string.h> +#include <stdlib.h> +#define __USE_XOPEN +#include <math.h> + +#define GDK_COLOR_RGBA(color) \ + ((guint32) (0xff | \ + (((color).red / 256) << 24) | \ + (((color).green / 256) << 16) | \ + (((color).blue / 256) << 8))) + +#define GDK_COLOR_RGB(color) \ + ((guint32) ((((color).red / 256) << 16) | \ + (((color).green / 256) << 8) | \ + (((color).blue / 256)))) + +#define ALPHA_TO_UCHAR(d) ((unsigned char) ((d) * 255)) + +#define DEBUG_FILL_STRUCT(s) memset ((s), 0xef, sizeof (*(s))) +#define CLAMP_UCHAR(v) ((guchar) (CLAMP (((int)v), (int)0, (int)255))) +#define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11) + +static void gtk_style_shade (GdkColor *a, + GdkColor *b, + gdouble k); +static void rgb_to_hls (gdouble *r, + gdouble *g, + gdouble *b); +static void hls_to_rgb (gdouble *h, + gdouble *l, + gdouble *s); + +/** + * The current theme. (Themes are singleton.) + */ +static MetaTheme *meta_current_theme = NULL; + +static GdkPixbuf * +colorize_pixbuf (GdkPixbuf *orig, + GdkColor *new_color) +{ + GdkPixbuf *pixbuf; + double intensity; + int x, y; + const guchar *src; + guchar *dest; + int orig_rowstride; + int dest_rowstride; + int width, height; + gboolean has_alpha; + const guchar *src_pixels; + guchar *dest_pixels; + + pixbuf = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (orig), gdk_pixbuf_get_has_alpha (orig), + gdk_pixbuf_get_bits_per_sample (orig), + gdk_pixbuf_get_width (orig), gdk_pixbuf_get_height (orig)); + + if (pixbuf == NULL) + return NULL; + + orig_rowstride = gdk_pixbuf_get_rowstride (orig); + dest_rowstride = gdk_pixbuf_get_rowstride (pixbuf); + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + has_alpha = gdk_pixbuf_get_has_alpha (orig); + src_pixels = gdk_pixbuf_get_pixels (orig); + dest_pixels = gdk_pixbuf_get_pixels (pixbuf); + + for (y = 0; y < height; y++) + { + src = src_pixels + y * orig_rowstride; + dest = dest_pixels + y * dest_rowstride; + + for (x = 0; x < width; x++) + { + double dr, dg, db; + + intensity = INTENSITY (src[0], src[1], src[2]) / 255.0; + + if (intensity <= 0.5) + { + /* Go from black at intensity = 0.0 to new_color at intensity = 0.5 */ + dr = (new_color->red * intensity * 2.0) / 65535.0; + dg = (new_color->green * intensity * 2.0) / 65535.0; + db = (new_color->blue * intensity * 2.0) / 65535.0; + } + else + { + /* Go from new_color at intensity = 0.5 to white at intensity = 1.0 */ + dr = (new_color->red + (65535 - new_color->red) * (intensity - 0.5) * 2.0) / 65535.0; + dg = (new_color->green + (65535 - new_color->green) * (intensity - 0.5) * 2.0) / 65535.0; + db = (new_color->blue + (65535 - new_color->blue) * (intensity - 0.5) * 2.0) / 65535.0; + } + + dest[0] = CLAMP_UCHAR (255 * dr); + dest[1] = CLAMP_UCHAR (255 * dg); + dest[2] = CLAMP_UCHAR (255 * db); + + if (has_alpha) + { + dest[3] = src[3]; + src += 4; + dest += 4; + } + else + { + src += 3; + dest += 3; + } + } + } + + return pixbuf; +} + +static void +color_composite (const GdkColor *bg, + const GdkColor *fg, + double alpha_d, + GdkColor *color) +{ + guint16 alpha; + + *color = *bg; + alpha = alpha_d * 0xffff; + color->red = color->red + (((fg->red - color->red) * alpha + 0x8000) >> 16); + color->green = color->green + (((fg->green - color->green) * alpha + 0x8000) >> 16); + color->blue = color->blue + (((fg->blue - color->blue) * alpha + 0x8000) >> 16); +} + +/** + * Sets all the fields of a border to dummy values. + * + * \param border The border whose fields should be reset. + */ +static void +init_border (GtkBorder *border) +{ + border->top = -1; + border->bottom = -1; + border->left = -1; + border->right = -1; +} + +/** + * Creates a new, empty MetaFrameLayout. The fields will be set to dummy + * values. + * + * \return The newly created MetaFrameLayout. + */ +MetaFrameLayout* +meta_frame_layout_new (void) +{ + MetaFrameLayout *layout; + + layout = g_new0 (MetaFrameLayout, 1); + + layout->refcount = 1; + + /* Fill with -1 values to detect invalid themes */ + layout->left_width = -1; + layout->right_width = -1; + layout->bottom_height = -1; + + init_border (&layout->title_border); + + layout->title_vertical_pad = -1; + + layout->right_titlebar_edge = -1; + layout->left_titlebar_edge = -1; + + layout->button_sizing = META_BUTTON_SIZING_LAST; + layout->button_aspect = 1.0; + layout->button_width = -1; + layout->button_height = -1; + + layout->has_title = TRUE; + layout->title_scale = 1.0; + + init_border (&layout->button_border); + + return layout; +} + +/** + * + */ +static gboolean +validate_border (const GtkBorder *border, + const char **bad) +{ + *bad = NULL; + + if (border->top < 0) + *bad = _("top"); + else if (border->bottom < 0) + *bad = _("bottom"); + else if (border->left < 0) + *bad = _("left"); + else if (border->right < 0) + *bad = _("right"); + + return *bad == NULL; +} + +/** + * Ensures that the theme supplied a particular dimension. When a + * MetaFrameLayout is created, all its integer fields are set to -1 + * by meta_frame_layout_new(). After an instance of this type + * should have been initialised, this function checks that + * a given field is not still at -1. It is never called directly, but + * rather via the CHECK_GEOMETRY_VALUE and CHECK_GEOMETRY_BORDER + * macros. + * + * \param val The value to check + * \param name The name to use in the error message + * \param[out] error Set to an error if val was not initialised + */ +static gboolean +validate_geometry_value (int val, + const char *name, + GError **error) +{ + if (val < 0) + { + g_set_error (error, META_THEME_ERROR, + META_THEME_ERROR_FRAME_GEOMETRY, + _("frame geometry does not specify \"%s\" dimension"), + name); + return FALSE; + } + else + return TRUE; +} + +static gboolean +validate_geometry_border (const GtkBorder *border, + const char *name, + GError **error) +{ + const char *bad; + + if (!validate_border (border, &bad)) + { + g_set_error (error, META_THEME_ERROR, + META_THEME_ERROR_FRAME_GEOMETRY, + _("frame geometry does not specify dimension \"%s\" for border \"%s\""), + bad, name); + return FALSE; + } + else + return TRUE; +} + +gboolean +meta_frame_layout_validate (const MetaFrameLayout *layout, + GError **error) +{ + g_return_val_if_fail (layout != NULL, FALSE); + +#define CHECK_GEOMETRY_VALUE(vname) if (!validate_geometry_value (layout->vname, #vname, error)) return FALSE + +#define CHECK_GEOMETRY_BORDER(bname) if (!validate_geometry_border (&layout->bname, #bname, error)) return FALSE + + CHECK_GEOMETRY_VALUE (left_width); + CHECK_GEOMETRY_VALUE (right_width); + CHECK_GEOMETRY_VALUE (bottom_height); + + CHECK_GEOMETRY_BORDER (title_border); + + CHECK_GEOMETRY_VALUE (title_vertical_pad); + + CHECK_GEOMETRY_VALUE (right_titlebar_edge); + CHECK_GEOMETRY_VALUE (left_titlebar_edge); + + switch (layout->button_sizing) + { + case META_BUTTON_SIZING_ASPECT: + if (layout->button_aspect < (0.1) || + layout->button_aspect > (15.0)) + { + g_set_error (error, META_THEME_ERROR, + META_THEME_ERROR_FRAME_GEOMETRY, + _("Button aspect ratio %g is not reasonable"), + layout->button_aspect); + return FALSE; + } + break; + case META_BUTTON_SIZING_FIXED: + CHECK_GEOMETRY_VALUE (button_width); + CHECK_GEOMETRY_VALUE (button_height); + break; + case META_BUTTON_SIZING_LAST: + g_set_error (error, META_THEME_ERROR, + META_THEME_ERROR_FRAME_GEOMETRY, + _("Frame geometry does not specify size of buttons")); + return FALSE; + } + + CHECK_GEOMETRY_BORDER (button_border); + + return TRUE; +} + +MetaFrameLayout* +meta_frame_layout_copy (const MetaFrameLayout *src) +{ + MetaFrameLayout *layout; + + layout = g_new0 (MetaFrameLayout, 1); + + *layout = *src; + + layout->refcount = 1; + + return layout; +} + +void +meta_frame_layout_ref (MetaFrameLayout *layout) +{ + g_return_if_fail (layout != NULL); + + layout->refcount += 1; +} + +void +meta_frame_layout_unref (MetaFrameLayout *layout) +{ + g_return_if_fail (layout != NULL); + g_return_if_fail (layout->refcount > 0); + + layout->refcount -= 1; + + if (layout->refcount == 0) + { + DEBUG_FILL_STRUCT (layout); + g_free (layout); + } +} + +void +meta_frame_layout_get_borders (const MetaFrameLayout *layout, + int text_height, + MetaFrameFlags flags, + int *top_height, + int *bottom_height, + int *left_width, + int *right_width) +{ + int buttons_height, title_height; + + g_return_if_fail (top_height != NULL); + g_return_if_fail (bottom_height != NULL); + g_return_if_fail (left_width != NULL); + g_return_if_fail (right_width != NULL); + + if (!layout->has_title) + text_height = 0; + + buttons_height = layout->button_height + + layout->button_border.top + layout->button_border.bottom; + title_height = text_height + + layout->title_vertical_pad + + layout->title_border.top + layout->title_border.bottom; + + if (top_height) + { + *top_height = MAX (buttons_height, title_height); + } + + if (left_width) + *left_width = layout->left_width; + if (right_width) + *right_width = layout->right_width; + + if (bottom_height) + { + if (flags & META_FRAME_SHADED) + *bottom_height = 0; + else + *bottom_height = layout->bottom_height; + } + + if (flags & META_FRAME_FULLSCREEN) + { + if (top_height) + *top_height = 0; + if (bottom_height) + *bottom_height = 0; + if (left_width) + *left_width = 0; + if (right_width) + *right_width = 0; + } +} + +static MetaButtonSpace* +rect_for_function (MetaFrameGeometry *fgeom, + MetaFrameFlags flags, + MetaButtonFunction function, + MetaTheme *theme) +{ + + /* Firstly, check version-specific things. */ + + if (META_THEME_ALLOWS(theme, META_THEME_SHADE_STICK_ABOVE_BUTTONS)) + { + switch (function) + { + case META_BUTTON_FUNCTION_SHADE: + if ((flags & META_FRAME_ALLOWS_SHADE) && !(flags & META_FRAME_SHADED)) + return &fgeom->shade_rect; + else + return NULL; + case META_BUTTON_FUNCTION_ABOVE: + if (!(flags & META_FRAME_ABOVE)) + return &fgeom->above_rect; + else + return NULL; + case META_BUTTON_FUNCTION_STICK: + if (!(flags & META_FRAME_STUCK)) + return &fgeom->stick_rect; + else + return NULL; + case META_BUTTON_FUNCTION_UNSHADE: + if ((flags & META_FRAME_ALLOWS_SHADE) && (flags & META_FRAME_SHADED)) + return &fgeom->unshade_rect; + else + return NULL; + case META_BUTTON_FUNCTION_UNABOVE: + if (flags & META_FRAME_ABOVE) + return &fgeom->unabove_rect; + else + return NULL; + case META_BUTTON_FUNCTION_UNSTICK: + if (flags & META_FRAME_STUCK) + return &fgeom->unstick_rect; + default: + /* just go on to the next switch block */; + } + } + + /* now consider the buttons which exist in all versions */ + + switch (function) + { + case META_BUTTON_FUNCTION_MENU: + if (flags & META_FRAME_ALLOWS_MENU) + return &fgeom->menu_rect; + else + return NULL; + case META_BUTTON_FUNCTION_MINIMIZE: + if (flags & META_FRAME_ALLOWS_MINIMIZE) + return &fgeom->min_rect; + else + return NULL; + case META_BUTTON_FUNCTION_MAXIMIZE: + if (flags & META_FRAME_ALLOWS_MAXIMIZE) + return &fgeom->max_rect; + else + return NULL; + case META_BUTTON_FUNCTION_CLOSE: + if (flags & META_FRAME_ALLOWS_DELETE) + return &fgeom->close_rect; + else + return NULL; + case META_BUTTON_FUNCTION_STICK: + case META_BUTTON_FUNCTION_SHADE: + case META_BUTTON_FUNCTION_ABOVE: + case META_BUTTON_FUNCTION_UNSTICK: + case META_BUTTON_FUNCTION_UNSHADE: + case META_BUTTON_FUNCTION_UNABOVE: + /* we are being asked for a >v1 button which hasn't been handled yet, + * so obviously we're not in a theme which supports that version. + * therefore, we don't show the button. return NULL and all will + * be well. + */ + return NULL; + + case META_BUTTON_FUNCTION_LAST: + return NULL; + } + + return NULL; +} + +static gboolean +strip_button (MetaButtonSpace *func_rects[MAX_BUTTONS_PER_CORNER], + GdkRectangle *bg_rects[MAX_BUTTONS_PER_CORNER], + int *n_rects, + MetaButtonSpace *to_strip) +{ + int i; + + i = 0; + while (i < *n_rects) + { + if (func_rects[i] == to_strip) + { + *n_rects -= 1; + + /* shift the other rects back in the array */ + while (i < *n_rects) + { + func_rects[i] = func_rects[i+1]; + bg_rects[i] = bg_rects[i+1]; + + ++i; + } + + func_rects[i] = NULL; + bg_rects[i] = NULL; + + return TRUE; + } + + ++i; + } + + return FALSE; /* did not strip anything */ +} + +void +meta_frame_layout_calc_geometry (const MetaFrameLayout *layout, + int text_height, + MetaFrameFlags flags, + int client_width, + int client_height, + const MetaButtonLayout *button_layout, + MetaFrameGeometry *fgeom, + MetaTheme *theme) +{ + int i, n_left, n_right, n_left_spacers, n_right_spacers; + int x; + int button_y; + int title_right_edge; + int width, height; + int button_width, button_height; + int min_size_for_rounding; + + /* the left/right rects in order; the max # of rects + * is the number of button functions + */ + MetaButtonSpace *left_func_rects[MAX_BUTTONS_PER_CORNER]; + MetaButtonSpace *right_func_rects[MAX_BUTTONS_PER_CORNER]; + GdkRectangle *left_bg_rects[MAX_BUTTONS_PER_CORNER]; + gboolean left_buttons_has_spacer[MAX_BUTTONS_PER_CORNER]; + GdkRectangle *right_bg_rects[MAX_BUTTONS_PER_CORNER]; + gboolean right_buttons_has_spacer[MAX_BUTTONS_PER_CORNER]; + + meta_frame_layout_get_borders (layout, text_height, + flags, + &fgeom->top_height, + &fgeom->bottom_height, + &fgeom->left_width, + &fgeom->right_width); + + width = client_width + fgeom->left_width + fgeom->right_width; + + height = ((flags & META_FRAME_SHADED) ? 0: client_height) + + fgeom->top_height + fgeom->bottom_height; + + fgeom->width = width; + fgeom->height = height; + + fgeom->top_titlebar_edge = layout->title_border.top; + fgeom->bottom_titlebar_edge = layout->title_border.bottom; + fgeom->left_titlebar_edge = layout->left_titlebar_edge; + fgeom->right_titlebar_edge = layout->right_titlebar_edge; + + /* gcc warnings */ + button_width = -1; + button_height = -1; + + switch (layout->button_sizing) + { + case META_BUTTON_SIZING_ASPECT: + button_height = fgeom->top_height - layout->button_border.top - layout->button_border.bottom; + button_width = button_height / layout->button_aspect; + break; + case META_BUTTON_SIZING_FIXED: + button_width = layout->button_width; + button_height = layout->button_height; + break; + case META_BUTTON_SIZING_LAST: + g_assert_not_reached (); + break; + } + + /* FIXME all this code sort of pretends that duplicate buttons + * with the same function are allowed, but that breaks the + * code in frames.c, so isn't really allowed right now. + * Would need left_close_rect, right_close_rect, etc. + */ + + /* Init all button rects to 0, lame hack */ + memset (ADDRESS_OF_BUTTON_RECTS (fgeom), '\0', + LENGTH_OF_BUTTON_RECTS); + + n_left = 0; + n_right = 0; + n_left_spacers = 0; + n_right_spacers = 0; + + if (!layout->hide_buttons) + { + /* Try to fill in rects */ + for (i = 0; i < MAX_BUTTONS_PER_CORNER && button_layout->left_buttons[i] != META_BUTTON_FUNCTION_LAST; i++) + { + left_func_rects[n_left] = rect_for_function (fgeom, flags, + button_layout->left_buttons[i], + theme); + if (left_func_rects[n_left] != NULL) + { + left_buttons_has_spacer[n_left] = button_layout->left_buttons_has_spacer[i]; + if (button_layout->left_buttons_has_spacer[i]) + ++n_left_spacers; + + ++n_left; + } + } + + for (i = 0; i < MAX_BUTTONS_PER_CORNER && button_layout->right_buttons[i] != META_BUTTON_FUNCTION_LAST; i++) + { + right_func_rects[n_right] = rect_for_function (fgeom, flags, + button_layout->right_buttons[i], + theme); + if (right_func_rects[n_right] != NULL) + { + right_buttons_has_spacer[n_right] = button_layout->right_buttons_has_spacer[i]; + if (button_layout->right_buttons_has_spacer[i]) + ++n_right_spacers; + + ++n_right; + } + } + } + + for (i = 0; i < MAX_BUTTONS_PER_CORNER; i++) + { + left_bg_rects[i] = NULL; + right_bg_rects[i] = NULL; + } + + for (i = 0; i < n_left; i++) + { + if (i == 0) /* prefer left background if only one button */ + left_bg_rects[i] = &fgeom->left_left_background; + else if (i == (n_left - 1)) + left_bg_rects[i] = &fgeom->left_right_background; + else + left_bg_rects[i] = &fgeom->left_middle_backgrounds[i - 1]; + } + + for (i = 0; i < n_right; i++) + { + /* prefer right background if only one button */ + if (i == (n_right - 1)) + right_bg_rects[i] = &fgeom->right_right_background; + else if (i == 0) + right_bg_rects[i] = &fgeom->right_left_background; + else + right_bg_rects[i] = &fgeom->right_middle_backgrounds[i - 1]; + } + + /* Be sure buttons fit */ + while (n_left > 0 || n_right > 0) + { + int space_used_by_buttons; + int space_available; + + space_available = fgeom->width - layout->left_titlebar_edge - layout->right_titlebar_edge; + + space_used_by_buttons = 0; + + space_used_by_buttons += button_width * n_left; + space_used_by_buttons += (button_width * 0.75) * n_left_spacers; + space_used_by_buttons += layout->button_border.left * n_left; + space_used_by_buttons += layout->button_border.right * n_left; + + space_used_by_buttons += button_width * n_right; + space_used_by_buttons += (button_width * 0.75) * n_right_spacers; + space_used_by_buttons += layout->button_border.left * n_right; + space_used_by_buttons += layout->button_border.right * n_right; + + if (space_used_by_buttons <= space_available) + break; /* Everything fits, bail out */ + + /* First try to remove separators */ + if (n_left_spacers > 0) + { + left_buttons_has_spacer[--n_left_spacers] = FALSE; + continue; + } + else if (n_right_spacers > 0) + { + right_buttons_has_spacer[--n_right_spacers] = FALSE; + continue; + } + + /* Otherwise we need to shave out a button. Shave + * above, stick, shade, min, max, close, then menu (menu is most useful); + * prefer the default button locations. + */ + if (strip_button (left_func_rects, left_bg_rects, + &n_left, &fgeom->above_rect)) + continue; + else if (strip_button (right_func_rects, right_bg_rects, + &n_right, &fgeom->above_rect)) + continue; + else if (strip_button (left_func_rects, left_bg_rects, + &n_left, &fgeom->stick_rect)) + continue; + else if (strip_button (right_func_rects, right_bg_rects, + &n_right, &fgeom->stick_rect)) + continue; + else if (strip_button (left_func_rects, left_bg_rects, + &n_left, &fgeom->shade_rect)) + continue; + else if (strip_button (right_func_rects, right_bg_rects, + &n_right, &fgeom->shade_rect)) + continue; + else if (strip_button (left_func_rects, left_bg_rects, + &n_left, &fgeom->min_rect)) + continue; + else if (strip_button (right_func_rects, right_bg_rects, + &n_right, &fgeom->min_rect)) + continue; + else if (strip_button (left_func_rects, left_bg_rects, + &n_left, &fgeom->max_rect)) + continue; + else if (strip_button (right_func_rects, right_bg_rects, + &n_right, &fgeom->max_rect)) + continue; + else if (strip_button (left_func_rects, left_bg_rects, + &n_left, &fgeom->close_rect)) + continue; + else if (strip_button (right_func_rects, right_bg_rects, + &n_right, &fgeom->close_rect)) + continue; + else if (strip_button (right_func_rects, right_bg_rects, + &n_right, &fgeom->menu_rect)) + continue; + else if (strip_button (left_func_rects, left_bg_rects, + &n_left, &fgeom->menu_rect)) + continue; + else + { + meta_bug ("Could not find a button to strip. n_left = %d n_right = %d\n", + n_left, n_right); + } + } + + /* center buttons vertically */ + button_y = (fgeom->top_height - + (button_height + layout->button_border.top + layout->button_border.bottom)) / 2 + layout->button_border.top; + + /* right edge of farthest-right button */ + x = width - layout->right_titlebar_edge; + + i = n_right - 1; + while (i >= 0) + { + MetaButtonSpace *rect; + + if (x < 0) /* if we go negative, leave the buttons we don't get to as 0-width */ + break; + + rect = right_func_rects[i]; + rect->visible.x = x - layout->button_border.right - button_width; + if (right_buttons_has_spacer[i]) + rect->visible.x -= (button_width * 0.75); + + rect->visible.y = button_y; + rect->visible.width = button_width; + rect->visible.height = button_height; + + if (flags & META_FRAME_MAXIMIZED) + { + rect->clickable.x = rect->visible.x; + rect->clickable.y = 0; + rect->clickable.width = rect->visible.width; + rect->clickable.height = button_height + button_y; + + if (i == n_right - 1) + rect->clickable.width += layout->right_titlebar_edge + layout->right_width + layout->button_border.right; + + } + else + g_memmove (&(rect->clickable), &(rect->visible), sizeof(rect->clickable)); + + *(right_bg_rects[i]) = rect->visible; + + x = rect->visible.x - layout->button_border.left; + + --i; + } + + /* save right edge of titlebar for later use */ + title_right_edge = x - layout->title_border.right; + + /* Now x changes to be position from the left and we go through + * the left-side buttons + */ + x = layout->left_titlebar_edge; + for (i = 0; i < n_left; i++) + { + MetaButtonSpace *rect; + + rect = left_func_rects[i]; + + rect->visible.x = x + layout->button_border.left; + rect->visible.y = button_y; + rect->visible.width = button_width; + rect->visible.height = button_height; + + if (flags & META_FRAME_MAXIMIZED) + { + if (i==0) + { + rect->clickable.x = 0; + rect->clickable.width = button_width + x; + } + else + { + rect->clickable.x = rect->visible.x; + rect->clickable.width = button_width; + } + + rect->clickable.y = 0; + rect->clickable.height = button_height + button_y; + } + else + g_memmove (&(rect->clickable), &(rect->visible), sizeof(rect->clickable)); + + + x = rect->visible.x + rect->visible.width + layout->button_border.right; + if (left_buttons_has_spacer[i]) + x += (button_width * 0.75); + + *(left_bg_rects[i]) = rect->visible; + } + + /* We always fill as much vertical space as possible with title rect, + * rather than centering it like the buttons + */ + fgeom->title_rect.x = x + layout->title_border.left; + fgeom->title_rect.y = layout->title_border.top; + fgeom->title_rect.width = title_right_edge - fgeom->title_rect.x; + fgeom->title_rect.height = fgeom->top_height - layout->title_border.top - layout->title_border.bottom; + + /* Nuke title if it won't fit */ + if (fgeom->title_rect.width < 0 || + fgeom->title_rect.height < 0) + { + fgeom->title_rect.width = 0; + fgeom->title_rect.height = 0; + } + + if (flags & META_FRAME_SHADED) + min_size_for_rounding = 0; + else + min_size_for_rounding = 5; + + fgeom->top_left_corner_rounded_radius = 0; + fgeom->top_right_corner_rounded_radius = 0; + fgeom->bottom_left_corner_rounded_radius = 0; + fgeom->bottom_right_corner_rounded_radius = 0; + + if (fgeom->top_height + fgeom->left_width >= min_size_for_rounding) + fgeom->top_left_corner_rounded_radius = layout->top_left_corner_rounded_radius; + if (fgeom->top_height + fgeom->right_width >= min_size_for_rounding) + fgeom->top_right_corner_rounded_radius = layout->top_right_corner_rounded_radius; + + if (fgeom->bottom_height + fgeom->left_width >= min_size_for_rounding) + fgeom->bottom_left_corner_rounded_radius = layout->bottom_left_corner_rounded_radius; + if (fgeom->bottom_height + fgeom->right_width >= min_size_for_rounding) + fgeom->bottom_right_corner_rounded_radius = layout->bottom_right_corner_rounded_radius; +} + +MetaGradientSpec* +meta_gradient_spec_new (MetaGradientType type) +{ + MetaGradientSpec *spec; + + spec = g_new (MetaGradientSpec, 1); + + spec->type = type; + spec->color_specs = NULL; + + return spec; +} + +static void +free_color_spec (gpointer spec, gpointer user_data) +{ + meta_color_spec_free (spec); +} + +void +meta_gradient_spec_free (MetaGradientSpec *spec) +{ + g_return_if_fail (spec != NULL); + + g_slist_foreach (spec->color_specs, free_color_spec, NULL); + g_slist_free (spec->color_specs); + + DEBUG_FILL_STRUCT (spec); + g_free (spec); +} + +GdkPixbuf* +meta_gradient_spec_render (const MetaGradientSpec *spec, + GtkWidget *widget, + int width, + int height) +{ + int n_colors; + GdkColor *colors; + GSList *tmp; + int i; + GdkPixbuf *pixbuf; + + n_colors = g_slist_length (spec->color_specs); + + if (n_colors == 0) + return NULL; + + colors = g_new (GdkColor, n_colors); + + i = 0; + tmp = spec->color_specs; + while (tmp != NULL) + { + meta_color_spec_render (tmp->data, widget, &colors[i]); + + tmp = tmp->next; + ++i; + } + + pixbuf = meta_gradient_create_multi (width, height, + colors, n_colors, + spec->type); + + g_free (colors); + + return pixbuf; +} + +gboolean +meta_gradient_spec_validate (MetaGradientSpec *spec, + GError **error) +{ + g_return_val_if_fail (spec != NULL, FALSE); + + if (g_slist_length (spec->color_specs) < 2) + { + g_set_error (error, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Gradients should have at least two colors")); + return FALSE; + } + + return TRUE; +} + +MetaAlphaGradientSpec* +meta_alpha_gradient_spec_new (MetaGradientType type, + int n_alphas) +{ + MetaAlphaGradientSpec *spec; + + g_return_val_if_fail (n_alphas > 0, NULL); + + spec = g_new0 (MetaAlphaGradientSpec, 1); + + spec->type = type; + spec->alphas = g_new0 (unsigned char, n_alphas); + spec->n_alphas = n_alphas; + + return spec; +} + +void +meta_alpha_gradient_spec_free (MetaAlphaGradientSpec *spec) +{ + g_return_if_fail (spec != NULL); + + g_free (spec->alphas); + g_free (spec); +} + +MetaColorSpec* +meta_color_spec_new (MetaColorSpecType type) +{ + MetaColorSpec *spec; + MetaColorSpec dummy; + int size; + + size = G_STRUCT_OFFSET (MetaColorSpec, data); + + switch (type) + { + case META_COLOR_SPEC_BASIC: + size += sizeof (dummy.data.basic); + break; + + case META_COLOR_SPEC_GTK: + size += sizeof (dummy.data.gtk); + break; + + case META_COLOR_SPEC_BLEND: + size += sizeof (dummy.data.blend); + break; + + case META_COLOR_SPEC_SHADE: + size += sizeof (dummy.data.shade); + break; + } + + spec = g_malloc0 (size); + + spec->type = type; + + return spec; +} + +void +meta_color_spec_free (MetaColorSpec *spec) +{ + g_return_if_fail (spec != NULL); + + switch (spec->type) + { + case META_COLOR_SPEC_BASIC: + DEBUG_FILL_STRUCT (&spec->data.basic); + break; + + case META_COLOR_SPEC_GTK: + DEBUG_FILL_STRUCT (&spec->data.gtk); + break; + + case META_COLOR_SPEC_BLEND: + if (spec->data.blend.foreground) + meta_color_spec_free (spec->data.blend.foreground); + if (spec->data.blend.background) + meta_color_spec_free (spec->data.blend.background); + DEBUG_FILL_STRUCT (&spec->data.blend); + break; + + case META_COLOR_SPEC_SHADE: + if (spec->data.shade.base) + meta_color_spec_free (spec->data.shade.base); + DEBUG_FILL_STRUCT (&spec->data.shade); + break; + } + + g_free (spec); +} + +MetaColorSpec* +meta_color_spec_new_from_string (const char *str, + GError **err) +{ + MetaColorSpec *spec; + + spec = NULL; + + if (str[0] == 'g' && str[1] == 't' && str[2] == 'k' && str[3] == ':') + { + /* GTK color */ + const char *bracket; + const char *end_bracket; + char *tmp; + GtkStateType state; + MetaGtkColorComponent component; + + bracket = str; + while (*bracket && *bracket != '[') + ++bracket; + + if (*bracket == '\0') + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("GTK color specification must have the state in brackets, e.g. gtk:fg[NORMAL] where NORMAL is the state; could not parse \"%s\""), + str); + return NULL; + } + + end_bracket = bracket; + ++end_bracket; + while (*end_bracket && *end_bracket != ']') + ++end_bracket; + + if (*end_bracket == '\0') + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("GTK color specification must have a close bracket after the state, e.g. gtk:fg[NORMAL] where NORMAL is the state; could not parse \"%s\""), + str); + return NULL; + } + + tmp = g_strndup (bracket + 1, end_bracket - bracket - 1); + state = meta_gtk_state_from_string (tmp); + if (((int) state) == -1) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Did not understand state \"%s\" in color specification"), + tmp); + g_free (tmp); + return NULL; + } + g_free (tmp); + + tmp = g_strndup (str + 4, bracket - str - 4); + component = meta_color_component_from_string (tmp); + if (component == META_GTK_COLOR_LAST) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Did not understand color component \"%s\" in color specification"), + tmp); + g_free (tmp); + return NULL; + } + g_free (tmp); + + spec = meta_color_spec_new (META_COLOR_SPEC_GTK); + spec->data.gtk.state = state; + spec->data.gtk.component = component; + g_assert (spec->data.gtk.state < N_GTK_STATES); + g_assert (spec->data.gtk.component < META_GTK_COLOR_LAST); + } + else if (str[0] == 'b' && str[1] == 'l' && str[2] == 'e' && str[3] == 'n' && + str[4] == 'd' && str[5] == '/') + { + /* blend */ + char **split; + double alpha; + char *end; + MetaColorSpec *fg; + MetaColorSpec *bg; + + split = g_strsplit (str, "/", 4); + + if (split[0] == NULL || split[1] == NULL || + split[2] == NULL || split[3] == NULL) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Blend format is \"blend/bg_color/fg_color/alpha\", \"%s\" does not fit the format"), + str); + g_strfreev (split); + return NULL; + } + + alpha = g_ascii_strtod (split[3], &end); + if (end == split[3]) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Could not parse alpha value \"%s\" in blended color"), + split[3]); + g_strfreev (split); + return NULL; + } + + if (alpha < (0.0 - 1e6) || alpha > (1.0 + 1e6)) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Alpha value \"%s\" in blended color is not between 0.0 and 1.0"), + split[3]); + g_strfreev (split); + return NULL; + } + + fg = NULL; + bg = NULL; + + bg = meta_color_spec_new_from_string (split[1], err); + if (bg == NULL) + { + g_strfreev (split); + return NULL; + } + + fg = meta_color_spec_new_from_string (split[2], err); + if (fg == NULL) + { + meta_color_spec_free (bg); + g_strfreev (split); + return NULL; + } + + g_strfreev (split); + + spec = meta_color_spec_new (META_COLOR_SPEC_BLEND); + spec->data.blend.alpha = alpha; + spec->data.blend.background = bg; + spec->data.blend.foreground = fg; + } + else if (str[0] == 's' && str[1] == 'h' && str[2] == 'a' && str[3] == 'd' && + str[4] == 'e' && str[5] == '/') + { + /* shade */ + char **split; + double factor; + char *end; + MetaColorSpec *base; + + split = g_strsplit (str, "/", 3); + + if (split[0] == NULL || split[1] == NULL || + split[2] == NULL) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Shade format is \"shade/base_color/factor\", \"%s\" does not fit the format"), + str); + g_strfreev (split); + return NULL; + } + + factor = g_ascii_strtod (split[2], &end); + if (end == split[2]) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Could not parse shade factor \"%s\" in shaded color"), + split[2]); + g_strfreev (split); + return NULL; + } + + if (factor < (0.0 - 1e6)) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Shade factor \"%s\" in shaded color is negative"), + split[2]); + g_strfreev (split); + return NULL; + } + + base = NULL; + + base = meta_color_spec_new_from_string (split[1], err); + if (base == NULL) + { + g_strfreev (split); + return NULL; + } + + g_strfreev (split); + + spec = meta_color_spec_new (META_COLOR_SPEC_SHADE); + spec->data.shade.factor = factor; + spec->data.shade.base = base; + } + else + { + spec = meta_color_spec_new (META_COLOR_SPEC_BASIC); + + if (!gdk_color_parse (str, &spec->data.basic.color)) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Could not parse color \"%s\""), + str); + meta_color_spec_free (spec); + return NULL; + } + } + + g_assert (spec); + + return spec; +} + +MetaColorSpec* +meta_color_spec_new_gtk (MetaGtkColorComponent component, + GtkStateType state) +{ + MetaColorSpec *spec; + + spec = meta_color_spec_new (META_COLOR_SPEC_GTK); + + spec->data.gtk.component = component; + spec->data.gtk.state = state; + + return spec; +} + +void +meta_color_spec_render (MetaColorSpec *spec, + GtkWidget *widget, + GdkColor *color) +{ + GtkStyle *widget_style; + + g_return_if_fail (spec != NULL); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + widget_style = gtk_widget_get_style (widget); + g_return_if_fail (widget_style != NULL); + + switch (spec->type) + { + case META_COLOR_SPEC_BASIC: + *color = spec->data.basic.color; + break; + + case META_COLOR_SPEC_GTK: + switch (spec->data.gtk.component) + { + case META_GTK_COLOR_BG: + *color = widget_style->bg[spec->data.gtk.state]; + break; + case META_GTK_COLOR_FG: + *color = widget_style->fg[spec->data.gtk.state]; + break; + case META_GTK_COLOR_BASE: + *color = widget_style->base[spec->data.gtk.state]; + break; + case META_GTK_COLOR_TEXT: + *color = widget_style->text[spec->data.gtk.state]; + break; + case META_GTK_COLOR_LIGHT: + *color = widget_style->light[spec->data.gtk.state]; + break; + case META_GTK_COLOR_DARK: + *color = widget_style->dark[spec->data.gtk.state]; + break; + case META_GTK_COLOR_MID: + *color = widget_style->mid[spec->data.gtk.state]; + break; + case META_GTK_COLOR_TEXT_AA: + *color = widget_style->text_aa[spec->data.gtk.state]; + break; + case META_GTK_COLOR_LAST: + g_assert_not_reached (); + break; + } + break; + + case META_COLOR_SPEC_BLEND: + { + GdkColor bg, fg; + + meta_color_spec_render (spec->data.blend.background, widget, &bg); + meta_color_spec_render (spec->data.blend.foreground, widget, &fg); + + color_composite (&bg, &fg, spec->data.blend.alpha, + &spec->data.blend.color); + + *color = spec->data.blend.color; + } + break; + + case META_COLOR_SPEC_SHADE: + { + meta_color_spec_render (spec->data.shade.base, widget, + &spec->data.shade.color); + + gtk_style_shade (&spec->data.shade.color, + &spec->data.shade.color, spec->data.shade.factor); + + *color = spec->data.shade.color; + } + break; + } +} + +/** + * Represents an operation as a string. + * + * \param type an operation, such as addition + * \return a string, such as "+" + */ +static const char* +op_name (PosOperatorType type) +{ + switch (type) + { + case POS_OP_ADD: + return "+"; + case POS_OP_SUBTRACT: + return "-"; + case POS_OP_MULTIPLY: + return "*"; + case POS_OP_DIVIDE: + return "/"; + case POS_OP_MOD: + return "%"; + case POS_OP_MAX: + return "`max`"; + case POS_OP_MIN: + return "`min`"; + case POS_OP_NONE: + break; + } + + return "<unknown>"; +} + +/** + * Parses a string and returns an operation. + * + * \param p a pointer into a string representing an operation; part of an + * expression somewhere, so not null-terminated + * \param len set to the length of the string found. Set to 0 if none is. + * \return the operation found. If none was, returns POS_OP_NONE. + */ +static PosOperatorType +op_from_string (const char *p, + int *len) +{ + *len = 0; + + switch (*p) + { + case '+': + *len = 1; + return POS_OP_ADD; + case '-': + *len = 1; + return POS_OP_SUBTRACT; + case '*': + *len = 1; + return POS_OP_MULTIPLY; + case '/': + *len = 1; + return POS_OP_DIVIDE; + case '%': + *len = 1; + return POS_OP_MOD; + + case '`': + if (p[0] == '`' && + p[1] == 'm' && + p[2] == 'a' && + p[3] == 'x' && + p[4] == '`') + { + *len = 5; + return POS_OP_MAX; + } + else if (p[0] == '`' && + p[1] == 'm' && + p[2] == 'i' && + p[3] == 'n' && + p[4] == '`') + { + *len = 5; + return POS_OP_MIN; + } + } + + return POS_OP_NONE; +} + +/** + * Frees an array of tokens. All the tokens and their associated memory + * will be freed. + * + * \param tokens an array of tokens to be freed + * \param n_tokens how many tokens are in the array. + */ +static void +free_tokens (PosToken *tokens, + int n_tokens) +{ + int i; + + /* n_tokens can be 0 since tokens may have been allocated more than + * it was initialized + */ + + for (i = 0; i < n_tokens; i++) + if (tokens[i].type == POS_TOKEN_VARIABLE) + g_free (tokens[i].d.v.name); + + g_free (tokens); +} + +/** + * Tokenises a number in an expression. + * + * \param p a pointer into a string representing an operation; part of an + * expression somewhere, so not null-terminated + * \param end_return set to a pointer to the end of the number found; but + * not updated if no number was found at all + * \param next set to either an integer or a float token + * \param[out] err set to the problem if there was a problem + * \return TRUE if a valid number was found, FALSE otherwise (and "err" will + * have been set) + * + * \bug The "while (*start)..." part: what's wrong with strchr-ish things? + * \bug The name is wrong: it doesn't parse anything. + * \ingroup tokenizer + */ +static gboolean +parse_number (const char *p, + const char **end_return, + PosToken *next, + GError **err) +{ + const char *start = p; + char *end; + gboolean is_float; + char *num_str; + + while (*p && (*p == '.' || g_ascii_isdigit (*p))) + ++p; + + if (p == start) + { + char buf[7] = { '\0' }; + buf[g_unichar_to_utf8 (g_utf8_get_char (p), buf)] = '\0'; + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_BAD_CHARACTER, + _("Coordinate expression contains character '%s' which is not allowed"), + buf); + return FALSE; + } + + *end_return = p; + + /* we need this to exclude floats like "1e6" */ + num_str = g_strndup (start, p - start); + start = num_str; + is_float = FALSE; + while (*start) + { + if (*start == '.') + is_float = TRUE; + ++start; + } + + if (is_float) + { + next->type = POS_TOKEN_DOUBLE; + next->d.d.val = g_ascii_strtod (num_str, &end); + + if (end == num_str) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression contains floating point number '%s' which could not be parsed"), + num_str); + g_free (num_str); + return FALSE; + } + } + else + { + next->type = POS_TOKEN_INT; + next->d.i.val = strtol (num_str, &end, 10); + if (end == num_str) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression contains integer '%s' which could not be parsed"), + num_str); + g_free (num_str); + return FALSE; + } + } + + g_free (num_str); + + g_assert (next->type == POS_TOKEN_INT || next->type == POS_TOKEN_DOUBLE); + + return TRUE; +} + +/** + * Whether a variable can validly appear as part of the name of a variable. + */ +#define IS_VARIABLE_CHAR(c) (g_ascii_isalpha ((c)) || (c) == '_') + +#if 0 +static void +debug_print_tokens (PosToken *tokens, + int n_tokens) +{ + int i; + + for (i = 0; i < n_tokens; i++) + { + PosToken *t = &tokens[i]; + + g_print (" "); + + switch (t->type) + { + case POS_TOKEN_INT: + g_print ("\"%d\"", t->d.i.val); + break; + case POS_TOKEN_DOUBLE: + g_print ("\"%g\"", t->d.d.val); + break; + case POS_TOKEN_OPEN_PAREN: + g_print ("\"(\""); + break; + case POS_TOKEN_CLOSE_PAREN: + g_print ("\")\""); + break; + case POS_TOKEN_VARIABLE: + g_print ("\"%s\"", t->d.v.name); + break; + case POS_TOKEN_OPERATOR: + g_print ("\"%s\"", op_name (t->d.o.op)); + break; + } + } + + g_print ("\n"); +} +#endif + +/** + * Tokenises an expression. + * + * \param expr The expression + * \param[out] tokens_p The resulting tokens + * \param[out] n_tokens_p The number of resulting tokens + * \param[out] err set to the problem if there was a problem + * + * \return True if the expression was successfully tokenised; false otherwise. + * + * \ingroup tokenizer + */ +static gboolean +pos_tokenize (const char *expr, + PosToken **tokens_p, + int *n_tokens_p, + GError **err) +{ + PosToken *tokens; + int n_tokens; + int allocated; + const char *p; + + *tokens_p = NULL; + *n_tokens_p = 0; + + allocated = 3; + n_tokens = 0; + tokens = g_new (PosToken, allocated); + + p = expr; + while (*p) + { + PosToken *next; + int len; + + if (n_tokens == allocated) + { + allocated *= 2; + tokens = g_renew (PosToken, tokens, allocated); + } + + next = &tokens[n_tokens]; + + switch (*p) + { + case '*': + case '/': + case '+': + case '-': /* negative numbers aren't allowed so this is easy */ + case '%': + case '`': + next->type = POS_TOKEN_OPERATOR; + next->d.o.op = op_from_string (p, &len); + if (next->d.o.op != POS_OP_NONE) + { + ++n_tokens; + p = p + (len - 1); /* -1 since we ++p later */ + } + else + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression contained unknown operator at the start of this text: \"%s\""), + p); + + goto error; + } + break; + + case '(': + next->type = POS_TOKEN_OPEN_PAREN; + ++n_tokens; + break; + + case ')': + next->type = POS_TOKEN_CLOSE_PAREN; + ++n_tokens; + break; + + case ' ': + case '\t': + case '\n': + break; + + default: + if (IS_VARIABLE_CHAR (*p)) + { + /* Assume variable */ + const char *start = p; + while (*p && IS_VARIABLE_CHAR (*p)) + ++p; + g_assert (p != start); + next->type = POS_TOKEN_VARIABLE; + next->d.v.name = g_strndup (start, p - start); + ++n_tokens; + --p; /* since we ++p again at the end of while loop */ + } + else + { + /* Assume number */ + const char *end; + + if (!parse_number (p, &end, next, err)) + goto error; + + ++n_tokens; + p = end - 1; /* -1 since we ++p again at the end of while loop */ + } + + break; + } + + ++p; + } + + if (n_tokens == 0) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression was empty or not understood")); + + goto error; + } + + *tokens_p = tokens; + *n_tokens_p = n_tokens; + + return TRUE; + + error: + g_assert (err == NULL || *err != NULL); + + free_tokens (tokens, n_tokens); + return FALSE; +} + +/** + * The type of a PosExpr: either integer, double, or an operation. + * \ingroup parser + */ +typedef enum +{ + POS_EXPR_INT, + POS_EXPR_DOUBLE, + POS_EXPR_OPERATOR +} PosExprType; + +/** + * Type and value of an expression in a parsed sequence. We don't + * keep expressions in a tree; if this is of type POS_EXPR_OPERATOR, + * the arguments of the operator will be in the array positions + * immediately preceding and following this operator; they cannot + * themselves be operators. + * + * \bug operator is char; it should really be of PosOperatorType. + * \ingroup parser + */ +typedef struct +{ + PosExprType type; + union + { + double double_val; + int int_val; + char operator; + } d; +} PosExpr; + +#if 0 +static void +debug_print_exprs (PosExpr *exprs, + int n_exprs) +{ + int i; + + for (i = 0; i < n_exprs; i++) + { + switch (exprs[i].type) + { + case POS_EXPR_INT: + g_print (" %d", exprs[i].d.int_val); + break; + case POS_EXPR_DOUBLE: + g_print (" %g", exprs[i].d.double_val); + break; + case POS_EXPR_OPERATOR: + g_print (" %s", op_name (exprs[i].d.operator)); + break; + } + } + g_print ("\n"); +} +#endif + +static gboolean +do_operation (PosExpr *a, + PosExpr *b, + PosOperatorType op, + GError **err) +{ + /* Promote types to double if required */ + if (a->type == POS_EXPR_DOUBLE || + b->type == POS_EXPR_DOUBLE) + { + if (a->type != POS_EXPR_DOUBLE) + { + a->type = POS_EXPR_DOUBLE; + a->d.double_val = a->d.int_val; + } + if (b->type != POS_EXPR_DOUBLE) + { + b->type = POS_EXPR_DOUBLE; + b->d.double_val = b->d.int_val; + } + } + + g_assert (a->type == b->type); + + if (a->type == POS_EXPR_INT) + { + switch (op) + { + case POS_OP_MULTIPLY: + a->d.int_val = a->d.int_val * b->d.int_val; + break; + case POS_OP_DIVIDE: + if (b->d.int_val == 0) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_DIVIDE_BY_ZERO, + _("Coordinate expression results in division by zero")); + return FALSE; + } + a->d.int_val = a->d.int_val / b->d.int_val; + break; + case POS_OP_MOD: + if (b->d.int_val == 0) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_DIVIDE_BY_ZERO, + _("Coordinate expression results in division by zero")); + return FALSE; + } + a->d.int_val = a->d.int_val % b->d.int_val; + break; + case POS_OP_ADD: + a->d.int_val = a->d.int_val + b->d.int_val; + break; + case POS_OP_SUBTRACT: + a->d.int_val = a->d.int_val - b->d.int_val; + break; + case POS_OP_MAX: + a->d.int_val = MAX (a->d.int_val, b->d.int_val); + break; + case POS_OP_MIN: + a->d.int_val = MIN (a->d.int_val, b->d.int_val); + break; + case POS_OP_NONE: + g_assert_not_reached (); + break; + } + } + else if (a->type == POS_EXPR_DOUBLE) + { + switch (op) + { + case POS_OP_MULTIPLY: + a->d.double_val = a->d.double_val * b->d.double_val; + break; + case POS_OP_DIVIDE: + if (b->d.double_val == 0.0) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_DIVIDE_BY_ZERO, + _("Coordinate expression results in division by zero")); + return FALSE; + } + a->d.double_val = a->d.double_val / b->d.double_val; + break; + case POS_OP_MOD: + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_MOD_ON_FLOAT, + _("Coordinate expression tries to use mod operator on a floating-point number")); + return FALSE; + case POS_OP_ADD: + a->d.double_val = a->d.double_val + b->d.double_val; + break; + case POS_OP_SUBTRACT: + a->d.double_val = a->d.double_val - b->d.double_val; + break; + case POS_OP_MAX: + a->d.double_val = MAX (a->d.double_val, b->d.double_val); + break; + case POS_OP_MIN: + a->d.double_val = MIN (a->d.double_val, b->d.double_val); + break; + case POS_OP_NONE: + g_assert_not_reached (); + break; + } + } + else + g_assert_not_reached (); + + return TRUE; +} + +static gboolean +do_operations (PosExpr *exprs, + int *n_exprs, + int precedence, + GError **err) +{ + int i; + +#if 0 + g_print ("Doing prec %d ops on %d exprs\n", precedence, *n_exprs); + debug_print_exprs (exprs, *n_exprs); +#endif + + i = 1; + while (i < *n_exprs) + { + gboolean compress; + + /* exprs[i-1] first operand + * exprs[i] operator + * exprs[i+1] second operand + * + * we replace first operand with result of mul/div/mod, + * or skip over operator and second operand if we have + * an add/subtract + */ + + if (exprs[i-1].type == POS_EXPR_OPERATOR) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression has an operator \"%s\" where an operand was expected"), + op_name (exprs[i-1].d.operator)); + return FALSE; + } + + if (exprs[i].type != POS_EXPR_OPERATOR) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression had an operand where an operator was expected")); + return FALSE; + } + + if (i == (*n_exprs - 1)) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression ended with an operator instead of an operand")); + return FALSE; + } + + g_assert ((i+1) < *n_exprs); + + if (exprs[i+1].type == POS_EXPR_OPERATOR) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression has operator \"%c\" following operator \"%c\" with no operand in between"), + exprs[i+1].d.operator, + exprs[i].d.operator); + return FALSE; + } + + compress = FALSE; + + switch (precedence) + { + case 2: + switch (exprs[i].d.operator) + { + case POS_OP_DIVIDE: + case POS_OP_MOD: + case POS_OP_MULTIPLY: + compress = TRUE; + if (!do_operation (&exprs[i-1], &exprs[i+1], + exprs[i].d.operator, + err)) + return FALSE; + break; + } + break; + case 1: + switch (exprs[i].d.operator) + { + case POS_OP_ADD: + case POS_OP_SUBTRACT: + compress = TRUE; + if (!do_operation (&exprs[i-1], &exprs[i+1], + exprs[i].d.operator, + err)) + return FALSE; + break; + } + break; + /* I have no rationale at all for making these low-precedence */ + case 0: + switch (exprs[i].d.operator) + { + case POS_OP_MAX: + case POS_OP_MIN: + compress = TRUE; + if (!do_operation (&exprs[i-1], &exprs[i+1], + exprs[i].d.operator, + err)) + return FALSE; + break; + } + break; + } + + if (compress) + { + /* exprs[i-1] first operand (now result) + * exprs[i] operator + * exprs[i+1] second operand + * exprs[i+2] new operator + * + * we move new operator just after first operand + */ + if ((i+2) < *n_exprs) + { + g_memmove (&exprs[i], &exprs[i+2], + sizeof (PosExpr) * (*n_exprs - i - 2)); + } + + *n_exprs -= 2; + } + else + { + /* Skip operator and next operand */ + i += 2; + } + } + + return TRUE; +} + +/** + * There is a predefined set of variables which can appear in an expression. + * Here we take a token representing a variable, and return the current value + * of that variable in a particular environment. + * (The value is always an integer.) + * + * There are supposedly some circumstances in which this function can be + * called from outside Marco, in which case env->theme will be NULL, and + * therefore we can't use it to find out quark values, so we do the comparison + * using strcmp, which is slower. + * + * \param t The token representing a variable + * \param[out] result The value of that variable; not set if the token did + * not represent a known variable + * \param env The environment within which t should be evaluated + * \param[out] err set to the problem if there was a problem + * + * \return true if we found the variable asked for, false if we didn't + * + * \bug shouldn't t be const? + * \bug we should perhaps consider some sort of lookup arrangement into an + * array; also, the duplication of code is unlovely; perhaps using glib + * string hashes instead of quarks would fix both problems? + * \ingroup parser + */ +static gboolean +pos_eval_get_variable (PosToken *t, + int *result, + const MetaPositionExprEnv *env, + GError **err) +{ + if (env->theme) + { + if (t->d.v.name_quark == env->theme->quark_width) + *result = env->rect.width; + else if (t->d.v.name_quark == env->theme->quark_height) + *result = env->rect.height; + else if (env->object_width >= 0 && + t->d.v.name_quark == env->theme->quark_object_width) + *result = env->object_width; + else if (env->object_height >= 0 && + t->d.v.name_quark == env->theme->quark_object_height) + *result = env->object_height; + else if (t->d.v.name_quark == env->theme->quark_left_width) + *result = env->left_width; + else if (t->d.v.name_quark == env->theme->quark_right_width) + *result = env->right_width; + else if (t->d.v.name_quark == env->theme->quark_top_height) + *result = env->top_height; + else if (t->d.v.name_quark == env->theme->quark_bottom_height) + *result = env->bottom_height; + else if (t->d.v.name_quark == env->theme->quark_mini_icon_width) + *result = env->mini_icon_width; + else if (t->d.v.name_quark == env->theme->quark_mini_icon_height) + *result = env->mini_icon_height; + else if (t->d.v.name_quark == env->theme->quark_icon_width) + *result = env->icon_width; + else if (t->d.v.name_quark == env->theme->quark_icon_height) + *result = env->icon_height; + else if (t->d.v.name_quark == env->theme->quark_title_width) + *result = env->title_width; + else if (t->d.v.name_quark == env->theme->quark_title_height) + *result = env->title_height; + else + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_UNKNOWN_VARIABLE, + _("Coordinate expression had unknown variable or constant \"%s\""), + t->d.v.name); + return FALSE; + } + } + else + { + if (strcmp (t->d.v.name, "width") == 0) + *result = env->rect.width; + else if (strcmp (t->d.v.name, "height") == 0) + *result = env->rect.height; + else if (env->object_width >= 0 && + strcmp (t->d.v.name, "object_width") == 0) + *result = env->object_width; + else if (env->object_height >= 0 && + strcmp (t->d.v.name, "object_height") == 0) + *result = env->object_height; + else if (strcmp (t->d.v.name, "left_width") == 0) + *result = env->left_width; + else if (strcmp (t->d.v.name, "right_width") == 0) + *result = env->right_width; + else if (strcmp (t->d.v.name, "top_height") == 0) + *result = env->top_height; + else if (strcmp (t->d.v.name, "bottom_height") == 0) + *result = env->bottom_height; + else if (strcmp (t->d.v.name, "mini_icon_width") == 0) + *result = env->mini_icon_width; + else if (strcmp (t->d.v.name, "mini_icon_height") == 0) + *result = env->mini_icon_height; + else if (strcmp (t->d.v.name, "icon_width") == 0) + *result = env->icon_width; + else if (strcmp (t->d.v.name, "icon_height") == 0) + *result = env->icon_height; + else if (strcmp (t->d.v.name, "title_width") == 0) + *result = env->title_width; + else if (strcmp (t->d.v.name, "title_height") == 0) + *result = env->title_height; + else + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_UNKNOWN_VARIABLE, + _("Coordinate expression had unknown variable or constant \"%s\""), + t->d.v.name); + return FALSE; + } + } + + return TRUE; +} + +/** + * Evaluates a sequence of tokens within a particular environment context, + * and returns the current value. May recur if parantheses are found. + * + * \param tokens A list of tokens to evaluate. + * \param n_tokens How many tokens are in the list. + * \param env The environment context in which to evaluate the expression. + * \param[out] result The current value of the expression + * + * \bug Yes, we really do reparse the expression every time it's evaluated. + * We should keep the parse tree around all the time and just + * run the new values through it. + * \ingroup parser + */ +static gboolean +pos_eval_helper (PosToken *tokens, + int n_tokens, + const MetaPositionExprEnv *env, + PosExpr *result, + GError **err) +{ + /* Lazy-ass hardcoded limit on number of terms in expression */ +#define MAX_EXPRS 32 + int paren_level; + int first_paren; + int i; + PosExpr exprs[MAX_EXPRS]; + int n_exprs; + int precedence; + + /* Our first goal is to get a list of PosExpr, essentially + * substituting variables and handling parentheses. + */ + + first_paren = 0; + paren_level = 0; + n_exprs = 0; + for (i = 0; i < n_tokens; i++) + { + PosToken *t = &tokens[i]; + + if (n_exprs >= MAX_EXPRS) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression parser overflowed its buffer.")); + return FALSE; + } + + if (paren_level == 0) + { + switch (t->type) + { + case POS_TOKEN_INT: + exprs[n_exprs].type = POS_EXPR_INT; + exprs[n_exprs].d.int_val = t->d.i.val; + ++n_exprs; + break; + + case POS_TOKEN_DOUBLE: + exprs[n_exprs].type = POS_EXPR_DOUBLE; + exprs[n_exprs].d.double_val = t->d.d.val; + ++n_exprs; + break; + + case POS_TOKEN_OPEN_PAREN: + ++paren_level; + if (paren_level == 1) + first_paren = i; + break; + + case POS_TOKEN_CLOSE_PAREN: + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_BAD_PARENS, + _("Coordinate expression had a close parenthesis with no open parenthesis")); + return FALSE; + + case POS_TOKEN_VARIABLE: + exprs[n_exprs].type = POS_EXPR_INT; + + /* FIXME we should just dump all this crap + * in a hash, maybe keep width/height out + * for optimization purposes + */ + if (!pos_eval_get_variable (t, &exprs[n_exprs].d.int_val, env, err)) + return FALSE; + + ++n_exprs; + break; + + case POS_TOKEN_OPERATOR: + exprs[n_exprs].type = POS_EXPR_OPERATOR; + exprs[n_exprs].d.operator = t->d.o.op; + ++n_exprs; + break; + } + } + else + { + g_assert (paren_level > 0); + + switch (t->type) + { + case POS_TOKEN_INT: + case POS_TOKEN_DOUBLE: + case POS_TOKEN_VARIABLE: + case POS_TOKEN_OPERATOR: + break; + + case POS_TOKEN_OPEN_PAREN: + ++paren_level; + break; + + case POS_TOKEN_CLOSE_PAREN: + if (paren_level == 1) + { + /* We closed a toplevel paren group, so recurse */ + if (!pos_eval_helper (&tokens[first_paren+1], + i - first_paren - 1, + env, + &exprs[n_exprs], + err)) + return FALSE; + + ++n_exprs; + } + + --paren_level; + break; + + } + } + } + + if (paren_level > 0) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_BAD_PARENS, + _("Coordinate expression had an open parenthesis with no close parenthesis")); + return FALSE; + } + + /* Now we have no parens and no vars; so we just do all the multiplies + * and divides, then all the add and subtract. + */ + if (n_exprs == 0) + { + g_set_error (err, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("Coordinate expression doesn't seem to have any operators or operands")); + return FALSE; + } + + /* precedence 1 ops */ + precedence = 2; + while (precedence >= 0) + { + if (!do_operations (exprs, &n_exprs, precedence, err)) + return FALSE; + --precedence; + } + + g_assert (n_exprs == 1); + + *result = *exprs; + + return TRUE; +} + +/* + * expr = int | double | expr * expr | expr / expr | + * expr + expr | expr - expr | (expr) + * + * so very not worth fooling with bison, yet so very painful by hand. + */ +/** + * Evaluates an expression. + * + * \param spec The expression to evaluate. + * \param env The environment context to evaluate the expression in. + * \param[out] val_p The integer value of the expression; if the expression + * is of type float, this will be rounded. If we return + * FALSE because the expression is invalid, this will be + * zero. + * \param[out] err The error, if anything went wrong. + * + * \return True if we evaluated the expression successfully; false otherwise. + * + * \bug Shouldn't spec be const? + * \ingroup parser + */ +static gboolean +pos_eval (MetaDrawSpec *spec, + const MetaPositionExprEnv *env, + int *val_p, + GError **err) +{ + PosExpr expr; + + *val_p = 0; + + if (pos_eval_helper (spec->tokens, spec->n_tokens, env, &expr, err)) + { + switch (expr.type) + { + case POS_EXPR_INT: + *val_p = expr.d.int_val; + break; + case POS_EXPR_DOUBLE: + *val_p = expr.d.double_val; + break; + case POS_EXPR_OPERATOR: + g_assert_not_reached (); + break; + } + return TRUE; + } + else + { + return FALSE; + } +} + +/* We always return both X and Y, but only one will be meaningful in + * most contexts. + */ + +gboolean +meta_parse_position_expression (MetaDrawSpec *spec, + const MetaPositionExprEnv *env, + int *x_return, + int *y_return, + GError **err) +{ + /* All positions are in a coordinate system with x, y at the origin. + * The expression can have -, +, *, / as operators, floating point + * or integer constants, and the variables "width" and "height" and + * optionally "object_width" and object_height". Negative numbers + * aren't allowed. + */ + int val; + + if (spec->constant) + val = spec->value; + else + { + if (pos_eval (spec, env, &spec->value, err) == FALSE) + { + g_assert (err == NULL || *err != NULL); + return FALSE; + } + + val = spec->value; + } + + if (x_return) + *x_return = env->rect.x + val; + if (y_return) + *y_return = env->rect.y + val; + + return TRUE; +} + + +gboolean +meta_parse_size_expression (MetaDrawSpec *spec, + const MetaPositionExprEnv *env, + int *val_return, + GError **err) +{ + int val; + + if (spec->constant) + val = spec->value; + else + { + if (pos_eval (spec, env, &spec->value, err) == FALSE) + { + g_assert (err == NULL || *err != NULL); + return FALSE; + } + + val = spec->value; + } + + if (val_return) + *val_return = MAX (val, 1); /* require that sizes be at least 1x1 */ + + return TRUE; +} + +/* To do this we tokenize, replace variable tokens + * that are constants, then reassemble. The purpose + * here is to optimize expressions so we don't do hash + * lookups to eval them. Obviously it's a tradeoff that + * slows down theme load times. + */ +gboolean +meta_theme_replace_constants (MetaTheme *theme, + PosToken *tokens, + int n_tokens, + GError **err) +{ + int i; + double dval; + int ival; + gboolean is_constant = TRUE; + + /* Loop through tokenized string looking for variables to replace */ + for (i = 0; i < n_tokens; i++) + { + PosToken *t = &tokens[i]; + + if (t->type == POS_TOKEN_VARIABLE) + { + if (meta_theme_lookup_int_constant (theme, t->d.v.name, &ival)) + { + g_free (t->d.v.name); + t->type = POS_TOKEN_INT; + t->d.i.val = ival; + } + else if (meta_theme_lookup_float_constant (theme, t->d.v.name, &dval)) + { + g_free (t->d.v.name); + t->type = POS_TOKEN_DOUBLE; + t->d.d.val = dval; + } + else + { + /* If we've found a variable that cannot be replaced then the + expression is not a constant expression and we want to + replace it with a GQuark */ + + t->d.v.name_quark = g_quark_from_string (t->d.v.name); + is_constant = FALSE; + } + } + } + + return is_constant; +} + +static int +parse_x_position_unchecked (MetaDrawSpec *spec, + const MetaPositionExprEnv *env) +{ + int retval; + GError *error; + + retval = 0; + error = NULL; + if (!meta_parse_position_expression (spec, env, &retval, NULL, &error)) + { + meta_warning (_("Theme contained an expression that resulted in an error: %s\n"), + error->message); + + g_error_free (error); + } + + return retval; +} + +static int +parse_y_position_unchecked (MetaDrawSpec *spec, + const MetaPositionExprEnv *env) +{ + int retval; + GError *error; + + retval = 0; + error = NULL; + if (!meta_parse_position_expression (spec, env, NULL, &retval, &error)) + { + meta_warning (_("Theme contained an expression that resulted in an error: %s\n"), + error->message); + + g_error_free (error); + } + + return retval; +} + +static int +parse_size_unchecked (MetaDrawSpec *spec, + MetaPositionExprEnv *env) +{ + int retval; + GError *error; + + retval = 0; + error = NULL; + if (!meta_parse_size_expression (spec, env, &retval, &error)) + { + meta_warning (_("Theme contained an expression that resulted in an error: %s\n"), + error->message); + + g_error_free (error); + } + + return retval; +} + +void +meta_draw_spec_free (MetaDrawSpec *spec) +{ + if (!spec) return; + free_tokens (spec->tokens, spec->n_tokens); + g_slice_free (MetaDrawSpec, spec); +} + +MetaDrawSpec * +meta_draw_spec_new (MetaTheme *theme, + const char *expr, + GError **error) +{ + MetaDrawSpec *spec; + + spec = g_slice_new0 (MetaDrawSpec); + + pos_tokenize (expr, &spec->tokens, &spec->n_tokens, NULL); + + spec->constant = meta_theme_replace_constants (theme, spec->tokens, + spec->n_tokens, NULL); + if (spec->constant) + { + gboolean result; + + result = pos_eval (spec, NULL, &spec->value, error); + if (result == FALSE) + { + meta_draw_spec_free (spec); + return NULL; + } + } + + return spec; +} + +MetaDrawOp* +meta_draw_op_new (MetaDrawType type) +{ + MetaDrawOp *op; + MetaDrawOp dummy; + int size; + + size = G_STRUCT_OFFSET (MetaDrawOp, data); + + switch (type) + { + case META_DRAW_LINE: + size += sizeof (dummy.data.line); + break; + + case META_DRAW_RECTANGLE: + size += sizeof (dummy.data.rectangle); + break; + + case META_DRAW_ARC: + size += sizeof (dummy.data.arc); + break; + + case META_DRAW_CLIP: + size += sizeof (dummy.data.clip); + break; + + case META_DRAW_TINT: + size += sizeof (dummy.data.tint); + break; + + case META_DRAW_GRADIENT: + size += sizeof (dummy.data.gradient); + break; + + case META_DRAW_IMAGE: + size += sizeof (dummy.data.image); + break; + + case META_DRAW_GTK_ARROW: + size += sizeof (dummy.data.gtk_arrow); + break; + + case META_DRAW_GTK_BOX: + size += sizeof (dummy.data.gtk_box); + break; + + case META_DRAW_GTK_VLINE: + size += sizeof (dummy.data.gtk_vline); + break; + + case META_DRAW_ICON: + size += sizeof (dummy.data.icon); + break; + + case META_DRAW_TITLE: + size += sizeof (dummy.data.title); + break; + case META_DRAW_OP_LIST: + size += sizeof (dummy.data.op_list); + break; + case META_DRAW_TILE: + size += sizeof (dummy.data.tile); + break; + } + + op = g_malloc0 (size); + + op->type = type; + + return op; +} + +void +meta_draw_op_free (MetaDrawOp *op) +{ + g_return_if_fail (op != NULL); + + switch (op->type) + { + case META_DRAW_LINE: + if (op->data.line.color_spec) + meta_color_spec_free (op->data.line.color_spec); + + meta_draw_spec_free (op->data.line.x1); + meta_draw_spec_free (op->data.line.y1); + meta_draw_spec_free (op->data.line.x2); + meta_draw_spec_free (op->data.line.y2); + break; + + case META_DRAW_RECTANGLE: + if (op->data.rectangle.color_spec) + g_free (op->data.rectangle.color_spec); + + meta_draw_spec_free (op->data.rectangle.x); + meta_draw_spec_free (op->data.rectangle.y); + meta_draw_spec_free (op->data.rectangle.width); + meta_draw_spec_free (op->data.rectangle.height); + break; + + case META_DRAW_ARC: + if (op->data.arc.color_spec) + g_free (op->data.arc.color_spec); + + meta_draw_spec_free (op->data.arc.x); + meta_draw_spec_free (op->data.arc.y); + meta_draw_spec_free (op->data.arc.width); + meta_draw_spec_free (op->data.arc.height); + break; + + case META_DRAW_CLIP: + meta_draw_spec_free (op->data.clip.x); + meta_draw_spec_free (op->data.clip.y); + meta_draw_spec_free (op->data.clip.width); + meta_draw_spec_free (op->data.clip.height); + break; + + case META_DRAW_TINT: + if (op->data.tint.color_spec) + meta_color_spec_free (op->data.tint.color_spec); + + if (op->data.tint.alpha_spec) + meta_alpha_gradient_spec_free (op->data.tint.alpha_spec); + + meta_draw_spec_free (op->data.tint.x); + meta_draw_spec_free (op->data.tint.y); + meta_draw_spec_free (op->data.tint.width); + meta_draw_spec_free (op->data.tint.height); + break; + + case META_DRAW_GRADIENT: + if (op->data.gradient.gradient_spec) + meta_gradient_spec_free (op->data.gradient.gradient_spec); + + if (op->data.gradient.alpha_spec) + meta_alpha_gradient_spec_free (op->data.gradient.alpha_spec); + + meta_draw_spec_free (op->data.gradient.x); + meta_draw_spec_free (op->data.gradient.y); + meta_draw_spec_free (op->data.gradient.width); + meta_draw_spec_free (op->data.gradient.height); + break; + + case META_DRAW_IMAGE: + if (op->data.image.alpha_spec) + meta_alpha_gradient_spec_free (op->data.image.alpha_spec); + + if (op->data.image.pixbuf) + g_object_unref (G_OBJECT (op->data.image.pixbuf)); + + if (op->data.image.colorize_spec) + meta_color_spec_free (op->data.image.colorize_spec); + + if (op->data.image.colorize_cache_pixbuf) + g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf)); + + meta_draw_spec_free (op->data.image.x); + meta_draw_spec_free (op->data.image.y); + meta_draw_spec_free (op->data.image.width); + meta_draw_spec_free (op->data.image.height); + break; + + case META_DRAW_GTK_ARROW: + meta_draw_spec_free (op->data.gtk_arrow.x); + meta_draw_spec_free (op->data.gtk_arrow.y); + meta_draw_spec_free (op->data.gtk_arrow.width); + meta_draw_spec_free (op->data.gtk_arrow.height); + break; + + case META_DRAW_GTK_BOX: + meta_draw_spec_free (op->data.gtk_box.x); + meta_draw_spec_free (op->data.gtk_box.y); + meta_draw_spec_free (op->data.gtk_box.width); + meta_draw_spec_free (op->data.gtk_box.height); + break; + + case META_DRAW_GTK_VLINE: + meta_draw_spec_free (op->data.gtk_vline.x); + meta_draw_spec_free (op->data.gtk_vline.y1); + meta_draw_spec_free (op->data.gtk_vline.y2); + break; + + case META_DRAW_ICON: + if (op->data.icon.alpha_spec) + meta_alpha_gradient_spec_free (op->data.icon.alpha_spec); + + meta_draw_spec_free (op->data.icon.x); + meta_draw_spec_free (op->data.icon.y); + meta_draw_spec_free (op->data.icon.width); + meta_draw_spec_free (op->data.icon.height); + break; + + case META_DRAW_TITLE: + if (op->data.title.color_spec) + meta_color_spec_free (op->data.title.color_spec); + + meta_draw_spec_free (op->data.title.x); + meta_draw_spec_free (op->data.title.y); + break; + + case META_DRAW_OP_LIST: + if (op->data.op_list.op_list) + meta_draw_op_list_unref (op->data.op_list.op_list); + + meta_draw_spec_free (op->data.op_list.x); + meta_draw_spec_free (op->data.op_list.y); + meta_draw_spec_free (op->data.op_list.width); + meta_draw_spec_free (op->data.op_list.height); + break; + + case META_DRAW_TILE: + if (op->data.tile.op_list) + meta_draw_op_list_unref (op->data.tile.op_list); + + meta_draw_spec_free (op->data.tile.x); + meta_draw_spec_free (op->data.tile.y); + meta_draw_spec_free (op->data.tile.width); + meta_draw_spec_free (op->data.tile.height); + meta_draw_spec_free (op->data.tile.tile_xoffset); + meta_draw_spec_free (op->data.tile.tile_yoffset); + meta_draw_spec_free (op->data.tile.tile_width); + meta_draw_spec_free (op->data.tile.tile_height); + break; + } + + g_free (op); +} + +static GdkPixbuf* +apply_alpha (GdkPixbuf *pixbuf, + MetaAlphaGradientSpec *spec, + gboolean force_copy) +{ + GdkPixbuf *new_pixbuf; + gboolean needs_alpha; + + g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL); + + needs_alpha = spec && (spec->n_alphas > 1 || + spec->alphas[0] != 0xff); + + if (!needs_alpha) + return pixbuf; + + if (!gdk_pixbuf_get_has_alpha (pixbuf)) + { + new_pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0); + g_object_unref (G_OBJECT (pixbuf)); + pixbuf = new_pixbuf; + } + else if (force_copy) + { + new_pixbuf = gdk_pixbuf_copy (pixbuf); + g_object_unref (G_OBJECT (pixbuf)); + pixbuf = new_pixbuf; + } + + g_assert (gdk_pixbuf_get_has_alpha (pixbuf)); + + meta_gradient_add_alpha (pixbuf, spec->alphas, spec->n_alphas, spec->type); + + return pixbuf; +} + +static GdkPixbuf* +pixbuf_tile (GdkPixbuf *tile, + int width, + int height) +{ + GdkPixbuf *pixbuf; + int tile_width; + int tile_height; + int i, j; + + tile_width = gdk_pixbuf_get_width (tile); + tile_height = gdk_pixbuf_get_height (tile); + + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + gdk_pixbuf_get_has_alpha (tile), + 8, width, height); + + i = 0; + while (i < width) + { + j = 0; + while (j < height) + { + int w, h; + + w = MIN (tile_width, width - i); + h = MIN (tile_height, height - j); + + gdk_pixbuf_copy_area (tile, + 0, 0, + w, h, + pixbuf, + i, j); + + j += tile_height; + } + + i += tile_width; + } + + return pixbuf; +} + +static GdkPixbuf * +replicate_rows (GdkPixbuf *src, + int src_x, + int src_y, + int width, + int height) +{ + unsigned int n_channels = gdk_pixbuf_get_n_channels (src); + unsigned int src_rowstride = gdk_pixbuf_get_rowstride (src); + unsigned char *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x + * n_channels); + unsigned char *dest_pixels; + GdkPixbuf *result; + unsigned int dest_rowstride; + int i; + + result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8, + width, height); + dest_rowstride = gdk_pixbuf_get_rowstride (result); + dest_pixels = gdk_pixbuf_get_pixels (result); + + for (i = 0; i < height; i++) + memcpy (dest_pixels + dest_rowstride * i, pixels, n_channels * width); + + return result; +} + +static GdkPixbuf * +replicate_cols (GdkPixbuf *src, + int src_x, + int src_y, + int width, + int height) +{ + unsigned int n_channels = gdk_pixbuf_get_n_channels (src); + unsigned int src_rowstride = gdk_pixbuf_get_rowstride (src); + unsigned char *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x + * n_channels); + unsigned char *dest_pixels; + GdkPixbuf *result; + unsigned int dest_rowstride; + int i, j; + + result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8, + width, height); + dest_rowstride = gdk_pixbuf_get_rowstride (result); + dest_pixels = gdk_pixbuf_get_pixels (result); + + for (i = 0; i < height; i++) + { + unsigned char *p = dest_pixels + dest_rowstride * i; + unsigned char *q = pixels + src_rowstride * i; + + unsigned char r = *(q++); + unsigned char g = *(q++); + unsigned char b = *(q++); + + if (n_channels == 4) + { + unsigned char a; + + a = *(q++); + + for (j = 0; j < width; j++) + { + *(p++) = r; + *(p++) = g; + *(p++) = b; + *(p++) = a; + } + } + else + { + for (j = 0; j < width; j++) + { + *(p++) = r; + *(p++) = g; + *(p++) = b; + } + } + } + + return result; +} + +static GdkPixbuf* +scale_and_alpha_pixbuf (GdkPixbuf *src, + MetaAlphaGradientSpec *alpha_spec, + MetaImageFillType fill_type, + int width, + int height, + gboolean vertical_stripes, + gboolean horizontal_stripes) +{ + GdkPixbuf *pixbuf; + GdkPixbuf *temp_pixbuf; + + pixbuf = NULL; + + pixbuf = src; + + if (gdk_pixbuf_get_width (pixbuf) == width && + gdk_pixbuf_get_height (pixbuf) == height) + { + g_object_ref (G_OBJECT (pixbuf)); + } + else + { + if (fill_type == META_IMAGE_FILL_TILE) + { + pixbuf = pixbuf_tile (pixbuf, width, height); + } + else + { + int src_h, src_w, dest_h, dest_w; + src_h = gdk_pixbuf_get_height (src); + src_w = gdk_pixbuf_get_width (src); + + /* prefer to replicate_cols if possible, as that + * is faster (no memory reads) + */ + if (horizontal_stripes) + { + dest_w = gdk_pixbuf_get_width (src); + dest_h = height; + } + else if (vertical_stripes) + { + dest_w = width; + dest_h = gdk_pixbuf_get_height (src); + } + + else + { + dest_w = width; + dest_h = height; + } + + if (dest_w == src_w && dest_h == src_h) + { + temp_pixbuf = src; + g_object_ref (G_OBJECT (temp_pixbuf)); + } + else + { + temp_pixbuf = gdk_pixbuf_scale_simple (src, + dest_w, dest_h, + GDK_INTERP_BILINEAR); + } + + /* prefer to replicate_cols if possible, as that + * is faster (no memory reads) + */ + if (horizontal_stripes) + { + pixbuf = replicate_cols (temp_pixbuf, 0, 0, width, height); + g_object_unref (G_OBJECT (temp_pixbuf)); + } + else if (vertical_stripes) + { + pixbuf = replicate_rows (temp_pixbuf, 0, 0, width, height); + g_object_unref (G_OBJECT (temp_pixbuf)); + } + else + { + pixbuf = temp_pixbuf; + } + } + } + + if (pixbuf) + pixbuf = apply_alpha (pixbuf, alpha_spec, pixbuf == src); + + return pixbuf; +} + +static GdkPixbuf* +draw_op_as_pixbuf (const MetaDrawOp *op, + GtkWidget *widget, + const MetaDrawInfo *info, + int width, + int height) +{ + /* Try to get the op as a pixbuf, assuming w/h in the op + * matches the width/height passed in. return NULL + * if the op can't be converted to an equivalent pixbuf. + */ + GdkPixbuf *pixbuf; + + pixbuf = NULL; + + switch (op->type) + { + case META_DRAW_LINE: + break; + + case META_DRAW_RECTANGLE: + if (op->data.rectangle.filled) + { + GdkColor color; + + meta_color_spec_render (op->data.rectangle.color_spec, + widget, + &color); + + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + FALSE, + 8, width, height); + + gdk_pixbuf_fill (pixbuf, GDK_COLOR_RGBA (color)); + } + break; + + case META_DRAW_ARC: + break; + + case META_DRAW_CLIP: + break; + + case META_DRAW_TINT: + { + GdkColor color; + guint32 rgba; + gboolean has_alpha; + + meta_color_spec_render (op->data.rectangle.color_spec, + widget, + &color); + + has_alpha = + op->data.tint.alpha_spec && + (op->data.tint.alpha_spec->n_alphas > 1 || + op->data.tint.alpha_spec->alphas[0] != 0xff); + + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + has_alpha, + 8, width, height); + + if (!has_alpha) + { + rgba = GDK_COLOR_RGBA (color); + + gdk_pixbuf_fill (pixbuf, rgba); + } + else if (op->data.tint.alpha_spec->n_alphas == 1) + { + rgba = GDK_COLOR_RGBA (color); + rgba &= ~0xff; + rgba |= op->data.tint.alpha_spec->alphas[0]; + + gdk_pixbuf_fill (pixbuf, rgba); + } + else + { + rgba = GDK_COLOR_RGBA (color); + + gdk_pixbuf_fill (pixbuf, rgba); + + meta_gradient_add_alpha (pixbuf, + op->data.tint.alpha_spec->alphas, + op->data.tint.alpha_spec->n_alphas, + op->data.tint.alpha_spec->type); + } + } + break; + + case META_DRAW_GRADIENT: + { + pixbuf = meta_gradient_spec_render (op->data.gradient.gradient_spec, + widget, width, height); + + pixbuf = apply_alpha (pixbuf, + op->data.gradient.alpha_spec, + FALSE); + } + break; + + + case META_DRAW_IMAGE: + { + if (op->data.image.colorize_spec) + { + GdkColor color; + + meta_color_spec_render (op->data.image.colorize_spec, + widget, &color); + + if (op->data.image.colorize_cache_pixbuf == NULL || + op->data.image.colorize_cache_pixel != GDK_COLOR_RGB (color)) + { + if (op->data.image.colorize_cache_pixbuf) + g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf)); + + /* const cast here */ + ((MetaDrawOp*)op)->data.image.colorize_cache_pixbuf = + colorize_pixbuf (op->data.image.pixbuf, + &color); + ((MetaDrawOp*)op)->data.image.colorize_cache_pixel = + GDK_COLOR_RGB (color); + } + + if (op->data.image.colorize_cache_pixbuf) + { + pixbuf = scale_and_alpha_pixbuf (op->data.image.colorize_cache_pixbuf, + op->data.image.alpha_spec, + op->data.image.fill_type, + width, height, + op->data.image.vertical_stripes, + op->data.image.horizontal_stripes); + } + } + else + { + pixbuf = scale_and_alpha_pixbuf (op->data.image.pixbuf, + op->data.image.alpha_spec, + op->data.image.fill_type, + width, height, + op->data.image.vertical_stripes, + op->data.image.horizontal_stripes); + } + break; + } + + case META_DRAW_GTK_ARROW: + case META_DRAW_GTK_BOX: + case META_DRAW_GTK_VLINE: + break; + + case META_DRAW_ICON: + if (info->mini_icon && + width <= gdk_pixbuf_get_width (info->mini_icon) && + height <= gdk_pixbuf_get_height (info->mini_icon)) + pixbuf = scale_and_alpha_pixbuf (info->mini_icon, + op->data.icon.alpha_spec, + op->data.icon.fill_type, + width, height, + FALSE, FALSE); + else if (info->icon) + pixbuf = scale_and_alpha_pixbuf (info->icon, + op->data.icon.alpha_spec, + op->data.icon.fill_type, + width, height, + FALSE, FALSE); + break; + + case META_DRAW_TITLE: + break; + + case META_DRAW_OP_LIST: + break; + + case META_DRAW_TILE: + break; + } + + return pixbuf; +} + +static void +fill_env (MetaPositionExprEnv *env, + const MetaDrawInfo *info, + MetaRectangle logical_region) +{ + /* FIXME this stuff could be raised into draw_op_list_draw() probably + */ + env->rect = logical_region; + env->object_width = -1; + env->object_height = -1; + if (info->fgeom) + { + env->left_width = info->fgeom->left_width; + env->right_width = info->fgeom->right_width; + env->top_height = info->fgeom->top_height; + env->bottom_height = info->fgeom->bottom_height; + } + else + { + env->left_width = 0; + env->right_width = 0; + env->top_height = 0; + env->bottom_height = 0; + } + + env->mini_icon_width = info->mini_icon ? gdk_pixbuf_get_width (info->mini_icon) : 0; + env->mini_icon_height = info->mini_icon ? gdk_pixbuf_get_height (info->mini_icon) : 0; + env->icon_width = info->icon ? gdk_pixbuf_get_width (info->icon) : 0; + env->icon_height = info->icon ? gdk_pixbuf_get_height (info->icon) : 0; + + env->title_width = info->title_layout_width; + env->title_height = info->title_layout_height; + env->theme = meta_current_theme; +} + +/* This code was originally rendering anti-aliased using X primitives, and + * now has been switched to draw anti-aliased using cairo. In general, the + * closest correspondence between X rendering and cairo rendering is given + * by offsetting the geometry by 0.5 pixels in both directions before rendering + * with cairo. This is because X samples at the upper left corner of the + * pixel while cairo averages over the entire pixel. However, in the cases + * where the X rendering was an exact rectangle with no "jaggies" + * we need to be a bit careful about applying the offset. We want to produce + * the exact same pixel-aligned rectangle, rather than a rectangle with + * fuzz around the edges. + */ +static void +meta_draw_op_draw_with_env (const MetaDrawOp *op, + GtkStyle *style_gtk, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + const MetaDrawInfo *info, + MetaRectangle rect, + MetaPositionExprEnv *env) +{ + GdkColor color; + cairo_t *cr; + + cr = gdk_cairo_create (drawable); + + cairo_set_line_width (cr, 1.0); + + if (clip) + { + gdk_cairo_rectangle (cr, clip); + cairo_clip (cr); + } + + switch (op->type) + { + case META_DRAW_LINE: + { + int x1, x2, y1, y2; + + meta_color_spec_render (op->data.line.color_spec, widget, &color); + gdk_cairo_set_source_color (cr, &color); + + if (op->data.line.width > 0) + cairo_set_line_width (cr, op->data.line.width); + + if (op->data.line.dash_on_length > 0 && + op->data.line.dash_off_length > 0) + { + double dash_list[2]; + dash_list[0] = op->data.line.dash_on_length; + dash_list[1] = op->data.line.dash_off_length; + cairo_set_dash (cr, dash_list, 2, 0); + } + + x1 = parse_x_position_unchecked (op->data.line.x1, env); + y1 = parse_y_position_unchecked (op->data.line.y1, env); + + if (!op->data.line.x2 && + !op->data.line.y2 && + op->data.line.width==0) + { + cairo_rectangle (cr, x1, y1, 1, 1); + cairo_fill (cr); + } + else + { + if (op->data.line.x2) + x2 = parse_x_position_unchecked (op->data.line.x2, env); + else + x2 = x1; + + if (op->data.line.y2) + y2 = parse_y_position_unchecked (op->data.line.y2, env); + else + y2 = y1; + + /* This is one of the cases where we are matching the exact + * pixel aligned rectangle produced by X. + */ + if (y1 == y2 || x1 == x2) + { + double offset = (op->data.line.width == 0 || + op->data.line.width % 2) ? .5 : 0; + /* X includes end points for lines of width 0 */ + double line_extend = op->data.line.width == 0 ? 1. : 0.; + + if (y1 == y2) + { + if (x2 < x1) + { + x1 ^= x2; + x2 ^= x1; + x1 ^= x2; + } + cairo_move_to (cr, x1, y1 + offset); + cairo_line_to (cr, x2 + line_extend, y2 + offset); + } + else + { + if (y2 < y1) + { + y1 ^= y2; + y2 ^= y1; + y1 ^= y2; + } + cairo_move_to (cr, x1 + offset, y1); + cairo_line_to (cr, x2 + offset, y2 + line_extend); + } + } + else + { + if (op->data.line.width <= 0) + { + cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE); + } + cairo_move_to (cr, x1 + .5, y1 + .5); + cairo_line_to (cr, x2 + .5, y2 + .5); + } + cairo_stroke (cr); + } + } + break; + + case META_DRAW_RECTANGLE: + { + int rx, ry, rwidth, rheight; + + meta_color_spec_render (op->data.rectangle.color_spec, widget, &color); + gdk_cairo_set_source_color (cr, &color); + + 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); + + /* Filled and stroked rectangles are the other cases + * we pixel-align to X rasterization + */ + if (op->data.rectangle.filled) + { + cairo_rectangle (cr, rx, ry, rwidth, rheight); + cairo_fill (cr); + } + else + { + cairo_rectangle (cr, rx + .5, ry + .5, rwidth, rheight); + cairo_stroke (cr); + } + } + break; + + case META_DRAW_ARC: + { + int rx, ry, rwidth, rheight; + double start_angle, end_angle; + double center_x, center_y; + + meta_color_spec_render (op->data.arc.color_spec, widget, &color); + gdk_cairo_set_source_color (cr, &color); + + 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); + + start_angle = op->data.arc.start_angle * (M_PI / 180.) + - (.25 * M_PI); /* start at 12 instead of 3 oclock */ + end_angle = start_angle + op->data.arc.extent_angle * (M_PI / 180.); + center_x = rx + (double)rwidth / 2. + .5; + center_y = ry + (double)rheight / 2. + .5; + + cairo_save (cr); + + cairo_translate (cr, center_x, center_y); + cairo_scale (cr, (double)rwidth / 2., (double)rheight / 2.); + + if (op->data.arc.extent_angle >= 0) + cairo_arc (cr, 0, 0, 1, start_angle, end_angle); + else + cairo_arc_negative (cr, 0, 0, 1, start_angle, end_angle); + + cairo_restore (cr); + + if (op->data.arc.filled) + { + cairo_line_to (cr, center_x, center_y); + cairo_fill (cr); + } + else + cairo_stroke (cr); + } + break; + + case META_DRAW_CLIP: + break; + + case META_DRAW_TINT: + { + int rx, ry, rwidth, rheight; + gboolean needs_alpha; + + needs_alpha = op->data.tint.alpha_spec && + (op->data.tint.alpha_spec->n_alphas > 1 || + op->data.tint.alpha_spec->alphas[0] != 0xff); + + rx = parse_x_position_unchecked (op->data.tint.x, env); + ry = parse_y_position_unchecked (op->data.tint.y, env); + rwidth = parse_size_unchecked (op->data.tint.width, env); + rheight = parse_size_unchecked (op->data.tint.height, env); + + if (!needs_alpha) + { + meta_color_spec_render (op->data.tint.color_spec, widget, &color); + gdk_cairo_set_source_color (cr, &color); + + cairo_rectangle (cr, rx, ry, rwidth, rheight); + cairo_fill (cr); + } + else + { + GdkPixbuf *pixbuf; + + pixbuf = draw_op_as_pixbuf (op, widget, info, + rwidth, rheight); + + if (pixbuf) + { + gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry); + cairo_paint (cr); + + g_object_unref (G_OBJECT (pixbuf)); + } + } + } + break; + + case META_DRAW_GRADIENT: + { + int rx, ry, rwidth, rheight; + GdkPixbuf *pixbuf; + + rx = parse_x_position_unchecked (op->data.gradient.x, env); + ry = parse_y_position_unchecked (op->data.gradient.y, env); + rwidth = parse_size_unchecked (op->data.gradient.width, env); + rheight = parse_size_unchecked (op->data.gradient.height, env); + + pixbuf = draw_op_as_pixbuf (op, widget, info, + rwidth, rheight); + + if (pixbuf) + { + gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry); + cairo_paint (cr); + + g_object_unref (G_OBJECT (pixbuf)); + } + } + break; + + case META_DRAW_IMAGE: + { + int rx, ry, rwidth, rheight; + GdkPixbuf *pixbuf; + + if (op->data.image.pixbuf) + { + env->object_width = gdk_pixbuf_get_width (op->data.image.pixbuf); + env->object_height = gdk_pixbuf_get_height (op->data.image.pixbuf); + } + + rwidth = parse_size_unchecked (op->data.image.width, env); + rheight = parse_size_unchecked (op->data.image.height, env); + + pixbuf = draw_op_as_pixbuf (op, widget, info, + rwidth, rheight); + + if (pixbuf) + { + rx = parse_x_position_unchecked (op->data.image.x, env); + ry = parse_y_position_unchecked (op->data.image.y, env); + + gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry); + cairo_paint (cr); + + g_object_unref (G_OBJECT (pixbuf)); + } + } + break; + + case META_DRAW_GTK_ARROW: + { + int rx, ry, rwidth, rheight; + + rx = parse_x_position_unchecked (op->data.gtk_arrow.x, env); + ry = parse_y_position_unchecked (op->data.gtk_arrow.y, env); + rwidth = parse_size_unchecked (op->data.gtk_arrow.width, env); + rheight = parse_size_unchecked (op->data.gtk_arrow.height, env); + + gtk_paint_arrow (style_gtk, + drawable, + op->data.gtk_arrow.state, + op->data.gtk_arrow.shadow, + (GdkRectangle*) clip, + widget, + "marco", + op->data.gtk_arrow.arrow, + op->data.gtk_arrow.filled, + rx, ry, rwidth, rheight); + } + break; + + case META_DRAW_GTK_BOX: + { + int rx, ry, rwidth, rheight; + + rx = parse_x_position_unchecked (op->data.gtk_box.x, env); + ry = parse_y_position_unchecked (op->data.gtk_box.y, env); + rwidth = parse_size_unchecked (op->data.gtk_box.width, env); + rheight = parse_size_unchecked (op->data.gtk_box.height, env); + + gtk_paint_box (style_gtk, + drawable, + op->data.gtk_box.state, + op->data.gtk_box.shadow, + (GdkRectangle*) clip, + widget, + "marco", + rx, ry, rwidth, rheight); + } + break; + + case META_DRAW_GTK_VLINE: + { + int rx, ry1, ry2; + + rx = parse_x_position_unchecked (op->data.gtk_vline.x, env); + ry1 = parse_y_position_unchecked (op->data.gtk_vline.y1, env); + ry2 = parse_y_position_unchecked (op->data.gtk_vline.y2, env); + + gtk_paint_vline (style_gtk, + drawable, + op->data.gtk_vline.state, + (GdkRectangle*) clip, + widget, + "marco", + 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); + + gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry); + cairo_paint (cr); + + g_object_unref (G_OBJECT (pixbuf)); + } + } + break; + + case META_DRAW_TITLE: + if (info->title_layout) + { + int rx, ry; + + meta_color_spec_render (op->data.title.color_spec, widget, &color); + gdk_cairo_set_source_color (cr, &color); + + rx = parse_x_position_unchecked (op->data.title.x, env); + ry = parse_y_position_unchecked (op->data.title.y, env); + + cairo_move_to (cr, rx, ry); + pango_cairo_show_layout (cr, info->title_layout); + } + break; + + case META_DRAW_OP_LIST: + { + MetaRectangle d_rect; + + d_rect.x = parse_x_position_unchecked (op->data.op_list.x, env); + d_rect.y = parse_y_position_unchecked (op->data.op_list.y, env); + d_rect.width = parse_size_unchecked (op->data.op_list.width, env); + d_rect.height = parse_size_unchecked (op->data.op_list.height, env); + + meta_draw_op_list_draw_with_style (op->data.op_list.op_list, + style_gtk, widget, drawable, clip, info, + d_rect); + } + break; + + case META_DRAW_TILE: + { + int rx, ry, rwidth, rheight; + int tile_xoffset, tile_yoffset; + GdkRectangle new_clip; + MetaRectangle tile; + + rx = parse_x_position_unchecked (op->data.tile.x, env); + ry = parse_y_position_unchecked (op->data.tile.y, env); + rwidth = parse_size_unchecked (op->data.tile.width, env); + rheight = parse_size_unchecked (op->data.tile.height, env); + + new_clip.x = rx; + new_clip.y = ry; + new_clip.width = rwidth; + new_clip.height = rheight; + + if (clip == NULL || gdk_rectangle_intersect ((GdkRectangle*)clip, &new_clip, + &new_clip)) + { + tile_xoffset = parse_x_position_unchecked (op->data.tile.tile_xoffset, env); + tile_yoffset = parse_y_position_unchecked (op->data.tile.tile_yoffset, env); + /* tile offset should not include x/y */ + tile_xoffset -= rect.x; + tile_yoffset -= rect.y; + + tile.width = parse_size_unchecked (op->data.tile.tile_width, env); + tile.height = parse_size_unchecked (op->data.tile.tile_height, env); + + tile.x = rx - tile_xoffset; + + while (tile.x < (rx + rwidth)) + { + tile.y = ry - tile_yoffset; + while (tile.y < (ry + rheight)) + { + meta_draw_op_list_draw_with_style (op->data.tile.op_list, + style_gtk, widget, drawable, &new_clip, info, + tile); + + tile.y += tile.height; + } + + tile.x += tile.width; + } + } + } + break; + } + + cairo_destroy (cr); +} + +void +meta_draw_op_draw_with_style (const MetaDrawOp *op, + GtkStyle *style_gtk, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + const MetaDrawInfo *info, + MetaRectangle logical_region) +{ + MetaPositionExprEnv env; + + g_return_if_fail (style_gtk->colormap == gdk_drawable_get_colormap (drawable)); + + fill_env (&env, info, logical_region); + + meta_draw_op_draw_with_env (op, style_gtk, widget, drawable, clip, + info, logical_region, + &env); + +} + +void +meta_draw_op_draw (const MetaDrawOp *op, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + const MetaDrawInfo *info, + MetaRectangle logical_region) +{ + meta_draw_op_draw_with_style (op, gtk_widget_get_style (widget), widget, + drawable, clip, info, logical_region); +} + +MetaDrawOpList* +meta_draw_op_list_new (int n_preallocs) +{ + MetaDrawOpList *op_list; + + g_return_val_if_fail (n_preallocs >= 0, NULL); + + op_list = g_new (MetaDrawOpList, 1); + + op_list->refcount = 1; + op_list->n_allocated = n_preallocs; + op_list->ops = g_new (MetaDrawOp*, op_list->n_allocated); + op_list->n_ops = 0; + + return op_list; +} + +void +meta_draw_op_list_ref (MetaDrawOpList *op_list) +{ + g_return_if_fail (op_list != NULL); + + op_list->refcount += 1; +} + +void +meta_draw_op_list_unref (MetaDrawOpList *op_list) +{ + g_return_if_fail (op_list != NULL); + g_return_if_fail (op_list->refcount > 0); + + op_list->refcount -= 1; + + if (op_list->refcount == 0) + { + int i; + + for (i = 0; i < op_list->n_ops; i++) + meta_draw_op_free (op_list->ops[i]); + + g_free (op_list->ops); + + DEBUG_FILL_STRUCT (op_list); + g_free (op_list); + } +} + +void +meta_draw_op_list_draw_with_style (const MetaDrawOpList *op_list, + GtkStyle *style_gtk, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + const MetaDrawInfo *info, + MetaRectangle rect) +{ + int i; + GdkRectangle active_clip; + GdkRectangle orig_clip; + MetaPositionExprEnv env; + + g_return_if_fail (style_gtk->colormap == gdk_drawable_get_colormap (drawable)); + + if (op_list->n_ops == 0) + return; + + fill_env (&env, info, rect); + + /* FIXME this can be optimized, potentially a lot, by + * compressing multiple ops when possible. For example, + * anything convertible to a pixbuf can be composited + * client-side, and putting a color tint over a pixbuf + * can be done without creating the solid-color pixbuf. + * + * To implement this my plan is to have the idea of a + * compiled draw op (with the string expressions already + * evaluated), we make an array of those, and then fold + * adjacent items when possible. + */ + if (clip) + { + orig_clip = *clip; + } + else + { + orig_clip.x = rect.x; + orig_clip.y = rect.y; + orig_clip.width = rect.width; + orig_clip.height = rect.height; + } + + active_clip = orig_clip; + + for (i = 0; i < op_list->n_ops; i++) + { + MetaDrawOp *op = op_list->ops[i]; + + if (op->type == META_DRAW_CLIP) + { + active_clip.x = parse_x_position_unchecked (op->data.clip.x, &env); + active_clip.y = parse_y_position_unchecked (op->data.clip.y, &env); + active_clip.width = parse_size_unchecked (op->data.clip.width, &env); + active_clip.height = parse_size_unchecked (op->data.clip.height, &env); + + gdk_rectangle_intersect (&orig_clip, &active_clip, &active_clip); + } + else if (active_clip.width > 0 && + active_clip.height > 0) + { + meta_draw_op_draw_with_env (op, + style_gtk, widget, drawable, &active_clip, info, + rect, + &env); + } + } +} + +void +meta_draw_op_list_draw (const MetaDrawOpList *op_list, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + const MetaDrawInfo *info, + MetaRectangle rect) + +{ + meta_draw_op_list_draw_with_style (op_list, gtk_widget_get_style (widget), widget, + drawable, clip, info, rect); +} + +void +meta_draw_op_list_append (MetaDrawOpList *op_list, + MetaDrawOp *op) +{ + if (op_list->n_ops == op_list->n_allocated) + { + op_list->n_allocated *= 2; + op_list->ops = g_renew (MetaDrawOp*, op_list->ops, op_list->n_allocated); + } + + op_list->ops[op_list->n_ops] = op; + op_list->n_ops += 1; +} + +gboolean +meta_draw_op_list_validate (MetaDrawOpList *op_list, + GError **error) +{ + g_return_val_if_fail (op_list != NULL, FALSE); + + /* empty lists are OK, nothing else to check really */ + + return TRUE; +} + +/* This is not done in validate, since we wouldn't know the name + * of the list to report the error. It might be nice to + * store names inside the list sometime. + */ +gboolean +meta_draw_op_list_contains (MetaDrawOpList *op_list, + MetaDrawOpList *child) +{ + int i; + + /* mmm, huge tree recursion */ + + for (i = 0; i < op_list->n_ops; i++) + { + if (op_list->ops[i]->type == META_DRAW_OP_LIST) + { + if (op_list->ops[i]->data.op_list.op_list == child) + return TRUE; + + if (meta_draw_op_list_contains (op_list->ops[i]->data.op_list.op_list, + child)) + return TRUE; + } + else if (op_list->ops[i]->type == META_DRAW_TILE) + { + if (op_list->ops[i]->data.tile.op_list == child) + return TRUE; + + if (meta_draw_op_list_contains (op_list->ops[i]->data.tile.op_list, + child)) + return TRUE; + } + } + + return FALSE; +} + +/** + * Constructor for a MetaFrameStyle. + * + * \param parent The parent style. Data not filled in here will be + * looked for in the parent style, and in its parent + * style, and so on. + * + * \return The newly-constructed style. + */ +MetaFrameStyle* +meta_frame_style_new (MetaFrameStyle *parent) +{ + MetaFrameStyle *style; + + style = g_new0 (MetaFrameStyle, 1); + + style->refcount = 1; + + /* Default alpha is fully opaque */ + style->window_background_alpha = 255; + + style->parent = parent; + if (parent) + meta_frame_style_ref (parent); + + return style; +} + +/** + * Increases the reference count of a frame style. + * If the style is NULL, this is a no-op. + * + * \param style The style. + */ +void +meta_frame_style_ref (MetaFrameStyle *style) +{ + g_return_if_fail (style != NULL); + + style->refcount += 1; +} + +static void +free_button_ops (MetaDrawOpList *op_lists[META_BUTTON_TYPE_LAST][META_BUTTON_STATE_LAST]) +{ + int i, j; + + for (i = 0; i < META_BUTTON_TYPE_LAST; i++) + for (j = 0; j < META_BUTTON_STATE_LAST; j++) + if (op_lists[i][j]) + meta_draw_op_list_unref (op_lists[i][j]); +} + +void +meta_frame_style_unref (MetaFrameStyle *style) +{ + g_return_if_fail (style != NULL); + g_return_if_fail (style->refcount > 0); + + style->refcount -= 1; + + if (style->refcount == 0) + { + int i; + + free_button_ops (style->buttons); + + for (i = 0; i < META_FRAME_PIECE_LAST; i++) + if (style->pieces[i]) + meta_draw_op_list_unref (style->pieces[i]); + + if (style->layout) + meta_frame_layout_unref (style->layout); + + if (style->window_background_color) + meta_color_spec_free (style->window_background_color); + + /* we hold a reference to any parent style */ + if (style->parent) + meta_frame_style_unref (style->parent); + + DEBUG_FILL_STRUCT (style); + g_free (style); + } +} + +static MetaDrawOpList* +get_button (MetaFrameStyle *style, + MetaButtonType type, + MetaButtonState state) +{ + MetaDrawOpList *op_list; + MetaFrameStyle *parent; + + parent = style; + op_list = NULL; + while (parent && op_list == NULL) + { + op_list = parent->buttons[type][state]; + parent = parent->parent; + } + + /* We fall back to middle button backgrounds if we don't + * have the ones on the sides + */ + + if (op_list == NULL && + (type == META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND || + type == META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND)) + return get_button (style, META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND, + state); + + if (op_list == NULL && + (type == META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND || + type == META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND)) + return get_button (style, META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND, + state); + + /* We fall back to normal if no prelight */ + if (op_list == NULL && + state == META_BUTTON_STATE_PRELIGHT) + return get_button (style, type, META_BUTTON_STATE_NORMAL); + + return op_list; +} + +gboolean +meta_frame_style_validate (MetaFrameStyle *style, + guint current_theme_version, + GError **error) +{ + int i, j; + + g_return_val_if_fail (style != NULL, FALSE); + g_return_val_if_fail (style->layout != NULL, FALSE); + + for (i = 0; i < META_BUTTON_TYPE_LAST; i++) + { + /* for now the "positional" buttons are optional */ + if (i >= META_BUTTON_TYPE_CLOSE) + { + for (j = 0; j < META_BUTTON_STATE_LAST; j++) + { + if (get_button (style, i, j) == NULL && + meta_theme_earliest_version_with_button (i) <= current_theme_version + ) + { + g_set_error (error, META_THEME_ERROR, + META_THEME_ERROR_FAILED, + _("<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; + } + } + } + } + + return TRUE; +} + +static void +button_rect (MetaButtonType type, + const MetaFrameGeometry *fgeom, + int middle_background_offset, + GdkRectangle *rect) +{ + switch (type) + { + case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND: + *rect = fgeom->left_left_background; + break; + + case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND: + *rect = fgeom->left_middle_backgrounds[middle_background_offset]; + break; + + case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND: + *rect = fgeom->left_right_background; + break; + + case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND: + *rect = fgeom->right_left_background; + break; + + case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND: + *rect = fgeom->right_middle_backgrounds[middle_background_offset]; + break; + + case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND: + *rect = fgeom->right_right_background; + break; + + case META_BUTTON_TYPE_CLOSE: + *rect = fgeom->close_rect.visible; + break; + + case META_BUTTON_TYPE_SHADE: + *rect = fgeom->shade_rect.visible; + break; + + case META_BUTTON_TYPE_UNSHADE: + *rect = fgeom->unshade_rect.visible; + break; + + case META_BUTTON_TYPE_ABOVE: + *rect = fgeom->above_rect.visible; + break; + + case META_BUTTON_TYPE_UNABOVE: + *rect = fgeom->unabove_rect.visible; + break; + + case META_BUTTON_TYPE_STICK: + *rect = fgeom->stick_rect.visible; + break; + + case META_BUTTON_TYPE_UNSTICK: + *rect = fgeom->unstick_rect.visible; + break; + + case META_BUTTON_TYPE_MAXIMIZE: + *rect = fgeom->max_rect.visible; + break; + + case META_BUTTON_TYPE_MINIMIZE: + *rect = fgeom->min_rect.visible; + break; + + case META_BUTTON_TYPE_MENU: + *rect = fgeom->menu_rect.visible; + break; + + case META_BUTTON_TYPE_LAST: + g_assert_not_reached (); + break; + } +} + +void +meta_frame_style_draw_with_style (MetaFrameStyle *style, + GtkStyle *style_gtk, + 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, j; + 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; + + g_return_if_fail (style_gtk->colormap == gdk_drawable_get_colormap (drawable)); + + titlebar_rect.x = 0; + titlebar_rect.y = 0; + 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; + + 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.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; + + 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; + + 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; + + 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; + + 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; + while (i < META_FRAME_PIECE_LAST) + { + GdkRectangle rect; + GdkRectangle combined_clip; + + switch ((MetaFramePiece) i) + { + case META_FRAME_PIECE_ENTIRE_BACKGROUND: + rect.x = 0; + rect.y = 0; + rect.width = fgeom->width; + rect.height = fgeom->height; + break; + + case META_FRAME_PIECE_TITLEBAR: + rect = titlebar_rect; + break; + + case META_FRAME_PIECE_LEFT_TITLEBAR_EDGE: + rect = left_titlebar_edge; + break; + + case META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE: + rect = right_titlebar_edge; + break; + + case META_FRAME_PIECE_TOP_TITLEBAR_EDGE: + rect = top_titlebar_edge; + break; + + case META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE: + rect = bottom_titlebar_edge; + break; + + case META_FRAME_PIECE_TITLEBAR_MIDDLE: + rect.x = left_titlebar_edge.x + left_titlebar_edge.width; + rect.y = top_titlebar_edge.y + top_titlebar_edge.height; + rect.width = titlebar_rect.width - left_titlebar_edge.width - + right_titlebar_edge.width; + rect.height = titlebar_rect.height - top_titlebar_edge.height - bottom_titlebar_edge.height; + break; + + case META_FRAME_PIECE_TITLE: + rect = fgeom->title_rect; + break; + + case META_FRAME_PIECE_LEFT_EDGE: + rect = left_edge; + break; + + case META_FRAME_PIECE_RIGHT_EDGE: + rect = right_edge; + break; + + case META_FRAME_PIECE_BOTTOM_EDGE: + rect = bottom_edge; + break; + + case META_FRAME_PIECE_OVERLAY: + rect.x = 0; + rect.y = 0; + rect.width = fgeom->width; + rect.height = fgeom->height; + break; + + case META_FRAME_PIECE_LAST: + g_assert_not_reached (); + break; + } + + 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 = parent->pieces[i]; + parent = parent->parent; + } + + if (op_list) + { + MetaRectangle m_rect; + m_rect = meta_rect (rect.x, rect.y, rect.width, rect.height); + meta_draw_op_list_draw_with_style (op_list, + style_gtk, + widget, + drawable, + &combined_clip, + &draw_info, + m_rect); + } + } + + + /* Draw buttons just before overlay */ + if ((i + 1) == META_FRAME_PIECE_OVERLAY) + { + int middle_bg_offset; + + middle_bg_offset = 0; + j = 0; + while (j < META_BUTTON_TYPE_LAST) + { + button_rect (j, fgeom, middle_bg_offset, &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; + + op_list = get_button (style, j, button_states[j]); + + if (op_list) + { + MetaRectangle m_rect; + m_rect = meta_rect (rect.x, rect.y, + rect.width, rect.height); + meta_draw_op_list_draw_with_style (op_list, + style_gtk, + widget, + drawable, + &combined_clip, + &draw_info, + m_rect); + } + } + + /* MIDDLE_BACKGROUND type may get drawn more than once */ + if ((j == META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND || + j == META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND) && + middle_bg_offset < MAX_MIDDLE_BACKGROUNDS) + { + ++middle_bg_offset; + } + else + { + middle_bg_offset = 0; + ++j; + } + } + } + + ++i; + } +} + +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) +{ + meta_frame_style_draw_with_style (style, gtk_widget_get_style (widget), widget, + drawable, x_offset, y_offset, + clip, fgeom, client_width, client_height, + title_layout, text_height, + button_states, mini_icon, icon); +} + +MetaFrameStyleSet* +meta_frame_style_set_new (MetaFrameStyleSet *parent) +{ + MetaFrameStyleSet *style_set; + + style_set = g_new0 (MetaFrameStyleSet, 1); + + style_set->parent = parent; + if (parent) + meta_frame_style_set_ref (parent); + + style_set->refcount = 1; + + return style_set; +} + +static void +free_focus_styles (MetaFrameStyle *focus_styles[META_FRAME_FOCUS_LAST]) +{ + int i; + + for (i = 0; i < META_FRAME_FOCUS_LAST; i++) + if (focus_styles[i]) + meta_frame_style_unref (focus_styles[i]); +} + +void +meta_frame_style_set_ref (MetaFrameStyleSet *style_set) +{ + g_return_if_fail (style_set != NULL); + + style_set->refcount += 1; +} + +void +meta_frame_style_set_unref (MetaFrameStyleSet *style_set) +{ + g_return_if_fail (style_set != NULL); + g_return_if_fail (style_set->refcount > 0); + + style_set->refcount -= 1; + + if (style_set->refcount == 0) + { + int i; + + for (i = 0; i < META_FRAME_RESIZE_LAST; i++) + { + free_focus_styles (style_set->normal_styles[i]); + free_focus_styles (style_set->shaded_styles[i]); + } + + free_focus_styles (style_set->maximized_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; + + switch (state) + { + case META_FRAME_STATE_NORMAL: + case META_FRAME_STATE_SHADED: + { + if (state == META_FRAME_STATE_SHADED) + style = style_set->shaded_styles[resize][focus]; + else + 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); + } + break; + default: + { + MetaFrameStyle **styles; + + styles = NULL; + + switch (state) + { + 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_SHADED: + 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; + + for (i = 0; i < META_FRAME_FOCUS_LAST; i++) + { + if (get_style (style_set, state, + META_FRAME_RESIZE_NONE, i) == NULL) + { + /* Translators: This error occurs when a <frame> tag is missing + * in theme XML. The "<frame ...>" is intended as a noun phrase, + * and the "missing" qualifies it. You should translate "whatever". + */ + 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; + } + } + + 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); + + for (i = 0; i < META_FRAME_RESIZE_LAST; i++) + for (j = 0; j < META_FRAME_FOCUS_LAST; j++) + 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; + } + + 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; +} + +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_topic (META_DEBUG_THEMES, "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\n"), + name, err->message); + g_error_free (err); + } + else + { + if (meta_current_theme) + meta_theme_free (meta_current_theme); + + meta_current_theme = new_theme; + + meta_topic (META_DEBUG_THEMES, "New theme is \"%s\"\n", meta_current_theme->name); + } +} + +MetaTheme* +meta_theme_new (void) +{ + MetaTheme *theme; + + theme = g_new0 (MetaTheme, 1); + + 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); + + /* Create our variable quarks so we can look up variables without + having to strcmp for the names */ + theme->quark_width = g_quark_from_static_string ("width"); + theme->quark_height = g_quark_from_static_string ("height"); + theme->quark_object_width = g_quark_from_static_string ("object_width"); + theme->quark_object_height = g_quark_from_static_string ("object_height"); + theme->quark_left_width = g_quark_from_static_string ("left_width"); + theme->quark_right_width = g_quark_from_static_string ("right_width"); + theme->quark_top_height = g_quark_from_static_string ("top_height"); + theme->quark_bottom_height = g_quark_from_static_string ("bottom_height"); + theme->quark_mini_icon_width = g_quark_from_static_string ("mini_icon_width"); + theme->quark_mini_icon_height = g_quark_from_static_string ("mini_icon_height"); + theme->quark_icon_width = g_quark_from_static_string ("icon_width"); + theme->quark_icon_height = g_quark_from_static_string ("icon_height"); + theme->quark_title_width = g_quark_from_static_string ("title_width"); + theme->quark_title_height = g_quark_from_static_string ("title_height"); + return theme; +} + + +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); + + /* be more careful when destroying the theme hash tables, + since they are only constructed as needed, and may be NULL. */ + if (theme->integer_constants) + g_hash_table_destroy (theme->integer_constants); + if (theme->images_by_filename) + g_hash_table_destroy (theme->images_by_filename); + if (theme->layouts_by_name) + g_hash_table_destroy (theme->layouts_by_name); + if (theme->draw_op_lists_by_name) + g_hash_table_destroy (theme->draw_op_lists_by_name); + if (theme->styles_by_name) + g_hash_table_destroy (theme->styles_by_name); + if (theme->style_sets_by_name) + g_hash_table_destroy (theme->style_sets_by_name); + + for (i = 0; i < META_FRAME_TYPE_LAST; i++) + if (theme->style_sets_by_type[i]) + meta_frame_style_set_unref (theme->style_sets_by_type[i]); + + DEBUG_FILL_STRUCT (theme); + g_free (theme); +} + +gboolean +meta_theme_validate (MetaTheme *theme, + GError **error) +{ + int i; + + g_return_val_if_fail (theme != NULL, FALSE); + + /* FIXME what else should be checked? */ + + g_assert (theme->name); + + if (theme->readable_name == NULL) + { + /* Translators: This error means that a necessary XML tag (whose name + * is given in angle brackets) was not found in a given theme (whose + * name is given second, in quotation marks). + */ + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <%s> set for theme \"%s\""), "name", theme->name); + return FALSE; + } + + if (theme->author == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <%s> set for theme \"%s\""), "author", theme->name); + return FALSE; + } + + if (theme->date == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <%s> set for theme \"%s\""), "date", theme->name); + return FALSE; + } + + if (theme->description == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <%s> set for theme \"%s\""), "description", theme->name); + return FALSE; + } + + if (theme->copyright == NULL) + { + g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED, + _("No <%s> set for theme \"%s\""), "copyright", theme->name); + return FALSE; + } + + for (i = 0; i < (int)META_FRAME_TYPE_LAST; i++) + 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; + } + + return TRUE; +} + +GdkPixbuf* +meta_theme_load_image (MetaTheme *theme, + const char *filename, + guint size_of_theme_icons, + GError **error) +{ + GdkPixbuf *pixbuf; + + pixbuf = g_hash_table_lookup (theme->images_by_filename, + filename); + + if (pixbuf == NULL) + { + + if (g_str_has_prefix (filename, "theme:") && + META_THEME_ALLOWS (theme, META_THEME_IMAGES_FROM_ICON_THEMES)) + { + pixbuf = gtk_icon_theme_load_icon ( + gtk_icon_theme_get_default (), + filename+6, + size_of_theme_icons, + 0, + error); + if (pixbuf == NULL) return NULL; + } + else + { + 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; + } + + /* re invert the styles used for focus/unfocussed while flashing a frame */ + if (((flags & META_FRAME_HAS_FOCUS) && !(flags & META_FRAME_IS_FLASHING)) + || (!(flags & META_FRAME_HAS_FOCUS) && (flags & META_FRAME_IS_FLASHING))) + focus = META_FRAME_FOCUS_YES; + else + focus = META_FRAME_FOCUS_NO; + + style = get_style (style_set, state, resize, focus); + + return style; +} + +MetaFrameStyle* +meta_theme_get_frame_style (MetaTheme *theme, + MetaFrameType type, + MetaFrameFlags flags) +{ + MetaFrameStyle *style; + + g_return_val_if_fail (type < META_FRAME_TYPE_LAST, NULL); + + style = theme_get_style (theme, type, flags); + + return style; +} + +double +meta_theme_get_title_scale (MetaTheme *theme, + MetaFrameType type, + MetaFrameFlags flags) +{ + MetaFrameStyle *style; + + g_return_val_if_fail (type < META_FRAME_TYPE_LAST, 1.0); + + style = theme_get_style (theme, type, flags); + + /* Parser is not supposed to allow this currently */ + if (style == NULL) + return 1.0; + + return style->layout->title_scale; +} + +void +meta_theme_draw_frame_with_style (MetaTheme *theme, + GtkStyle *style_gtk, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + int x_offset, + int y_offset, + MetaFrameType type, + MetaFrameFlags flags, + int client_width, + int client_height, + PangoLayout *title_layout, + int text_height, + const MetaButtonLayout *button_layout, + MetaButtonState button_states[META_BUTTON_TYPE_LAST], + GdkPixbuf *mini_icon, + GdkPixbuf *icon) +{ + 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, + button_layout, + &fgeom, + theme); + + meta_frame_style_draw_with_style (style, + style_gtk, + 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_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, + const MetaButtonLayout *button_layout, + MetaButtonState button_states[META_BUTTON_TYPE_LAST], + GdkPixbuf *mini_icon, + GdkPixbuf *icon) +{ + meta_theme_draw_frame_with_style (theme, gtk_widget_get_style (widget), widget, + drawable, clip, x_offset, y_offset, type,flags, + client_width, client_height, + title_layout, text_height, + button_layout, button_states, + mini_icon, icon); +} + +void +meta_theme_draw_frame_by_name (MetaTheme *theme, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + int x_offset, + int y_offset, + const gchar *style_name, + MetaFrameFlags flags, + int client_width, + int client_height, + PangoLayout *title_layout, + int text_height, + const MetaButtonLayout *button_layout, + MetaButtonState button_states[META_BUTTON_TYPE_LAST], + GdkPixbuf *mini_icon, + GdkPixbuf *icon) +{ + MetaFrameGeometry fgeom; + MetaFrameStyle *style; + + style = meta_theme_lookup_style (theme, style_name); + + /* 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, + button_layout, + &fgeom, + theme); + + 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_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; + + g_return_if_fail (type < META_FRAME_TYPE_LAST); + + if (top_height) + *top_height = 0; + if (bottom_height) + *bottom_height = 0; + if (left_width) + *left_width = 0; + if (right_width) + *right_width = 0; + + style = theme_get_style (theme, type, flags); + + /* 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, + const MetaButtonLayout *button_layout, + 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, + button_layout, + fgeom, + theme); +} + +MetaFrameLayout* +meta_theme_lookup_layout (MetaTheme *theme, + const char *name) +{ + return g_hash_table_lookup (theme->layouts_by_name, name); +} + +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); +} + +MetaDrawOpList* +meta_theme_lookup_draw_op_list (MetaTheme *theme, + const char *name) +{ + return g_hash_table_lookup (theme->draw_op_lists_by_name, name); +} + +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); +} + +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)); + + return TRUE; +} + +gboolean +meta_theme_lookup_int_constant (MetaTheme *theme, + const char *name, + int *value) +{ + gpointer old_value; + + *value = 0; + + 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); + + return TRUE; +} + +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; + } +} + +gboolean +meta_theme_define_color_constant (MetaTheme *theme, + const char *name, + const char *value, + GError **error) +{ + if (theme->color_constants == NULL) + theme->color_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->color_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->color_constants, + g_strdup (name), + g_strdup (value)); + + return TRUE; +} + +/** + * Looks up a colour constant. + * + * \param theme the theme containing the constant + * \param name the name of the constant + * \param value [out] the string representation of the colour, or NULL if it + * doesn't exist + * \return TRUE if it exists, FALSE otherwise + */ +gboolean +meta_theme_lookup_color_constant (MetaTheme *theme, + const char *name, + char **value) +{ + char *result; + + *value = NULL; + + if (theme->color_constants == NULL) + return FALSE; + + result = g_hash_table_lookup (theme->color_constants, name); + + if (result) + { + *value = result; + return TRUE; + } + else + { + return FALSE; + } +} + + +PangoFontDescription* +meta_gtk_widget_get_font_desc (GtkWidget *widget, + double scale, + const PangoFontDescription *override) +{ + PangoFontDescription *font_desc; + + g_return_val_if_fail (gtk_widget_get_realized (widget), NULL); + + font_desc = pango_font_description_copy (gtk_widget_get_style (widget)->font_desc); + + if (override) + pango_font_description_merge (font_desc, override, TRUE); + + pango_font_description_set_size (font_desc, + MAX (pango_font_description_get_size (font_desc) * scale, 1)); + + return font_desc; +} + +/** + * Returns the height of the letters in a particular font. + * + * \param font_desc the font + * \param context the context of the font + * \return the height of the letters + */ +int +meta_pango_font_desc_get_text_height (const PangoFontDescription *font_desc, + PangoContext *context) +{ + PangoFontMetrics *metrics; + PangoLanguage *lang; + int retval; + + lang = pango_context_get_language (context); + metrics = pango_context_get_metrics (context, font_desc, lang); + + retval = PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) + + pango_font_metrics_get_descent (metrics)); + + pango_font_metrics_unref (metrics); + + return retval; +} + +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, MetaTheme *theme) +{ + if (META_THEME_ALLOWS(theme, META_THEME_SHADE_STICK_ABOVE_BUTTONS)) + { + if (strcmp ("shade", str) == 0) + return META_BUTTON_TYPE_SHADE; + else if (strcmp ("above", str) == 0) + return META_BUTTON_TYPE_ABOVE; + else if (strcmp ("stick", str) == 0) + return META_BUTTON_TYPE_STICK; + else if (strcmp ("unshade", str) == 0) + return META_BUTTON_TYPE_UNSHADE; + else if (strcmp ("unabove", str) == 0) + return META_BUTTON_TYPE_UNABOVE; + else if (strcmp ("unstick", str) == 0) + return META_BUTTON_TYPE_UNSTICK; + } + + 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 if (strcmp ("left_left_background", str) == 0) + return META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND; + else if (strcmp ("left_middle_background", str) == 0) + return META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND; + else if (strcmp ("left_right_background", str) == 0) + return META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND; + else if (strcmp ("right_left_background", str) == 0) + return META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND; + else if (strcmp ("right_middle_background", str) == 0) + return META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND; + else if (strcmp ("right_right_background", str) == 0) + return META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND; + 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_SHADE: + return "shade"; + case META_BUTTON_TYPE_ABOVE: + return "above"; + case META_BUTTON_TYPE_STICK: + return "stick"; + case META_BUTTON_TYPE_UNSHADE: + return "unshade"; + case META_BUTTON_TYPE_UNABOVE: + return "unabove"; + case META_BUTTON_TYPE_UNSTICK: + return "unstick"; + case META_BUTTON_TYPE_MENU: + return "menu"; + case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND: + return "left_left_background"; + case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND: + return "left_middle_background"; + case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND: + return "left_right_background"; + case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND: + return "right_left_background"; + case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND: + return "right_middle_background"; + case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND: + return "right_right_background"; + case META_BUTTON_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; + else if (strcmp ("border", str) == 0) + return META_FRAME_TYPE_BORDER; +#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"; + case META_FRAME_TYPE_BORDER: + return "border"; +#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 if (strcmp ("none", str) == 0) + return GTK_ARROW_NONE; + 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"; + case GTK_ARROW_NONE: + return "none"; + } + + return "<unknown>"; +} + +/** + * Returns a fill_type from a string. The inverse of + * meta_image_fill_type_to_string(). + * + * \param str a string representing a fill_type + * \result the fill_type, or -1 if it represents no fill_type. + */ +MetaImageFillType +meta_image_fill_type_from_string (const char *str) +{ + if (strcmp ("tile", str) == 0) + return META_IMAGE_FILL_TILE; + else if (strcmp ("scale", str) == 0) + return META_IMAGE_FILL_SCALE; + else + return -1; +} + +/** + * Returns a string representation of a fill_type. The inverse of + * meta_image_fill_type_from_string(). + * + * \param fill_type the fill type + * \result a string representing that type + */ +const char* +meta_image_fill_type_to_string (MetaImageFillType fill_type) +{ + switch (fill_type) + { + case META_IMAGE_FILL_TILE: + return "tile"; + case META_IMAGE_FILL_SCALE: + return "scale"; + } + + return "<unknown>"; +} + +/** + * Takes a colour "a", scales the lightness and saturation by a certain amount, + * and sets "b" to the resulting colour. + * gtkstyle.c cut-and-pastage. + * + * \param a the starting colour + * \param b [out] the resulting colour + * \param k amount to scale lightness and saturation by + */ +static void +gtk_style_shade (GdkColor *a, + GdkColor *b, + gdouble k) +{ + gdouble red; + gdouble green; + gdouble blue; + + red = (gdouble) a->red / 65535.0; + green = (gdouble) a->green / 65535.0; + blue = (gdouble) a->blue / 65535.0; + + rgb_to_hls (&red, &green, &blue); + + green *= k; + if (green > 1.0) + green = 1.0; + else if (green < 0.0) + green = 0.0; + + blue *= k; + if (blue > 1.0) + blue = 1.0; + else if (blue < 0.0) + blue = 0.0; + + hls_to_rgb (&red, &green, &blue); + + b->red = red * 65535.0; + b->green = green * 65535.0; + b->blue = blue * 65535.0; +} + +/** + * Converts a red/green/blue triplet to a hue/lightness/saturation triplet. + * + * \param r on input, red; on output, hue + * \param g on input, green; on output, lightness + * \param b on input, blue; on output, saturation + */ +static void +rgb_to_hls (gdouble *r, + gdouble *g, + gdouble *b) +{ + gdouble min; + gdouble max; + gdouble red; + gdouble green; + gdouble blue; + gdouble h, l, s; + gdouble delta; + + red = *r; + green = *g; + blue = *b; + + if (red > green) + { + if (red > blue) + max = red; + else + max = blue; + + if (green < blue) + min = green; + else + min = blue; + } + else + { + if (green > blue) + max = green; + else + max = blue; + + if (red < blue) + min = red; + else + min = blue; + } + + l = (max + min) / 2; + s = 0; + h = 0; + + if (max != min) + { + if (l <= 0.5) + s = (max - min) / (max + min); + else + s = (max - min) / (2 - max - min); + + delta = max -min; + if (red == max) + h = (green - blue) / delta; + else if (green == max) + h = 2 + (blue - red) / delta; + else if (blue == max) + h = 4 + (red - green) / delta; + + h *= 60; + if (h < 0.0) + h += 360; + } + + *r = h; + *g = l; + *b = s; +} + +/** + * Converts a hue/lightness/saturation triplet to a red/green/blue triplet. + * + * \param h on input, hue; on output, red + * \param l on input, lightness; on output, green + * \param s on input, saturation; on output, blue + */ +static void +hls_to_rgb (gdouble *h, + gdouble *l, + gdouble *s) +{ + gdouble hue; + gdouble lightness; + gdouble saturation; + gdouble m1, m2; + gdouble r, g, b; + + lightness = *l; + saturation = *s; + + if (lightness <= 0.5) + m2 = lightness * (1 + saturation); + else + m2 = lightness + saturation - lightness * saturation; + m1 = 2 * lightness - m2; + + if (saturation == 0) + { + *h = lightness; + *l = lightness; + *s = lightness; + } + else + { + hue = *h + 120; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + r = m1 + (m2 - m1) * hue / 60; + else if (hue < 180) + r = m2; + else if (hue < 240) + r = m1 + (m2 - m1) * (240 - hue) / 60; + else + r = m1; + + hue = *h; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + g = m1 + (m2 - m1) * hue / 60; + else if (hue < 180) + g = m2; + else if (hue < 240) + g = m1 + (m2 - m1) * (240 - hue) / 60; + else + g = m1; + + hue = *h - 120; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + b = m1 + (m2 - m1) * hue / 60; + else if (hue < 180) + b = m2; + else if (hue < 240) + b = m1 + (m2 - m1) * (240 - hue) / 60; + else + b = m1; + + *h = r; + *l = g; + *s = b; + } +} + +#if 0 +/* These are some functions I'm saving to use in optimizing + * MetaDrawOpList, namely to pre-composite pixbufs on client side + * prior to rendering to the server + */ +static void +draw_bg_solid_composite (const MetaTextureSpec *bg, + const MetaTextureSpec *fg, + double alpha, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + MetaTextureDrawMode mode, + double xalign, + double yalign, + int x, + int y, + int width, + 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); + + switch (fg->type) + { + case META_TEXTURE_SOLID: + { + GdkColor fg_color; + + meta_color_spec_render (fg->data.solid.color_spec, + widget, + &fg_color); + + color_composite (&bg_color, &fg_color, + alpha, &fg_color); + + draw_color_rectangle (widget, drawable, &fg_color, clip, + x, y, width, height); + } + break; + + case META_TEXTURE_GRADIENT: + /* FIXME I think we could just composite all the colors in + * the gradient prior to generating the gradient? + */ + /* FALL THRU */ + case META_TEXTURE_IMAGE: + { + 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), + gdk_pixbuf_get_height (pixbuf)); + + if (composited == NULL) + { + g_object_unref (G_OBJECT (pixbuf)); + return; + } + + gdk_pixbuf_composite_color (pixbuf, + composited, + 0, 0, + gdk_pixbuf_get_width (pixbuf), + gdk_pixbuf_get_height (pixbuf), + 0.0, 0.0, /* offsets */ + 1.0, 1.0, /* scale */ + GDK_INTERP_BILINEAR, + 255 * alpha, + 0, 0, /* check offsets */ + 0, /* check size */ + GDK_COLOR_RGB (bg_color), + GDK_COLOR_RGB (bg_color)); + + /* Need to draw background since pixbuf is not + * necessarily covering the whole thing + */ + 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_COMPOSITE: + case META_TEXTURE_SHAPE_LIST: + g_assert_not_reached (); + break; + } +} + +static void +draw_bg_gradient_composite (const MetaTextureSpec *bg, + const MetaTextureSpec *fg, + double alpha, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + MetaTextureDrawMode mode, + double xalign, + double yalign, + int x, + int y, + int width, + int height) +{ + 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: + case META_TEXTURE_GRADIENT: + case META_TEXTURE_IMAGE: + { + GdkPixbuf *bg_pixbuf; + GdkPixbuf *fg_pixbuf; + GdkPixbuf *composited; + int fg_width, fg_height; + + bg_pixbuf = meta_texture_spec_render (bg, widget, mode, 255, + width, height); + + if (bg_pixbuf == NULL) + return; + + fg_pixbuf = meta_texture_spec_render (fg, widget, mode, 255, + width, height); + + if (fg_pixbuf == NULL) + { + 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), + gdk_pixbuf_get_height (bg_pixbuf)); + + if (composited == NULL) + { + g_object_unref (G_OBJECT (bg_pixbuf)); + g_object_unref (G_OBJECT (fg_pixbuf)); + return; + } + + fg_width = gdk_pixbuf_get_width (fg_pixbuf); + fg_height = gdk_pixbuf_get_height (fg_pixbuf); + + /* If we wanted to be all cool we could deal with the + * 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, + y + (height - fg_height) * yalign, + gdk_pixbuf_get_width (fg_pixbuf), + gdk_pixbuf_get_height (fg_pixbuf), + 0.0, 0.0, /* offsets */ + 1.0, 1.0, /* scale */ + GDK_INTERP_BILINEAR, + 255 * alpha); + + gdk_cairo_set_source_pixbuf (cr, composited, x, y); + cairo_paint (cr); + + g_object_unref (G_OBJECT (bg_pixbuf)); + g_object_unref (G_OBJECT (fg_pixbuf)); + g_object_unref (G_OBJECT (composited)); + } + break; + + case META_TEXTURE_BLANK: + case META_TEXTURE_SHAPE_LIST: + case META_TEXTURE_COMPOSITE: + g_assert_not_reached (); + break; + } +} +#endif + +/** + * Returns the earliest version of the theme format which required support + * for a particular button. (For example, "shade" first appeared in v2, and + * "close" in v1.) + * + * \param type the button type + * \return the number of the theme format + */ +guint +meta_theme_earliest_version_with_button (MetaButtonType type) +{ + switch (type) + { + case META_BUTTON_TYPE_CLOSE: + case META_BUTTON_TYPE_MAXIMIZE: + case META_BUTTON_TYPE_MINIMIZE: + case META_BUTTON_TYPE_MENU: + case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND: + case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND: + case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND: + case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND: + case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND: + case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND: + return 1; + + case META_BUTTON_TYPE_SHADE: + case META_BUTTON_TYPE_ABOVE: + case META_BUTTON_TYPE_STICK: + case META_BUTTON_TYPE_UNSHADE: + case META_BUTTON_TYPE_UNABOVE: + case META_BUTTON_TYPE_UNSTICK: + return 2; + + default: + meta_warning("Unknown button %d\n", type); + return 1; + } +} diff --git a/src/ui/theme.h b/src/ui/theme.h new file mode 100644 index 00000000..4df8b00e --- /dev/null +++ b/src/ui/theme.h @@ -0,0 +1,1190 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco Theme Rendering */ + +/* + * Copyright (C) 2001 Havoc Pennington + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef META_THEME_H +#define META_THEME_H + +#include "boxes.h" +#include "gradient.h" +#include "common.h" +#include <gtk/gtk.h> + +typedef struct _MetaFrameStyle MetaFrameStyle; +typedef struct _MetaFrameStyleSet MetaFrameStyleSet; +typedef struct _MetaDrawOp MetaDrawOp; +typedef struct _MetaDrawOpList MetaDrawOpList; +typedef struct _MetaGradientSpec MetaGradientSpec; +typedef struct _MetaAlphaGradientSpec MetaAlphaGradientSpec; +typedef struct _MetaColorSpec MetaColorSpec; +typedef struct _MetaFrameLayout MetaFrameLayout; +typedef struct _MetaButtonSpace MetaButtonSpace; +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_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; + +/** + * Whether a button's size is calculated from the area around it (aspect + * sizing) or is given as a fixed height and width in pixels (fixed sizing). + * + * \bug This could be done away with; see the comment at the top of + * MetaFrameLayout. + */ +typedef enum +{ + META_BUTTON_SIZING_ASPECT, + META_BUTTON_SIZING_FIXED, + META_BUTTON_SIZING_LAST +} MetaButtonSizing; + +/** + * Various parameters used to calculate the geometry of a frame. + * They are used inside a MetaFrameStyle. + * This corresponds closely to the <frame_geometry> tag in a theme file. + * + * \bug button_sizing isn't really necessary, because we could easily say + * that if button_aspect is zero, the height and width are fixed values. + * This would also mean that MetaButtonSizing didn't need to exist, and + * save code. + **/ +struct _MetaFrameLayout +{ + /** Reference count. */ + int refcount; + + /** Size of left side */ + int left_width; + /** Size of right side */ + int right_width; + /** Size of bottom side */ + int bottom_height; + + /** Border of blue title region + * \bug (blue?!) + **/ + GtkBorder title_border; + + /** Extra height for inside of title region, above the font height */ + int title_vertical_pad; + + /** Right indent of buttons from edges of frame */ + int right_titlebar_edge; + /** Left indent of buttons from edges of frame */ + int left_titlebar_edge; + + /** + * Sizing rule of buttons, either META_BUTTON_SIZING_ASPECT + * (in which case button_aspect will be honoured, and + * button_width and button_height set from it), or + * META_BUTTON_SIZING_FIXED (in which case we read the width + * and height directly). + */ + MetaButtonSizing button_sizing; + + /** + * Ratio of height/width. Honoured only if + * button_sizing==META_BUTTON_SIZING_ASPECT. + * Otherwise we figure out the height from the button_border. + */ + double button_aspect; + + /** Width of a button; set even when we are using aspect sizing */ + int button_width; + + /** Height of a button; set even when we are using aspect sizing */ + int button_height; + + /** Space around buttons */ + GtkBorder button_border; + + /** scale factor for title text */ + double title_scale; + + /** Whether title text will be displayed */ + guint has_title : 1; + + /** Whether we should hide the buttons */ + guint hide_buttons : 1; + + /** Radius of the top left-hand corner; 0 if not rounded */ + guint top_left_corner_rounded_radius; + /** Radius of the top right-hand corner; 0 if not rounded */ + guint top_right_corner_rounded_radius; + /** Radius of the bottom left-hand corner; 0 if not rounded */ + guint bottom_left_corner_rounded_radius; + /** Radius of the bottom right-hand corner; 0 if not rounded */ + guint bottom_right_corner_rounded_radius; +}; + +/** + * The computed size of a button (really just a way of tying its + * visible and clickable areas together). + * The reason for two different rectangles here is Fitts' law & maximized + * windows; see bug #97703 for more details. + */ +struct _MetaButtonSpace +{ + /** The screen area where the button's image is drawn */ + GdkRectangle visible; + /** The screen area where the button can be activated by clicking */ + GdkRectangle clickable; +}; + +/** + * Calculated actual geometry of the frame + */ +struct _MetaFrameGeometry +{ + int left_width; + int right_width; + int top_height; + int bottom_height; + + int width; + int height; + + GdkRectangle title_rect; + + int left_titlebar_edge; + int right_titlebar_edge; + int top_titlebar_edge; + int bottom_titlebar_edge; + + /* used for a memset hack */ +#define ADDRESS_OF_BUTTON_RECTS(fgeom) (((char*)(fgeom)) + G_STRUCT_OFFSET (MetaFrameGeometry, close_rect)) +#define LENGTH_OF_BUTTON_RECTS (G_STRUCT_OFFSET (MetaFrameGeometry, right_right_background) + sizeof (GdkRectangle) - G_STRUCT_OFFSET (MetaFrameGeometry, close_rect)) + + /* The button rects (if changed adjust memset hack) */ + MetaButtonSpace close_rect; + MetaButtonSpace max_rect; + MetaButtonSpace min_rect; + MetaButtonSpace menu_rect; + MetaButtonSpace shade_rect; + MetaButtonSpace above_rect; + MetaButtonSpace stick_rect; + MetaButtonSpace unshade_rect; + MetaButtonSpace unabove_rect; + MetaButtonSpace unstick_rect; + +#define MAX_MIDDLE_BACKGROUNDS (MAX_BUTTONS_PER_CORNER - 2) + GdkRectangle left_left_background; + GdkRectangle left_middle_backgrounds[MAX_MIDDLE_BACKGROUNDS]; + GdkRectangle left_right_background; + GdkRectangle right_left_background; + GdkRectangle right_middle_backgrounds[MAX_MIDDLE_BACKGROUNDS]; + GdkRectangle right_right_background; + /* End of button rects (if changed adjust memset hack) */ + + /* Round corners */ + guint top_left_corner_rounded_radius; + guint top_right_corner_rounded_radius; + guint bottom_left_corner_rounded_radius; + guint bottom_right_corner_rounded_radius; +}; + +typedef enum +{ + META_IMAGE_FILL_SCALE, /* default, needs to be all-bits-zero for g_new0 */ + META_IMAGE_FILL_TILE +} MetaImageFillType; + +typedef enum +{ + META_COLOR_SPEC_BASIC, + META_COLOR_SPEC_GTK, + META_COLOR_SPEC_BLEND, + META_COLOR_SPEC_SHADE +} MetaColorSpecType; + +typedef enum +{ + META_GTK_COLOR_FG, + META_GTK_COLOR_BG, + META_GTK_COLOR_LIGHT, + META_GTK_COLOR_DARK, + META_GTK_COLOR_MID, + META_GTK_COLOR_TEXT, + META_GTK_COLOR_BASE, + META_GTK_COLOR_TEXT_AA, + META_GTK_COLOR_LAST +} MetaGtkColorComponent; + +struct _MetaColorSpec +{ + MetaColorSpecType type; + union + { + struct { + GdkColor color; + } basic; + struct { + MetaGtkColorComponent component; + GtkStateType state; + } gtk; + struct { + MetaColorSpec *foreground; + MetaColorSpec *background; + double alpha; + + GdkColor color; + } blend; + struct { + MetaColorSpec *base; + double factor; + + GdkColor color; + } shade; + } data; +}; + +struct _MetaGradientSpec +{ + MetaGradientType type; + GSList *color_specs; +}; + +struct _MetaAlphaGradientSpec +{ + MetaGradientType type; + unsigned char *alphas; + int n_alphas; +}; + +struct _MetaDrawInfo +{ + GdkPixbuf *mini_icon; + GdkPixbuf *icon; + PangoLayout *title_layout; + int title_layout_width; + int title_layout_height; + const MetaFrameGeometry *fgeom; +}; + +/** + * A drawing operation in our simple vector drawing language. + */ +typedef enum +{ + /** Basic drawing-- line */ + META_DRAW_LINE, + /** Basic drawing-- rectangle */ + META_DRAW_RECTANGLE, + /** Basic drawing-- arc */ + META_DRAW_ARC, + + /** Clip to a rectangle */ + META_DRAW_CLIP, + + /* Texture thingies */ + + /** Just a filled rectangle with alpha */ + META_DRAW_TINT, + META_DRAW_GRADIENT, + META_DRAW_IMAGE, + + /** GTK theme engine stuff */ + META_DRAW_GTK_ARROW, + META_DRAW_GTK_BOX, + 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, + /** tiled draw op list */ + META_DRAW_TILE +} MetaDrawType; + +typedef enum +{ + POS_TOKEN_INT, + POS_TOKEN_DOUBLE, + POS_TOKEN_OPERATOR, + POS_TOKEN_VARIABLE, + POS_TOKEN_OPEN_PAREN, + 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; + +/** + * A token, as output by the tokeniser. + * + * \ingroup tokenizer + */ +typedef struct +{ + PosTokenType type; + + union + { + struct { + int val; + } i; + + struct { + double val; + } d; + + struct { + PosOperatorType op; + } o; + + struct { + char *name; + GQuark name_quark; + } v; + + } d; +} PosToken; + +/** + * A computed expression in our simple vector drawing language. + * While it appears to take the form of a tree, this is actually + * merely a list; concerns such as precedence of operators are + * currently recomputed on every recalculation. + * + * Created by meta_draw_spec_new(), destroyed by meta_draw_spec_free(). + * pos_eval() fills this with ...FIXME. Are tokens a tree or a list? + * \ingroup parser + */ +typedef struct _MetaDrawSpec +{ + /** + * If this spec is constant, this is the value of the constant; + * otherwise it is zero. + */ + int value; + + /** A list of tokens in the expression. */ + PosToken *tokens; + + /** How many tokens are in the tokens list. */ + int n_tokens; + + /** Does the expression contain any variables? */ + gboolean constant : 1; +} MetaDrawSpec; + +/** + * A single drawing operation in our simple vector drawing language. + */ +struct _MetaDrawOp +{ + MetaDrawType type; + + /* Positions are strings because they can be expressions */ + union + { + struct { + MetaColorSpec *color_spec; + int dash_on_length; + int dash_off_length; + int width; + MetaDrawSpec *x1; + MetaDrawSpec *y1; + MetaDrawSpec *x2; + MetaDrawSpec *y2; + } line; + + struct { + MetaColorSpec *color_spec; + gboolean filled; + MetaDrawSpec *x; + MetaDrawSpec *y; + MetaDrawSpec *width; + MetaDrawSpec *height; + } rectangle; + + struct { + MetaColorSpec *color_spec; + gboolean filled; + MetaDrawSpec *x; + MetaDrawSpec *y; + MetaDrawSpec *width; + MetaDrawSpec *height; + double start_angle; + double extent_angle; + } arc; + + struct { + MetaDrawSpec *x; + MetaDrawSpec *y; + MetaDrawSpec *width; + MetaDrawSpec *height; + } clip; + + struct { + MetaColorSpec *color_spec; + MetaAlphaGradientSpec *alpha_spec; + MetaDrawSpec *x; + MetaDrawSpec *y; + MetaDrawSpec *width; + MetaDrawSpec *height; + } tint; + + struct { + MetaGradientSpec *gradient_spec; + MetaAlphaGradientSpec *alpha_spec; + MetaDrawSpec *x; + MetaDrawSpec *y; + MetaDrawSpec *width; + MetaDrawSpec *height; + } gradient; + + struct { + MetaColorSpec *colorize_spec; + MetaAlphaGradientSpec *alpha_spec; + GdkPixbuf *pixbuf; + MetaDrawSpec *x; + MetaDrawSpec *y; + MetaDrawSpec *width; + MetaDrawSpec *height; + + guint32 colorize_cache_pixel; + GdkPixbuf *colorize_cache_pixbuf; + MetaImageFillType fill_type; + unsigned int vertical_stripes : 1; + unsigned int horizontal_stripes : 1; + } image; + + struct { + GtkStateType state; + GtkShadowType shadow; + GtkArrowType arrow; + gboolean filled; + + MetaDrawSpec *x; + MetaDrawSpec *y; + MetaDrawSpec *width; + MetaDrawSpec *height; + } gtk_arrow; + + struct { + GtkStateType state; + GtkShadowType shadow; + MetaDrawSpec *x; + MetaDrawSpec *y; + MetaDrawSpec *width; + MetaDrawSpec *height; + } gtk_box; + + struct { + GtkStateType state; + MetaDrawSpec *x; + MetaDrawSpec *y1; + MetaDrawSpec *y2; + } gtk_vline; + + struct { + MetaAlphaGradientSpec *alpha_spec; + MetaDrawSpec *x; + MetaDrawSpec *y; + MetaDrawSpec *width; + MetaDrawSpec *height; + MetaImageFillType fill_type; + } icon; + + struct { + MetaColorSpec *color_spec; + MetaDrawSpec *x; + MetaDrawSpec *y; + } title; + + struct { + MetaDrawOpList *op_list; + MetaDrawSpec *x; + MetaDrawSpec *y; + MetaDrawSpec *width; + MetaDrawSpec *height; + } op_list; + + struct { + MetaDrawOpList *op_list; + MetaDrawSpec *x; + MetaDrawSpec *y; + MetaDrawSpec *width; + MetaDrawSpec *height; + MetaDrawSpec *tile_xoffset; + MetaDrawSpec *tile_yoffset; + MetaDrawSpec *tile_width; + MetaDrawSpec *tile_height; + } tile; + + } data; +}; + +/** + * A list of MetaDrawOp objects. Maintains a reference count. + * Grows as necessary and allows the allocation of unused spaces + * to keep reallocations to a minimum. + * + * \bug Do we really win anything from not using the equivalent + * GLib structures? + */ +struct _MetaDrawOpList +{ + int refcount; + MetaDrawOp **ops; + int n_ops; + int n_allocated; +}; + +typedef enum +{ + META_BUTTON_STATE_NORMAL, + META_BUTTON_STATE_PRESSED, + META_BUTTON_STATE_PRELIGHT, + META_BUTTON_STATE_LAST +} MetaButtonState; + +typedef enum +{ + /* Ordered so that background is drawn first */ + META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND, + META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND, + META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND, + META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND, + META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND, + META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND, + META_BUTTON_TYPE_CLOSE, + META_BUTTON_TYPE_MAXIMIZE, + META_BUTTON_TYPE_MINIMIZE, + META_BUTTON_TYPE_MENU, + META_BUTTON_TYPE_SHADE, + META_BUTTON_TYPE_ABOVE, + META_BUTTON_TYPE_STICK, + META_BUTTON_TYPE_UNSHADE, + META_BUTTON_TYPE_UNABOVE, + META_BUTTON_TYPE_UNSTICK, + META_BUTTON_TYPE_LAST +} MetaButtonType; + +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. + * + * On the corners, horizontal pieces are arbitrarily given the + * corner area: + * + * ===== |==== + * | | + * | rather than | + * + */ + + /* entire frame */ + META_FRAME_PIECE_ENTIRE_BACKGROUND, + /* entire titlebar background */ + META_FRAME_PIECE_TITLEBAR, + /* portion of the titlebar background inside the titlebar + * background edges + */ + META_FRAME_PIECE_TITLEBAR_MIDDLE, + /* left end of titlebar */ + META_FRAME_PIECE_LEFT_TITLEBAR_EDGE, + /* right end of titlebar */ + META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE, + /* top edge of titlebar */ + META_FRAME_PIECE_TOP_TITLEBAR_EDGE, + /* bottom edge of titlebar */ + META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE, + /* render over title background (text area) */ + META_FRAME_PIECE_TITLE, + /* left edge of the frame */ + META_FRAME_PIECE_LEFT_EDGE, + /* right edge of the frame */ + META_FRAME_PIECE_RIGHT_EDGE, + /* bottom edge of the frame */ + META_FRAME_PIECE_BOTTOM_EDGE, + /* place over entire frame, after drawing everything else */ + META_FRAME_PIECE_OVERLAY, + /* Used to get size of the enum */ + META_FRAME_PIECE_LAST +} MetaFramePiece; + +#define N_GTK_STATES 5 + +/** + * How to draw a frame in a particular state (say, a focussed, non-maximised, + * resizable frame). This corresponds closely to the <frame_style> tag + * in a theme file. + */ +struct _MetaFrameStyle +{ + /** Reference count. */ + int refcount; + /** + * Parent style. + * Settings which are unspecified here will be taken from there. + */ + MetaFrameStyle *parent; + /** Operations for drawing each kind of button in each state. */ + MetaDrawOpList *buttons[META_BUTTON_TYPE_LAST][META_BUTTON_STATE_LAST]; + /** Operations for drawing each piece of the frame. */ + MetaDrawOpList *pieces[META_FRAME_PIECE_LAST]; + /** + * Details such as the height and width of each edge, the corner rounding, + * and the aspect ratio of the buttons. + */ + MetaFrameLayout *layout; + /** + * Background colour of the window. Only present in theme formats + * 2 and above. Can be NULL to use the standard GTK theme engine. + */ + MetaColorSpec *window_background_color; + /** + * Transparency of the window background. 0=transparent; 255=opaque. + */ + guint8 window_background_alpha; +}; + +/* Kinds of frame... + * + * normal -> noresize / vert only / horz only / both + * focused / unfocused + * max -> focused / unfocused + * shaded -> focused / unfocused + * max/shaded -> focused / unfocused + * + * so 4 states with 8 sub-states in one, 2 sub-states in the other 3, + * meaning 14 total + * + * 14 window states times 7 or 8 window types. Except some + * window types never get a frame so that narrows it down a bit. + * + */ +typedef enum +{ + META_FRAME_STATE_NORMAL, + META_FRAME_STATE_MAXIMIZED, + META_FRAME_STATE_SHADED, + META_FRAME_STATE_MAXIMIZED_AND_SHADED, + META_FRAME_STATE_LAST +} MetaFrameState; + +typedef enum +{ + META_FRAME_RESIZE_NONE, + META_FRAME_RESIZE_VERTICAL, + META_FRAME_RESIZE_HORIZONTAL, + META_FRAME_RESIZE_BOTH, + META_FRAME_RESIZE_LAST +} MetaFrameResize; + +typedef enum +{ + META_FRAME_FOCUS_NO, + META_FRAME_FOCUS_YES, + META_FRAME_FOCUS_LAST +} MetaFrameFocus; + +/** + * How to draw frames at different times: when it's maximised or not, shaded + * or not, when it's focussed or not, and (for non-maximised windows), when + * it can be horizontally or vertically resized, both, or neither. + * Not all window types actually get a frame. + * + * A theme contains one of these objects for each type of window (each + * MetaFrameType), that is, normal, dialogue (modal and non-modal), etc. + * + * This corresponds closely to the <frame_style_set> tag in a theme file. + */ +struct _MetaFrameStyleSet +{ + int refcount; + MetaFrameStyleSet *parent; + MetaFrameStyle *normal_styles[META_FRAME_RESIZE_LAST][META_FRAME_FOCUS_LAST]; + MetaFrameStyle *maximized_styles[META_FRAME_FOCUS_LAST]; + MetaFrameStyle *shaded_styles[META_FRAME_RESIZE_LAST][META_FRAME_FOCUS_LAST]; + MetaFrameStyle *maximized_and_shaded_styles[META_FRAME_FOCUS_LAST]; +}; + +/** + * A theme. This is a singleton class which groups all settings from a theme + * on disk together. + * + * \bug It is rather useless to keep the metadata fields in core, I think. + */ +struct _MetaTheme +{ + /** Name of the theme (on disk), e.g. "Crux" */ + char *name; + /** Path to the files associated with the theme */ + char *dirname; + /** + * Filename of the XML theme file. + * \bug Kept lying around for no discernable reason. + */ + char *filename; + /** Metadata: Human-readable name of the theme. */ + char *readable_name; + /** Metadata: Author of the theme. */ + char *author; + /** Metadata: Copyright holder. */ + char *copyright; + /** Metadata: Date of the theme. */ + char *date; + /** Metadata: Description of the theme. */ + char *description; + /** Version of the theme format. Older versions cannot use the features + * of newer versions even if they think they can (this is to allow forward + * and backward compatibility. + */ + guint format_version; + + /** Symbol table of integer constants. */ + GHashTable *integer_constants; + /** Symbol table of float constants. */ + GHashTable *float_constants; + /** + * Symbol table of colour constants (hex triples, and triples + * plus alpha). + * */ + GHashTable *color_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]; + + GQuark quark_width; + GQuark quark_height; + GQuark quark_object_width; + GQuark quark_object_height; + GQuark quark_left_width; + GQuark quark_right_width; + GQuark quark_top_height; + GQuark quark_bottom_height; + GQuark quark_mini_icon_width; + GQuark quark_mini_icon_height; + GQuark quark_icon_width; + GQuark quark_icon_height; + GQuark quark_title_width; + GQuark quark_title_height; +}; + +struct _MetaPositionExprEnv +{ + MetaRectangle rect; + /* 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); +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, + int text_height, + MetaFrameFlags flags, + int *top_height, + int *bottom_height, + int *left_width, + int *right_width); +void meta_frame_layout_calc_geometry (const MetaFrameLayout *layout, + int text_height, + MetaFrameFlags flags, + int client_width, + int client_height, + const MetaButtonLayout *button_layout, + MetaFrameGeometry *fgeom, + MetaTheme *theme); + +gboolean meta_frame_layout_validate (const MetaFrameLayout *layout, + GError **error); + +gboolean meta_parse_position_expression (MetaDrawSpec *spec, + const MetaPositionExprEnv *env, + int *x_return, + int *y_return, + GError **err); +gboolean meta_parse_size_expression (MetaDrawSpec *spec, + const MetaPositionExprEnv *env, + int *val_return, + GError **err); + +MetaDrawSpec* meta_draw_spec_new (MetaTheme *theme, + const char *expr, + GError **error); +void meta_draw_spec_free (MetaDrawSpec *spec); + +MetaColorSpec* meta_color_spec_new (MetaColorSpecType type); +MetaColorSpec* meta_color_spec_new_from_string (const char *str, + GError **err); +MetaColorSpec* meta_color_spec_new_gtk (MetaGtkColorComponent component, + GtkStateType state); +void meta_color_spec_free (MetaColorSpec *spec); +void meta_color_spec_render (MetaColorSpec *spec, + GtkWidget *widget, + GdkColor *color); + + +MetaDrawOp* meta_draw_op_new (MetaDrawType type); +void meta_draw_op_free (MetaDrawOp *op); +void meta_draw_op_draw (const MetaDrawOp *op, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + const MetaDrawInfo *info, + /* logical region being drawn */ + MetaRectangle logical_region); + +void meta_draw_op_draw_with_style (const MetaDrawOp *op, + GtkStyle *style_gtk, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + const MetaDrawInfo *info, + /* logical region being drawn */ + MetaRectangle logical_region); + +MetaDrawOpList* meta_draw_op_list_new (int n_preallocs); +void meta_draw_op_list_ref (MetaDrawOpList *op_list); +void meta_draw_op_list_unref (MetaDrawOpList *op_list); +void meta_draw_op_list_draw (const MetaDrawOpList *op_list, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + const MetaDrawInfo *info, + MetaRectangle rect); +void meta_draw_op_list_draw_with_style (const MetaDrawOpList *op_list, + GtkStyle *style_gtk, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + const MetaDrawInfo *info, + MetaRectangle rect); +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); +GdkPixbuf* meta_gradient_spec_render (const MetaGradientSpec *desc, + GtkWidget *widget, + int width, + int height); +gboolean meta_gradient_spec_validate (MetaGradientSpec *spec, + GError **error); + +MetaAlphaGradientSpec* meta_alpha_gradient_spec_new (MetaGradientType type, + int n_alphas); +void meta_alpha_gradient_spec_free (MetaAlphaGradientSpec *spec); + + +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, + 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); + + +void meta_frame_style_draw_with_style (MetaFrameStyle *style, + GtkStyle *style_gtk, + 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, + guint current_theme_version, + 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, + guint size_of_theme_icons, + GError **error); + +MetaFrameStyle* meta_theme_get_frame_style (MetaTheme *theme, + MetaFrameType type, + MetaFrameFlags flags); + +double meta_theme_get_title_scale (MetaTheme *theme, + MetaFrameType type, + MetaFrameFlags flags); + +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, + const MetaButtonLayout *button_layout, + MetaButtonState button_states[META_BUTTON_TYPE_LAST], + GdkPixbuf *mini_icon, + GdkPixbuf *icon); + +void meta_theme_draw_frame_by_name (MetaTheme *theme, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + int x_offset, + int y_offset, + const gchar *style_name, + MetaFrameFlags flags, + int client_width, + int client_height, + PangoLayout *title_layout, + int text_height, + const MetaButtonLayout *button_layout, + MetaButtonState button_states[META_BUTTON_TYPE_LAST], + GdkPixbuf *mini_icon, + GdkPixbuf *icon); + +void meta_theme_draw_frame_with_style (MetaTheme *theme, + GtkStyle *style_gtk, + GtkWidget *widget, + GdkDrawable *drawable, + const GdkRectangle *clip, + int x_offset, + int y_offset, + MetaFrameType type, + MetaFrameFlags flags, + int client_width, + int client_height, + PangoLayout *title_layout, + int text_height, + const MetaButtonLayout *button_layout, + MetaButtonState button_states[META_BUTTON_TYPE_LAST], + GdkPixbuf *mini_icon, + GdkPixbuf *icon); + +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, + const MetaButtonLayout *button_layout, + 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); + +gboolean meta_theme_define_color_constant (MetaTheme *theme, + const char *name, + const char *value, + GError **error); +gboolean meta_theme_lookup_color_constant (MetaTheme *theme, + const char *name, + char **value); + +gboolean meta_theme_replace_constants (MetaTheme *theme, + PosToken *tokens, + int n_tokens, + GError **err); + +/* random stuff */ + +PangoFontDescription* meta_gtk_widget_get_font_desc (GtkWidget *widget, + double scale, + const PangoFontDescription *override); +int meta_pango_font_desc_get_text_height (const PangoFontDescription *font_desc, + PangoContext *context); + + +/* 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, + MetaTheme *theme); +const char* meta_button_type_to_string (MetaButtonType 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); +MetaImageFillType meta_image_fill_type_from_string (const char *str); +const char* meta_image_fill_type_to_string (MetaImageFillType fill_type); + +guint meta_theme_earliest_version_with_button (MetaButtonType type); + + +#define META_THEME_ALLOWS(theme, feature) (theme->format_version >= feature) + +/* What version of the theme file format were various features introduced in? */ +#define META_THEME_SHADE_STICK_ABOVE_BUTTONS 2 +#define META_THEME_UBIQUITOUS_CONSTANTS 2 +#define META_THEME_VARIED_ROUND_CORNERS 2 +#define META_THEME_IMAGES_FROM_ICON_THEMES 2 +#define META_THEME_UNRESIZABLE_SHADED_STYLES 2 +#define META_THEME_DEGREES_IN_ARCS 2 +#define META_THEME_HIDDEN_BUTTONS 2 +#define META_THEME_COLOR_CONSTANTS 2 +#define META_THEME_FRAME_BACKGROUNDS 2 + +#endif diff --git a/src/ui/themewidget.c b/src/ui/themewidget.c new file mode 100644 index 00000000..2d4e99fe --- /dev/null +++ b/src/ui/themewidget.c @@ -0,0 +1,183 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco 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; + +GType +meta_area_get_type (void) +{ + static GType 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 = g_type_class_peek (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/ui/themewidget.h b/src/ui/themewidget.h new file mode 100644 index 00000000..5679d7ab --- /dev/null +++ b/src/ui/themewidget.h @@ -0,0 +1,78 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco 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/gtk.h> + +#ifndef META_THEME_WIDGET_H +#define META_THEME_WIDGET_H + +#define META_TYPE_AREA (meta_area_get_type ()) +#define META_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_AREA, MetaArea)) +#define META_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), META_TYPE_AREA, MetaAreaClass)) +#define META_IS_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), META_TYPE_AREA)) +#define META_IS_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), META_TYPE_AREA)) +#define META_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_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; +}; + + +GType 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/ui/ui.c b/src/ui/ui.c new file mode 100644 index 00000000..c41bc891 --- /dev/null +++ b/src/ui/ui.c @@ -0,0 +1,1117 @@ +/* Marco interface for talking to GTK+ UI module */ + +/* + * Copyright (C) 2002 Havoc Pennington + * stock icon code Copyright (C) 2002 Jorn Baayen <[email protected]> + * + * 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 "prefs.h" +#include "ui.h" +#include "frames.h" +#include "util.h" +#include "menu.h" +#include "core.h" +#include "theme.h" + +#include <string.h> +#include <stdlib.h> + +static void meta_ui_accelerator_parse(const char* accel, guint* keysym, guint* keycode, GdkModifierType* keymask); + +struct _MetaUI { + Display* xdisplay; + Screen* xscreen; + MetaFrames* frames; + + /* For double-click tracking */ + guint button_click_number; + Window button_click_window; + int button_click_x; + int button_click_y; + guint32 button_click_time; +}; + +void meta_ui_init(int* argc, char*** argv) +{ + if (!gtk_init_check (argc, argv)) + { + meta_fatal ("Unable to open X display %s\n", XDisplayName (NULL)); + } +} + +Display* meta_ui_get_display(void) +{ + return GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); +} + +/* We do some of our event handling in frames.c, which expects + * GDK events delivered by GTK+. However, since the transition to + * client side windows, we can't let GDK see button events, since the + * client-side tracking of implicit and explicit grabs it does will + * get confused by our direct use of X grabs in the core code. + * + * So we do a very minimal GDK => GTK event conversion here and send on the + * events we care about, and then filter them out so they don't go + * through the normal GDK event handling. + * + * To reduce the amount of code, the only events fields filled out + * below are the ones that frames.c uses. If frames.c is modified to + * use more fields, more fields need to be filled out below. + */ + +static gboolean +maybe_redirect_mouse_event (XEvent *xevent) +{ + GdkDisplay *gdisplay; + MetaUI *ui; + GdkEvent gevent; + GdkWindow *gdk_window; + Window window; + + switch (xevent->type) + { + case ButtonPress: + case ButtonRelease: + window = xevent->xbutton.window; + break; + case MotionNotify: + window = xevent->xmotion.window; + break; + case EnterNotify: + case LeaveNotify: + window = xevent->xcrossing.window; + break; + default: + return FALSE; + } + + gdisplay = gdk_x11_lookup_xdisplay (xevent->xany.display); + ui = g_object_get_data (G_OBJECT (gdisplay), "meta-ui"); + if (!ui) + return FALSE; + + gdk_window = gdk_window_lookup_for_display (gdisplay, window); + if (gdk_window == NULL) + return FALSE; + + /* If GDK already thinks it has a grab, we better let it see events; this + * is the menu-navigation case and events need to get sent to the appropriate + * (client-side) subwindow for individual menu items. + */ + if (gdk_display_pointer_is_grabbed (gdisplay)) + return FALSE; + + memset (&gevent, 0, sizeof (gevent)); + + switch (xevent->type) + { + case ButtonPress: + case ButtonRelease: + if (xevent->type == ButtonPress) + { + GtkSettings *settings = gtk_settings_get_default (); + int double_click_time; + int double_click_distance; + + g_object_get (settings, + "gtk-double-click-time", &double_click_time, + "gtk-double-click-distance", &double_click_distance, + NULL); + + if (xevent->xbutton.button == ui->button_click_number && + xevent->xbutton.window == ui->button_click_window && + xevent->xbutton.time < ui->button_click_time + double_click_time && + ABS (xevent->xbutton.x - ui->button_click_x) <= double_click_distance && + ABS (xevent->xbutton.y - ui->button_click_y) <= double_click_distance) + { + gevent.button.type = GDK_2BUTTON_PRESS; + + ui->button_click_number = 0; + } + else + { + gevent.button.type = GDK_BUTTON_PRESS; + ui->button_click_number = xevent->xbutton.button; + ui->button_click_window = xevent->xbutton.window; + ui->button_click_time = xevent->xbutton.time; + ui->button_click_x = xevent->xbutton.x; + ui->button_click_y = xevent->xbutton.y; + } + } + else + { + gevent.button.type = GDK_BUTTON_RELEASE; + } + + gevent.button.window = gdk_window; + gevent.button.button = xevent->xbutton.button; + gevent.button.time = xevent->xbutton.time; + gevent.button.x = xevent->xbutton.x; + gevent.button.y = xevent->xbutton.y; + gevent.button.x_root = xevent->xbutton.x_root; + gevent.button.y_root = xevent->xbutton.y_root; + + break; + case MotionNotify: + gevent.motion.type = GDK_MOTION_NOTIFY; + gevent.motion.window = gdk_window; + break; + case EnterNotify: + case LeaveNotify: + gevent.crossing.type = xevent->type == EnterNotify ? GDK_ENTER_NOTIFY : GDK_LEAVE_NOTIFY; + gevent.crossing.window = gdk_window; + gevent.crossing.x = xevent->xcrossing.x; + gevent.crossing.y = xevent->xcrossing.y; + break; + default: + g_assert_not_reached (); + break; + } + + /* If we've gotten here, we've filled in the gdk_event and should send it on */ + gtk_main_do_event (&gevent); + + return TRUE; +} + +typedef struct _EventFunc EventFunc; + +struct _EventFunc +{ + MetaEventFunc func; + gpointer data; +}; + +static EventFunc *ef = NULL; + +static GdkFilterReturn +filter_func (GdkXEvent *xevent, + GdkEvent *event, + gpointer data) +{ + g_return_val_if_fail (ef != NULL, GDK_FILTER_CONTINUE); + + if ((* ef->func) (xevent, ef->data) || + maybe_redirect_mouse_event (xevent)) + return GDK_FILTER_REMOVE; + else + return GDK_FILTER_CONTINUE; +} + +void +meta_ui_add_event_func (Display *xdisplay, + MetaEventFunc func, + gpointer data) +{ + g_return_if_fail (ef == NULL); + + ef = g_new (EventFunc, 1); + ef->func = func; + ef->data = data; + + gdk_window_add_filter (NULL, filter_func, ef); +} + +/* removal is by data due to proxy function */ +void +meta_ui_remove_event_func (Display *xdisplay, + MetaEventFunc func, + gpointer data) +{ + g_return_if_fail (ef != NULL); + + gdk_window_remove_filter (NULL, filter_func, ef); + + g_free (ef); + ef = NULL; +} + +MetaUI* +meta_ui_new (Display *xdisplay, + Screen *screen) +{ + GdkDisplay *gdisplay; + MetaUI *ui; + + ui = g_new0 (MetaUI, 1); + ui->xdisplay = xdisplay; + ui->xscreen = screen; + + gdisplay = gdk_x11_lookup_xdisplay (xdisplay); + g_assert (gdisplay == gdk_display_get_default ()); + + g_assert (xdisplay == GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + ui->frames = meta_frames_new (XScreenNumberOfScreen (screen)); + gtk_widget_realize (GTK_WIDGET (ui->frames)); + + g_object_set_data (G_OBJECT (gdisplay), "meta-ui", ui); + + return ui; +} + +void +meta_ui_free (MetaUI *ui) +{ + GdkDisplay *gdisplay; + + gtk_widget_destroy (GTK_WIDGET (ui->frames)); + + gdisplay = gdk_x11_lookup_xdisplay (ui->xdisplay); + g_object_set_data (G_OBJECT (gdisplay), "meta-ui", NULL); + + g_free (ui); +} + +void +meta_ui_get_frame_geometry (MetaUI *ui, + Window frame_xwindow, + int *top_height, int *bottom_height, + int *left_width, int *right_width) +{ + meta_frames_get_geometry (ui->frames, frame_xwindow, + top_height, bottom_height, + left_width, right_width); +} + +Window +meta_ui_create_frame_window (MetaUI *ui, + Display *xdisplay, + Visual *xvisual, + gint x, + gint y, + gint width, + gint height, + gint screen_no) +{ + GdkDisplay *display = gdk_x11_lookup_xdisplay (xdisplay); + GdkScreen *screen = gdk_display_get_screen (display, screen_no); + GdkWindowAttr attrs; + gint attributes_mask; + GdkWindow *window; + GdkVisual *visual; + GdkColormap *cmap = gdk_screen_get_default_colormap (screen); + + /* Default depth/visual handles clients with weird visuals; they can + * always be children of the root depth/visual obviously, but + * e.g. DRI games can't be children of a parent that has the same + * visual as the client. + */ + if (!xvisual) + visual = gdk_screen_get_system_visual (screen); + else + { + visual = gdk_x11_screen_lookup_visual (screen, + XVisualIDFromVisual (xvisual)); + cmap = gdk_colormap_new (visual, FALSE); + } + + attrs.title = NULL; + + /* frame.c is going to replace the event mask immediately, but + * we still have to set it here to let GDK know what it is. + */ + attrs.event_mask = + GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | + GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_FOCUS_CHANGE_MASK; + attrs.x = x; + attrs.y = y; + attrs.wclass = GDK_INPUT_OUTPUT; + attrs.visual = visual; + attrs.colormap = cmap; + attrs.window_type = GDK_WINDOW_CHILD; + attrs.cursor = NULL; + attrs.wmclass_name = NULL; + attrs.wmclass_class = NULL; + attrs.override_redirect = FALSE; + + attrs.width = width; + attrs.height = height; + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + + window = + gdk_window_new (gdk_screen_get_root_window(screen), + &attrs, attributes_mask); + + gdk_window_resize (window, width, height); + + meta_frames_manage_window (ui->frames, GDK_WINDOW_XID (window), window); + + return GDK_WINDOW_XID (window); +} + +void +meta_ui_destroy_frame_window (MetaUI *ui, + Window xwindow) +{ + meta_frames_unmanage_window (ui->frames, xwindow); +} + +void +meta_ui_move_resize_frame (MetaUI *ui, + Window frame, + int x, + int y, + int width, + int height) +{ + meta_frames_move_resize_frame (ui->frames, frame, x, y, width, height); +} + +void +meta_ui_map_frame (MetaUI *ui, + Window xwindow) +{ + GdkWindow *window; + + window = gdk_xid_table_lookup (xwindow); + + if (window) + gdk_window_show_unraised (window); +} + +void +meta_ui_unmap_frame (MetaUI *ui, + Window xwindow) +{ + GdkWindow *window; + + window = gdk_xid_table_lookup (xwindow); + + if (window) + gdk_window_hide (window); +} + +void +meta_ui_unflicker_frame_bg (MetaUI *ui, + Window xwindow, + int target_width, + int target_height) +{ + meta_frames_unflicker_bg (ui->frames, xwindow, + target_width, target_height); +} + +void +meta_ui_repaint_frame (MetaUI *ui, + Window xwindow) +{ + meta_frames_repaint_frame (ui->frames, xwindow); +} + +void +meta_ui_reset_frame_bg (MetaUI *ui, + Window xwindow) +{ + meta_frames_reset_bg (ui->frames, xwindow); +} + +void +meta_ui_apply_frame_shape (MetaUI *ui, + Window xwindow, + int new_window_width, + int new_window_height, + gboolean window_has_shape) +{ + meta_frames_apply_shapes (ui->frames, xwindow, + new_window_width, new_window_height, + window_has_shape); +} + +void +meta_ui_queue_frame_draw (MetaUI *ui, + Window xwindow) +{ + meta_frames_queue_draw (ui->frames, xwindow); +} + + +void +meta_ui_set_frame_title (MetaUI *ui, + Window xwindow, + const char *title) +{ + meta_frames_set_title (ui->frames, xwindow, title); +} + +MetaWindowMenu* +meta_ui_window_menu_new (MetaUI *ui, + Window client_xwindow, + MetaMenuOp ops, + MetaMenuOp insensitive, + unsigned long active_workspace, + int n_workspaces, + MetaWindowMenuFunc func, + gpointer data) +{ + return meta_window_menu_new (ui->frames, + ops, insensitive, + client_xwindow, + active_workspace, + n_workspaces, + func, data); +} + +void +meta_ui_window_menu_popup (MetaWindowMenu *menu, + int root_x, + int root_y, + int button, + guint32 timestamp) +{ + meta_window_menu_popup (menu, root_x, root_y, button, timestamp); +} + +void +meta_ui_window_menu_free (MetaWindowMenu *menu) +{ + meta_window_menu_free (menu); +} + +struct _MetaImageWindow +{ + GtkWidget *window; + GdkPixmap *pixmap; +}; + +MetaImageWindow* +meta_image_window_new (Display *xdisplay, + int screen_number, + int max_width, + int max_height) +{ + MetaImageWindow *iw; + GdkDisplay *gdisplay; + GdkScreen *gscreen; + + iw = g_new (MetaImageWindow, 1); + iw->window = gtk_window_new (GTK_WINDOW_POPUP); + + gdisplay = gdk_x11_lookup_xdisplay (xdisplay); + gscreen = gdk_display_get_screen (gdisplay, screen_number); + + gtk_window_set_screen (GTK_WINDOW (iw->window), gscreen); + + gtk_widget_realize (iw->window); + iw->pixmap = gdk_pixmap_new (iw->window->window, + max_width, max_height, + -1); + + gtk_widget_set_size_request (iw->window, 1, 1); + gtk_widget_set_double_buffered (iw->window, FALSE); + gtk_widget_set_app_paintable (iw->window, TRUE); + + return iw; +} + +void +meta_image_window_free (MetaImageWindow *iw) +{ + gtk_widget_destroy (iw->window); + g_object_unref (G_OBJECT (iw->pixmap)); + g_free (iw); +} + +void +meta_image_window_set_showing (MetaImageWindow *iw, + gboolean showing) +{ + if (showing) + gtk_widget_show_all (iw->window); + else + { + gtk_widget_hide (iw->window); + meta_core_increment_event_serial (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + } +} + +void +meta_image_window_set (MetaImageWindow *iw, + GdkPixbuf *pixbuf, + int x, + int y) +{ + cairo_t *cr; + + /* We use a back pixmap to avoid having to handle exposes, because + * it's really too slow for large clients being minimized, etc. + * and this way flicker is genuinely zero. + */ + + gdk_draw_pixbuf (iw->pixmap, + iw->window->style->black_gc, + pixbuf, + 0, 0, + 0, 0, + gdk_pixbuf_get_width (pixbuf), + gdk_pixbuf_get_height (pixbuf), + GDK_RGB_DITHER_NORMAL, + 0, 0); + cr = gdk_cairo_create (iw->pixmap); + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + cairo_paint (cr); + cairo_destroy (cr); + + gdk_window_set_back_pixmap (iw->window->window, + iw->pixmap, + FALSE); + + gdk_window_move_resize (iw->window->window, + x, y, + gdk_pixbuf_get_width (pixbuf), + gdk_pixbuf_get_height (pixbuf)); + + gdk_window_clear (iw->window->window); +} + +static GdkColormap* +get_cmap (GdkPixmap *pixmap) +{ + GdkColormap *cmap; + + cmap = gdk_drawable_get_colormap (pixmap); + if (cmap) + g_object_ref (G_OBJECT (cmap)); + + if (cmap == NULL) + { + if (gdk_drawable_get_depth (pixmap) == 1) + { + meta_verbose ("Using NULL colormap for snapshotting bitmap\n"); + cmap = NULL; + } + else + { + meta_verbose ("Using system cmap to snapshot pixmap\n"); + cmap = gdk_screen_get_system_colormap (gdk_drawable_get_screen (pixmap)); + + g_object_ref (G_OBJECT (cmap)); + } + } + + /* Be sure we aren't going to blow up due to visual mismatch */ + if (cmap && + (gdk_colormap_get_visual (cmap)->depth != + gdk_drawable_get_depth (pixmap))) + { + cmap = NULL; + meta_verbose ("Switching back to NULL cmap because of depth mismatch\n"); + } + + return cmap; +} + +GdkPixbuf* +meta_gdk_pixbuf_get_from_window (GdkPixbuf *dest, + Window xwindow, + int src_x, + int src_y, + int dest_x, + int dest_y, + int width, + int height) +{ + GdkDrawable *drawable; + GdkPixbuf *retval; + GdkColormap *cmap; + + retval = NULL; + + drawable = gdk_xid_table_lookup (xwindow); + + if (drawable) + g_object_ref (G_OBJECT (drawable)); + else + drawable = gdk_window_foreign_new (xwindow); + + cmap = get_cmap (drawable); + + retval = gdk_pixbuf_get_from_drawable (dest, + drawable, + cmap, + src_x, src_y, + dest_x, dest_y, + width, height); + + if (cmap) + g_object_unref (G_OBJECT (cmap)); + g_object_unref (G_OBJECT (drawable)); + + return retval; +} + +GdkPixbuf* +meta_gdk_pixbuf_get_from_pixmap (GdkPixbuf *dest, + Pixmap xpixmap, + int src_x, + int src_y, + int dest_x, + int dest_y, + int width, + int height) +{ + GdkDrawable *drawable; + GdkPixbuf *retval; + GdkColormap *cmap; + + retval = NULL; + cmap = NULL; + + drawable = gdk_xid_table_lookup (xpixmap); + + if (drawable) + g_object_ref (G_OBJECT (drawable)); + else + drawable = gdk_pixmap_foreign_new (xpixmap); + + if (drawable) + { + cmap = get_cmap (drawable); + + retval = gdk_pixbuf_get_from_drawable (dest, + drawable, + cmap, + src_x, src_y, + dest_x, dest_y, + width, height); + } + if (cmap) + g_object_unref (G_OBJECT (cmap)); + if (drawable) + g_object_unref (G_OBJECT (drawable)); + + return retval; +} + +void +meta_ui_push_delay_exposes (MetaUI *ui) +{ + meta_frames_push_delay_exposes (ui->frames); +} + +void +meta_ui_pop_delay_exposes (MetaUI *ui) +{ + meta_frames_pop_delay_exposes (ui->frames); +} + +GdkPixbuf* +meta_ui_get_default_window_icon (MetaUI *ui) +{ + static GdkPixbuf *default_icon = NULL; + + if (default_icon == NULL) + { + GtkIconTheme *theme; + gboolean icon_exists; + + theme = gtk_icon_theme_get_default (); + + icon_exists = gtk_icon_theme_has_icon (theme, META_DEFAULT_ICON_NAME); + + if (icon_exists) + default_icon = gtk_icon_theme_load_icon (theme, + META_DEFAULT_ICON_NAME, + META_ICON_WIDTH, + 0, + NULL); + else + default_icon = gtk_icon_theme_load_icon (theme, + "gtk-missing-image", + META_ICON_WIDTH, + 0, + NULL); + + g_assert (default_icon); + } + + g_object_ref (G_OBJECT (default_icon)); + + return default_icon; +} + +GdkPixbuf* +meta_ui_get_default_mini_icon (MetaUI *ui) +{ + static GdkPixbuf *default_icon = NULL; + + if (default_icon == NULL) + { + GtkIconTheme *theme; + gboolean icon_exists; + + theme = gtk_icon_theme_get_default (); + + icon_exists = gtk_icon_theme_has_icon (theme, META_DEFAULT_ICON_NAME); + + if (icon_exists) + default_icon = gtk_icon_theme_load_icon (theme, + META_DEFAULT_ICON_NAME, + META_MINI_ICON_WIDTH, + 0, + NULL); + else + default_icon = gtk_icon_theme_load_icon (theme, + "gtk-missing-image", + META_MINI_ICON_WIDTH, + 0, + NULL); + + g_assert (default_icon); + } + + g_object_ref (G_OBJECT (default_icon)); + + return default_icon; +} + +gboolean +meta_ui_window_should_not_cause_focus (Display *xdisplay, + Window xwindow) +{ + GdkWindow *window; + + window = gdk_xid_table_lookup (xwindow); + + /* we shouldn't cause focus if we're an override redirect + * toplevel which is not foreign + */ + if (window && gdk_window_get_window_type (window) == GDK_WINDOW_TEMP) + return TRUE; + else + return FALSE; +} + +char* +meta_text_property_to_utf8 (Display *xdisplay, + const XTextProperty *prop) +{ + char **list; + int count; + char *retval; + + list = NULL; + + count = gdk_text_property_to_utf8_list (gdk_x11_xatom_to_atom (prop->encoding), + prop->format, + prop->value, + prop->nitems, + &list); + + if (count == 0) + retval = NULL; + else + { + retval = list[0]; + list[0] = g_strdup (""); /* something to free */ + } + + g_strfreev (list); + + return retval; +} + +void +meta_ui_theme_get_frame_borders (MetaUI *ui, + MetaFrameType type, + MetaFrameFlags flags, + int *top_height, + int *bottom_height, + int *left_width, + int *right_width) +{ + int text_height; + PangoContext *context; + const PangoFontDescription *font_desc; + GtkStyle *default_style; + + if (meta_ui_have_a_theme ()) + { + context = gtk_widget_get_pango_context (GTK_WIDGET (ui->frames)); + font_desc = meta_prefs_get_titlebar_font (); + + if (!font_desc) + { + default_style = gtk_widget_get_default_style (); + font_desc = default_style->font_desc; + } + + text_height = meta_pango_font_desc_get_text_height (font_desc, context); + + meta_theme_get_frame_borders (meta_theme_get_current (), + type, text_height, flags, + top_height, bottom_height, + left_width, right_width); + } + else + { + *top_height = *bottom_height = *left_width = *right_width = 0; + } +} + +void +meta_ui_set_current_theme (const char *name, + gboolean force_reload) +{ + meta_theme_set_current (name, force_reload); + meta_invalidate_default_icons (); +} + +gboolean +meta_ui_have_a_theme (void) +{ + return meta_theme_get_current () != NULL; +} + +static void +meta_ui_accelerator_parse (const char *accel, + guint *keysym, + guint *keycode, + GdkModifierType *keymask) +{ + if (accel[0] == '0' && accel[1] == 'x') + { + *keysym = 0; + *keycode = (guint) strtoul (accel, NULL, 16); + *keymask = 0; + } + else + gtk_accelerator_parse (accel, keysym, keymask); +} + +gboolean +meta_ui_parse_accelerator (const char *accel, + unsigned int *keysym, + unsigned int *keycode, + MetaVirtualModifier *mask) +{ + GdkModifierType gdk_mask = 0; + guint gdk_sym = 0; + guint gdk_code = 0; + + *keysym = 0; + *keycode = 0; + *mask = 0; + + if (strcmp (accel, "disabled") == 0) + return TRUE; + + meta_ui_accelerator_parse (accel, &gdk_sym, &gdk_code, &gdk_mask); + if (gdk_mask == 0 && gdk_sym == 0 && gdk_code == 0) + return FALSE; + + if (gdk_sym == None && gdk_code == 0) + return FALSE; + + if (gdk_mask & GDK_RELEASE_MASK) /* we don't allow this */ + return FALSE; + + *keysym = gdk_sym; + *keycode = gdk_code; + + if (gdk_mask & GDK_SHIFT_MASK) + *mask |= META_VIRTUAL_SHIFT_MASK; + if (gdk_mask & GDK_CONTROL_MASK) + *mask |= META_VIRTUAL_CONTROL_MASK; + if (gdk_mask & GDK_MOD1_MASK) + *mask |= META_VIRTUAL_ALT_MASK; + if (gdk_mask & GDK_MOD2_MASK) + *mask |= META_VIRTUAL_MOD2_MASK; + if (gdk_mask & GDK_MOD3_MASK) + *mask |= META_VIRTUAL_MOD3_MASK; + if (gdk_mask & GDK_MOD4_MASK) + *mask |= META_VIRTUAL_MOD4_MASK; + if (gdk_mask & GDK_MOD5_MASK) + *mask |= META_VIRTUAL_MOD5_MASK; + if (gdk_mask & GDK_SUPER_MASK) + *mask |= META_VIRTUAL_SUPER_MASK; + if (gdk_mask & GDK_HYPER_MASK) + *mask |= META_VIRTUAL_HYPER_MASK; + if (gdk_mask & GDK_META_MASK) + *mask |= META_VIRTUAL_META_MASK; + + return TRUE; +} + +/* Caller responsible for freeing return string of meta_ui_accelerator_name! */ +gchar* +meta_ui_accelerator_name (unsigned int keysym, + MetaVirtualModifier mask) +{ + GdkModifierType mods = 0; + + if (keysym == 0 && mask == 0) + { + return g_strdup ("disabled"); + } + + if (mask & META_VIRTUAL_SHIFT_MASK) + mods |= GDK_SHIFT_MASK; + if (mask & META_VIRTUAL_CONTROL_MASK) + mods |= GDK_CONTROL_MASK; + if (mask & META_VIRTUAL_ALT_MASK) + mods |= GDK_MOD1_MASK; + if (mask & META_VIRTUAL_MOD2_MASK) + mods |= GDK_MOD2_MASK; + if (mask & META_VIRTUAL_MOD3_MASK) + mods |= GDK_MOD3_MASK; + if (mask & META_VIRTUAL_MOD4_MASK) + mods |= GDK_MOD4_MASK; + if (mask & META_VIRTUAL_MOD5_MASK) + mods |= GDK_MOD5_MASK; + if (mask & META_VIRTUAL_SUPER_MASK) + mods |= GDK_SUPER_MASK; + if (mask & META_VIRTUAL_HYPER_MASK) + mods |= GDK_HYPER_MASK; + if (mask & META_VIRTUAL_META_MASK) + mods |= GDK_META_MASK; + + return gtk_accelerator_name (keysym, mods); + +} + +gboolean +meta_ui_parse_modifier (const char *accel, + MetaVirtualModifier *mask) +{ + GdkModifierType gdk_mask = 0; + guint gdk_sym = 0; + guint gdk_code = 0; + + *mask = 0; + + if (accel == NULL || strcmp (accel, "disabled") == 0) + return TRUE; + + meta_ui_accelerator_parse (accel, &gdk_sym, &gdk_code, &gdk_mask); + if (gdk_mask == 0 && gdk_sym == 0 && gdk_code == 0) + return FALSE; + + if (gdk_sym != None || gdk_code != 0) + return FALSE; + + if (gdk_mask & GDK_RELEASE_MASK) /* we don't allow this */ + return FALSE; + + if (gdk_mask & GDK_SHIFT_MASK) + *mask |= META_VIRTUAL_SHIFT_MASK; + if (gdk_mask & GDK_CONTROL_MASK) + *mask |= META_VIRTUAL_CONTROL_MASK; + if (gdk_mask & GDK_MOD1_MASK) + *mask |= META_VIRTUAL_ALT_MASK; + if (gdk_mask & GDK_MOD2_MASK) + *mask |= META_VIRTUAL_MOD2_MASK; + if (gdk_mask & GDK_MOD3_MASK) + *mask |= META_VIRTUAL_MOD3_MASK; + if (gdk_mask & GDK_MOD4_MASK) + *mask |= META_VIRTUAL_MOD4_MASK; + if (gdk_mask & GDK_MOD5_MASK) + *mask |= META_VIRTUAL_MOD5_MASK; + if (gdk_mask & GDK_SUPER_MASK) + *mask |= META_VIRTUAL_SUPER_MASK; + if (gdk_mask & GDK_HYPER_MASK) + *mask |= META_VIRTUAL_HYPER_MASK; + if (gdk_mask & GDK_META_MASK) + *mask |= META_VIRTUAL_META_MASK; + + return TRUE; +} + +gboolean +meta_ui_window_is_widget (MetaUI *ui, + Window xwindow) +{ + GdkWindow *window; + + window = gdk_xid_table_lookup (xwindow); + + if (window) + { + void *user_data = NULL; + gdk_window_get_user_data (window, &user_data); + return user_data != NULL && user_data != ui->frames; + } + else + return FALSE; +} + +/* stock icon code Copyright (C) 2002 Jorn Baayen <[email protected]> */ + +typedef struct { + char* stock_id; + const guint8* icon_data; +} MetaStockIcon; + +int meta_ui_get_drag_threshold(MetaUI* ui) +{ + int threshold = 8; + GtkSettings* settings = gtk_widget_get_settings(GTK_WIDGET(ui->frames)); + + g_object_get(G_OBJECT(settings), "gtk-dnd-drag-threshold", &threshold, NULL); + + return threshold; +} + +MetaUIDirection meta_ui_get_direction(void) +{ + if (gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL) + { + return META_UI_DIRECTION_RTL; + } + + return META_UI_DIRECTION_LTR; +} + +GdkPixbuf* meta_ui_get_pixbuf_from_pixmap(Pixmap pmap) +{ + GdkPixmap* gpmap; + GdkScreen* screen; + GdkPixbuf* pixbuf; + GdkColormap* cmap; + int width; + int height; + int depth; + + gpmap = gdk_pixmap_foreign_new(pmap); + screen = gdk_drawable_get_screen(gpmap); + + #if GTK_CHECK_VERSION(3, 0, 0) + width = gdk_window_get_width(GDK_WINDOW(gpmap)); + height = gdk_window_get_height(GDK_WINDOW(gpmap)); + #else + gdk_drawable_get_size(GDK_DRAWABLE(gpmap), &width, &height); + #endif + + depth = gdk_drawable_get_depth(GDK_DRAWABLE(gpmap)); + + if (depth <= 24) + { + cmap = gdk_screen_get_system_colormap(screen); + } + else + { + cmap = gdk_screen_get_rgba_colormap(screen); + } + + pixbuf = gdk_pixbuf_get_from_drawable(NULL, gpmap, cmap, 0, 0, 0, 0, width, height); + + g_object_unref(gpmap); + + return pixbuf; +} |