summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
authorHavoc Pennington <hp@redhat.com>2007-12-19 21:17:50 +0000
committerHavoc Pennington <hp@src.gnome.org>2007-12-19 21:17:50 +0000
commit72b08c82b16a850fd4f0cd52d58812b4180fe969 (patch)
tree587cf32237864f2ff6241c1cbf1045074eed130d /src/ui
parent3880951458910982807fc20ee9f11e22e947ddd2 (diff)
downloadmetacity-72b08c82b16a850fd4f0cd52d58812b4180fe969.tar.gz
metacity-72b08c82b16a850fd4f0cd52d58812b4180fe969.tar.bz2
sort source files into these directories according to which part of the WM
2007-12-19 Havoc Pennington <hp@redhat.com> * src/ui, src/core, src/include: sort source files into these directories according to which part of the WM they are supposed to be in. In an eventual plan, we should also create src/compositor/render, src/compositor/fallback and move some of the compositor stuff into that. * autogen.sh: require a newer automake, so we don't have to use a recursive build * src/ui/tabpopup.c: put in a hack to make the build temporarily work, want to commit the large rearrangement before fixing this not to include workspace.h or frame.h * src/core/iconcache.c (meta_read_icons): temporarily break this to get the build to work, want to commit the large rearrangement before fixing this file not to include theme.h svn path=/trunk/; revision=3491
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/colors.c381
-rw-r--r--src/ui/colors.h46
-rw-r--r--src/ui/draw-workspace.c232
-rw-r--r--src/ui/draw-workspace.h61
-rw-r--r--src/ui/fixedtip.c111
-rw-r--r--src/ui/fixedtip.h36
-rw-r--r--src/ui/frames.c2821
-rw-r--r--src/ui/frames.h162
-rw-r--r--src/ui/gradient.c842
-rw-r--r--src/ui/gradient.h65
-rw-r--r--src/ui/menu.c527
-rw-r--r--src/ui/menu.h62
-rw-r--r--src/ui/metaaccellabel.c457
-rw-r--r--src/ui/metaaccellabel.h106
-rw-r--r--src/ui/metacity-dialog.c438
-rw-r--r--src/ui/preview-widget.c462
-rw-r--r--src/ui/preview-widget.h84
-rw-r--r--src/ui/resizepopup.c217
-rw-r--r--src/ui/tabpopup.c948
-rw-r--r--src/ui/testgradient.c348
-rw-r--r--src/ui/theme-parser.c4699
-rw-r--r--src/ui/theme-parser.h32
-rw-r--r--src/ui/theme-viewer.c1317
-rw-r--r--src/ui/theme.c6201
-rw-r--r--src/ui/theme.h981
-rw-r--r--src/ui/themewidget.c183
-rw-r--r--src/ui/themewidget.h78
-rw-r--r--src/ui/ui.c993
28 files changed, 22890 insertions, 0 deletions
diff --git a/src/ui/colors.c b/src/ui/colors.c
new file mode 100644
index 0000000..2110590
--- /dev/null
+++ b/src/ui/colors.c
@@ -0,0 +1,381 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity RGB color stuff */
+
+/*
+ * 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 "colors.h"
+
+struct _MetaUIColors
+{
+ PangoColor fg[5];
+ PangoColor bg[5];
+ PangoColor light[5];
+ PangoColor dark[5];
+ PangoColor mid[5];
+ PangoColor text[5];
+ PangoColor base[5];
+ PangoColor text_aa[5];
+};
+
+static void
+visual_decompose_mask (gulong mask,
+ gint *shift,
+ gint *prec)
+{
+ /* This code is from GTK+, (C) GTK+ Team */
+ *shift = 0;
+ *prec = 0;
+
+ while (!(mask & 0x1))
+ {
+ (*shift)++;
+ mask >>= 1;
+ }
+
+ while (mask & 0x1)
+ {
+ (*prec)++;
+ mask >>= 1;
+ }
+}
+
+void
+meta_screen_init_visual_info (MetaScreen *screen)
+{
+ Visual *xvisual;
+ int nxvisuals;
+ XVisualInfo *visual_list;
+ XVisualInfo visual_template;
+
+ /* root window visual */
+ xvisual = DefaultVisual (screen->display->xdisplay,
+ screen->number);
+
+ visual_template.visualid = XVisualIDFromVisual (xvisual);
+ visual_list = XGetVisualInfo (screen->display->xdisplay,
+ VisualIDMask, &visual_template, &nxvisuals);
+
+ if (nxvisuals != 1)
+ meta_warning ("Matched weird number of visuals %d\n", nxvisuals);
+
+ screen->visual_info = *visual_list;
+
+ meta_verbose ("Using visual class %d\n", screen->visual_info.class);
+
+ XFree (visual_list);
+}
+
+gulong
+meta_screen_get_x_pixel (MetaScreen *screen,
+ const PangoColor *color)
+{
+ /* This code is derived from GTK+, (C) GTK+ Team */
+ gulong pixel;
+
+ if (screen->visual_info.class == TrueColor ||
+ screen->visual_info.class == DirectColor)
+ {
+ int red_prec, red_shift, green_prec, green_shift, blue_prec, blue_shift;
+
+ visual_decompose_mask (screen->visual_info.red_mask,
+ &red_shift, &red_prec);
+ visual_decompose_mask (screen->visual_info.green_mask,
+ &green_shift, &green_prec);
+ visual_decompose_mask (screen->visual_info.blue_mask,
+ &blue_shift, &blue_prec);
+
+ pixel = (((color->red >> (16 - red_prec)) << red_shift) +
+ ((color->green >> (16 - green_prec)) << green_shift) +
+ ((color->blue >> (16 - blue_prec)) << blue_shift));
+ }
+ else
+ {
+#define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11)
+ double r, g, b;
+
+ r = color->red / (double) 0xffff;
+ g = color->green / (double) 0xffff;
+ b = color->blue / (double) 0xffff;
+
+ /* Now this is a low-bloat GdkRGB replacement! */
+ if (INTENSITY (r, g, b) > 0.5)
+ pixel = WhitePixel (screen->display->xdisplay, screen->number);
+ else
+ pixel = BlackPixel (screen->display->xdisplay, screen->number);
+#undef INTENSITY
+ }
+
+ return pixel;
+}
+
+void
+meta_screen_set_ui_colors (MetaScreen *screen,
+ const MetaUIColors *colors)
+{
+ screen->colors = *colors;
+ meta_screen_queue_frame_redraws (screen);
+}
+
+/* Straight out of gtkstyle.c */
+static PangoColor meta_default_normal_fg = { 0, 0, 0 };
+static PangoColor meta_default_active_fg = { 0, 0, 0 };
+static PangoColor meta_default_prelight_fg = { 0, 0, 0 };
+static PangoColor meta_default_selected_fg = { 0xffff, 0xffff, 0xffff };
+static PangoColor meta_default_insensitive_fg = { 0x7530, 0x7530, 0x7530 };
+
+static PangoColor meta_default_normal_bg = { 0xd6d6, 0xd6d6, 0xd6d6 };
+static PangoColor meta_default_active_bg = { 0xc350, 0xc350, 0xc350 };
+static PangoColor meta_default_prelight_bg = { 0xea60, 0xea60, 0xea60 };
+static PangoColor meta_default_selected_bg = { 0, 0, 0x9c40 };
+static PangoColor meta_default_insensitive_bg = { 0xd6d6, 0xd6d6, 0xd6d6 };
+
+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;
+}
+
+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;
+ }
+}
+
+static void
+style_shade (PangoColor *a,
+ PangoColor *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;
+}
+
+#define LIGHTNESS_MULT 1.3
+#define DARKNESS_MULT 0.7
+void
+meta_screen_init_ui_colors (MetaScreen *screen)
+{
+ int i;
+ MetaUIColors *colors;
+
+ colors = &screen->colors;
+
+ colors->fg[META_STATE_NORMAL] = meta_default_normal_fg;
+ colors->fg[META_STATE_ACTIVE] = meta_default_active_fg;
+ colors->fg[META_STATE_PRELIGHT] = meta_default_prelight_fg;
+ colors->fg[META_STATE_SELECTED] = meta_default_selected_fg;
+ colors->fg[META_STATE_INSENSITIVE] = meta_default_insensitive_fg;
+
+ colors->bg[META_STATE_NORMAL] = meta_default_normal_bg;
+ colors->bg[META_STATE_ACTIVE] = meta_default_active_bg;
+ colors->bg[META_STATE_PRELIGHT] = meta_default_prelight_bg;
+ colors->bg[META_STATE_SELECTED] = meta_default_selected_bg;
+ colors->bg[META_STATE_INSENSITIVE] = meta_default_insensitive_bg;
+
+ for (i = 0; i < 4; i++)
+ {
+ colors->text[i] = colors->fg[i];
+ colors->base[i].red = G_MAXUSHORT;
+ colors->base[i].green = G_MAXUSHORT;
+ colors->base[i].blue = G_MAXUSHORT;
+ }
+
+ colors->base[META_STATE_SELECTED] = meta_default_selected_bg;
+ colors->base[META_STATE_INSENSITIVE] = meta_default_prelight_bg;
+ colors->text[META_STATE_INSENSITIVE] = meta_default_insensitive_fg;
+
+ for (i = 0; i < 5; i++)
+ {
+ style_shade (&colors->bg[i], &colors->light[i], LIGHTNESS_MULT);
+ style_shade (&colors->bg[i], &colors->dark[i], DARKNESS_MULT);
+
+ colors->mid[i].red = (colors->light[i].red + colors->dark[i].red) / 2;
+ colors->mid[i].green = (colors->light[i].green + colors->dark[i].green) / 2;
+ colors->mid[i].blue = (colors->light[i].blue + colors->dark[i].blue) / 2;
+
+ colors->text_aa[i].red = (colors->text[i].red + colors->base[i].red) / 2;
+ colors->text_aa[i].green = (colors->text[i].green + colors->base[i].green) / 2;
+ colors->text_aa[i].blue = (colors->text[i].blue + colors->base[i].blue) / 2;
+ }
+}
diff --git a/src/ui/colors.h b/src/ui/colors.h
new file mode 100644
index 0000000..1bf4735
--- /dev/null
+++ b/src/ui/colors.h
@@ -0,0 +1,46 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity RGB color stuff */
+
+/*
+ * 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_COLORS_H
+#define META_COLORS_H
+
+/* Colors/state stuff matches GTK.
+ */
+typedef struct _MetaUIColors MetaUIColors;
+
+/* This stuff will all just be XlibRGB eventually. Right now
+ * it has a stub implementation.
+ */
+
+#include "screen.h"
+#include "util.h"
+
+gulong meta_screen_get_x_pixel (MetaScreen *screen,
+ const PangoColor *color);
+void meta_screen_init_visual_info (MetaScreen *screen);
+void meta_screen_set_ui_colors (MetaScreen *screen,
+ const MetaUIColors *colors);
+void meta_screen_init_ui_colors (MetaScreen *screen);
+
+
+#endif
diff --git a/src/ui/draw-workspace.c b/src/ui/draw-workspace.c
new file mode 100644
index 0000000..f7c98b6
--- /dev/null
+++ b/src/ui/draw-workspace.c
@@ -0,0 +1,232 @@
+/* -*- 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 metacity, 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;
+
+ is_active = win->is_active;
+
+ cr = gdk_cairo_create (drawable);
+ cairo_rectangle (cr, winrect->x, winrect->y, winrect->width, winrect->height);
+ cairo_clip (cr);
+
+ if (is_active)
+ color = &widget->style->light[state];
+ else
+ color = &widget->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 = &widget->style->fg[state];
+ else
+ color = &widget->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;
+
+ 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;
+
+ if (workspace_background)
+ {
+ gdk_draw_pixbuf (drawable,
+ GTK_WIDGET (widget)->style->dark_gc[state],
+ workspace_background,
+ 0, 0,
+ x, y,
+ -1, -1,
+ GDK_RGB_DITHER_MAX,
+ 0, 0);
+ }
+ else
+ {
+ cairo_t *cr;
+
+ cr = gdk_cairo_create (widget->window);
+ gdk_cairo_set_source_color (cr, &widget->style->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 0000000..de60550
--- /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 metacity, 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/gdkdrawable.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gtk/gtkwidget.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 0000000..a147022
--- /dev/null
+++ b/src/ui/fixedtip.c
@@ -0,0 +1,111 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity 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"
+
+static GtkWidget *tip = NULL;
+static GtkWidget *label = NULL;
+static int screen_right_edge = 0;
+static int screen_bottom_edge = 0;
+
+static gint
+expose_handler (GtkTooltips *tooltips)
+{
+ gtk_paint_flat_box (tip->style, tip->window,
+ 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);
+ {
+ 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_policy (GTK_WINDOW (tip), FALSE, FALSE, TRUE);
+ 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 0000000..3b3e6ff
--- /dev/null
+++ b/src/ui/fixedtip.h
@@ -0,0 +1,36 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity 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.
+ */
+
+#ifndef META_FIXED_TIP_H
+#define META_FIXED_TIP_H
+
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+
+void meta_fixed_tip_show (Display *xdisplay, int screen_number,
+ int root_x, int root_y,
+ const char *markup_text);
+void meta_fixed_tip_hide (void);
+
+
+#endif
diff --git a/src/ui/frames.c b/src/ui/frames.c
new file mode 100644
index 0000000..ef7e009
--- /dev/null
+++ b/src/ui/frames.c
@@ -0,0 +1,2821 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity 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_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;
+
+GtkType
+meta_frames_get_type (void)
+{
+ static GtkType 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,
+ 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
+meta_frames_style_set (GtkWidget *widget,
+ GtkStyle *prev_style)
+{
+ MetaFrames *frames;
+
+ frames = META_FRAMES (widget);
+
+ meta_frames_font_changed (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, 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, 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);
+}
+
+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);
+
+ /* 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, 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,
+ 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);
+
+ 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, 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, 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, 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,
+ RootWindow (gdk_display, 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, frame->xwindow,
+ META_CORE_GET_CLIENT_XWINDOW, &client_window,
+ META_CORE_GET_END);
+
+ XShapeCombineShape (gdk_display, 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, shape_window,
+ ShapeBounding, 0, 0, window_xregion, ShapeUnion);
+
+ /* Now copy shape_window shape to the real frame */
+ XShapeCombineShape (gdk_display, frame->xwindow, ShapeBounding,
+ 0, 0,
+ shape_window,
+ ShapeBounding,
+ ShapeSet);
+
+ XDestroyWindow (gdk_display, 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, 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;
+
+ gdk_drawable_get_size (frame->window, &old_width, &old_height);
+ 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,
+ 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 = _("Unmaximize 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,
+ 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, 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,
+ frame->xwindow,
+ event->time);
+ else
+ meta_core_shade (gdk_display,
+ frame->xwindow,
+ event->time);
+ }
+ }
+ break;
+
+ case META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE:
+ {
+ meta_core_get (gdk_display, frame->xwindow,
+ META_CORE_GET_FRAME_FLAGS, &flags,
+ META_CORE_GET_END);
+
+ if (flags & META_FRAME_ALLOWS_MAXIMIZE)
+ {
+ meta_core_toggle_maximize (gdk_display, frame->xwindow);
+ }
+ }
+ break;
+
+ case META_ACTION_TITLEBAR_MINIMIZE:
+ {
+ meta_core_get (gdk_display, frame->xwindow,
+ META_CORE_GET_FRAME_FLAGS, &flags,
+ META_CORE_GET_END);
+
+ if (flags & META_FRAME_ALLOWS_MINIMIZE)
+ {
+ meta_core_minimize (gdk_display, 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,
+ frame->xwindow,
+ event->time);
+ break;
+
+ case META_ACTION_TITLEBAR_MENU:
+ meta_core_show_window_menu (gdk_display,
+ 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,
+ 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)
+ {
+ return meta_frame_double_click_event (frame, event);
+ }
+
+ if (meta_core_get_grab_op (gdk_display) !=
+ 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,
+ 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,
+ 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, 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,
+ frame->xwindow,
+ event->x_root,
+ event->y_root,
+ event->button,
+ event->time);
+ else
+ meta_core_begin_grab_op (gdk_display,
+ 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, 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,
+ 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) ==
+ META_GRAB_OP_CLICKING_MENU)
+ {
+ Window grab_frame;
+
+ grab_frame = meta_core_get_grab_frame (gdk_display);
+
+ 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, 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);
+
+ 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) &&
+ ((int) event->button) == meta_core_get_grab_button (gdk_display))
+ {
+ 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, frame->xwindow);
+
+ meta_core_end_grab_op (gdk_display, 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,
+ frame->xwindow,
+ event->time);
+ meta_core_maximize (gdk_display, frame->xwindow);
+ }
+ meta_core_end_grab_op (gdk_display, event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_UNMAXIMIZE:
+ if (control == META_FRAME_CONTROL_UNMAXIMIZE)
+ meta_core_unmaximize (gdk_display, frame->xwindow);
+
+ meta_core_end_grab_op (gdk_display, event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_DELETE:
+ if (control == META_FRAME_CONTROL_DELETE)
+ meta_core_delete (gdk_display, frame->xwindow, event->time);
+
+ meta_core_end_grab_op (gdk_display, event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_MENU:
+ meta_core_end_grab_op (gdk_display, event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_SHADE:
+ if (control == META_FRAME_CONTROL_SHADE)
+ meta_core_shade (gdk_display, frame->xwindow, event->time);
+
+ meta_core_end_grab_op (gdk_display, event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_UNSHADE:
+ if (control == META_FRAME_CONTROL_UNSHADE)
+ meta_core_unshade (gdk_display, frame->xwindow, event->time);
+
+ meta_core_end_grab_op (gdk_display, event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_ABOVE:
+ if (control == META_FRAME_CONTROL_ABOVE)
+ meta_core_make_above (gdk_display, frame->xwindow);
+
+ meta_core_end_grab_op (gdk_display, event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_UNABOVE:
+ if (control == META_FRAME_CONTROL_UNABOVE)
+ meta_core_unmake_above (gdk_display, frame->xwindow);
+
+ meta_core_end_grab_op (gdk_display, event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_STICK:
+ if (control == META_FRAME_CONTROL_STICK)
+ meta_core_stick (gdk_display, frame->xwindow);
+
+ meta_core_end_grab_op (gdk_display, event->time);
+ break;
+
+ case META_GRAB_OP_CLICKING_UNSTICK:
+ if (control == META_FRAME_CONTROL_UNSTICK)
+ meta_core_unstick (gdk_display, frame->xwindow);
+
+ meta_core_end_grab_op (gdk_display, 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,
+ 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);
+
+ 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;
+}
+
+/* Cut and paste from GDK */
+static GdkGC *
+get_bg_gc (GdkWindow *window, int x_offset, int y_offset)
+{
+ GdkWindowObject *private = (GdkWindowObject *)window;
+ guint gc_mask = 0;
+ GdkGCValues gc_values;
+
+ if (private->bg_pixmap == GDK_PARENT_RELATIVE_BG && private->parent)
+ {
+ return get_bg_gc (GDK_WINDOW (private->parent),
+ x_offset + private->x,
+ y_offset + private->y);
+ }
+ else if (private->bg_pixmap &&
+ private->bg_pixmap != GDK_PARENT_RELATIVE_BG &&
+ private->bg_pixmap != GDK_NO_BG)
+ {
+ gc_values.fill = GDK_TILED;
+ gc_values.tile = private->bg_pixmap;
+ gc_values.ts_x_origin = x_offset;
+ gc_values.ts_y_origin = y_offset;
+
+ gc_mask = GDK_GC_FILL | GDK_GC_TILE | GDK_GC_TS_X_ORIGIN | GDK_GC_TS_Y_ORIGIN;
+
+ return gdk_gc_new_with_values (window, &gc_values, gc_mask);
+ }
+ else
+ {
+ GdkGC *gc = gdk_gc_new (window);
+
+ gdk_gc_set_foreground (gc, &(private->bg_color));
+
+ return gc;
+ }
+}
+
+static void
+clear_backing (GdkPixmap *pixmap,
+ GdkWindow *window,
+ int xoffset, int yoffset)
+{
+ GdkGC *tmp_gc = get_bg_gc (window, xoffset, yoffset);
+
+ gdk_draw_rectangle (pixmap, tmp_gc, TRUE,
+ 0, 0, -1, -1);
+
+ g_object_unref (tmp_gc);
+}
+
+/* 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, 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, 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;
+
+ gdk_drawable_get_size (drawable, &rect.width, &rect.height);
+ 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)
+{
+ GdkGC *gc;
+ int i;
+
+ gc = gdk_gc_new (window);
+
+ for (i = 0; i < 4; i++)
+ {
+ CachedFramePiece *piece;
+ piece = &pixels->piece[i];
+
+ if (piece->pixmap)
+ {
+ gdk_draw_drawable (window, gc, piece->pixmap,
+ 0, 0,
+ piece->rect.x, piece->rect.y,
+ -1, -1);
+ subtract_from_region (region, piece->pixmap,
+ piece->rect.x, piece->rect.y);
+ }
+ }
+
+ g_object_unref (gc);
+}
+
+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);
+ grab_op = meta_core_get_grab_op (gdk_display);
+ 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, 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, 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 Metacity 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 (meta_theme_get_current (),
+ 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 (meta_theme_get_current (),
+ 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, 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);
+
+ /* Fill in color.pixel */
+
+ gdk_rgb_find_color (gtk_widget_get_colormap (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 (GTK_WIDGET (frames)->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, 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);
+ }
+
+ 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 0000000..c4ad8bf
--- /dev/null
+++ b/src/ui/frames.h
@@ -0,0 +1,162 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity 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) (GTK_CHECK_CAST ((obj), META_TYPE_FRAMES, MetaFrames))
+#define META_FRAMES_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), META_TYPE_FRAMES, MetaFramesClass))
+#define META_IS_FRAMES(obj) (GTK_CHECK_TYPE ((obj), META_TYPE_FRAMES))
+#define META_IS_FRAMES_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), META_TYPE_FRAMES))
+#define META_FRAMES_GET_CLASS(obj) (GTK_CHECK_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;
+ 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 0000000..6790623
--- /dev/null
+++ b/src/ui/gradient.c
@@ -0,0 +1,842 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity 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 ("metacity: vertical alpha channel gradient not implemented yet\n");
+ break;
+
+ case META_GRADIENT_DIAGONAL:
+ g_printerr ("metacity: 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 0000000..58d6d40
--- /dev/null
+++ b/src/ui/gradient.h
@@ -0,0 +1,65 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity 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/gdkcolor.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 0000000..41f603d
--- /dev/null
+++ b/src/ui/menu.c
@@ -0,0 +1,527 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity 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, METACITY_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, METACITY_STOCK_MAXIMIZE, FALSE, N_("Ma_ximize") },
+ /* Translators: Translate this string the same way as you do in libwnck! */
+ { META_MENU_OP_UNMAXIMIZE, MENU_ITEM_NORMAL, NULL, 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, METACITY_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,
+ 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,
+ 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.
+ */
+static char*
+get_workspace_name_with_accel (Display *display,
+ Window xroot,
+ int index)
+{
+ const char *name;
+ int number;
+
+ 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"), &number) == 1)
+ {
+ 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 workstation 0, but workstation 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;
+
+ image = gtk_image_new_from_stock (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",
+ GTK_SIGNAL_FUNC (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",
+ GTK_SIGNAL_FUNC (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;
+
+ 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 0000000..39bb7fa
--- /dev/null
+++ b/src/ui/menu.h
@@ -0,0 +1,62 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity 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 METACITY_STOCK_DELETE "metacity-delete"
+#define METACITY_STOCK_MINIMIZE "metacity-minimize"
+#define METACITY_STOCK_MAXIMIZE "metacity-maximize"
+
+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 0000000..cdc8636
--- /dev/null
+++ b/src/ui/metaaccellabel.c
@@ -0,0 +1,457 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity 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/gtkmain.h>
+#include <gtk/gtkaccelmap.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;
+
+
+GtkType
+meta_accel_label_get_type (void)
+{
+ static GtkType 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 0000000..2282327
--- /dev/null
+++ b/src/ui/metaaccellabel.h
@@ -0,0 +1,106 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity 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/gtklabel.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) (GTK_CHECK_CAST ((obj), META_TYPE_ACCEL_LABEL, MetaAccelLabel))
+#define META_ACCEL_LABEL_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), META_TYPE_ACCEL_LABEL, MetaAccelLabelClass))
+#define META_IS_ACCEL_LABEL(obj) (GTK_CHECK_TYPE ((obj), META_TYPE_ACCEL_LABEL))
+#define META_IS_ACCEL_LABEL_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), META_TYPE_ACCEL_LABEL))
+#define META_ACCEL_LABEL_GET_CLASS(obj) (GTK_CHECK_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);
+};
+
+GtkType 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/metacity-dialog.c b/src/ui/metacity-dialog.c
new file mode 100644
index 0000000..d47a9b3
--- /dev/null
+++ b/src/ui/metacity-dialog.c
@@ -0,0 +1,438 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity dialog process */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2004 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 <gtk/gtk.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libintl.h>
+#define _(x) dgettext (GETTEXT_PACKAGE, x)
+#define N_(x) x
+
+#include <gdk/gdkx.h>
+#include <X11/Xatom.h>
+
+static Window
+window_from_string (const char *str)
+{
+ char *end;
+ unsigned long l;
+
+ end = NULL;
+
+ l = strtoul (str, &end, 16);
+
+ if (end == NULL || end == str)
+ {
+ g_printerr (_("Could not parse \"%s\" as an integer"),
+ str);
+ return None;
+ }
+
+ if (*end != '\0')
+ {
+ g_printerr (_("Did not understand trailing characters \"%s\" in string \"%s\""),
+ end, str);
+ return None;
+ }
+
+ return l;
+}
+
+static void
+on_realize (GtkWidget *dialog,
+ void *data)
+{
+ const char *parent_str = data;
+ Window xwindow;
+
+ xwindow = window_from_string (parent_str);
+
+ gdk_error_trap_push ();
+ XSetTransientForHint (gdk_display, GDK_WINDOW_XID (dialog->window),
+ xwindow);
+ XSync (gdk_display, False);
+ gdk_error_trap_pop ();
+}
+
+static int
+kill_window_question (const char *window_name,
+ const char *parent_str,
+ guint32 timestamp)
+{
+ GtkWidget *dialog;
+ char *str, *tmp;
+
+ tmp = g_markup_escape_text (window_name, -1);
+ str = g_strdup_printf (_("\"%s\" is not responding."), tmp);
+ g_free (tmp);
+ dialog = gtk_message_dialog_new (NULL, 0,
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_NONE,
+ "<big><b>%s</b></big>\n\n<i>%s</i>",
+ str,
+ _("You may choose to wait a short while "
+ "for it to continue or force the application "
+ "to quit entirely."));
+ g_free (str);
+ gtk_window_set_icon_name (GTK_WINDOW (dialog), "stock_dialog-warning");
+
+ gtk_label_set_use_markup (GTK_LABEL (GTK_MESSAGE_DIALOG (dialog)->label), TRUE);
+ gtk_label_set_line_wrap (GTK_LABEL (GTK_MESSAGE_DIALOG (dialog)->label), TRUE);
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ _("_Wait"),
+ GTK_RESPONSE_REJECT,
+ _("_Force Quit"),
+ GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_REJECT);
+
+ g_signal_connect (G_OBJECT (dialog), "realize",
+ G_CALLBACK (on_realize), (char*) parent_str);
+
+ gtk_widget_realize (dialog);
+ gdk_x11_window_set_user_time (dialog->window, timestamp);
+
+ /* return our PID, then window ID that should be killed */
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
+ g_print ("%d\n%s\n", (int) getpid (), parent_str);
+ else
+ g_print ("%d\n0x0\n", (int) getpid ());
+
+ return 0;
+}
+
+static char*
+latin1_to_utf8 (const char *text)
+{
+ GString *str;
+ const char *p;
+
+ str = g_string_new ("");
+
+ p = text;
+ while (*p)
+ {
+ g_string_append_unichar (str, *p);
+ ++p;
+ }
+
+ return g_string_free (str, FALSE);
+}
+
+enum
+{
+ COLUMN_TITLE,
+ COLUMN_CLASS,
+ COLUMN_LAST
+};
+
+static GtkWidget*
+create_lame_apps_list (char **lame_apps)
+{
+ GtkTreeSelection *selection;
+ GtkCellRenderer *cell;
+ GtkWidget *tree_view;
+ GtkTreeViewColumn *column;
+ GtkListStore *model;
+ GtkTreeIter iter;
+ int i;
+
+ model = gtk_list_store_new (COLUMN_LAST,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
+
+ g_object_unref (G_OBJECT (model));
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
+
+ gtk_tree_selection_set_mode (GTK_TREE_SELECTION (selection),
+ GTK_SELECTION_NONE);
+
+ i = 0;
+ while (lame_apps[i])
+ {
+ char *s;
+
+ gtk_list_store_append (model, &iter);
+
+ /* window class is latin-1 */
+ s = latin1_to_utf8 (lame_apps[i+1]);
+
+ gtk_list_store_set (model,
+ &iter,
+ COLUMN_TITLE, lame_apps[i],
+ COLUMN_CLASS, s,
+ -1);
+
+ g_free (s);
+
+ i += 2;
+ }
+
+ cell = gtk_cell_renderer_text_new ();
+
+ g_object_set (G_OBJECT (cell),
+ "xpad", 2,
+ NULL);
+
+ column = gtk_tree_view_column_new_with_attributes (_("Title"),
+ cell,
+ "text", COLUMN_TITLE,
+ NULL);
+
+ gtk_tree_view_column_set_sort_column_id (column, COLUMN_TITLE);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view),
+ GTK_TREE_VIEW_COLUMN (column));
+
+ cell = gtk_cell_renderer_text_new ();
+
+ column = gtk_tree_view_column_new_with_attributes (_("Class"),
+ cell,
+ "text", COLUMN_CLASS,
+ NULL);
+
+ gtk_tree_view_column_set_sort_column_id (column, COLUMN_CLASS);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view),
+ GTK_TREE_VIEW_COLUMN (column));
+
+ return tree_view;
+}
+
+static int
+warn_about_no_sm_support (char **lame_apps,
+ guint32 timestamp)
+{
+ GtkWidget *dialog;
+ GtkWidget *list;
+ GtkWidget *sw;
+ GtkWidget *button;
+
+ dialog = gtk_message_dialog_new (NULL,
+ 0,
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_NONE,
+ _("These windows do not support \"save current setup\" and will have to be restarted manually next time you log in."));
+ gtk_window_set_icon_name (GTK_WINDOW (dialog), "stock_dialog-warning");
+
+ g_signal_connect (G_OBJECT (dialog),
+ "response",
+ G_CALLBACK (gtk_main_quit),
+ NULL);
+
+ /* Wait 4 minutes then force quit, so we don't wait around all night */
+ g_timeout_add (4 * 60 * 1000, (GSourceFunc) gtk_main_quit, NULL);
+
+ button = gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
+ gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
+ list = create_lame_apps_list (lame_apps);
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_container_set_border_width (GTK_CONTAINER (sw), 3);
+
+ gtk_container_add (GTK_CONTAINER (sw), list);
+
+ /* sw as geometry widget */
+ gtk_window_set_geometry_hints (GTK_WINDOW (dialog),
+ sw, NULL, 0);
+
+ gtk_window_set_resizable (GTK_WINDOW(dialog), TRUE);
+
+ /* applies to geometry widget; try to avoid scrollbars,
+ * but don't make the window huge
+ */
+ gtk_window_set_default_size (GTK_WINDOW (dialog),
+ 400, 225);
+
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
+ sw,
+ TRUE, TRUE, 0);
+
+ gtk_window_stick (GTK_WINDOW (dialog));
+
+ gtk_widget_realize (dialog);
+ gdk_x11_window_set_user_time (dialog->window, timestamp);
+
+ gtk_widget_grab_focus (button);
+ gtk_widget_show_all (dialog);
+
+ gtk_main ();
+
+ return 0;
+}
+
+static int
+error_about_command (const char *gconf_key,
+ const char *command,
+ const char *error,
+ guint32 timestamp)
+{
+ GtkWidget *dialog;
+
+ /* FIXME offer to change the value of the command's gconf key */
+
+ if (*command != '\0')
+ dialog = gtk_message_dialog_new (NULL, 0,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ _("There was an error running \"%s\":\n"
+ "%s."),
+ command, error);
+ else
+ dialog = gtk_message_dialog_new (NULL, 0,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "%s", error);
+ gtk_window_set_icon_name (GTK_WINDOW (dialog), "stock_dialog-error");
+
+ gtk_widget_realize (dialog);
+ gdk_x11_window_set_user_time (dialog->window, timestamp);
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+
+ gtk_widget_destroy (dialog);
+
+ return 0;
+}
+
+static gchar *screen = NULL;
+static gchar *timestamp_string = NULL;
+static gboolean isset_kill_window_question = FALSE;
+static gboolean isset_warn_about_no_sm_support = FALSE;
+static gboolean isset_command_failed_error = FALSE;
+static gchar **remaining_args;
+
+static const GOptionEntry options[] = {
+ { "screen", 0, 0, G_OPTION_ARG_STRING, &screen, NULL, NULL},
+ { "timestamp", 0, 0, G_OPTION_ARG_STRING, &timestamp_string, NULL, NULL},
+ { "kill-window-question", 'k', 0, G_OPTION_ARG_NONE,
+ &isset_kill_window_question, NULL, NULL},
+ { "warn-about-no-sm-support", 'w', 0, G_OPTION_ARG_NONE,
+ &isset_warn_about_no_sm_support, NULL, NULL},
+ { "command-failed-error", 'c', 0, G_OPTION_ARG_NONE,
+ &isset_command_failed_error, NULL, NULL},
+ { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY,
+ &remaining_args, NULL, NULL},
+ { NULL}
+};
+
+int
+main (int argc, char **argv)
+{
+ GOptionContext *ctx;
+ guint32 timestamp = 0;
+ gint num_args = 0;
+
+ bindtextdomain (GETTEXT_PACKAGE, METACITY_LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ gtk_init (&argc, &argv);
+
+ ctx = g_option_context_new ("- Dialogs for metacity. "
+ "This program is intented for use by metacity only.");
+ g_option_context_add_main_entries (ctx, options, GETTEXT_PACKAGE);
+ g_option_context_parse (ctx, &argc, &argv, NULL);
+ g_option_context_free (ctx);
+
+ if (timestamp_string != NULL)
+ {
+ timestamp = strtoul (timestamp_string, NULL, 10);
+ }
+
+ if (remaining_args != NULL)
+ {
+ num_args = g_strv_length (remaining_args);
+ }
+
+ if ((isset_kill_window_question && isset_warn_about_no_sm_support) ||
+ (isset_kill_window_question && isset_command_failed_error) ||
+ (isset_warn_about_no_sm_support && isset_command_failed_error) ||
+ timestamp == 0)
+ {
+ g_printerr ("bad args to metacity-dialog\n");
+ return 1;
+ }
+
+ else if (isset_kill_window_question)
+ {
+ if (num_args < 2)
+ {
+ g_printerr ("bad args to metacity-dialog\n");
+ return 1;
+ }
+ else
+ {
+ return kill_window_question (remaining_args[0],
+ remaining_args[1], timestamp);
+ }
+ }
+
+ else if (isset_warn_about_no_sm_support)
+ {
+ /* argc must be even because we want title-class pairs */
+ if (num_args == 0 || (num_args % 2) != 0)
+ {
+ g_printerr ("bad args to metacity-dialog\n");
+ return 1;
+ }
+ else
+ {
+ return warn_about_no_sm_support (&remaining_args[0], timestamp);
+ }
+ }
+
+ else if (isset_command_failed_error)
+ {
+ /* the args are the gconf key of the failed command, the text of
+ * the command, and the error message
+ */
+ if (num_args != 3)
+ {
+ g_printerr ("bad args to metacity-dialog\n");
+ return 1;
+ }
+ else
+ {
+ return error_about_command (remaining_args[0],
+ remaining_args[1], remaining_args[2], timestamp);
+ }
+ }
+ else
+ {
+ g_printerr ("bad args to metacity-dialog\n");
+ return 1;
+ }
+}
diff --git a/src/ui/preview-widget.c b/src/ui/preview-widget.c
new file mode 100644
index 0000000..08048e7
--- /dev/null
+++ b/src/ui/preview-widget.c
@@ -0,0 +1,462 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity 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 "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;
+
+GtkType
+meta_preview_get_type (void)
+{
+ static GtkType 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 = gtk_type_class (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_FLAGS (preview, GTK_NO_WINDOW);
+
+ 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;
+ 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 (widget)->border_width;
+
+ client_width = widget->allocation.width - preview->left_width - preview->right_width - border_width * 2;
+ client_height = widget->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 (widget)->border_width;
+
+ meta_theme_draw_frame (preview->theme,
+ widget,
+ widget->window,
+ &event->area,
+ widget->allocation.x + border_width,
+ widget->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;
+
+ preview = META_PREVIEW (widget);
+
+ ensure_info (preview);
+
+ req->width = preview->left_width + preview->right_width;
+ req->height = preview->top_height + preview->bottom_height;
+
+ if (GTK_BIN (preview)->child &&
+ GTK_WIDGET_VISIBLE (GTK_BIN (preview)->child))
+ {
+ GtkRequisition child_requisition;
+
+ gtk_widget_size_request (GTK_BIN (preview)->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;
+ }
+
+ req->width += GTK_CONTAINER (widget)->border_width * 2;
+ req->height += GTK_CONTAINER (widget)->border_width * 2;
+}
+
+static void
+meta_preview_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ MetaPreview *preview;
+ int border_width;
+ GtkAllocation child_allocation;
+
+ preview = META_PREVIEW (widget);
+
+ ensure_info (preview);
+
+ widget->allocation = *allocation;
+
+ border_width = GTK_CONTAINER (widget)->border_width;
+
+ if (GTK_BIN (widget)->child &&
+ GTK_WIDGET_VISIBLE (GTK_BIN (widget)->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 (widget)->child, &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));
+}
+
+#include "inlinepixbufs.h"
+
+GdkPixbuf*
+meta_preview_get_icon (void)
+{
+ static GdkPixbuf *default_icon = NULL;
+
+ if (default_icon == NULL)
+ {
+ GdkPixbuf *base;
+
+ base = gdk_pixbuf_new_from_inline (-1, default_icon_data,
+ FALSE,
+ NULL);
+
+ g_assert (base);
+
+ default_icon = gdk_pixbuf_scale_simple (base,
+ META_ICON_WIDTH,
+ META_ICON_HEIGHT,
+ GDK_INTERP_BILINEAR);
+
+ g_object_unref (G_OBJECT (base));
+ }
+
+ return default_icon;
+}
+
+GdkPixbuf*
+meta_preview_get_mini_icon (void)
+{
+ static GdkPixbuf *default_icon = NULL;
+
+ if (default_icon == NULL)
+ {
+ GdkPixbuf *base;
+
+ base = gdk_pixbuf_new_from_inline (-1, default_icon_data,
+ FALSE,
+ NULL);
+
+ g_assert (base);
+
+ default_icon = gdk_pixbuf_scale_simple (base,
+ META_MINI_ICON_WIDTH,
+ META_MINI_ICON_HEIGHT,
+ GDK_INTERP_BILINEAR);
+
+ g_object_unref (G_OBJECT (base));
+ }
+
+ return default_icon;
+}
diff --git a/src/ui/preview-widget.h b/src/ui/preview-widget.h
new file mode 100644
index 0000000..bfa964b
--- /dev/null
+++ b/src/ui/preview-widget.h
@@ -0,0 +1,84 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity 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/gtkbin.h>
+
+#ifndef META_PREVIEW_WIDGET_H
+#define META_PREVIEW_WIDGET_H
+
+#define META_TYPE_PREVIEW (meta_preview_get_type ())
+#define META_PREVIEW(obj) (GTK_CHECK_CAST ((obj), META_TYPE_PREVIEW, MetaPreview))
+#define META_PREVIEW_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), META_TYPE_PREVIEW, MetaPreviewClass))
+#define META_IS_PREVIEW(obj) (GTK_CHECK_TYPE ((obj), META_TYPE_PREVIEW))
+#define META_IS_PREVIEW_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), META_TYPE_PREVIEW))
+#define META_PREVIEW_GET_CLASS(obj) (GTK_CHECK_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;
+};
+
+
+GtkType 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);
+
+
+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 0000000..f68e3c3
--- /dev/null
+++ b/src/ui/resizepopup.c
@@ -0,0 +1,217 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity 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/gtkwindow.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkframe.h>
+#include <gtk/gtkmain.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);
+
+ 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 0000000..3d4e863
--- /dev/null
+++ b/src/ui/tabpopup.c
@@ -0,0 +1,948 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity 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.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;
+
+ popup = data;
+
+ if (!popup->outline || popup->current_selected_entry == NULL)
+ return FALSE;
+
+ te = popup->current_selected_entry;
+
+ gdk_draw_rectangle (widget->window,
+ widget->style->white_gc,
+ FALSE,
+ 0, 0,
+ te->rect.width - 1,
+ te->rect.height - 1);
+
+ gdk_draw_rectangle (widget->window,
+ widget->style->white_gc,
+ FALSE,
+ te->inner_rect.x - 1, te->inner_rect.y - 1,
+ te->inner_rect.width + 1,
+ te->inner_rect.height + 1);
+
+ 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);
+ }
+ }
+}
+
+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);
+
+ 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) (GTK_CHECK_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 GtkType 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 = gtk_type_class (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;
+
+ 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;
+
+ gdk_draw_rectangle (widget->window,
+ widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
+ FALSE,
+ x, y, w, h);
+ gdk_draw_rectangle (widget->window,
+ widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
+ FALSE,
+ x - 1, y - 1, w + 2, h + 2);
+
+#if 0
+ gdk_draw_rectangle (widget->window,
+ widget->style->bg_gc[GTK_STATE_SELECTED],
+ TRUE,
+ x, y, w, h);
+#endif
+#if 0
+ gtk_paint_focus (widget->style, widget->window,
+ &event->area, widget, "meta-tab-image",
+ x, y, w, h);
+#endif
+ }
+
+ 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) (GTK_CHECK_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 GtkType 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;
+ 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)
+ {
+ i = SELECT_OUTLINE_WIDTH - 1;
+ while (i >= 0)
+ {
+ gdk_draw_rectangle (widget->window,
+ widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
+ FALSE,
+ i,
+ i,
+ widget->allocation.width - i * 2 - 1,
+ widget->allocation.height - i * 2 - 1);
+
+ --i;
+ }
+ }
+
+ return TRUE;
+}
+
diff --git a/src/ui/testgradient.c b/src/ui/testgradient.c
new file mode 100644
index 0000000..28de317
--- /dev/null
+++ b/src/ui/testgradient.c
@@ -0,0 +1,348 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity 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,
+ GdkGC *gc,
+ int width,
+ int height);
+
+static void
+draw_checkerboard (GdkDrawable *drawable,
+ int width,
+ int height)
+{
+ gint i, j, xcount, ycount;
+ GdkGC *gc1, *gc2;
+ GdkColor color;
+
+#define CHECK_SIZE 10
+#define SPACING 2
+
+ /* It would be a bit more efficient to keep these
+ * GC's around instead of recreating on each expose, but
+ * this is the lazy/slow way.
+ */
+ gc1 = gdk_gc_new (drawable);
+ color.red = 30000;
+ color.green = 30000;
+ color.blue = 30000;
+ gdk_gc_set_rgb_fg_color (gc1, &color);
+
+ gc2 = gdk_gc_new (drawable);
+ color.red = 50000;
+ color.green = 50000;
+ color.blue = 50000;
+ gdk_gc_set_rgb_fg_color (gc2, &color);
+
+ xcount = 0;
+ i = SPACING;
+ while (i < width)
+ {
+ j = SPACING;
+ ycount = xcount % 2; /* start with even/odd depending on row */
+ while (j < height)
+ {
+ GdkGC *gc;
+
+ if (ycount % 2)
+ gc = gc1;
+ else
+ gc = gc2;
+
+ /* 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.
+ */
+ gdk_draw_rectangle (drawable,
+ gc,
+ TRUE,
+ i, j,
+ CHECK_SIZE,
+ CHECK_SIZE);
+
+ j += CHECK_SIZE + SPACING;
+ ++ycount;
+ }
+
+ i += CHECK_SIZE + SPACING;
+ ++xcount;
+ }
+
+ g_object_unref (G_OBJECT (gc1));
+ g_object_unref (G_OBJECT (gc2));
+}
+
+static void
+render_simple (GdkDrawable *drawable,
+ GdkGC *gc,
+ 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_draw_pixbuf (drawable,
+ gc,
+ pixbuf,
+ 0, 0,
+ 0, 0, width, height,
+ GDK_RGB_DITHER_MAX,
+ 0, 0);
+
+ g_object_unref (G_OBJECT (pixbuf));
+}
+
+static void
+render_vertical_func (GdkDrawable *drawable,
+ GdkGC *gc,
+ int width, int height)
+{
+ render_simple (drawable, gc, width, height, META_GRADIENT_VERTICAL, FALSE);
+}
+
+static void
+render_horizontal_func (GdkDrawable *drawable,
+ GdkGC *gc,
+ int width, int height)
+{
+ render_simple (drawable, gc, width, height, META_GRADIENT_HORIZONTAL, FALSE);
+}
+
+static void
+render_diagonal_func (GdkDrawable *drawable,
+ GdkGC *gc,
+ int width, int height)
+{
+ render_simple (drawable, gc, width, height, META_GRADIENT_DIAGONAL, FALSE);
+}
+
+static void
+render_diagonal_alpha_func (GdkDrawable *drawable,
+ GdkGC *gc,
+ int width, int height)
+{
+ render_simple (drawable, gc, width, height, META_GRADIENT_DIAGONAL, TRUE);
+}
+
+static void
+render_multi (GdkDrawable *drawable,
+ GdkGC *gc,
+ 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_pixbuf_render_to_drawable (pixbuf,
+ drawable,
+ gc,
+ 0, 0,
+ 0, 0, width, height,
+ GDK_RGB_DITHER_NORMAL,
+ 0, 0);
+
+ g_object_unref (G_OBJECT (pixbuf));
+#undef N_COLORS
+}
+
+static void
+render_vertical_multi_func (GdkDrawable *drawable,
+ GdkGC *gc,
+ int width, int height)
+{
+ render_multi (drawable, gc, width, height, META_GRADIENT_VERTICAL);
+}
+
+static void
+render_horizontal_multi_func (GdkDrawable *drawable,
+ GdkGC *gc,
+ int width, int height)
+{
+ render_multi (drawable, gc, width, height, META_GRADIENT_HORIZONTAL);
+}
+
+static void
+render_diagonal_multi_func (GdkDrawable *drawable,
+ GdkGC *gc,
+ int width, int height)
+{
+ render_multi (drawable, gc, width, height, META_GRADIENT_DIAGONAL);
+}
+
+static void
+render_interwoven_func (GdkDrawable *drawable,
+ GdkGC *gc,
+ 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_pixbuf_render_to_drawable (pixbuf,
+ drawable,
+ gc,
+ 0, 0,
+ 0, 0, width, height,
+ GDK_RGB_DITHER_NORMAL,
+ 0, 0);
+
+ g_object_unref (G_OBJECT (pixbuf));
+}
+
+static gboolean
+expose_callback (GtkWidget *widget,
+ GdkEventExpose *event,
+ gpointer data)
+{
+ RenderGradientFunc func = data;
+
+ (* func) (widget->window,
+ widget->style->fg_gc[widget->state],
+ widget->allocation.width,
+ widget->allocation.height);
+
+ 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 0000000..2a98b81
--- /dev/null
+++ b/src/ui/theme-parser.c
@@ -0,0 +1,4699 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity theme parsing */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "theme-parser.h"
+#include "util.h"
+#include <string.h>
+#include <stdlib.h>
+
+typedef enum
+{
+ STATE_START,
+ STATE_THEME,
+ /* info section */
+ STATE_INFO,
+ STATE_NAME,
+ STATE_AUTHOR,
+ STATE_COPYRIGHT,
+ STATE_DATE,
+ STATE_DESCRIPTION,
+ /* constants */
+ STATE_CONSTANT,
+ /* geometry */
+ STATE_FRAME_GEOMETRY,
+ STATE_DISTANCE,
+ STATE_BORDER,
+ 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,
+ /* style set */
+ STATE_FRAME_STYLE_SET,
+ STATE_FRAME,
+ /* assigning style sets to windows */
+ STATE_WINDOW,
+ /* and menu icons */
+ STATE_MENU_ICON,
+ /* fallback icons */
+ 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);
+
+static void parse_menu_icon_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error);
+
+static void start_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error);
+static void end_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error);
+static void text_handler (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error);
+
+static GMarkupParser metacity_theme_parser = {
+ start_element_handler,
+ end_element_handler,
+ text_handler,
+ NULL,
+ NULL
+};
+
+static void
+set_error (GError **err,
+ GMarkupParseContext *context,
+ int error_domain,
+ int error_code,
+ const char *format,
+ ...)
+{
+ int line, ch;
+ va_list args;
+ char *str;
+
+ g_markup_parse_context_get_position (context, &line, &ch);
+
+ va_start (args, format);
+ str = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ g_set_error (err, error_domain, error_code,
+ _("Line %d character %d: %s"),
+ line, ch, str);
+
+ g_free (str);
+}
+
+static void
+add_context_to_error (GError **err,
+ GMarkupParseContext *context)
+{
+ int line, ch;
+ char *str;
+
+ if (err == NULL || *err == NULL)
+ return;
+
+ g_markup_parse_context_get_position (context, &line, &ch);
+
+ str = g_strdup_printf (_("Line %d character %d: %s"),
+ line, ch, (*err)->message);
+ g_free ((*err)->message);
+ (*err)->message = str;
+}
+
+static void
+parse_info_init (ParseInfo *info)
+{
+ info->theme_file = NULL;
+ info->states = g_slist_prepend (NULL, GINT_TO_POINTER (STATE_START));
+ info->theme = NULL;
+ info->name = NULL;
+ info->layout = NULL;
+ info->op_list = NULL;
+ info->op = NULL;
+ info->style = NULL;
+ info->style_set = NULL;
+ info->piece = META_FRAME_PIECE_LAST;
+ info->button_type = META_BUTTON_TYPE_LAST;
+ info->button_state = META_BUTTON_STATE_LAST;
+}
+
+static void
+parse_info_free (ParseInfo *info)
+{
+ g_free (info->theme_file);
+ g_free (info->theme_dir);
+
+ g_slist_free (info->states);
+
+ if (info->theme)
+ meta_theme_free (info->theme);
+
+ if (info->layout)
+ meta_frame_layout_unref (info->layout);
+
+ if (info->op_list)
+ meta_draw_op_list_unref (info->op_list);
+
+ if (info->op)
+ meta_draw_op_free (info->op);
+
+ if (info->style)
+ meta_frame_style_unref (info->style);
+
+ if (info->style_set)
+ meta_frame_style_set_unref (info->style_set);
+}
+
+static void
+push_state (ParseInfo *info,
+ ParseState state)
+{
+ info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
+}
+
+static void
+pop_state (ParseInfo *info)
+{
+ g_return_if_fail (info->states != NULL);
+
+ info->states = g_slist_remove (info->states, info->states->data);
+}
+
+static ParseState
+peek_state (ParseInfo *info)
+{
+ g_return_val_if_fail (info->states != NULL, STATE_START);
+
+ return GPOINTER_TO_INT (info->states->data);
+}
+
+#define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
+
+typedef struct
+{
+ const char *name;
+ const char **retloc;
+} LocateAttr;
+
+static gboolean
+locate_attributes (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error,
+ const char *first_attribute_name,
+ const char **first_attribute_retloc,
+ ...)
+{
+ va_list args;
+ const char *name;
+ const char **retloc;
+ int n_attrs;
+#define MAX_ATTRS 24
+ LocateAttr attrs[MAX_ATTRS];
+ gboolean retval;
+ int i;
+
+ g_return_val_if_fail (first_attribute_name != NULL, FALSE);
+ g_return_val_if_fail (first_attribute_retloc != NULL, FALSE);
+
+ retval = TRUE;
+
+ n_attrs = 1;
+ attrs[0].name = first_attribute_name;
+ attrs[0].retloc = first_attribute_retloc;
+ *first_attribute_retloc = NULL;
+
+ va_start (args, first_attribute_retloc);
+
+ name = va_arg (args, const char*);
+ retloc = va_arg (args, const char**);
+
+ while (name != NULL)
+ {
+ g_return_val_if_fail (retloc != NULL, FALSE);
+
+ g_assert (n_attrs < MAX_ATTRS);
+
+ attrs[n_attrs].name = name;
+ attrs[n_attrs].retloc = retloc;
+ n_attrs += 1;
+ *retloc = NULL;
+
+ name = va_arg (args, const char*);
+ retloc = va_arg (args, const char**);
+ }
+
+ 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)
+ {
+ 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;
+ }
+
+ 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;
+
+ if (name == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"%s\" attribute on <%s> element"),
+ "name", element_name);
+ return;
+ }
+
+ if (value == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"%s\" attribute on <%s> element"),
+ "value", element_name);
+ return;
+ }
+
+ if (strchr (value, '.') && parse_double (value, &dval, context, error))
+ {
+ g_clear_error (error);
+
+ 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))
+ {
+ g_clear_error (error);
+
+ if (!meta_theme_define_int_constant (info->theme,
+ name,
+ ival,
+ error))
+ {
+ add_context_to_error (error, context);
+ return;
+ }
+ }
+ else
+ {
+ g_clear_error (error);
+
+ 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;
+
+ if (name == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"%s\" attribute on <%s> element"),
+ "name", element_name);
+ 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 (name == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"%s\" attribute on <%s> element"),
+ "name", element_name);
+ return;
+ }
+
+ if (meta_theme_lookup_draw_op_list (info->theme, name))
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("<%s> name \"%s\" used a second time"),
+ element_name, name);
+ return;
+ }
+
+ g_assert (info->op_list == NULL);
+ info->op_list = meta_draw_op_list_new (2);
+
+ meta_theme_insert_draw_op_list (info->theme, name, info->op_list);
+
+ push_state (info, STATE_DRAW_OPS);
+ }
+ else if (ELEMENT_IS ("frame_style"))
+ {
+ const char *name = NULL;
+ const char *parent = NULL;
+ const char *geometry = NULL;
+ 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 (name == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"%s\" attribute on <%s> element"),
+ "name", element_name);
+ return;
+ }
+
+ if (meta_theme_lookup_style (info->theme, name))
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("<%s> name \"%s\" used a second time"),
+ element_name, name);
+ return;
+ }
+
+ parent_style = NULL;
+ if (parent)
+ {
+ parent_style = meta_theme_lookup_style (info->theme, parent);
+ if (parent_style == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("<%s> parent \"%s\" has not been defined"),
+ element_name, parent);
+ return;
+ }
+ }
+
+ layout = NULL;
+ if (geometry)
+ {
+ layout = meta_theme_lookup_layout (info->theme, geometry);
+ if (layout == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("<%s> geometry \"%s\" has not been defined"),
+ element_name, geometry);
+ return;
+ }
+ }
+ else if (parent_style)
+ {
+ layout = parent_style->layout;
+ }
+
+ if (layout == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("<%s> must specify either a geometry or a parent that has a geometry"),
+ element_name);
+ return;
+ }
+
+ g_assert (info->style == NULL);
+
+ info->style = meta_frame_style_new (parent_style);
+ g_assert (info->style->layout == NULL);
+ meta_frame_layout_ref (layout);
+ info->style->layout = layout;
+
+ 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 (name == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"%s\" attribute on <%s> element"),
+ "name", element_name);
+ return;
+ }
+
+ if (meta_theme_lookup_style_set (info->theme, name))
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("<%s> name \"%s\" used a second time"),
+ element_name, name);
+ return;
+ }
+
+ parent_set = NULL;
+ if (parent)
+ {
+ parent_set = meta_theme_lookup_style_set (info->theme, parent);
+ if (parent_set == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("<%s> parent \"%s\" has not been defined"),
+ element_name, parent);
+ return;
+ }
+ }
+
+ g_assert (info->style_set == NULL);
+
+ info->style_set = meta_frame_style_set_new (parent_set);
+
+ meta_theme_insert_style_set (info->theme, name, info->style_set);
+
+ push_state (info, STATE_FRAME_STYLE_SET);
+ }
+ else if (ELEMENT_IS ("window"))
+ {
+ const char *type_name = NULL;
+ const char *style_set_name = NULL;
+ MetaFrameStyleSet *style_set;
+ MetaFrameType type;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "type", &type_name, "style_set", &style_set_name,
+ NULL))
+ return;
+
+ if (type_name == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"%s\" attribute on <%s> element"),
+ "type", element_name);
+ return;
+ }
+
+ if (style_set_name == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"%s\" attribute on <%s> element"),
+ "style_set", element_name);
+ return;
+ }
+
+ type = meta_frame_type_from_string (type_name);
+
+ if (type == META_FRAME_TYPE_LAST)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Unknown type \"%s\" on <%s> element"),
+ type_name, element_name);
+ return;
+ }
+
+ style_set = meta_theme_lookup_style_set (info->theme,
+ style_set_name);
+
+ if (style_set == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Unknown style_set \"%s\" on <%s> element"),
+ style_set_name, element_name);
+ return;
+ }
+
+ if (info->theme->style_sets_by_type[type] != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Window type \"%s\" has already been assigned a style set"),
+ type_name);
+ return;
+ }
+
+ meta_frame_style_set_ref (style_set);
+ info->theme->style_sets_by_type[type] = style_set;
+
+ push_state (info, STATE_WINDOW);
+ }
+ else if (ELEMENT_IS ("menu_icon"))
+ {
+ /* 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"))
+ {
+ const char *icon = NULL;
+ const char *mini_icon = NULL;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "icon", &icon,
+ "mini_icon", &mini_icon,
+ NULL))
+ return;
+
+ if (icon)
+ {
+ if (info->theme->fallback_icon != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Theme already has a fallback icon"));
+ return;
+ }
+
+ info->theme->fallback_icon = meta_theme_load_image(info->theme, icon, 64, error);
+ }
+
+ if (mini_icon)
+ {
+ if (info->theme->fallback_mini_icon != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Theme already has a fallback mini_icon"));
+ return;
+ }
+
+ info->theme->fallback_mini_icon = meta_theme_load_image(info->theme, mini_icon, 16, error);
+ }
+
+ 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;
+
+ if (name == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"name\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (value == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"value\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ val = 0;
+ if (!parse_positive_integer (value, &val, context, 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;
+
+ if (name == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"name\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (value == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"value\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ val = 0;
+ if (!parse_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;
+
+ if (name == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"name\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (top == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"top\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (bottom == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"bottom\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (left == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"left\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (right == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"right\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ top_val = 0;
+ if (!parse_positive_integer (top, &top_val, context, 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 (color == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"color\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (x1 == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"x1\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (y1 == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"y1\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (x2 == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"x2\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (y2 == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"y2\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+#if 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);
+ op->data.line.x2 = meta_draw_spec_new (info->theme, x2, NULL);
+ 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 (color == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"color\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (x == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"x\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (y == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"y\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (width == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"width\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (height == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"height\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+#if 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 (color == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"color\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (x == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"x\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (y == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"y\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (width == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"width\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (height == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"height\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (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,
+ _("No \"start_angle\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (extent_angle == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"extent_angle\" attribute on element <%s>"), element_name);
+ return;
+ }
+ }
+
+#if 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 (x == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"x\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (y == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"y\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (width == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"width\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (height == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"height\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+#if 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 (color == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"color\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (x == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"x\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (y == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"y\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (width == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"width\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (height == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"height\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (alpha == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"alpha\" attribute on element <%s>"), element_name);
+ return;
+ }
+#if 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 (type == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"type\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (x == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"x\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (y == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"y\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (width == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"width\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (height == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"height\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+#if 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 (x == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"x\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (y == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"y\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (width == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"width\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (height == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"height\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (filename == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"filename\" attribute on element <%s>"), element_name);
+ return;
+ }
+#if 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 (state == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"state\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (shadow == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"shadow\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (arrow == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"arrow\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (x == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"x\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (y == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"y\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (width == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"width\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (height == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"height\" attribute on element <%s>"), element_name);
+ return;
+ }
+#if 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 (state == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"state\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (shadow == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"shadow\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (x == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"x\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (y == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"y\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (width == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"width\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (height == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"height\" attribute on element <%s>"), element_name);
+ return;
+ }
+#if 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 (state == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"state\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (x == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"x\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (y1 == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"y1\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (y2 == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"y2\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+#if 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 (x == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"x\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (y == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"y\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (width == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"width\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (height == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"height\" attribute on element <%s>"), element_name);
+ return;
+ }
+#if 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 (color == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"color\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (x == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"x\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+ if (y == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"y\" attribute on element <%s>"), element_name);
+ return;
+ }
+
+#if 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;
+
+ if (name == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"%s\" attribute on <%s> element"), "name", element_name);
+ 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;
+
+ if (name == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"%s\" attribute on <%s> element"), "name", element_name);
+ return;
+ }
+
+ if (tile_width == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"%s\" attribute on <%s> element"), "tile_width", element_name);
+ return;
+ }
+
+ if (tile_height == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"%s\" attribute on <%s> element"), "tile_height", element_name);
+ 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;
+
+ if (value == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"value\" attribute on <%s> element"),
+ element_name);
+ return;
+ }
+
+ color_spec = 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;
+
+ if (position == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"position\" attribute on <%s> element"),
+ element_name);
+ return;
+ }
+
+ info->piece = meta_frame_piece_from_string (position);
+ if (info->piece == META_FRAME_PIECE_LAST)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Unknown position \"%s\" for frame piece"),
+ position);
+ return;
+ }
+
+ if (info->style->pieces[info->piece] != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Frame style already has a piece at position %s"),
+ position);
+ return;
+ }
+
+ g_assert (info->op_list == NULL);
+
+ if (draw_ops)
+ {
+ MetaDrawOpList *op_list;
+
+ op_list = meta_theme_lookup_draw_op_list (info->theme,
+ draw_ops);
+
+ if (op_list == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No <draw_ops> with the name \"%s\" has been defined"),
+ draw_ops);
+ return;
+ }
+
+ meta_draw_op_list_ref (op_list);
+ info->op_list = op_list;
+ }
+
+ push_state (info, STATE_PIECE);
+ }
+ else if (ELEMENT_IS ("button"))
+ {
+ const char *function = NULL;
+ const char *state = NULL;
+ const char *draw_ops = NULL;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values,
+ error,
+ "function", &function,
+ "state", &state,
+ "draw_ops", &draw_ops,
+ NULL))
+ return;
+
+ if (function == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"function\" attribute on <%s> element"),
+ element_name);
+ return;
+ }
+
+ if (state == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"state\" attribute on <%s> element"),
+ element_name);
+ return;
+ }
+
+ info->button_type = meta_button_type_from_string (function, 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);
+ }
+ 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;
+
+ if (focus == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"focus\" attribute on <%s> element"),
+ element_name);
+ return;
+ }
+
+ if (state == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"state\" attribute on <%s> element"),
+ element_name);
+ return;
+ }
+
+ if (style == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"style\" attribute on <%s> element"),
+ element_name);
+ return;
+ }
+
+ frame_focus = meta_frame_focus_from_string (focus);
+ if (frame_focus == META_FRAME_FOCUS_LAST)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("\"%s\" is not a valid value for focus attribute"),
+ focus);
+ return;
+ }
+
+ frame_state = meta_frame_state_from_string (state);
+ if (frame_state == META_FRAME_STATE_LAST)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("\"%s\" is not a valid value for state attribute"),
+ focus);
+ return;
+ }
+
+ frame_style = meta_theme_lookup_style (info->theme, style);
+
+ if (frame_style == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("A style called \"%s\" has not been defined"),
+ style);
+ return;
+ }
+
+ switch (frame_state)
+ {
+ case META_FRAME_STATE_NORMAL:
+ if (resize == NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("No \"resize\" attribute on <%s> element"),
+ element_name);
+ return;
+ }
+
+
+ frame_resize = meta_frame_resize_from_string (resize);
+ if (frame_resize == META_FRAME_RESIZE_LAST)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("\"%s\" is not a valid value for resize attribute"),
+ focus);
+ return;
+ }
+
+ 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");
+ }
+}
+
+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;
+ 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;
+ 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,
+ _("<name> specified twice for this theme"));
+ return;
+ }
+
+ info->theme->readable_name = g_strndup (text, text_len);
+ break;
+ case STATE_AUTHOR:
+ if (info->theme->author != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("<author> specified twice for this theme"));
+ return;
+ }
+
+ info->theme->author = g_strndup (text, text_len);
+ break;
+ case STATE_COPYRIGHT:
+ if (info->theme->copyright != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("<copyright> specified twice for this theme"));
+ return;
+ }
+
+ info->theme->copyright = g_strndup (text, text_len);
+ break;
+ case STATE_DATE:
+ if (info->theme->date != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("<date> specified twice for this theme"));
+ return;
+ }
+
+ info->theme->date = g_strndup (text, text_len);
+ break;
+ case STATE_DESCRIPTION:
+ if (info->theme->description != NULL)
+ {
+ set_error (error, context, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("<description> specified twice for this theme"));
+ return;
+ }
+
+ info->theme->description = g_strndup (text, text_len);
+ break;
+ case STATE_CONSTANT:
+ NO_TEXT ("constant");
+ break;
+ case STATE_FRAME_GEOMETRY:
+ NO_TEXT ("frame_geometry");
+ break;
+ case STATE_DISTANCE:
+ NO_TEXT ("distance");
+ break;
+ case STATE_BORDER:
+ NO_TEXT ("border");
+ break;
+ case STATE_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;
+ 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 METACITY_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 (METACITY_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 (METACITY_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 METACITY_DATADIR */
+ if (text == NULL)
+ {
+ theme_dir = g_build_filename (METACITY_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 (&metacity_theme_parser,
+ 0, &info, NULL);
+
+ error = NULL;
+ if (!g_markup_parse_context_parse (context,
+ text,
+ length,
+ &error))
+ goto out;
+
+ error = NULL;
+ if (!g_markup_parse_context_end_parse (context, &error))
+ goto out;
+
+ 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 0000000..035d700
--- /dev/null
+++ b/src/ui/theme-parser.h
@@ -0,0 +1,32 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity theme parsing */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "theme.h"
+
+#ifndef META_THEME_PARSER_H
+#define META_THEME_PARSER_H
+
+MetaTheme* meta_theme_load (const char *theme_name,
+ GError **err);
+
+#endif
diff --git a/src/ui/theme-viewer.c b/src/ui/theme-viewer.c
new file mode 100644
index 0000000..4b97de6
--- /dev/null
+++ b/src/ui/theme-viewer.c
@@ -0,0 +1,1317 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity theme viewer and test app main() */
+
+/*
+ * Copyright (C) 2002 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * 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 <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);
+ gtk_object_sink (GTK_OBJECT (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 ();
+
+ gtk_toolbar_insert_stock (GTK_TOOLBAR (toolbar),
+ GTK_STOCK_NEW,
+ _("Open another one of these windows"),
+ NULL,
+ NULL, NULL,
+ -1); /* -1 means "append" */
+
+ gtk_toolbar_insert_stock (GTK_TOOLBAR (toolbar),
+ GTK_STOCK_OPEN,
+ _("This is a demo button with an 'open' icon"),
+ NULL,
+ NULL, NULL,
+ -1); /* -1 means "append" */
+
+ gtk_toolbar_insert_stock (GTK_TOOLBAR (toolbar),
+ GTK_STOCK_QUIT,
+ _("This is a demo button with a 'quit' icon"),
+ NULL,
+ NULL, NULL,
+ -1); /* -1 means "append" */
+
+ 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;
+ 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, METACITY_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 ("METACITY_DEBUG") != NULL)
+ {
+ meta_set_debugging (TRUE);
+ meta_set_verbose (TRUE);
+ }
+
+ start = clock ();
+ err = NULL;
+ if (argc == 1)
+ global_theme = meta_theme_load ("Atlanta", &err);
+ else if (argc == 2)
+ global_theme = meta_theme_load (argv[1], &err);
+ else
+ {
+ g_printerr (_("Usage: metacity-theme-viewer [THEMENAME]\n"));
+ exit (1);
+ }
+ end = clock ();
+
+ if (global_theme == NULL)
+ {
+ g_printerr (_("Error loading theme: %s\n"),
+ err->message);
+ g_error_free (err);
+ exit (1);
+ }
+
+ g_print (_("Loaded theme \"%s\" in %g seconds\n"),
+ global_theme->name,
+ (end - start) / (double) CLOCKS_PER_SEC);
+
+ run_theme_benchmark ();
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_default_size (GTK_WINDOW (window), 350, 350);
+
+ 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 0000000..51f88d6
--- /dev/null
+++ b/src/ui/theme.c
@@ -0,0 +1,6201 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity Theme Rendering */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "theme.h"
+#include "theme-parser.h"
+#include "util.h"
+#include "gradient.h"
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkimage.h>
+#include <gtk/gtkicontheme.h>
+#include <string.h>
+#include <stdlib.h>
+#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);
+
+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);
+}
+
+static void
+init_border (GtkBorder *border)
+{
+ border->top = -1;
+ border->bottom = -1;
+ border->left = -1;
+ border->right = -1;
+}
+
+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;
+}
+
+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;
+ 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];
+ GdkRectangle *right_bg_rects[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;
+
+ 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)
+ ++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)
+ ++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 += 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 += 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 */
+
+ /* 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;
+ 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;
+
+ *(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;
+ spec->data.blend.color_set = FALSE;
+ }
+ 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;
+ spec->data.shade.color_set = FALSE;
+ }
+ else
+ {
+ spec = meta_color_spec_new (META_COLOR_SPEC_BASIC);
+
+ if (!gdk_color_parse (str, &spec->data.basic.color))
+ {
+ g_set_error (err, META_THEME_ERROR,
+ META_THEME_ERROR_FAILED,
+ _("Could not parse color \"%s\""),
+ str);
+ meta_color_spec_free (spec);
+ return NULL;
+ }
+ }
+
+ g_assert (spec);
+
+ return spec;
+}
+
+MetaColorSpec*
+meta_color_spec_new_gtk (MetaGtkColorComponent component,
+ GtkStateType state)
+{
+ MetaColorSpec *spec;
+
+ spec = meta_color_spec_new (META_COLOR_SPEC_GTK);
+
+ spec->data.gtk.component = component;
+ spec->data.gtk.state = state;
+
+ return spec;
+}
+
+void
+meta_color_spec_render (MetaColorSpec *spec,
+ GtkWidget *widget,
+ GdkColor *color)
+{
+ g_return_if_fail (spec != NULL);
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+ g_return_if_fail (widget->style != NULL);
+
+ switch (spec->type)
+ {
+ case META_COLOR_SPEC_BASIC:
+ *color = spec->data.basic.color;
+ break;
+
+ case META_COLOR_SPEC_GTK:
+ switch (spec->data.gtk.component)
+ {
+ case META_GTK_COLOR_BG:
+ *color = widget->style->bg[spec->data.gtk.state];
+ break;
+ case META_GTK_COLOR_FG:
+ *color = widget->style->fg[spec->data.gtk.state];
+ break;
+ case META_GTK_COLOR_BASE:
+ *color = widget->style->base[spec->data.gtk.state];
+ break;
+ case META_GTK_COLOR_TEXT:
+ *color = widget->style->text[spec->data.gtk.state];
+ break;
+ case META_GTK_COLOR_LIGHT:
+ *color = widget->style->light[spec->data.gtk.state];
+ break;
+ case META_GTK_COLOR_DARK:
+ *color = widget->style->dark[spec->data.gtk.state];
+ break;
+ case META_GTK_COLOR_MID:
+ *color = widget->style->mid[spec->data.gtk.state];
+ break;
+ case META_GTK_COLOR_TEXT_AA:
+ *color = widget->style->text_aa[spec->data.gtk.state];
+ break;
+ case META_GTK_COLOR_LAST:
+ g_assert_not_reached ();
+ break;
+ }
+ break;
+
+ case META_COLOR_SPEC_BLEND:
+ {
+ GdkColor bg, fg;
+
+ if (spec->data.blend.color_set == FALSE)
+ {
+ 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);
+ spec->data.blend.color_set = TRUE;
+ }
+
+ *color = spec->data.blend.color;
+ }
+ break;
+
+ case META_COLOR_SPEC_SHADE:
+ {
+ if (spec->data.shade.color_set == FALSE)
+ {
+ 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);
+ spec->data.shade.color_set = TRUE;
+ }
+
+ *color = spec->data.shade.color;
+ }
+ break;
+ }
+}
+
+static const char*
+op_name (PosOperatorType type)
+{
+ switch (type)
+ {
+ case POS_OP_ADD:
+ return "+";
+ case POS_OP_SUBTRACT:
+ return "-";
+ case POS_OP_MULTIPLY:
+ return "*";
+ case POS_OP_DIVIDE:
+ return "/";
+ case POS_OP_MOD:
+ return "%";
+ case POS_OP_MAX:
+ return "`max`";
+ case POS_OP_MIN:
+ return "`min`";
+ case POS_OP_NONE:
+ break;
+ }
+
+ return "<unknown>";
+}
+
+static PosOperatorType
+op_from_string (const char *p,
+ int *len)
+{
+ *len = 0;
+
+ switch (*p)
+ {
+ case '+':
+ *len = 1;
+ return POS_OP_ADD;
+ case '-':
+ *len = 1;
+ return POS_OP_SUBTRACT;
+ case '*':
+ *len = 1;
+ return POS_OP_MULTIPLY;
+ case '/':
+ *len = 1;
+ return POS_OP_DIVIDE;
+ case '%':
+ *len = 1;
+ return POS_OP_MOD;
+
+ case '`':
+ if (p[0] == '`' &&
+ p[1] == 'm' &&
+ p[2] == 'a' &&
+ p[3] == 'x' &&
+ p[4] == '`')
+ {
+ *len = 5;
+ return POS_OP_MAX;
+ }
+ else if (p[0] == '`' &&
+ p[1] == 'm' &&
+ p[2] == 'i' &&
+ p[3] == 'n' &&
+ p[4] == '`')
+ {
+ *len = 5;
+ return POS_OP_MIN;
+ }
+ }
+
+ return POS_OP_NONE;
+}
+
+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);
+}
+
+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;
+}
+
+#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
+
+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;
+}
+
+typedef enum
+{
+ POS_EXPR_INT,
+ POS_EXPR_DOUBLE,
+ POS_EXPR_OPERATOR
+} PosExprType;
+
+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;
+}
+
+static gboolean
+pos_eval_get_variable (PosToken *t,
+ int *result,
+ const MetaPositionExprEnv *env,
+ GError **err)
+{
+ /* In certain circumstances (when the theme parser is used outside
+ of metacity) env->theme will be NULL so we run the slow variable search */
+ 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;
+}
+
+static gboolean
+pos_eval_helper (PosToken *tokens,
+ int n_tokens,
+ const MetaPositionExprEnv *env,
+ PosExpr *result,
+ GError **err)
+{
+ /* lazy-ass hardcoded limit on expression size */
+#define MAX_EXPRS 32
+ int paren_level;
+ int first_paren;
+ int i;
+ PosExpr exprs[MAX_EXPRS];
+ int n_exprs;
+ int precedence;
+
+#if 0
+ g_print ("Pos eval helper on %d tokens:\n", n_tokens);
+#endif
+
+ /* Our first goal is to get a list of PosExpr, essentially
+ * substituting variables and handling parentheses.
+ */
+
+ first_paren = 0;
+ paren_level = 0;
+ n_exprs = 0;
+ 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.
+ */
+static gboolean
+pos_eval (MetaDrawSpec *spec,
+ const MetaPositionExprEnv *env,
+ int *val_p,
+ GError **err)
+{
+ PosExpr expr;
+
+ *val_p = 0;
+
+ if (pos_eval_helper (spec->tokens, spec->n_tokens, env, &expr, err))
+ {
+ switch (expr.type)
+ {
+ case POS_EXPR_INT:
+ *val_p = expr.d.int_val;
+ break;
+ case POS_EXPR_DOUBLE:
+ *val_p = expr.d.double_val;
+ break;
+ case POS_EXPR_OPERATOR:
+ g_assert_not_reached ();
+ break;
+ }
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+/* We always return both X and Y, but only one will be meaningful in
+ * most contexts.
+ */
+
+gboolean
+meta_parse_position_expression (MetaDrawSpec *spec,
+ const MetaPositionExprEnv *env,
+ int *x_return,
+ int *y_return,
+ GError **err)
+{
+ /* All positions are in a coordinate system with x, y at the origin.
+ * The expression can have -, +, *, / as operators, floating point
+ * or integer constants, and the variables "width" and "height" and
+ * optionally "object_width" and object_height". Negative numbers
+ * aren't allowed.
+ */
+ int val;
+
+ if (spec->constant)
+ val = spec->value;
+ else
+ {
+ if (pos_eval (spec, env, &spec->value, err) == FALSE)
+ {
+ g_assert (err == NULL || *err != NULL);
+ return FALSE;
+ }
+
+ val = spec->value;
+ }
+
+ if (x_return)
+ *x_return = env->rect.x + val;
+ if (y_return)
+ *y_return = env->rect.y + val;
+
+ return TRUE;
+}
+
+
+gboolean
+meta_parse_size_expression (MetaDrawSpec *spec,
+ const MetaPositionExprEnv *env,
+ int *val_return,
+ GError **err)
+{
+ int val;
+
+ if (spec->constant)
+ val = spec->value;
+ else
+ {
+ if (pos_eval (spec, env, &spec->value, err) == FALSE)
+ {
+ g_assert (err == NULL || *err != NULL);
+ return FALSE;
+ }
+
+ val = spec->value;
+ }
+
+ if (val_return)
+ *val_return = MAX (val, 1); /* require that sizes be at least 1x1 */
+
+ return TRUE;
+}
+
+/* To do this we tokenize, replace variable tokens
+ * that are constants, then reassemble. The purpose
+ * here is to optimize expressions so we don't do hash
+ * lookups to eval them. Obviously it's a tradeoff that
+ * slows down theme load times.
+ */
+gboolean
+meta_theme_replace_constants (MetaTheme *theme,
+ PosToken *tokens,
+ int n_tokens,
+ GError **err)
+{
+ int i;
+ double dval;
+ int ival;
+ gboolean is_constant = TRUE;
+
+ /* Loop through tokenized string looking for variables to replace */
+ for (i = 0; i < n_tokens; i++)
+ {
+ PosToken *t = &tokens[i];
+
+ if (t->type == POS_TOKEN_VARIABLE)
+ {
+ if (meta_theme_lookup_int_constant (theme, t->d.v.name, &ival))
+ {
+ t->type = POS_TOKEN_INT;
+ t->d.i.val = ival;
+ }
+ else if (meta_theme_lookup_float_constant (theme, t->d.v.name, &dval))
+ {
+ t->type = POS_TOKEN_DOUBLE;
+ t->d.d.val = dval;
+ }
+ else
+ {
+ /* If we've found a variable that cannot be replaced then the
+ expression is not a constant expression and we want to
+ replace it with a GQuark */
+
+ t->d.v.name_quark = g_quark_from_string (t->d.v.name);
+ is_constant = FALSE;
+ }
+ }
+ }
+
+ return is_constant;
+}
+
+static int
+parse_x_position_unchecked (MetaDrawSpec *spec,
+ const MetaPositionExprEnv *env)
+{
+ int retval;
+ GError *error;
+
+ retval = 0;
+ error = NULL;
+ if (!meta_parse_position_expression (spec, env, &retval, NULL, &error))
+ {
+ meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
+ error->message);
+
+ g_error_free (error);
+ }
+
+ return retval;
+}
+
+static int
+parse_y_position_unchecked (MetaDrawSpec *spec,
+ const MetaPositionExprEnv *env)
+{
+ int retval;
+ GError *error;
+
+ retval = 0;
+ error = NULL;
+ if (!meta_parse_position_expression (spec, env, NULL, &retval, &error))
+ {
+ meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
+ error->message);
+
+ g_error_free (error);
+ }
+
+ return retval;
+}
+
+static int
+parse_size_unchecked (MetaDrawSpec *spec,
+ MetaPositionExprEnv *env)
+{
+ int retval;
+ GError *error;
+
+ retval = 0;
+ error = NULL;
+ if (!meta_parse_size_expression (spec, env, &retval, &error))
+ {
+ meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
+ error->message);
+
+ g_error_free (error);
+ }
+
+ return retval;
+}
+
+void
+meta_draw_spec_free (MetaDrawSpec *spec)
+{
+ free_tokens (spec->tokens, spec->n_tokens);
+ g_slice_free (MetaDrawSpec, spec);
+}
+
+MetaDrawSpec *
+meta_draw_spec_new (MetaTheme *theme,
+ const char *expr,
+ GError **error)
+{
+ MetaDrawSpec *spec;
+
+ spec = g_slice_new0 (MetaDrawSpec);
+
+ pos_tokenize (expr, &spec->tokens, &spec->n_tokens, NULL);
+
+ spec->constant = meta_theme_replace_constants (theme, spec->tokens,
+ spec->n_tokens, NULL);
+ if (spec->constant)
+ {
+ gboolean result;
+
+ result = pos_eval (spec, NULL, &spec->value, error);
+ if (result == FALSE)
+ {
+ meta_draw_spec_free (spec);
+ return NULL;
+ }
+ }
+
+ return spec;
+}
+
+MetaDrawOp*
+meta_draw_op_new (MetaDrawType type)
+{
+ MetaDrawOp *op;
+ MetaDrawOp dummy;
+ int size;
+
+ size = G_STRUCT_OFFSET (MetaDrawOp, data);
+
+ switch (type)
+ {
+ case META_DRAW_LINE:
+ size += sizeof (dummy.data.line);
+ break;
+
+ case META_DRAW_RECTANGLE:
+ size += sizeof (dummy.data.rectangle);
+ break;
+
+ case META_DRAW_ARC:
+ size += sizeof (dummy.data.arc);
+ break;
+
+ case META_DRAW_CLIP:
+ size += sizeof (dummy.data.clip);
+ break;
+
+ case META_DRAW_TINT:
+ size += sizeof (dummy.data.tint);
+ break;
+
+ case META_DRAW_GRADIENT:
+ size += sizeof (dummy.data.gradient);
+ break;
+
+ case META_DRAW_IMAGE:
+ size += sizeof (dummy.data.image);
+ break;
+
+ case META_DRAW_GTK_ARROW:
+ size += sizeof (dummy.data.gtk_arrow);
+ break;
+
+ case META_DRAW_GTK_BOX:
+ size += sizeof (dummy.data.gtk_box);
+ break;
+
+ case META_DRAW_GTK_VLINE:
+ size += sizeof (dummy.data.gtk_vline);
+ break;
+
+ case META_DRAW_ICON:
+ size += sizeof (dummy.data.icon);
+ break;
+
+ case META_DRAW_TITLE:
+ size += sizeof (dummy.data.title);
+ break;
+ case META_DRAW_OP_LIST:
+ size += sizeof (dummy.data.op_list);
+ break;
+ case META_DRAW_TILE:
+ size += sizeof (dummy.data.tile);
+ break;
+ }
+
+ op = g_malloc0 (size);
+
+ op->type = type;
+
+ return op;
+}
+
+void
+meta_draw_op_free (MetaDrawOp *op)
+{
+ g_return_if_fail (op != NULL);
+
+ switch (op->type)
+ {
+ case META_DRAW_LINE:
+ if (op->data.line.color_spec)
+ meta_color_spec_free (op->data.line.color_spec);
+
+ meta_draw_spec_free (op->data.line.x1);
+ meta_draw_spec_free (op->data.line.y1);
+ meta_draw_spec_free (op->data.line.x2);
+ meta_draw_spec_free (op->data.line.y2);
+ break;
+
+ case META_DRAW_RECTANGLE:
+ if (op->data.rectangle.color_spec)
+ g_free (op->data.rectangle.color_spec);
+
+ meta_draw_spec_free (op->data.rectangle.x);
+ meta_draw_spec_free (op->data.rectangle.y);
+ meta_draw_spec_free (op->data.rectangle.width);
+ meta_draw_spec_free (op->data.rectangle.height);
+ break;
+
+ case META_DRAW_ARC:
+ if (op->data.arc.color_spec)
+ g_free (op->data.arc.color_spec);
+
+ meta_draw_spec_free (op->data.arc.x);
+ meta_draw_spec_free (op->data.arc.y);
+ meta_draw_spec_free (op->data.arc.width);
+ meta_draw_spec_free (op->data.arc.height);
+ break;
+
+ case META_DRAW_CLIP:
+ meta_draw_spec_free (op->data.clip.x);
+ meta_draw_spec_free (op->data.clip.y);
+ meta_draw_spec_free (op->data.clip.width);
+ meta_draw_spec_free (op->data.clip.height);
+ break;
+
+ case META_DRAW_TINT:
+ if (op->data.tint.color_spec)
+ meta_color_spec_free (op->data.tint.color_spec);
+
+ if (op->data.tint.alpha_spec)
+ meta_alpha_gradient_spec_free (op->data.tint.alpha_spec);
+
+ meta_draw_spec_free (op->data.tint.x);
+ meta_draw_spec_free (op->data.tint.y);
+ meta_draw_spec_free (op->data.tint.width);
+ meta_draw_spec_free (op->data.tint.height);
+ break;
+
+ case META_DRAW_GRADIENT:
+ if (op->data.gradient.gradient_spec)
+ meta_gradient_spec_free (op->data.gradient.gradient_spec);
+
+ if (op->data.gradient.alpha_spec)
+ meta_alpha_gradient_spec_free (op->data.gradient.alpha_spec);
+
+ meta_draw_spec_free (op->data.gradient.x);
+ meta_draw_spec_free (op->data.gradient.y);
+ meta_draw_spec_free (op->data.gradient.width);
+ meta_draw_spec_free (op->data.gradient.height);
+ break;
+
+ case META_DRAW_IMAGE:
+ if (op->data.image.alpha_spec)
+ meta_alpha_gradient_spec_free (op->data.image.alpha_spec);
+
+ if (op->data.image.pixbuf)
+ g_object_unref (G_OBJECT (op->data.image.pixbuf));
+
+ if (op->data.image.colorize_spec)
+ meta_color_spec_free (op->data.image.colorize_spec);
+
+ if (op->data.image.colorize_cache_pixbuf)
+ g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf));
+
+ meta_draw_spec_free (op->data.image.x);
+ meta_draw_spec_free (op->data.image.y);
+ meta_draw_spec_free (op->data.image.width);
+ meta_draw_spec_free (op->data.image.height);
+ break;
+
+ case META_DRAW_GTK_ARROW:
+ meta_draw_spec_free (op->data.gtk_arrow.x);
+ meta_draw_spec_free (op->data.gtk_arrow.y);
+ meta_draw_spec_free (op->data.gtk_arrow.width);
+ meta_draw_spec_free (op->data.gtk_arrow.height);
+ break;
+
+ case META_DRAW_GTK_BOX:
+ meta_draw_spec_free (op->data.gtk_box.x);
+ meta_draw_spec_free (op->data.gtk_box.y);
+ meta_draw_spec_free (op->data.gtk_box.width);
+ meta_draw_spec_free (op->data.gtk_box.height);
+ break;
+
+ case META_DRAW_GTK_VLINE:
+ meta_draw_spec_free (op->data.gtk_vline.x);
+ meta_draw_spec_free (op->data.gtk_vline.y1);
+ meta_draw_spec_free (op->data.gtk_vline.y2);
+ break;
+
+ case META_DRAW_ICON:
+ if (op->data.icon.alpha_spec)
+ meta_alpha_gradient_spec_free (op->data.icon.alpha_spec);
+
+ meta_draw_spec_free (op->data.icon.x);
+ meta_draw_spec_free (op->data.icon.y);
+ meta_draw_spec_free (op->data.icon.width);
+ meta_draw_spec_free (op->data.icon.height);
+ break;
+
+ case META_DRAW_TITLE:
+ if (op->data.title.color_spec)
+ meta_color_spec_free (op->data.title.color_spec);
+
+ meta_draw_spec_free (op->data.title.x);
+ meta_draw_spec_free (op->data.title.y);
+ break;
+
+ case META_DRAW_OP_LIST:
+ if (op->data.op_list.op_list)
+ meta_draw_op_list_unref (op->data.op_list.op_list);
+
+ meta_draw_spec_free (op->data.op_list.x);
+ meta_draw_spec_free (op->data.op_list.y);
+ meta_draw_spec_free (op->data.op_list.width);
+ meta_draw_spec_free (op->data.op_list.height);
+ break;
+
+ case META_DRAW_TILE:
+ if (op->data.tile.op_list)
+ meta_draw_op_list_unref (op->data.tile.op_list);
+
+ meta_draw_spec_free (op->data.tile.x);
+ meta_draw_spec_free (op->data.tile.y);
+ meta_draw_spec_free (op->data.tile.width);
+ meta_draw_spec_free (op->data.tile.height);
+ meta_draw_spec_free (op->data.tile.tile_xoffset);
+ meta_draw_spec_free (op->data.tile.tile_yoffset);
+ meta_draw_spec_free (op->data.tile.tile_width);
+ meta_draw_spec_free (op->data.tile.tile_height);
+ break;
+ }
+
+ g_free (op);
+}
+
+static GdkGC*
+get_gc_for_primitive (GtkWidget *widget,
+ GdkDrawable *drawable,
+ MetaColorSpec *color_spec,
+ const GdkRectangle *clip,
+ int line_width)
+{
+ GdkGC *gc;
+ GdkGCValues values;
+ GdkColor color;
+
+ meta_color_spec_render (color_spec, widget, &color);
+
+ values.foreground = color;
+
+ gdk_rgb_find_color (gdk_drawable_get_colormap (drawable),
+ &values.foreground);
+
+ values.line_width = line_width;
+
+ gc = gdk_gc_new_with_values (drawable, &values,
+ GDK_GC_FOREGROUND | GDK_GC_LINE_WIDTH);
+
+ if (clip)
+ gdk_gc_set_clip_rectangle (gc,
+ (GdkRectangle*) clip); /* const cast */
+
+ return gc;
+}
+
+static GdkPixbuf*
+apply_alpha (GdkPixbuf *pixbuf,
+ MetaAlphaGradientSpec *spec,
+ gboolean force_copy)
+{
+ GdkPixbuf *new_pixbuf;
+ gboolean needs_alpha;
+
+ g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
+
+ needs_alpha = spec && (spec->n_alphas > 1 ||
+ spec->alphas[0] != 0xff);
+
+ if (!needs_alpha)
+ return pixbuf;
+
+ if (!gdk_pixbuf_get_has_alpha (pixbuf))
+ {
+ new_pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
+ g_object_unref (G_OBJECT (pixbuf));
+ pixbuf = new_pixbuf;
+ }
+ else if (force_copy)
+ {
+ new_pixbuf = gdk_pixbuf_copy (pixbuf);
+ g_object_unref (G_OBJECT (pixbuf));
+ pixbuf = new_pixbuf;
+ }
+
+ g_assert (gdk_pixbuf_get_has_alpha (pixbuf));
+
+ meta_gradient_add_alpha (pixbuf, spec->alphas, spec->n_alphas, spec->type);
+
+ return pixbuf;
+}
+
+static void
+render_pixbuf (GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ GdkPixbuf *pixbuf,
+ int x,
+ int y)
+{
+ /* grumble, render_to_drawable_alpha does not accept a clip
+ * mask, so we have to go through some BS
+ */
+ /* FIXME once GTK 1.3.13 has been out a while we can use
+ * render_to_drawable() which now does alpha with clip.
+ *
+ * Though the gdk_rectangle_intersect() check may be a useful
+ * optimization anyway.
+ */
+ GdkRectangle pixbuf_rect;
+ GdkRectangle draw_rect;
+
+ pixbuf_rect.x = x;
+ pixbuf_rect.y = y;
+ pixbuf_rect.width = gdk_pixbuf_get_width (pixbuf);
+ pixbuf_rect.height = gdk_pixbuf_get_height (pixbuf);
+
+ if (clip)
+ {
+ if (!gdk_rectangle_intersect ((GdkRectangle*)clip,
+ &pixbuf_rect, &draw_rect))
+ return;
+ }
+ else
+ {
+ draw_rect = pixbuf_rect;
+ }
+
+ gdk_draw_pixbuf (drawable,
+ NULL,
+ pixbuf,
+ draw_rect.x - pixbuf_rect.x,
+ draw_rect.y - pixbuf_rect.y,
+ draw_rect.x, draw_rect.y,
+ draw_rect.width,
+ draw_rect.height,
+ GDK_RGB_DITHER_NORMAL,
+ draw_rect.x - pixbuf_rect.x,
+ draw_rect.y - pixbuf_rect.y);
+}
+
+static GdkPixbuf*
+pixbuf_tile (GdkPixbuf *tile,
+ int width,
+ int height)
+{
+ GdkPixbuf *pixbuf;
+ int tile_width;
+ int tile_height;
+ int i, j;
+
+ tile_width = gdk_pixbuf_get_width (tile);
+ tile_height = gdk_pixbuf_get_height (tile);
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ gdk_pixbuf_get_has_alpha (tile),
+ 8, width, height);
+
+ i = 0;
+ while (i < width)
+ {
+ j = 0;
+ while (j < height)
+ {
+ int w, h;
+
+ w = MIN (tile_width, width - i);
+ h = MIN (tile_height, height - j);
+
+ gdk_pixbuf_copy_area (tile,
+ 0, 0,
+ w, h,
+ pixbuf,
+ i, j);
+
+ j += tile_height;
+ }
+
+ i += tile_width;
+ }
+
+ return pixbuf;
+}
+
+static GdkPixbuf *
+replicate_rows (GdkPixbuf *src,
+ int src_x,
+ int src_y,
+ int width,
+ int height)
+{
+ unsigned int n_channels = gdk_pixbuf_get_n_channels (src);
+ unsigned int src_rowstride = gdk_pixbuf_get_rowstride (src);
+ unsigned char *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x
+ * n_channels);
+ unsigned char *dest_pixels;
+ GdkPixbuf *result;
+ unsigned int dest_rowstride;
+ int i;
+
+ result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
+ width, height);
+ dest_rowstride = gdk_pixbuf_get_rowstride (result);
+ dest_pixels = gdk_pixbuf_get_pixels (result);
+
+ for (i = 0; i < height; i++)
+ memcpy (dest_pixels + dest_rowstride * i, pixels, n_channels * width);
+
+ return result;
+}
+
+static GdkPixbuf *
+replicate_cols (GdkPixbuf *src,
+ int src_x,
+ int src_y,
+ int width,
+ int height)
+{
+ unsigned int n_channels = gdk_pixbuf_get_n_channels (src);
+ unsigned int src_rowstride = gdk_pixbuf_get_rowstride (src);
+ unsigned char *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x
+ * n_channels);
+ unsigned char *dest_pixels;
+ GdkPixbuf *result;
+ unsigned int dest_rowstride;
+ int i, j;
+
+ result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
+ width, height);
+ dest_rowstride = gdk_pixbuf_get_rowstride (result);
+ dest_pixels = gdk_pixbuf_get_pixels (result);
+
+ for (i = 0; i < height; i++)
+ {
+ unsigned char *p = dest_pixels + dest_rowstride * i;
+ unsigned char *q = pixels + src_rowstride * i;
+
+ unsigned char r = *(q++);
+ unsigned char g = *(q++);
+ unsigned char b = *(q++);
+
+ if (n_channels == 4)
+ {
+ unsigned char a;
+
+ a = *(q++);
+
+ for (j = 0; j < width; j++)
+ {
+ *(p++) = r;
+ *(p++) = g;
+ *(p++) = b;
+ *(p++) = a;
+ }
+ }
+ else
+ {
+ for (j = 0; j < width; j++)
+ {
+ *(p++) = r;
+ *(p++) = g;
+ *(p++) = b;
+ }
+ }
+ }
+
+ return result;
+}
+
+static GdkPixbuf*
+scale_and_alpha_pixbuf (GdkPixbuf *src,
+ MetaAlphaGradientSpec *alpha_spec,
+ MetaImageFillType fill_type,
+ int width,
+ int height,
+ gboolean vertical_stripes,
+ gboolean horizontal_stripes)
+{
+ GdkPixbuf *pixbuf;
+ GdkPixbuf *temp_pixbuf;
+
+ pixbuf = NULL;
+
+ pixbuf = src;
+
+ if (gdk_pixbuf_get_width (pixbuf) == width &&
+ gdk_pixbuf_get_height (pixbuf) == height)
+ {
+ g_object_ref (G_OBJECT (pixbuf));
+ }
+ else
+ {
+ if (fill_type == META_IMAGE_FILL_TILE)
+ {
+ pixbuf = pixbuf_tile (pixbuf, width, height);
+ }
+ else
+ {
+ int src_h, src_w, dest_h, dest_w;
+ src_h = gdk_pixbuf_get_height (src);
+ src_w = gdk_pixbuf_get_width (src);
+
+ /* prefer to replicate_cols if possible, as that
+ * is faster (no memory reads)
+ */
+ if (horizontal_stripes)
+ {
+ dest_w = gdk_pixbuf_get_width (src);
+ dest_h = height;
+ }
+ else if (vertical_stripes)
+ {
+ dest_w = width;
+ dest_h = gdk_pixbuf_get_height (src);
+ }
+
+ else
+ {
+ dest_w = width;
+ dest_h = height;
+ }
+
+ if (dest_w == src_w && dest_h == src_h)
+ {
+ temp_pixbuf = src;
+ g_object_ref (G_OBJECT (temp_pixbuf));
+ }
+ else
+ {
+ temp_pixbuf = gdk_pixbuf_scale_simple (src,
+ dest_w, dest_h,
+ GDK_INTERP_BILINEAR);
+ }
+
+ /* prefer to replicate_cols if possible, as that
+ * is faster (no memory reads)
+ */
+ if (horizontal_stripes)
+ {
+ pixbuf = replicate_cols (temp_pixbuf, 0, 0, width, height);
+ g_object_unref (G_OBJECT (temp_pixbuf));
+ }
+ else if (vertical_stripes)
+ {
+ pixbuf = replicate_rows (temp_pixbuf, 0, 0, width, height);
+ g_object_unref (G_OBJECT (temp_pixbuf));
+ }
+ else
+ {
+ pixbuf = temp_pixbuf;
+ }
+ }
+ }
+
+ if (pixbuf)
+ pixbuf = apply_alpha (pixbuf, alpha_spec, pixbuf == src);
+
+ return pixbuf;
+}
+
+static GdkPixbuf*
+draw_op_as_pixbuf (const MetaDrawOp *op,
+ GtkWidget *widget,
+ const MetaDrawInfo *info,
+ int width,
+ int height)
+{
+ /* Try to get the op as a pixbuf, assuming w/h in the op
+ * matches the width/height passed in. return NULL
+ * if the op can't be converted to an equivalent pixbuf.
+ */
+ GdkPixbuf *pixbuf;
+
+ pixbuf = NULL;
+
+ switch (op->type)
+ {
+ case META_DRAW_LINE:
+ break;
+
+ case META_DRAW_RECTANGLE:
+ if (op->data.rectangle.filled)
+ {
+ GdkColor color;
+
+ meta_color_spec_render (op->data.rectangle.color_spec,
+ widget,
+ &color);
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ FALSE,
+ 8, width, height);
+
+ gdk_pixbuf_fill (pixbuf, GDK_COLOR_RGBA (color));
+ }
+ break;
+
+ case META_DRAW_ARC:
+ break;
+
+ case META_DRAW_CLIP:
+ break;
+
+ case META_DRAW_TINT:
+ {
+ GdkColor color;
+ guint32 rgba;
+ gboolean has_alpha;
+
+ meta_color_spec_render (op->data.rectangle.color_spec,
+ widget,
+ &color);
+
+ has_alpha =
+ op->data.tint.alpha_spec &&
+ (op->data.tint.alpha_spec->n_alphas > 1 ||
+ op->data.tint.alpha_spec->alphas[0] != 0xff);
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ has_alpha,
+ 8, width, height);
+
+ if (!has_alpha)
+ {
+ rgba = GDK_COLOR_RGBA (color);
+
+ gdk_pixbuf_fill (pixbuf, rgba);
+ }
+ else if (op->data.tint.alpha_spec->n_alphas == 1)
+ {
+ rgba = GDK_COLOR_RGBA (color);
+ rgba &= ~0xff;
+ rgba |= op->data.tint.alpha_spec->alphas[0];
+
+ gdk_pixbuf_fill (pixbuf, rgba);
+ }
+ else
+ {
+ rgba = GDK_COLOR_RGBA (color);
+
+ gdk_pixbuf_fill (pixbuf, rgba);
+
+ meta_gradient_add_alpha (pixbuf,
+ op->data.tint.alpha_spec->alphas,
+ op->data.tint.alpha_spec->n_alphas,
+ op->data.tint.alpha_spec->type);
+ }
+ }
+ break;
+
+ case META_DRAW_GRADIENT:
+ {
+ pixbuf = meta_gradient_spec_render (op->data.gradient.gradient_spec,
+ widget, width, height);
+
+ pixbuf = apply_alpha (pixbuf,
+ op->data.gradient.alpha_spec,
+ FALSE);
+ }
+ break;
+
+
+ case META_DRAW_IMAGE:
+ {
+ if (op->data.image.colorize_spec)
+ {
+ GdkColor color;
+
+ meta_color_spec_render (op->data.image.colorize_spec,
+ widget, &color);
+
+ if (op->data.image.colorize_cache_pixbuf == NULL ||
+ op->data.image.colorize_cache_pixel != GDK_COLOR_RGB (color))
+ {
+ if (op->data.image.colorize_cache_pixbuf)
+ g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf));
+
+ /* const cast here */
+ ((MetaDrawOp*)op)->data.image.colorize_cache_pixbuf =
+ colorize_pixbuf (op->data.image.pixbuf,
+ &color);
+ ((MetaDrawOp*)op)->data.image.colorize_cache_pixel =
+ GDK_COLOR_RGB (color);
+ }
+
+ if (op->data.image.colorize_cache_pixbuf)
+ {
+ pixbuf = scale_and_alpha_pixbuf (op->data.image.colorize_cache_pixbuf,
+ op->data.image.alpha_spec,
+ op->data.image.fill_type,
+ width, height,
+ op->data.image.vertical_stripes,
+ op->data.image.horizontal_stripes);
+ }
+ }
+ else
+ {
+ pixbuf = scale_and_alpha_pixbuf (op->data.image.pixbuf,
+ op->data.image.alpha_spec,
+ op->data.image.fill_type,
+ width, height,
+ op->data.image.vertical_stripes,
+ op->data.image.horizontal_stripes);
+ }
+ break;
+ }
+
+ case META_DRAW_GTK_ARROW:
+ case META_DRAW_GTK_BOX:
+ case META_DRAW_GTK_VLINE:
+ break;
+
+ case META_DRAW_ICON:
+ if (info->mini_icon &&
+ width <= gdk_pixbuf_get_width (info->mini_icon) &&
+ height <= gdk_pixbuf_get_height (info->mini_icon))
+ pixbuf = scale_and_alpha_pixbuf (info->mini_icon,
+ op->data.icon.alpha_spec,
+ op->data.icon.fill_type,
+ width, height,
+ FALSE, FALSE);
+ else if (info->icon)
+ pixbuf = scale_and_alpha_pixbuf (info->icon,
+ op->data.icon.alpha_spec,
+ op->data.icon.fill_type,
+ width, height,
+ FALSE, FALSE);
+ break;
+
+ case META_DRAW_TITLE:
+ break;
+
+ case META_DRAW_OP_LIST:
+ break;
+
+ case META_DRAW_TILE:
+ break;
+ }
+
+ return pixbuf;
+}
+
+static void
+fill_env (MetaPositionExprEnv *env,
+ const MetaDrawInfo *info,
+ MetaRectangle logical_region)
+{
+ /* FIXME this stuff could be raised into draw_op_list_draw() probably
+ */
+ env->rect = logical_region;
+ env->object_width = -1;
+ env->object_height = -1;
+ if (info->fgeom)
+ {
+ env->left_width = info->fgeom->left_width;
+ env->right_width = info->fgeom->right_width;
+ env->top_height = info->fgeom->top_height;
+ env->bottom_height = info->fgeom->bottom_height;
+ }
+ else
+ {
+ env->left_width = 0;
+ env->right_width = 0;
+ env->top_height = 0;
+ env->bottom_height = 0;
+ }
+
+ env->mini_icon_width = info->mini_icon ? gdk_pixbuf_get_width (info->mini_icon) : 0;
+ env->mini_icon_height = info->mini_icon ? gdk_pixbuf_get_height (info->mini_icon) : 0;
+ env->icon_width = info->icon ? gdk_pixbuf_get_width (info->icon) : 0;
+ env->icon_height = info->icon ? gdk_pixbuf_get_height (info->icon) : 0;
+
+ env->title_width = info->title_layout_width;
+ env->title_height = info->title_layout_height;
+ env->theme = meta_current_theme;
+}
+
+static void
+meta_draw_op_draw_with_env (const MetaDrawOp *op,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ const MetaDrawInfo *info,
+ MetaRectangle rect,
+ MetaPositionExprEnv *env)
+{
+ GdkGC *gc;
+
+ switch (op->type)
+ {
+ case META_DRAW_LINE:
+ {
+ int x1, x2, y1, y2;
+
+ gc = get_gc_for_primitive (widget, drawable,
+ op->data.line.color_spec,
+ clip,
+ op->data.line.width);
+
+ if (op->data.line.dash_on_length > 0 &&
+ op->data.line.dash_off_length > 0)
+ {
+ gint8 dash_list[2];
+ dash_list[0] = op->data.line.dash_on_length;
+ dash_list[1] = op->data.line.dash_off_length;
+ gdk_gc_set_dashes (gc, 0, dash_list, 2);
+ }
+
+ x1 = parse_x_position_unchecked (op->data.line.x1, env);
+ y1 = parse_y_position_unchecked (op->data.line.y1, env);
+ x2 = parse_x_position_unchecked (op->data.line.x2, env);
+ y2 = parse_y_position_unchecked (op->data.line.y2, env);
+
+ gdk_draw_line (drawable, gc, x1, y1, x2, y2);
+
+ g_object_unref (G_OBJECT (gc));
+ }
+ break;
+
+ case META_DRAW_RECTANGLE:
+ {
+ int rx, ry, rwidth, rheight;
+
+ gc = get_gc_for_primitive (widget, drawable,
+ op->data.rectangle.color_spec,
+ clip, 0);
+
+ rx = parse_x_position_unchecked (op->data.rectangle.x, env);
+ ry = parse_y_position_unchecked (op->data.rectangle.y, env);
+ rwidth = parse_size_unchecked (op->data.rectangle.width, env);
+ rheight = parse_size_unchecked (op->data.rectangle.height, env);
+
+ gdk_draw_rectangle (drawable, gc,
+ op->data.rectangle.filled,
+ rx, ry, rwidth, rheight);
+
+ g_object_unref (G_OBJECT (gc));
+ }
+ break;
+
+ case META_DRAW_ARC:
+ {
+ int rx, ry, rwidth, rheight;
+
+ gc = get_gc_for_primitive (widget, drawable,
+ op->data.arc.color_spec,
+ clip, 0);
+
+ rx = parse_x_position_unchecked (op->data.arc.x, env);
+ ry = parse_y_position_unchecked (op->data.arc.y, env);
+ rwidth = parse_size_unchecked (op->data.arc.width, env);
+ rheight = parse_size_unchecked (op->data.arc.height, env);
+
+ gdk_draw_arc (drawable,
+ gc,
+ op->data.arc.filled,
+ rx, ry, rwidth, rheight,
+ op->data.arc.start_angle * (360.0 * 64.0) -
+ (90.0 * 64.0), /* start at 12 instead of 3 oclock */
+ op->data.arc.extent_angle * (360.0 * 64.0));
+
+ g_object_unref (G_OBJECT (gc));
+ }
+ break;
+
+ case META_DRAW_CLIP:
+ break;
+
+ case META_DRAW_TINT:
+ {
+ int rx, ry, rwidth, rheight;
+ gboolean needs_alpha;
+
+ needs_alpha = op->data.tint.alpha_spec &&
+ (op->data.tint.alpha_spec->n_alphas > 1 ||
+ op->data.tint.alpha_spec->alphas[0] != 0xff);
+
+ rx = parse_x_position_unchecked (op->data.tint.x, env);
+ ry = parse_y_position_unchecked (op->data.tint.y, env);
+ rwidth = parse_size_unchecked (op->data.tint.width, env);
+ rheight = parse_size_unchecked (op->data.tint.height, env);
+
+ if (!needs_alpha)
+ {
+ gc = get_gc_for_primitive (widget, drawable,
+ op->data.tint.color_spec,
+ clip, 0);
+
+ gdk_draw_rectangle (drawable, gc,
+ TRUE,
+ rx, ry, rwidth, rheight);
+
+ g_object_unref (G_OBJECT (gc));
+ }
+ else
+ {
+ GdkPixbuf *pixbuf;
+
+ pixbuf = draw_op_as_pixbuf (op, widget, info,
+ rwidth, rheight);
+
+ if (pixbuf)
+ {
+ render_pixbuf (drawable, clip, pixbuf, rx, ry);
+
+ g_object_unref (G_OBJECT (pixbuf));
+ }
+ }
+ }
+ break;
+
+ case META_DRAW_GRADIENT:
+ {
+ int rx, ry, rwidth, rheight;
+ GdkPixbuf *pixbuf;
+
+ rx = parse_x_position_unchecked (op->data.gradient.x, env);
+ ry = parse_y_position_unchecked (op->data.gradient.y, env);
+ rwidth = parse_size_unchecked (op->data.gradient.width, env);
+ rheight = parse_size_unchecked (op->data.gradient.height, env);
+
+ pixbuf = draw_op_as_pixbuf (op, widget, info,
+ rwidth, rheight);
+
+ if (pixbuf)
+ {
+ render_pixbuf (drawable, clip, pixbuf, rx, ry);
+
+ g_object_unref (G_OBJECT (pixbuf));
+ }
+ }
+ break;
+
+ case META_DRAW_IMAGE:
+ {
+ int rx, ry, rwidth, rheight;
+ GdkPixbuf *pixbuf;
+
+ if (op->data.image.pixbuf)
+ {
+ env->object_width = gdk_pixbuf_get_width (op->data.image.pixbuf);
+ env->object_height = gdk_pixbuf_get_height (op->data.image.pixbuf);
+ }
+
+ rwidth = parse_size_unchecked (op->data.image.width, env);
+ rheight = parse_size_unchecked (op->data.image.height, env);
+
+ pixbuf = draw_op_as_pixbuf (op, widget, info,
+ rwidth, rheight);
+
+ if (pixbuf)
+ {
+ rx = parse_x_position_unchecked (op->data.image.x, env);
+ ry = parse_y_position_unchecked (op->data.image.y, env);
+
+ render_pixbuf (drawable, clip, pixbuf, rx, ry);
+
+ g_object_unref (G_OBJECT (pixbuf));
+ }
+ }
+ break;
+
+ case META_DRAW_GTK_ARROW:
+ {
+ int rx, ry, rwidth, rheight;
+
+ rx = parse_x_position_unchecked (op->data.gtk_arrow.x, env);
+ ry = parse_y_position_unchecked (op->data.gtk_arrow.y, env);
+ rwidth = parse_size_unchecked (op->data.gtk_arrow.width, env);
+ rheight = parse_size_unchecked (op->data.gtk_arrow.height, env);
+
+ gtk_paint_arrow (widget->style,
+ drawable,
+ op->data.gtk_arrow.state,
+ op->data.gtk_arrow.shadow,
+ (GdkRectangle*) clip,
+ widget,
+ "metacity",
+ op->data.gtk_arrow.arrow,
+ op->data.gtk_arrow.filled,
+ rx, ry, rwidth, rheight);
+ }
+ break;
+
+ case META_DRAW_GTK_BOX:
+ {
+ int rx, ry, rwidth, rheight;
+
+ rx = parse_x_position_unchecked (op->data.gtk_box.x, env);
+ ry = parse_y_position_unchecked (op->data.gtk_box.y, env);
+ rwidth = parse_size_unchecked (op->data.gtk_box.width, env);
+ rheight = parse_size_unchecked (op->data.gtk_box.height, env);
+
+ gtk_paint_box (widget->style,
+ drawable,
+ op->data.gtk_box.state,
+ op->data.gtk_box.shadow,
+ (GdkRectangle*) clip,
+ widget,
+ "metacity",
+ rx, ry, rwidth, rheight);
+ }
+ break;
+
+ case META_DRAW_GTK_VLINE:
+ {
+ int rx, ry1, ry2;
+
+ rx = parse_x_position_unchecked (op->data.gtk_vline.x, env);
+ ry1 = parse_y_position_unchecked (op->data.gtk_vline.y1, env);
+ ry2 = parse_y_position_unchecked (op->data.gtk_vline.y2, env);
+
+ gtk_paint_vline (widget->style,
+ drawable,
+ op->data.gtk_vline.state,
+ (GdkRectangle*) clip,
+ widget,
+ "metacity",
+ ry1, ry2, rx);
+ }
+ break;
+
+ case META_DRAW_ICON:
+ {
+ int rx, ry, rwidth, rheight;
+ GdkPixbuf *pixbuf;
+
+ rwidth = parse_size_unchecked (op->data.icon.width, env);
+ rheight = parse_size_unchecked (op->data.icon.height, env);
+
+ pixbuf = draw_op_as_pixbuf (op, widget, info,
+ rwidth, rheight);
+
+ if (pixbuf)
+ {
+ rx = parse_x_position_unchecked (op->data.icon.x, env);
+ ry = parse_y_position_unchecked (op->data.icon.y, env);
+
+ render_pixbuf (drawable, clip, pixbuf, rx, ry);
+
+ g_object_unref (G_OBJECT (pixbuf));
+ }
+ }
+ break;
+
+ case META_DRAW_TITLE:
+ if (info->title_layout)
+ {
+ int rx, ry;
+
+ gc = get_gc_for_primitive (widget, drawable,
+ op->data.title.color_spec,
+ clip, 0);
+
+ rx = parse_x_position_unchecked (op->data.title.x, env);
+ ry = parse_y_position_unchecked (op->data.title.y, env);
+
+ gdk_draw_layout (drawable, gc,
+ rx, ry,
+ info->title_layout);
+
+ g_object_unref (G_OBJECT (gc));
+ }
+ break;
+
+ case META_DRAW_OP_LIST:
+ {
+ MetaRectangle d_rect;
+
+ d_rect.x = parse_x_position_unchecked (op->data.op_list.x, env);
+ d_rect.y = parse_y_position_unchecked (op->data.op_list.y, env);
+ d_rect.width = parse_size_unchecked (op->data.op_list.width, env);
+ d_rect.height = parse_size_unchecked (op->data.op_list.height, env);
+
+ meta_draw_op_list_draw (op->data.op_list.op_list,
+ 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 (op->data.tile.op_list,
+ widget, drawable, &new_clip, info,
+ tile);
+
+ tile.y += tile.height;
+ }
+
+ tile.x += tile.width;
+ }
+ }
+ }
+ break;
+ }
+}
+
+void
+meta_draw_op_draw (const MetaDrawOp *op,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ const MetaDrawInfo *info,
+ MetaRectangle logical_region)
+{
+ MetaPositionExprEnv env;
+
+ fill_env (&env, info, logical_region);
+
+ meta_draw_op_draw_with_env (op, widget, drawable, clip,
+ info, logical_region,
+ &env);
+
+}
+
+MetaDrawOpList*
+meta_draw_op_list_new (int n_preallocs)
+{
+ MetaDrawOpList *op_list;
+
+ g_return_val_if_fail (n_preallocs >= 0, NULL);
+
+ op_list = g_new (MetaDrawOpList, 1);
+
+ op_list->refcount = 1;
+ op_list->n_allocated = n_preallocs;
+ op_list->ops = g_new (MetaDrawOp*, op_list->n_allocated);
+ op_list->n_ops = 0;
+
+ return op_list;
+}
+
+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 (const MetaDrawOpList *op_list,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ const GdkRectangle *clip,
+ const MetaDrawInfo *info,
+ MetaRectangle rect)
+{
+ int i;
+ GdkRectangle active_clip;
+ GdkRectangle orig_clip;
+ MetaPositionExprEnv env;
+
+ 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,
+ widget, drawable, &active_clip, info,
+ rect,
+ &env);
+ }
+ }
+}
+
+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;
+}
+
+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;
+}
+
+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 (MetaFrameStyle *style,
+ GtkWidget *widget,
+ GdkDrawable *drawable,
+ int x_offset,
+ int y_offset,
+ const GdkRectangle *clip,
+ const MetaFrameGeometry *fgeom,
+ int client_width,
+ int client_height,
+ PangoLayout *title_layout,
+ int text_height,
+ MetaButtonState button_states[META_BUTTON_TYPE_LAST],
+ GdkPixbuf *mini_icon,
+ GdkPixbuf *icon)
+{
+ int i, 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;
+
+ 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 (op_list,
+ 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 (op_list,
+ 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;
+ }
+}
+
+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)
+ {
+ 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)
+ {
+ 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:<