From 28a029a4990d2a84f9d6a0b890eba812ea503998 Mon Sep 17 00:00:00 2001 From: Perberos Date: Thu, 1 Dec 2011 23:52:01 -0300 Subject: moving from https://github.com/perberos/mate-desktop-environment --- src/ui/draw-workspace.c | 229 ++ src/ui/draw-workspace.h | 61 + src/ui/fixedtip.c | 133 + src/ui/fixedtip.h | 69 + src/ui/frames.c | 2940 +++++++++++++++++++++ src/ui/frames.h | 163 ++ src/ui/gradient.c | 842 ++++++ src/ui/gradient.h | 65 + src/ui/menu.c | 509 ++++ src/ui/menu.h | 51 + src/ui/metaaccellabel.c | 456 ++++ src/ui/metaaccellabel.h | 106 + src/ui/preview-widget.c | 601 +++++ src/ui/preview-widget.h | 87 + src/ui/resizepopup.c | 217 ++ src/ui/tabpopup.c | 967 +++++++ src/ui/testgradient.c | 336 +++ src/ui/theme-parser.c | 4212 ++++++++++++++++++++++++++++++ src/ui/theme-parser.h | 32 + src/ui/theme-viewer.c | 1338 ++++++++++ src/ui/theme.c | 6652 +++++++++++++++++++++++++++++++++++++++++++++++ src/ui/theme.h | 1190 +++++++++ src/ui/themewidget.c | 183 ++ src/ui/themewidget.h | 78 + src/ui/ui.c | 1117 ++++++++ 25 files changed, 22634 insertions(+) create mode 100644 src/ui/draw-workspace.c create mode 100644 src/ui/draw-workspace.h create mode 100644 src/ui/fixedtip.c create mode 100644 src/ui/fixedtip.h create mode 100644 src/ui/frames.c create mode 100644 src/ui/frames.h create mode 100644 src/ui/gradient.c create mode 100644 src/ui/gradient.h create mode 100644 src/ui/menu.c create mode 100644 src/ui/menu.h create mode 100644 src/ui/metaaccellabel.c create mode 100644 src/ui/metaaccellabel.h create mode 100644 src/ui/preview-widget.c create mode 100644 src/ui/preview-widget.h create mode 100644 src/ui/resizepopup.c create mode 100644 src/ui/tabpopup.c create mode 100644 src/ui/testgradient.c create mode 100644 src/ui/theme-parser.c create mode 100644 src/ui/theme-parser.h create mode 100644 src/ui/theme-viewer.c create mode 100644 src/ui/theme.c create mode 100644 src/ui/theme.h create mode 100644 src/ui/themewidget.c create mode 100644 src/ui/themewidget.h create mode 100644 src/ui/ui.c (limited to 'src/ui') diff --git a/src/ui/draw-workspace.c b/src/ui/draw-workspace.c new file mode 100644 index 00000000..b1a7c733 --- /dev/null +++ b/src/ui/draw-workspace.c @@ -0,0 +1,229 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Draw a workspace */ + +/* This file should not be modified to depend on other files in + * libwnck or marco, since it's used in both of them + */ + +/* + * Copyright (C) 2002 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "draw-workspace.h" + + +static void +get_window_rect (const WnckWindowDisplayInfo *win, + int screen_width, + int screen_height, + const GdkRectangle *workspace_rect, + GdkRectangle *rect) +{ + double width_ratio, height_ratio; + int x, y, width, height; + + width_ratio = (double) workspace_rect->width / (double) screen_width; + height_ratio = (double) workspace_rect->height / (double) screen_height; + + x = win->x; + y = win->y; + width = win->width; + height = win->height; + + x *= width_ratio; + y *= height_ratio; + width *= width_ratio; + height *= height_ratio; + + x += workspace_rect->x; + y += workspace_rect->y; + + if (width < 3) + width = 3; + if (height < 3) + height = 3; + + rect->x = x; + rect->y = y; + rect->width = width; + rect->height = height; +} + +static void +draw_window (GtkWidget *widget, + GdkDrawable *drawable, + const WnckWindowDisplayInfo *win, + const GdkRectangle *winrect, + GtkStateType state) +{ + cairo_t *cr; + GdkPixbuf *icon; + int icon_x, icon_y, icon_w, icon_h; + gboolean is_active; + GdkColor *color; + GtkStyle *style; + + is_active = win->is_active; + + cr = gdk_cairo_create (drawable); + cairo_rectangle (cr, winrect->x, winrect->y, winrect->width, winrect->height); + cairo_clip (cr); + + style = gtk_widget_get_style (widget); + if (is_active) + color = &style->light[state]; + else + color = &style->bg[state]; + cairo_set_source_rgb (cr, + color->red / 65535., + color->green / 65535., + color->blue / 65535.); + + cairo_rectangle (cr, + winrect->x + 1, winrect->y + 1, + MAX (0, winrect->width - 2), MAX (0, winrect->height - 2)); + cairo_fill (cr); + + + icon = win->icon; + + icon_w = icon_h = 0; + + if (icon) + { + icon_w = gdk_pixbuf_get_width (icon); + icon_h = gdk_pixbuf_get_height (icon); + + /* If the icon is too big, fall back to mini icon. + * We don't arbitrarily scale the icon, because it's + * just too slow on my Athlon 850. + */ + if (icon_w > (winrect->width - 2) || + icon_h > (winrect->height - 2)) + { + icon = win->mini_icon; + if (icon) + { + icon_w = gdk_pixbuf_get_width (icon); + icon_h = gdk_pixbuf_get_height (icon); + + /* Give up. */ + if (icon_w > (winrect->width - 2) || + icon_h > (winrect->height - 2)) + icon = NULL; + } + } + } + + if (icon) + { + icon_x = winrect->x + (winrect->width - icon_w) / 2; + icon_y = winrect->y + (winrect->height - icon_h) / 2; + + cairo_save (cr); + gdk_cairo_set_source_pixbuf (cr, icon, icon_x, icon_y); + cairo_rectangle (cr, icon_x, icon_y, icon_w, icon_h); + cairo_clip (cr); + cairo_paint (cr); + cairo_restore (cr); + } + + if (is_active) + color = &style->fg[state]; + else + color = &style->fg[state]; + + cairo_set_source_rgb (cr, + color->red / 65535., + color->green / 65535., + color->blue / 65535.); + cairo_set_line_width (cr, 1.0); + cairo_rectangle (cr, + winrect->x + 0.5, winrect->y + 0.5, + MAX (0, winrect->width - 1), MAX (0, winrect->height - 1)); + cairo_stroke (cr); + + cairo_destroy (cr); +} + +void +wnck_draw_workspace (GtkWidget *widget, + GdkDrawable *drawable, + int x, + int y, + int width, + int height, + int screen_width, + int screen_height, + GdkPixbuf *workspace_background, + gboolean is_active, + const WnckWindowDisplayInfo *windows, + int n_windows) +{ + int i; + GdkRectangle workspace_rect; + GtkStateType state; + cairo_t *cr; + + workspace_rect.x = x; + workspace_rect.y = y; + workspace_rect.width = width; + workspace_rect.height = height; + + if (is_active) + state = GTK_STATE_SELECTED; + else if (workspace_background) + state = GTK_STATE_PRELIGHT; + else + state = GTK_STATE_NORMAL; + + cr = gdk_cairo_create (drawable); + + if (workspace_background) + { + gdk_cairo_set_source_pixbuf (cr, workspace_background, x, y); + cairo_paint (cr); + } + else + { + gdk_cairo_set_source_color (cr, >k_widget_get_style (widget)->dark[state]); + cairo_rectangle (cr, x, y, width, height); + cairo_fill (cr); + } + + cairo_destroy (cr); + + i = 0; + while (i < n_windows) + { + const WnckWindowDisplayInfo *win = &windows[i]; + GdkRectangle winrect; + + get_window_rect (win, screen_width, + screen_height, &workspace_rect, &winrect); + + draw_window (widget, + drawable, + win, + &winrect, + state); + + ++i; + } +} diff --git a/src/ui/draw-workspace.h b/src/ui/draw-workspace.h new file mode 100644 index 00000000..a8b77cab --- /dev/null +++ b/src/ui/draw-workspace.h @@ -0,0 +1,61 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Draw a workspace */ + +/* This file should not be modified to depend on other files in + * libwnck or marco, since it's used in both of them + */ + +/* + * Copyright (C) 2002 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef WNCK_DRAW_WORKSPACE_H +#define WNCK_DRAW_WORKSPACE_H + +#include +#include +#include + +typedef struct +{ + GdkPixbuf *icon; + GdkPixbuf *mini_icon; + int x; + int y; + int width; + int height; + + guint is_active : 1; + +} WnckWindowDisplayInfo; + +void wnck_draw_workspace (GtkWidget *widget, + GdkDrawable *drawable, + int x, + int y, + int width, + int height, + int screen_width, + int screen_height, + GdkPixbuf *workspace_background, + gboolean is_active, + const WnckWindowDisplayInfo *windows, + int n_windows); + +#endif diff --git a/src/ui/fixedtip.c b/src/ui/fixedtip.c new file mode 100644 index 00000000..93370636 --- /dev/null +++ b/src/ui/fixedtip.c @@ -0,0 +1,133 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco fixed tooltip routine */ + +/* + * Copyright (C) 2001 Havoc Pennington + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include +#include "fixedtip.h" +#include "ui.h" + +/** + * The floating rectangle. This is a GtkWindow, and it contains + * the "label" widget, below. + */ +static GtkWidget *tip = NULL; + +/** + * The actual text that gets displayed. + */ +static GtkWidget *label = NULL; +/* + * X coordinate of the right-hand edge of the screen. + * + * \bug This appears to be a bug; screen_right_edge is calculated only when + * the window is redrawn. Actually we should never cache it because + * different windows are different sizes. + */ +static int screen_right_edge = 0; +/* + * Y coordinate of the bottom edge of the screen. + * + * \bug As with screen_right_edge. + */ +static int screen_bottom_edge = 0; + +static gint +expose_handler (GtkTooltips *tooltips) +{ + gtk_paint_flat_box (gtk_widget_get_style (tip), gtk_widget_get_window (tip), + GTK_STATE_NORMAL, GTK_SHADOW_OUT, + NULL, tip, "tooltip", + 0, 0, -1, -1); + + return FALSE; +} + +void +meta_fixed_tip_show (Display *xdisplay, int screen_number, + int root_x, int root_y, + const char *markup_text) +{ + int w, h; + + if (tip == NULL) + { + tip = gtk_window_new (GTK_WINDOW_POPUP); + gtk_window_set_type_hint (GTK_WINDOW(tip), GDK_WINDOW_TYPE_HINT_TOOLTIP); + + { + GdkScreen *gdk_screen; + GdkRectangle monitor; + gint mon_num; + + gdk_screen = gdk_display_get_screen (gdk_display_get_default (), + screen_number); + gtk_window_set_screen (GTK_WINDOW (tip), + gdk_screen); + mon_num = gdk_screen_get_monitor_at_point (gdk_screen, root_x, root_y); + gdk_screen_get_monitor_geometry (gdk_screen, mon_num, &monitor); + screen_right_edge = monitor.x + monitor.width; + screen_bottom_edge = monitor.y + monitor.height; + } + + gtk_widget_set_app_paintable (tip, TRUE); + gtk_window_set_resizable (GTK_WINDOW (tip), FALSE); + gtk_widget_set_name (tip, "gtk-tooltips"); + gtk_container_set_border_width (GTK_CONTAINER (tip), 4); + + g_signal_connect_swapped (tip, "expose_event", + G_CALLBACK (expose_handler), NULL); + + label = gtk_label_new (NULL); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0.5, 0.5); + gtk_widget_show (label); + + gtk_container_add (GTK_CONTAINER (tip), label); + + g_signal_connect (tip, "destroy", + G_CALLBACK (gtk_widget_destroyed), &tip); + } + + gtk_label_set_markup (GTK_LABEL (label), markup_text); + + gtk_window_get_size (GTK_WINDOW (tip), &w, &h); + + if (meta_ui_get_direction() == META_UI_DIRECTION_RTL) + root_x = MAX(0, root_x - w); + + if ((root_x + w) > screen_right_edge) + root_x -= (root_x + w) - screen_right_edge; + + gtk_window_move (GTK_WINDOW (tip), root_x, root_y); + + gtk_widget_show (tip); +} + +void +meta_fixed_tip_hide (void) +{ + if (tip) + { + gtk_widget_destroy (tip); + tip = NULL; + } +} diff --git a/src/ui/fixedtip.h b/src/ui/fixedtip.h new file mode 100644 index 00000000..28a618d9 --- /dev/null +++ b/src/ui/fixedtip.h @@ -0,0 +1,69 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * Copyright (C) 2001 Havoc Pennington + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +/** + * \file fixedtip.h Marco fixed tooltip routine + * + * Sometimes we want to display a small floating rectangle with helpful + * text near the pointer. For example, if the user holds the mouse over + * the maximise button, we can display a tooltip saying "Maximize". + * The text is localised, of course. + * + * This file contains the functions to create and delete these tooltips. + * + * \todo Since we now consider MetaDisplay a singleton, there can be + * only one tooltip per display; this might quite simply live in + * display.c. Alternatively, it could move to frames.c, which + * is the only place this business is called anyway. + * + * \todo Apparently some UI needs changing (check bugzilla) + */ + +#ifndef META_FIXED_TIP_H +#define META_FIXED_TIP_H + +#include +#include + +/** + * Displays a tooltip. There can be only one across the entire system. + * This function behaves identically whether or not a tooltip is already + * displayed, but if it is the window will be reused rather than destroyed + * and recreated. + * + * \param xdisplay An X display. + * \param screen_number The number of the screen. + * \param root_x The X coordinate where the tooltip should appear + * \param root_y The Y coordinate where the tooltip should appear + * \param markup_text Text to display in the tooltip; can contain markup + */ +void meta_fixed_tip_show (Display *xdisplay, int screen_number, + int root_x, int root_y, + const char *markup_text); + +/** + * Removes the tooltip that was created by meta_fixed_tip_show(). If there + * is no tooltip currently visible, this is a no-op. + */ +void meta_fixed_tip_hide (void); + + +#endif diff --git a/src/ui/frames.c b/src/ui/frames.c new file mode 100644 index 00000000..bb4c863c --- /dev/null +++ b/src/ui/frames.c @@ -0,0 +1,2940 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco window frame manager widget */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2003 Red Hat, Inc. + * Copyright (C) 2005, 2006 Elijah Newren + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include +#include +#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 +#endif + +#define DEFAULT_INNER_BUTTON_BORDER 3 + +static void meta_frames_class_init (MetaFramesClass *klass); +static void meta_frames_init (MetaFrames *frames); +static void meta_frames_destroy (GtkObject *object); +static void meta_frames_finalize (GObject *object); +static void meta_frames_style_set (GtkWidget *widget, + GtkStyle *prev_style); +static void meta_frames_realize (GtkWidget *widget); +static void meta_frames_unrealize (GtkWidget *widget); + +static void meta_frames_update_prelit_control (MetaFrames *frames, + MetaUIFrame *frame, + MetaFrameControl control); +static gboolean meta_frames_button_press_event (GtkWidget *widget, + GdkEventButton *event); +static gboolean meta_frames_button_release_event (GtkWidget *widget, + GdkEventButton *event); +static gboolean meta_frames_motion_notify_event (GtkWidget *widget, + GdkEventMotion *event); +static gboolean meta_frames_destroy_event (GtkWidget *widget, + GdkEventAny *event); +static gboolean meta_frames_expose_event (GtkWidget *widget, + GdkEventExpose *event); +static gboolean meta_frames_enter_notify_event (GtkWidget *widget, + GdkEventCrossing *event); +static gboolean meta_frames_leave_notify_event (GtkWidget *widget, + GdkEventCrossing *event); + +static void meta_frames_attach_style (MetaFrames *frames, + MetaUIFrame *frame); + +static void meta_frames_paint_to_drawable (MetaFrames *frames, + MetaUIFrame *frame, + GdkDrawable *drawable, + GdkRegion *region, + int x_offset, + int y_offset); + +static void meta_frames_set_window_background (MetaFrames *frames, + MetaUIFrame *frame); + +static void meta_frames_calc_geometry (MetaFrames *frames, + MetaUIFrame *frame, + MetaFrameGeometry *fgeom); + +static void meta_frames_ensure_layout (MetaFrames *frames, + MetaUIFrame *frame); + +static MetaUIFrame* meta_frames_lookup_window (MetaFrames *frames, + Window xwindow); + +static void meta_frames_font_changed (MetaFrames *frames); +static void meta_frames_button_layout_changed (MetaFrames *frames); + + +static GdkRectangle* control_rect (MetaFrameControl control, + MetaFrameGeometry *fgeom); +static MetaFrameControl get_control (MetaFrames *frames, + MetaUIFrame *frame, + int x, + int y); +static void clear_tip (MetaFrames *frames); +static void invalidate_all_caches (MetaFrames *frames); +static void invalidate_whole_window (MetaFrames *frames, + MetaUIFrame *frame); + +static GtkWidgetClass *parent_class = NULL; + +GType +meta_frames_get_type (void) +{ + static GType frames_type = 0; + + if (!frames_type) + { + static const GtkTypeInfo frames_info = + { + "MetaFrames", + sizeof (MetaFrames), + sizeof (MetaFramesClass), + (GtkClassInitFunc) meta_frames_class_init, + (GtkObjectInitFunc) meta_frames_init, + /* reserved_1 */ NULL, + /* reserved_2 */ NULL, + (GtkClassInitFunc) NULL, + }; + + frames_type = gtk_type_unique (GTK_TYPE_WINDOW, &frames_info); + } + + return frames_type; +} + +static void +meta_frames_class_init (MetaFramesClass *class) +{ + GObjectClass *gobject_class; + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + + gobject_class = G_OBJECT_CLASS (class); + object_class = (GtkObjectClass*) class; + widget_class = (GtkWidgetClass*) class; + + parent_class = g_type_class_peek_parent (class); + + gobject_class->finalize = meta_frames_finalize; + object_class->destroy = meta_frames_destroy; + + widget_class->style_set = meta_frames_style_set; + + widget_class->realize = meta_frames_realize; + widget_class->unrealize = meta_frames_unrealize; + + widget_class->expose_event = meta_frames_expose_event; + widget_class->destroy_event = meta_frames_destroy_event; + widget_class->button_press_event = meta_frames_button_press_event; + widget_class->button_release_event = meta_frames_button_release_event; + widget_class->motion_notify_event = meta_frames_motion_notify_event; + widget_class->enter_notify_event = meta_frames_enter_notify_event; + widget_class->leave_notify_event = meta_frames_leave_notify_event; +} + +static gint +unsigned_long_equal (gconstpointer v1, + gconstpointer v2) +{ + return *((const gulong*) v1) == *((const gulong*) v2); +} + +static guint +unsigned_long_hash (gconstpointer v) +{ + gulong val = * (const gulong *) v; + + /* I'm not sure this works so well. */ +#if GLIB_SIZEOF_LONG > 4 + return (guint) (val ^ (val >> 32)); +#else + return val; +#endif +} + +static void +prefs_changed_callback (MetaPreference pref, + void *data) +{ + switch (pref) + { + case META_PREF_TITLEBAR_FONT: + meta_frames_font_changed (META_FRAMES (data)); + break; + case META_PREF_BUTTON_LAYOUT: + meta_frames_button_layout_changed (META_FRAMES (data)); + break; + default: + break; + } +} + +static void +meta_frames_init (MetaFrames *frames) +{ + GTK_WINDOW (frames)->type = GTK_WINDOW_POPUP; + + frames->text_heights = g_hash_table_new (NULL, NULL); + + frames->frames = g_hash_table_new (unsigned_long_hash, unsigned_long_equal); + + frames->tooltip_timeout = 0; + + frames->expose_delay_count = 0; + + frames->invalidate_cache_timeout_id = 0; + frames->invalidate_frames = NULL; + frames->cache = g_hash_table_new (g_direct_hash, g_direct_equal); + + gtk_widget_set_double_buffered (GTK_WIDGET (frames), FALSE); + + meta_prefs_add_listener (prefs_changed_callback, frames); +} + +static void +listify_func (gpointer key, gpointer value, gpointer data) +{ + GSList **listp; + + listp = data; + *listp = g_slist_prepend (*listp, value); +} + +static void +meta_frames_destroy (GtkObject *object) +{ + GSList *winlist; + GSList *tmp; + MetaFrames *frames; + + frames = META_FRAMES (object); + + clear_tip (frames); + + winlist = NULL; + g_hash_table_foreach (frames->frames, listify_func, &winlist); + + /* Unmanage all frames */ + for (tmp = winlist; tmp != NULL; tmp = tmp->next) + { + MetaUIFrame *frame; + + frame = tmp->data; + + meta_frames_unmanage_window (frames, frame->xwindow); + } + g_slist_free (winlist); + + GTK_OBJECT_CLASS (parent_class)->destroy (object); +} + +static void +meta_frames_finalize (GObject *object) +{ + MetaFrames *frames; + + frames = META_FRAMES (object); + + meta_prefs_remove_listener (prefs_changed_callback, frames); + + g_hash_table_destroy (frames->text_heights); + + invalidate_all_caches (frames); + if (frames->invalidate_cache_timeout_id) + g_source_remove (frames->invalidate_cache_timeout_id); + + g_assert (g_hash_table_size (frames->frames) == 0); + g_hash_table_destroy (frames->frames); + g_hash_table_destroy (frames->cache); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +typedef struct +{ + MetaRectangle rect; + GdkPixmap *pixmap; +} CachedFramePiece; + +typedef struct +{ + /* Caches of the four rendered sides in a MetaFrame. + * Order: top (titlebar), left, right, bottom. + */ + CachedFramePiece piece[4]; +} CachedPixels; + +static CachedPixels * +get_cache (MetaFrames *frames, + MetaUIFrame *frame) +{ + CachedPixels *pixels; + + pixels = g_hash_table_lookup (frames->cache, frame); + + if (!pixels) + { + pixels = g_new0 (CachedPixels, 1); + g_hash_table_insert (frames->cache, frame, pixels); + } + + return pixels; +} + +static void +invalidate_cache (MetaFrames *frames, + MetaUIFrame *frame) +{ + CachedPixels *pixels = get_cache (frames, frame); + int i; + + for (i = 0; i < 4; i++) + if (pixels->piece[i].pixmap) + g_object_unref (pixels->piece[i].pixmap); + + g_free (pixels); + g_hash_table_remove (frames->cache, frame); +} + +static void +invalidate_all_caches (MetaFrames *frames) +{ + GList *l; + + for (l = frames->invalidate_frames; l; l = l->next) + { + MetaUIFrame *frame = l->data; + + invalidate_cache (frames, frame); + } + + g_list_free (frames->invalidate_frames); + frames->invalidate_frames = NULL; +} + +static gboolean +invalidate_cache_timeout (gpointer data) +{ + MetaFrames *frames = data; + + invalidate_all_caches (frames); + frames->invalidate_cache_timeout_id = 0; + return FALSE; +} + +static void +queue_recalc_func (gpointer key, gpointer value, gpointer data) +{ + MetaUIFrame *frame; + MetaFrames *frames; + + frames = META_FRAMES (data); + frame = value; + + /* If a resize occurs it will cause a redraw, but the + * resize may not actually be needed so we always redraw + * in case of color change. + */ + meta_frames_set_window_background (frames, frame); + + invalidate_whole_window (frames, frame); + meta_core_queue_frame_resize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow); + if (frame->layout) + { + /* save title to recreate layout */ + g_free (frame->title); + + frame->title = g_strdup (pango_layout_get_text (frame->layout)); + + g_object_unref (G_OBJECT (frame->layout)); + frame->layout = NULL; + } +} + +static void +meta_frames_font_changed (MetaFrames *frames) +{ + if (g_hash_table_size (frames->text_heights) > 0) + { + g_hash_table_destroy (frames->text_heights); + frames->text_heights = g_hash_table_new (NULL, NULL); + } + + /* Queue a draw/resize on all frames */ + g_hash_table_foreach (frames->frames, + queue_recalc_func, frames); + +} + +static void +queue_draw_func (gpointer key, gpointer value, gpointer data) +{ + MetaUIFrame *frame; + MetaFrames *frames; + + frames = META_FRAMES (data); + frame = value; + + /* If a resize occurs it will cause a redraw, but the + * resize may not actually be needed so we always redraw + * in case of color change. + */ + meta_frames_set_window_background (frames, frame); + + invalidate_whole_window (frames, frame); +} + +static void +meta_frames_button_layout_changed (MetaFrames *frames) +{ + g_hash_table_foreach (frames->frames, + queue_draw_func, frames); +} + +static void +reattach_style_func (gpointer key, gpointer value, gpointer data) +{ + MetaUIFrame *frame; + MetaFrames *frames; + + frames = META_FRAMES (data); + frame = value; + + meta_frames_attach_style (frames, frame); +} + +static void +meta_frames_style_set (GtkWidget *widget, + GtkStyle *prev_style) +{ + MetaFrames *frames; + + frames = META_FRAMES (widget); + + meta_frames_font_changed (frames); + + g_hash_table_foreach (frames->frames, + reattach_style_func, frames); + + GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); +} + +static void +meta_frames_ensure_layout (MetaFrames *frames, + MetaUIFrame *frame) +{ + GtkWidget *widget; + MetaFrameFlags flags; + MetaFrameType type; + MetaFrameStyle *style; + + g_return_if_fail (GTK_WIDGET_REALIZED (frames)); + + widget = GTK_WIDGET (frames); + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_FRAME_TYPE, &type, + META_CORE_GET_END); + + style = meta_theme_get_frame_style (meta_theme_get_current (), + type, flags); + + if (style != frame->cache_style) + { + if (frame->layout) + { + /* save title to recreate layout */ + g_free (frame->title); + + frame->title = g_strdup (pango_layout_get_text (frame->layout)); + + g_object_unref (G_OBJECT (frame->layout)); + frame->layout = NULL; + } + } + + frame->cache_style = style; + + if (frame->layout == NULL) + { + gpointer key, value; + PangoFontDescription *font_desc; + double scale; + int size; + + scale = meta_theme_get_title_scale (meta_theme_get_current (), + type, + flags); + + frame->layout = gtk_widget_create_pango_layout (widget, frame->title); + + pango_layout_set_auto_dir (frame->layout, FALSE); + + font_desc = meta_gtk_widget_get_font_desc (widget, scale, + meta_prefs_get_titlebar_font ()); + + size = pango_font_description_get_size (font_desc); + + if (g_hash_table_lookup_extended (frames->text_heights, + GINT_TO_POINTER (size), + &key, &value)) + { + frame->text_height = GPOINTER_TO_INT (value); + } + else + { + frame->text_height = + meta_pango_font_desc_get_text_height (font_desc, + gtk_widget_get_pango_context (widget)); + + g_hash_table_replace (frames->text_heights, + GINT_TO_POINTER (size), + GINT_TO_POINTER (frame->text_height)); + } + + pango_layout_set_font_description (frame->layout, + font_desc); + + pango_font_description_free (font_desc); + + /* Save some RAM */ + g_free (frame->title); + frame->title = NULL; + } +} + +static void +meta_frames_calc_geometry (MetaFrames *frames, + MetaUIFrame *frame, + MetaFrameGeometry *fgeom) +{ + int width, height; + MetaFrameFlags flags; + MetaFrameType type; + MetaButtonLayout button_layout; + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_CLIENT_WIDTH, &width, + META_CORE_GET_CLIENT_HEIGHT, &height, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_FRAME_TYPE, &type, + META_CORE_GET_END); + + meta_frames_ensure_layout (frames, frame); + + meta_prefs_get_button_layout (&button_layout); + + meta_theme_calc_geometry (meta_theme_get_current (), + type, + frame->text_height, + flags, + width, height, + &button_layout, + fgeom); +} + +MetaFrames* +meta_frames_new (int screen_number) +{ + GdkScreen *screen; + + screen = gdk_display_get_screen (gdk_display_get_default (), + screen_number); + + return g_object_new (META_TYPE_FRAMES, + "screen", screen, + NULL); +} + +/* In order to use a style with a window it has to be attached to that + * window. Actually, the colormaps just have to match, but since GTK+ + * already takes care of making sure that its cheap to attach a style + * to multiple windows with the same colormap, we can just go ahead + * and attach separately for each window. + */ +static void +meta_frames_attach_style (MetaFrames *frames, + MetaUIFrame *frame) +{ + if (frame->style != NULL) + gtk_style_detach (frame->style); + + /* Weirdly, gtk_style_attach() steals a reference count from the style passed in */ + g_object_ref (GTK_WIDGET (frames)->style); + frame->style = gtk_style_attach (GTK_WIDGET (frames)->style, frame->window); +} + +void +meta_frames_manage_window (MetaFrames *frames, + Window xwindow, + GdkWindow *window) +{ + MetaUIFrame *frame; + + g_assert (window); + + frame = g_new (MetaUIFrame, 1); + + frame->window = window; + + gdk_window_set_user_data (frame->window, frames); + + frame->style = NULL; + meta_frames_attach_style (frames, frame); + + /* Don't set event mask here, it's in frame.c */ + + frame->xwindow = xwindow; + frame->cache_style = NULL; + frame->layout = NULL; + frame->text_height = -1; + frame->title = NULL; + frame->expose_delayed = FALSE; + frame->shape_applied = FALSE; + frame->prelit_control = META_FRAME_CONTROL_NONE; + + /* Don't set the window background yet; we need frame->xwindow to be + * registered with its MetaWindow, which happens after this function + * and meta_ui_create_frame_window() return to meta_window_ensure_frame(). + */ + + meta_core_grab_buttons (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + + g_hash_table_replace (frames->frames, &frame->xwindow, frame); +} + +void +meta_frames_unmanage_window (MetaFrames *frames, + Window xwindow) +{ + MetaUIFrame *frame; + + clear_tip (frames); + + frame = g_hash_table_lookup (frames->frames, &xwindow); + + if (frame) + { + /* invalidating all caches ensures the frame + * is not actually referenced anymore + */ + invalidate_all_caches (frames); + + /* restore the cursor */ + meta_core_set_screen_cursor (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + META_CURSOR_DEFAULT); + + gdk_window_set_user_data (frame->window, NULL); + + if (frames->last_motion_frame == frame) + frames->last_motion_frame = NULL; + + g_hash_table_remove (frames->frames, &frame->xwindow); + + gtk_style_detach (frame->style); + + gdk_window_destroy (frame->window); + + if (frame->layout) + g_object_unref (G_OBJECT (frame->layout)); + + if (frame->title) + g_free (frame->title); + + g_free (frame); + } + else + meta_warning ("Frame 0x%lx not managed, can't unmanage\n", xwindow); +} + +static void +meta_frames_realize (GtkWidget *widget) +{ + if (GTK_WIDGET_CLASS (parent_class)->realize) + GTK_WIDGET_CLASS (parent_class)->realize (widget); +} + +static void +meta_frames_unrealize (GtkWidget *widget) +{ + if (GTK_WIDGET_CLASS (parent_class)->unrealize) + GTK_WIDGET_CLASS (parent_class)->unrealize (widget); +} + +static MetaUIFrame* +meta_frames_lookup_window (MetaFrames *frames, + Window xwindow) +{ + MetaUIFrame *frame; + + frame = g_hash_table_lookup (frames->frames, &xwindow); + + return frame; +} + +void +meta_frames_get_geometry (MetaFrames *frames, + Window xwindow, + int *top_height, int *bottom_height, + int *left_width, int *right_width) +{ + MetaFrameFlags flags; + MetaUIFrame *frame; + MetaFrameType type; + + frame = meta_frames_lookup_window (frames, xwindow); + + if (frame == NULL) + meta_bug ("No such frame 0x%lx\n", xwindow); + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_FRAME_TYPE, &type, + META_CORE_GET_END); + + g_return_if_fail (type < META_FRAME_TYPE_LAST); + + meta_frames_ensure_layout (frames, frame); + + /* We can't get the full geometry, because that depends on + * the client window size and probably we're being called + * by the core move/resize code to decide on the client + * window size + */ + meta_theme_get_frame_borders (meta_theme_get_current (), + type, + frame->text_height, + flags, + top_height, bottom_height, + left_width, right_width); +} + +void +meta_frames_reset_bg (MetaFrames *frames, + Window xwindow) +{ + MetaUIFrame *frame; + + frame = meta_frames_lookup_window (frames, xwindow); + + meta_frames_set_window_background (frames, frame); +} + +static void +set_background_none (Display *xdisplay, + Window xwindow) +{ + XSetWindowAttributes attrs; + + attrs.background_pixmap = None; + XChangeWindowAttributes (xdisplay, xwindow, + CWBackPixmap, &attrs); +} + +void +meta_frames_unflicker_bg (MetaFrames *frames, + Window xwindow, + int target_width, + int target_height) +{ + MetaUIFrame *frame; + + frame = meta_frames_lookup_window (frames, xwindow); + g_return_if_fail (frame != NULL); + +#if 0 + pixmap = gdk_pixmap_new (frame->window, + width, height, + -1); + + /* Oops, no way to get the background here */ + + meta_frames_paint_to_drawable (frames, frame, pixmap); +#endif + + set_background_none (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); +} + +void +meta_frames_apply_shapes (MetaFrames *frames, + Window xwindow, + int new_window_width, + int new_window_height, + gboolean window_has_shape) +{ +#ifdef HAVE_SHAPE + /* Apply shapes as if window had new_window_width, new_window_height */ + MetaUIFrame *frame; + MetaFrameGeometry fgeom; + XRectangle xrect; + Region corners_xregion; + Region window_xregion; + + frame = meta_frames_lookup_window (frames, xwindow); + g_return_if_fail (frame != NULL); + + meta_frames_calc_geometry (frames, frame, &fgeom); + + if (!(fgeom.top_left_corner_rounded_radius != 0 || + fgeom.top_right_corner_rounded_radius != 0 || + fgeom.bottom_left_corner_rounded_radius != 0 || + fgeom.bottom_right_corner_rounded_radius != 0 || + window_has_shape)) + { + if (frame->shape_applied) + { + meta_topic (META_DEBUG_SHAPES, + "Unsetting shape mask on frame 0x%lx\n", + frame->xwindow); + + XShapeCombineMask (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + ShapeBounding, 0, 0, None, ShapeSet); + frame->shape_applied = FALSE; + } + else + { + meta_topic (META_DEBUG_SHAPES, + "Frame 0x%lx still doesn't need a shape mask\n", + frame->xwindow); + } + + return; /* nothing to do */ + } + + corners_xregion = XCreateRegion (); + + if (fgeom.top_left_corner_rounded_radius != 0) + { + const int corner = fgeom.top_left_corner_rounded_radius; + const float radius = sqrt(corner) + corner; + int i; + + for (i=0; ixwindow); + + screen = gtk_widget_get_screen (GTK_WIDGET (frames)); + screen_number = gdk_x11_screen_get_screen_number (screen); + + attrs.override_redirect = True; + + shape_window = XCreateWindow (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + RootWindow (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), screen_number), + -5000, -5000, + new_window_width, + new_window_height, + 0, + CopyFromParent, + CopyFromParent, + (Visual *)CopyFromParent, + CWOverrideRedirect, + &attrs); + + /* Copy the client's shape to the temporary shape_window */ + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_CLIENT_XWINDOW, &client_window, + META_CORE_GET_END); + + XShapeCombineShape (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), shape_window, ShapeBounding, + fgeom.left_width, + fgeom.top_height, + client_window, + ShapeBounding, + ShapeSet); + + /* Punch the client area out of the normal frame shape, + * then union it with the shape_window's existing shape + */ + client_xregion = XCreateRegion (); + + xrect.x = fgeom.left_width; + xrect.y = fgeom.top_height; + xrect.width = new_window_width - fgeom.right_width - xrect.x; + xrect.height = new_window_height - fgeom.bottom_height - xrect.y; + + XUnionRectWithRegion (&xrect, client_xregion, client_xregion); + + XSubtractRegion (window_xregion, client_xregion, window_xregion); + + XDestroyRegion (client_xregion); + + XShapeCombineRegion (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), shape_window, + ShapeBounding, 0, 0, window_xregion, ShapeUnion); + + /* Now copy shape_window shape to the real frame */ + XShapeCombineShape (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, ShapeBounding, + 0, 0, + shape_window, + ShapeBounding, + ShapeSet); + + XDestroyWindow (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), shape_window); + } + else + { + /* No shape on the client, so just do simple stuff */ + + meta_topic (META_DEBUG_SHAPES, + "Frame 0x%lx has shaped corners\n", + frame->xwindow); + + XShapeCombineRegion (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + ShapeBounding, 0, 0, window_xregion, ShapeSet); + } + + frame->shape_applied = TRUE; + + XDestroyRegion (window_xregion); +#endif /* HAVE_SHAPE */ +} + +void +meta_frames_move_resize_frame (MetaFrames *frames, + Window xwindow, + int x, + int y, + int width, + int height) +{ + MetaUIFrame *frame = meta_frames_lookup_window (frames, xwindow); + int old_x, old_y, old_width, old_height; + + #if GTK_CHECK_VERSION(3, 0, 0) + old_width = gdk_window_get_width(GDK_WINDOW(frame->window)); + old_height = gdk_window_get_height(GDK_WINDOW(frame->window)); + #else + gdk_drawable_get_size(frame->window, &old_width, &old_height); + #endif + + gdk_window_get_position (frame->window, &old_x, &old_y); + + gdk_window_move_resize (frame->window, x, y, width, height); + + if (old_width != width || old_height != height) + invalidate_whole_window (frames, frame); +} + +void +meta_frames_queue_draw (MetaFrames *frames, + Window xwindow) +{ + MetaUIFrame *frame; + + frame = meta_frames_lookup_window (frames, xwindow); + + invalidate_whole_window (frames, frame); +} + +void +meta_frames_set_title (MetaFrames *frames, + Window xwindow, + const char *title) +{ + MetaUIFrame *frame; + + frame = meta_frames_lookup_window (frames, xwindow); + + g_assert (frame); + + g_free (frame->title); + frame->title = g_strdup (title); + + if (frame->layout) + { + g_object_unref (frame->layout); + frame->layout = NULL; + } + + invalidate_whole_window (frames, frame); +} + +void +meta_frames_repaint_frame (MetaFrames *frames, + Window xwindow) +{ + MetaUIFrame *frame; + + frame = meta_frames_lookup_window (frames, xwindow); + + g_assert (frame); + + /* repaint everything, so the other frame don't + * lag behind if they are exposed + */ + gdk_window_process_all_updates (); +} + +static void +show_tip_now (MetaFrames *frames) +{ + const char *tiptext; + MetaUIFrame *frame; + int x, y, root_x, root_y; + Window root, child; + guint mask; + MetaFrameControl control; + + frame = frames->last_motion_frame; + if (frame == NULL) + return; + + XQueryPointer (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + &root, &child, + &root_x, &root_y, + &x, &y, + &mask); + + control = get_control (frames, frame, x, y); + + tiptext = NULL; + switch (control) + { + case META_FRAME_CONTROL_TITLE: + break; + case META_FRAME_CONTROL_DELETE: + tiptext = _("Close Window"); + break; + case META_FRAME_CONTROL_MENU: + tiptext = _("Window Menu"); + break; + case META_FRAME_CONTROL_MINIMIZE: + tiptext = _("Minimize Window"); + break; + case META_FRAME_CONTROL_MAXIMIZE: + tiptext = _("Maximize Window"); + break; + case META_FRAME_CONTROL_UNMAXIMIZE: + tiptext = _("Restore Window"); + break; + case META_FRAME_CONTROL_SHADE: + tiptext = _("Roll Up Window"); + break; + case META_FRAME_CONTROL_UNSHADE: + tiptext = _("Unroll Window"); + break; + case META_FRAME_CONTROL_ABOVE: + tiptext = _("Keep Window On Top"); + break; + case META_FRAME_CONTROL_UNABOVE: + tiptext = _("Remove Window From Top"); + break; + case META_FRAME_CONTROL_STICK: + tiptext = _("Always On Visible Workspace"); + break; + case META_FRAME_CONTROL_UNSTICK: + tiptext = _("Put Window On Only One Workspace"); + break; + case META_FRAME_CONTROL_RESIZE_SE: + break; + case META_FRAME_CONTROL_RESIZE_S: + break; + case META_FRAME_CONTROL_RESIZE_SW: + break; + case META_FRAME_CONTROL_RESIZE_N: + break; + case META_FRAME_CONTROL_RESIZE_NE: + break; + case META_FRAME_CONTROL_RESIZE_NW: + break; + case META_FRAME_CONTROL_RESIZE_W: + break; + case META_FRAME_CONTROL_RESIZE_E: + break; + case META_FRAME_CONTROL_NONE: + break; + case META_FRAME_CONTROL_CLIENT_AREA: + break; + } + + if (tiptext) + { + MetaFrameGeometry fgeom; + GdkRectangle *rect; + int dx, dy; + int screen_number; + + meta_frames_calc_geometry (frames, frame, &fgeom); + + rect = control_rect (control, &fgeom); + + /* get conversion delta for root-to-frame coords */ + dx = root_x - x; + dy = root_y - y; + + /* Align the tooltip to the button right end if RTL */ + if (meta_ui_get_direction() == META_UI_DIRECTION_RTL) + dx += rect->width; + + screen_number = gdk_screen_get_number (gtk_widget_get_screen (GTK_WIDGET (frames))); + + meta_fixed_tip_show (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + screen_number, + rect->x + dx, + rect->y + rect->height + 2 + dy, + tiptext); + } +} + +static gboolean +tip_timeout_func (gpointer data) +{ + MetaFrames *frames; + + frames = data; + + show_tip_now (frames); + + return FALSE; +} + +#define TIP_DELAY 450 +static void +queue_tip (MetaFrames *frames) +{ + clear_tip (frames); + + frames->tooltip_timeout = g_timeout_add (TIP_DELAY, + tip_timeout_func, + frames); +} + +static void +clear_tip (MetaFrames *frames) +{ + if (frames->tooltip_timeout) + { + g_source_remove (frames->tooltip_timeout); + frames->tooltip_timeout = 0; + } + meta_fixed_tip_hide (); +} + +static void +redraw_control (MetaFrames *frames, + MetaUIFrame *frame, + MetaFrameControl control) +{ + MetaFrameGeometry fgeom; + GdkRectangle *rect; + + meta_frames_calc_geometry (frames, frame, &fgeom); + + rect = control_rect (control, &fgeom); + + gdk_window_invalidate_rect (frame->window, rect, FALSE); + invalidate_cache (frames, frame); +} + +static gboolean +meta_frame_titlebar_event (MetaUIFrame *frame, + GdkEventButton *event, + int action) +{ + MetaFrameFlags flags; + + switch (action) + { + case META_ACTION_TITLEBAR_TOGGLE_SHADE: + { + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_END); + + if (flags & META_FRAME_ALLOWS_SHADE) + { + if (flags & META_FRAME_SHADED) + meta_core_unshade (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + event->time); + else + meta_core_shade (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + event->time); + } + } + break; + + case META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE: + { + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_END); + + if (flags & META_FRAME_ALLOWS_MAXIMIZE) + { + meta_core_toggle_maximize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + } + } + break; + + case META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE_HORIZONTALLY: + { + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_END); + + if (flags & META_FRAME_ALLOWS_MAXIMIZE) + { + meta_core_toggle_maximize_horizontally (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + } + } + break; + + case META_ACTION_TITLEBAR_TOGGLE_MAXIMIZE_VERTICALLY: + { + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_END); + + if (flags & META_FRAME_ALLOWS_MAXIMIZE) + { + meta_core_toggle_maximize_vertically (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + } + } + break; + + case META_ACTION_TITLEBAR_MINIMIZE: + { + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_END); + + if (flags & META_FRAME_ALLOWS_MINIMIZE) + { + meta_core_minimize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + } + } + break; + + case META_ACTION_TITLEBAR_NONE: + /* Yaay, a sane user that doesn't use that other weird crap! */ + break; + + case META_ACTION_TITLEBAR_LOWER: + meta_core_user_lower_and_unfocus (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + event->time); + break; + + case META_ACTION_TITLEBAR_MENU: + meta_core_show_window_menu (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + event->x_root, + event->y_root, + event->button, + event->time); + break; + + case META_ACTION_TITLEBAR_LAST: + break; + } + + return TRUE; +} + +static gboolean +meta_frame_double_click_event (MetaUIFrame *frame, + GdkEventButton *event) +{ + int action = meta_prefs_get_action_double_click_titlebar (); + + return meta_frame_titlebar_event (frame, event, action); +} + +static gboolean +meta_frame_middle_click_event (MetaUIFrame *frame, + GdkEventButton *event) +{ + int action = meta_prefs_get_action_middle_click_titlebar(); + + return meta_frame_titlebar_event (frame, event, action); +} + +static gboolean +meta_frame_right_click_event(MetaUIFrame *frame, + GdkEventButton *event) +{ + int action = meta_prefs_get_action_right_click_titlebar(); + + return meta_frame_titlebar_event (frame, event, action); +} + +static gboolean +meta_frames_button_press_event (GtkWidget *widget, + GdkEventButton *event) +{ + MetaUIFrame *frame; + MetaFrames *frames; + MetaFrameControl control; + + frames = META_FRAMES (widget); + + /* Remember that the display may have already done something with this event. + * If so there's probably a GrabOp in effect. + */ + + frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window)); + if (frame == NULL) + return FALSE; + + clear_tip (frames); + + control = get_control (frames, frame, event->x, event->y); + + /* focus on click, even if click was on client area */ + if (event->button == 1 && + !(control == META_FRAME_CONTROL_MINIMIZE || + control == META_FRAME_CONTROL_DELETE || + control == META_FRAME_CONTROL_MAXIMIZE)) + { + meta_topic (META_DEBUG_FOCUS, + "Focusing window with frame 0x%lx due to button 1 press\n", + frame->xwindow); + meta_core_user_focus (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + event->time); + } + + /* don't do the rest of this if on client area */ + if (control == META_FRAME_CONTROL_CLIENT_AREA) + return FALSE; /* not on the frame, just passed through from client */ + + /* We want to shade even if we have a GrabOp, since we'll have a move grab + * if we double click the titlebar. + */ + if (control == META_FRAME_CONTROL_TITLE && + event->button == 1 && + event->type == GDK_2BUTTON_PRESS) + { + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + return meta_frame_double_click_event (frame, event); + } + + if (meta_core_get_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())) != + META_GRAB_OP_NONE) + return FALSE; /* already up to something */ + + if (event->button == 1 && + (control == META_FRAME_CONTROL_MAXIMIZE || + control == META_FRAME_CONTROL_UNMAXIMIZE || + control == META_FRAME_CONTROL_MINIMIZE || + control == META_FRAME_CONTROL_DELETE || + control == META_FRAME_CONTROL_SHADE || + control == META_FRAME_CONTROL_UNSHADE || + control == META_FRAME_CONTROL_ABOVE || + control == META_FRAME_CONTROL_UNABOVE || + control == META_FRAME_CONTROL_STICK || + control == META_FRAME_CONTROL_UNSTICK || + control == META_FRAME_CONTROL_MENU)) + { + MetaGrabOp op = META_GRAB_OP_NONE; + + switch (control) + { + case META_FRAME_CONTROL_MINIMIZE: + op = META_GRAB_OP_CLICKING_MINIMIZE; + break; + case META_FRAME_CONTROL_MAXIMIZE: + op = META_GRAB_OP_CLICKING_MAXIMIZE; + break; + case META_FRAME_CONTROL_UNMAXIMIZE: + op = META_GRAB_OP_CLICKING_UNMAXIMIZE; + break; + case META_FRAME_CONTROL_DELETE: + op = META_GRAB_OP_CLICKING_DELETE; + break; + case META_FRAME_CONTROL_MENU: + op = META_GRAB_OP_CLICKING_MENU; + break; + case META_FRAME_CONTROL_SHADE: + op = META_GRAB_OP_CLICKING_SHADE; + break; + case META_FRAME_CONTROL_UNSHADE: + op = META_GRAB_OP_CLICKING_UNSHADE; + break; + case META_FRAME_CONTROL_ABOVE: + op = META_GRAB_OP_CLICKING_ABOVE; + break; + case META_FRAME_CONTROL_UNABOVE: + op = META_GRAB_OP_CLICKING_UNABOVE; + break; + case META_FRAME_CONTROL_STICK: + op = META_GRAB_OP_CLICKING_STICK; + break; + case META_FRAME_CONTROL_UNSTICK: + op = META_GRAB_OP_CLICKING_UNSTICK; + break; + default: + g_assert_not_reached (); + break; + } + + meta_core_begin_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + op, + TRUE, + TRUE, + event->button, + 0, + event->time, + event->x_root, + event->y_root); + + frame->prelit_control = control; + redraw_control (frames, frame, control); + + if (op == META_GRAB_OP_CLICKING_MENU) + { + MetaFrameGeometry fgeom; + GdkRectangle *rect; + int dx, dy; + + meta_frames_calc_geometry (frames, frame, &fgeom); + + rect = control_rect (META_FRAME_CONTROL_MENU, &fgeom); + + /* get delta to convert to root coords */ + dx = event->x_root - event->x; + dy = event->y_root - event->y; + + /* Align to the right end of the menu rectangle if RTL */ + if (meta_ui_get_direction() == META_UI_DIRECTION_RTL) + dx += rect->width; + + meta_core_show_window_menu (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + rect->x + dx, + rect->y + rect->height + dy, + event->button, + event->time); + } + } + else if (event->button == 1 && + (control == META_FRAME_CONTROL_RESIZE_SE || + control == META_FRAME_CONTROL_RESIZE_S || + control == META_FRAME_CONTROL_RESIZE_SW || + control == META_FRAME_CONTROL_RESIZE_NE || + control == META_FRAME_CONTROL_RESIZE_N || + control == META_FRAME_CONTROL_RESIZE_NW || + control == META_FRAME_CONTROL_RESIZE_E || + control == META_FRAME_CONTROL_RESIZE_W)) + { + MetaGrabOp op; + gboolean titlebar_is_onscreen; + + op = META_GRAB_OP_NONE; + + switch (control) + { + case META_FRAME_CONTROL_RESIZE_SE: + op = META_GRAB_OP_RESIZING_SE; + break; + case META_FRAME_CONTROL_RESIZE_S: + op = META_GRAB_OP_RESIZING_S; + break; + case META_FRAME_CONTROL_RESIZE_SW: + op = META_GRAB_OP_RESIZING_SW; + break; + case META_FRAME_CONTROL_RESIZE_NE: + op = META_GRAB_OP_RESIZING_NE; + break; + case META_FRAME_CONTROL_RESIZE_N: + op = META_GRAB_OP_RESIZING_N; + break; + case META_FRAME_CONTROL_RESIZE_NW: + op = META_GRAB_OP_RESIZING_NW; + break; + case META_FRAME_CONTROL_RESIZE_E: + op = META_GRAB_OP_RESIZING_E; + break; + case META_FRAME_CONTROL_RESIZE_W: + op = META_GRAB_OP_RESIZING_W; + break; + default: + g_assert_not_reached (); + break; + } + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_IS_TITLEBAR_ONSCREEN, &titlebar_is_onscreen, + META_CORE_GET_END); + + if (!titlebar_is_onscreen) + meta_core_show_window_menu (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + event->x_root, + event->y_root, + event->button, + event->time); + else + meta_core_begin_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + op, + TRUE, + TRUE, + event->button, + 0, + event->time, + event->x_root, + event->y_root); + } + else if (control == META_FRAME_CONTROL_TITLE && + event->button == 1) + { + MetaFrameFlags flags; + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_END); + + if (flags & META_FRAME_ALLOWS_MOVE) + { + meta_core_begin_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + META_GRAB_OP_MOVING, + TRUE, + TRUE, + event->button, + 0, + event->time, + event->x_root, + event->y_root); + } + } + else if (event->button == 2) + { + return meta_frame_middle_click_event (frame, event); + } + else if (event->button == 3) + { + return meta_frame_right_click_event (frame, event); + } + + return TRUE; +} + +void +meta_frames_notify_menu_hide (MetaFrames *frames) +{ + if (meta_core_get_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())) == + META_GRAB_OP_CLICKING_MENU) + { + Window grab_frame; + + grab_frame = meta_core_get_grab_frame (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + + if (grab_frame != None) + { + MetaUIFrame *frame; + + frame = meta_frames_lookup_window (frames, grab_frame); + + if (frame) + { + redraw_control (frames, frame, + META_FRAME_CONTROL_MENU); + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), CurrentTime); + } + } + } +} + +static gboolean +meta_frames_button_release_event (GtkWidget *widget, + GdkEventButton *event) +{ + MetaUIFrame *frame; + MetaFrames *frames; + MetaGrabOp op; + + frames = META_FRAMES (widget); + + frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window)); + if (frame == NULL) + return FALSE; + + clear_tip (frames); + + op = meta_core_get_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + + if (op == META_GRAB_OP_NONE) + return FALSE; + + /* We only handle the releases we handled the presses for (things + * involving frame controls). Window ops that don't require a + * frame are handled in the Xlib part of the code, display.c/window.c + */ + if (frame->xwindow == meta_core_get_grab_frame (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())) && + ((int) event->button) == meta_core_get_grab_button (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()))) + { + MetaFrameControl control; + + control = get_control (frames, frame, event->x, event->y); + + switch (op) + { + case META_GRAB_OP_CLICKING_MINIMIZE: + if (control == META_FRAME_CONTROL_MINIMIZE) + meta_core_minimize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_MAXIMIZE: + if (control == META_FRAME_CONTROL_MAXIMIZE) + { + /* Focus the window on the maximize */ + meta_core_user_focus (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + event->time); + meta_core_maximize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + } + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_UNMAXIMIZE: + if (control == META_FRAME_CONTROL_UNMAXIMIZE) + meta_core_unmaximize (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_DELETE: + if (control == META_FRAME_CONTROL_DELETE) + meta_core_delete (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, event->time); + + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_MENU: + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_SHADE: + if (control == META_FRAME_CONTROL_SHADE) + meta_core_shade (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, event->time); + + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_UNSHADE: + if (control == META_FRAME_CONTROL_UNSHADE) + meta_core_unshade (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, event->time); + + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_ABOVE: + if (control == META_FRAME_CONTROL_ABOVE) + meta_core_make_above (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_UNABOVE: + if (control == META_FRAME_CONTROL_UNABOVE) + meta_core_unmake_above (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_STICK: + if (control == META_FRAME_CONTROL_STICK) + meta_core_stick (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + case META_GRAB_OP_CLICKING_UNSTICK: + if (control == META_FRAME_CONTROL_UNSTICK) + meta_core_unstick (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow); + + meta_core_end_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), event->time); + break; + + default: + break; + } + + /* Update the prelit control regardless of what button the mouse + * was released over; needed so that the new button can become + * prelit so to let the user know that it can now be pressed. + * :) + */ + meta_frames_update_prelit_control (frames, frame, control); + } + + return TRUE; +} + +static void +meta_frames_update_prelit_control (MetaFrames *frames, + MetaUIFrame *frame, + MetaFrameControl control) +{ + MetaFrameControl old_control; + MetaCursor cursor; + + + meta_verbose ("Updating prelit control from %u to %u\n", + frame->prelit_control, control); + + cursor = META_CURSOR_DEFAULT; + + switch (control) + { + case META_FRAME_CONTROL_CLIENT_AREA: + break; + case META_FRAME_CONTROL_NONE: + break; + case META_FRAME_CONTROL_TITLE: + break; + case META_FRAME_CONTROL_DELETE: + break; + case META_FRAME_CONTROL_MENU: + break; + case META_FRAME_CONTROL_MINIMIZE: + break; + case META_FRAME_CONTROL_MAXIMIZE: + break; + case META_FRAME_CONTROL_UNMAXIMIZE: + break; + case META_FRAME_CONTROL_SHADE: + break; + case META_FRAME_CONTROL_UNSHADE: + break; + case META_FRAME_CONTROL_ABOVE: + break; + case META_FRAME_CONTROL_UNABOVE: + break; + case META_FRAME_CONTROL_STICK: + break; + case META_FRAME_CONTROL_UNSTICK: + break; + case META_FRAME_CONTROL_RESIZE_SE: + cursor = META_CURSOR_SE_RESIZE; + break; + case META_FRAME_CONTROL_RESIZE_S: + cursor = META_CURSOR_SOUTH_RESIZE; + break; + case META_FRAME_CONTROL_RESIZE_SW: + cursor = META_CURSOR_SW_RESIZE; + break; + case META_FRAME_CONTROL_RESIZE_N: + cursor = META_CURSOR_NORTH_RESIZE; + break; + case META_FRAME_CONTROL_RESIZE_NE: + cursor = META_CURSOR_NE_RESIZE; + break; + case META_FRAME_CONTROL_RESIZE_NW: + cursor = META_CURSOR_NW_RESIZE; + break; + case META_FRAME_CONTROL_RESIZE_W: + cursor = META_CURSOR_WEST_RESIZE; + break; + case META_FRAME_CONTROL_RESIZE_E: + cursor = META_CURSOR_EAST_RESIZE; + break; + } + + /* set/unset the prelight cursor */ + meta_core_set_screen_cursor (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + frame->xwindow, + cursor); + + switch (control) + { + case META_FRAME_CONTROL_MENU: + case META_FRAME_CONTROL_MINIMIZE: + case META_FRAME_CONTROL_MAXIMIZE: + case META_FRAME_CONTROL_DELETE: + case META_FRAME_CONTROL_SHADE: + case META_FRAME_CONTROL_UNSHADE: + case META_FRAME_CONTROL_ABOVE: + case META_FRAME_CONTROL_UNABOVE: + case META_FRAME_CONTROL_STICK: + case META_FRAME_CONTROL_UNSTICK: + case META_FRAME_CONTROL_UNMAXIMIZE: + /* leave control set */ + break; + default: + /* Only prelight buttons */ + control = META_FRAME_CONTROL_NONE; + break; + } + + if (control == frame->prelit_control) + return; + + /* Save the old control so we can unprelight it */ + old_control = frame->prelit_control; + + frame->prelit_control = control; + + redraw_control (frames, frame, old_control); + redraw_control (frames, frame, control); +} + +static gboolean +meta_frames_motion_notify_event (GtkWidget *widget, + GdkEventMotion *event) +{ + MetaUIFrame *frame; + MetaFrames *frames; + MetaGrabOp grab_op; + + frames = META_FRAMES (widget); + + frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window)); + if (frame == NULL) + return FALSE; + + clear_tip (frames); + + frames->last_motion_frame = frame; + + grab_op = meta_core_get_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + + switch (grab_op) + { + case META_GRAB_OP_CLICKING_MENU: + case META_GRAB_OP_CLICKING_DELETE: + case META_GRAB_OP_CLICKING_MINIMIZE: + case META_GRAB_OP_CLICKING_MAXIMIZE: + case META_GRAB_OP_CLICKING_UNMAXIMIZE: + case META_GRAB_OP_CLICKING_SHADE: + case META_GRAB_OP_CLICKING_UNSHADE: + case META_GRAB_OP_CLICKING_ABOVE: + case META_GRAB_OP_CLICKING_UNABOVE: + case META_GRAB_OP_CLICKING_STICK: + case META_GRAB_OP_CLICKING_UNSTICK: + { + MetaFrameControl control; + int x, y; + + gdk_window_get_pointer (frame->window, &x, &y, NULL); + + /* Control is set to none unless it matches + * the current grab + */ + control = get_control (frames, frame, x, y); + if (! ((control == META_FRAME_CONTROL_MENU && + grab_op == META_GRAB_OP_CLICKING_MENU) || + (control == META_FRAME_CONTROL_DELETE && + grab_op == META_GRAB_OP_CLICKING_DELETE) || + (control == META_FRAME_CONTROL_MINIMIZE && + grab_op == META_GRAB_OP_CLICKING_MINIMIZE) || + ((control == META_FRAME_CONTROL_MAXIMIZE || + control == META_FRAME_CONTROL_UNMAXIMIZE) && + (grab_op == META_GRAB_OP_CLICKING_MAXIMIZE || + grab_op == META_GRAB_OP_CLICKING_UNMAXIMIZE)) || + (control == META_FRAME_CONTROL_SHADE && + grab_op == META_GRAB_OP_CLICKING_SHADE) || + (control == META_FRAME_CONTROL_UNSHADE && + grab_op == META_GRAB_OP_CLICKING_UNSHADE) || + (control == META_FRAME_CONTROL_ABOVE && + grab_op == META_GRAB_OP_CLICKING_ABOVE) || + (control == META_FRAME_CONTROL_UNABOVE && + grab_op == META_GRAB_OP_CLICKING_UNABOVE) || + (control == META_FRAME_CONTROL_STICK && + grab_op == META_GRAB_OP_CLICKING_STICK) || + (control == META_FRAME_CONTROL_UNSTICK && + grab_op == META_GRAB_OP_CLICKING_UNSTICK))) + control = META_FRAME_CONTROL_NONE; + + /* Update prelit control and cursor */ + meta_frames_update_prelit_control (frames, frame, control); + + /* No tooltip while in the process of clicking */ + } + break; + case META_GRAB_OP_NONE: + { + MetaFrameControl control; + int x, y; + + gdk_window_get_pointer (frame->window, &x, &y, NULL); + + control = get_control (frames, frame, x, y); + + /* Update prelit control and cursor */ + meta_frames_update_prelit_control (frames, frame, control); + + queue_tip (frames); + } + break; + + default: + break; + } + + return TRUE; +} + +static gboolean +meta_frames_destroy_event (GtkWidget *widget, + GdkEventAny *event) +{ + MetaUIFrame *frame; + MetaFrames *frames; + + frames = META_FRAMES (widget); + + frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window)); + if (frame == NULL) + return FALSE; + + return TRUE; +} + +#if !GTK_CHECK_VERSION(2,21,6) +/* Copied from GDK */ +static cairo_surface_t * +_gdk_drawable_ref_cairo_surface (GdkDrawable *drawable) +{ + g_return_val_if_fail (GDK_IS_DRAWABLE (drawable), NULL); + + return GDK_DRAWABLE_GET_CLASS (drawable)->ref_cairo_surface (drawable); +} + +static cairo_pattern_t * +gdk_window_get_background_pattern (GdkWindow *window) +{ + GdkWindowObject *private = (GdkWindowObject *) window; + cairo_pattern_t *pattern; + + g_return_val_if_fail (GDK_IS_WINDOW (window), NULL); + + if (private->bg_pixmap == GDK_PARENT_RELATIVE_BG) + pattern = NULL; + else if (private->bg_pixmap != GDK_NO_BG && + private->bg_pixmap != NULL) + { + static cairo_user_data_key_t key; + cairo_surface_t *surface; + + surface = _gdk_drawable_ref_cairo_surface (private->bg_pixmap); + pattern = cairo_pattern_create_for_surface (surface); + cairo_surface_destroy (surface); + + cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT); + cairo_pattern_set_user_data (pattern, + &key, + g_object_ref (private->bg_pixmap), + g_object_unref); + } + else + { + pattern = + cairo_pattern_create_rgb (private->bg_color.red / 65535., + private->bg_color.green / 65535., + private->bg_color.blue / 65535.); + } + + return pattern; +} +#endif + +static void +setup_bg_cr (cairo_t *cr, GdkWindow *window, int x_offset, int y_offset) +{ + GdkWindow *parent = gdk_window_get_parent (window); + cairo_pattern_t *bg_pattern; + + bg_pattern = gdk_window_get_background_pattern (window); + if (bg_pattern == NULL && parent) + { + gint window_x, window_y; + + gdk_window_get_position (window, &window_x, &window_y); + setup_bg_cr (cr, parent, x_offset + window_x, y_offset + window_y); + } + else if (bg_pattern) + { + cairo_translate (cr, - x_offset, - y_offset); + cairo_set_source (cr, bg_pattern); + cairo_translate (cr, x_offset, y_offset); + } +} + +static void +clear_backing (GdkPixmap *pixmap, + GdkWindow *window, + int xoffset, int yoffset) +{ + int width, height; + cairo_t *cr = gdk_cairo_create (pixmap); + + setup_bg_cr (cr, window, xoffset, yoffset); + + #if GTK_CHECK_VERSION(3, 0, 0) + width = gdk_window_get_width(GDK_WINDOW(pixmap)); + height = gdk_window_get_height(GDK_WINDOW(pixmap)); + #else + gdk_drawable_get_size(GDK_DRAWABLE(pixmap), &width, &height); + #endif + + cairo_rectangle (cr, 0, 0, width, height); + cairo_fill (cr); + + cairo_destroy (cr); +} + +/* Returns a pixmap with a piece of the windows frame painted on it. +*/ + +static GdkPixmap * +generate_pixmap (MetaFrames *frames, + MetaUIFrame *frame, + MetaRectangle rect) +{ + GdkRectangle rectangle; + GdkRegion *region; + GdkPixmap *result; + + rectangle.x = rect.x; + rectangle.y = rect.y; + rectangle.width = MAX (rect.width, 1); + rectangle.height = MAX (rect.height, 1); + + result = gdk_pixmap_new (frame->window, + rectangle.width, rectangle.height, -1); + + clear_backing (result, frame->window, rectangle.x, rectangle.y); + + region = gdk_region_rectangle (&rectangle); + + meta_frames_paint_to_drawable (frames, frame, result, region, + -rectangle.x, -rectangle.y); + + gdk_region_destroy (region); + + return result; +} + + +static void +populate_cache (MetaFrames *frames, + MetaUIFrame *frame) +{ + int top, bottom, left, right; + int width, height; + int frame_width, frame_height, screen_width, screen_height; + CachedPixels *pixels; + MetaFrameType frame_type; + MetaFrameFlags frame_flags; + int i; + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_WIDTH, &frame_width, + META_CORE_GET_FRAME_HEIGHT, &frame_height, + META_CORE_GET_SCREEN_WIDTH, &screen_width, + META_CORE_GET_SCREEN_HEIGHT, &screen_height, + META_CORE_GET_CLIENT_WIDTH, &width, + META_CORE_GET_CLIENT_HEIGHT, &height, + META_CORE_GET_FRAME_TYPE, &frame_type, + META_CORE_GET_FRAME_FLAGS, &frame_flags, + META_CORE_GET_END); + + /* don't cache extremely large windows */ + if (frame_width > 2 * screen_width || + frame_height > 2 * screen_height) + { + return; + } + + meta_theme_get_frame_borders (meta_theme_get_current (), + frame_type, + frame->text_height, + frame_flags, + &top, &bottom, &left, &right); + + pixels = get_cache (frames, frame); + + /* Setup the rectangles for the four frame borders. First top, then + left, right and bottom. */ + pixels->piece[0].rect.x = 0; + pixels->piece[0].rect.y = 0; + pixels->piece[0].rect.width = left + width + right; + pixels->piece[0].rect.height = top; + + pixels->piece[1].rect.x = 0; + pixels->piece[1].rect.y = top; + pixels->piece[1].rect.width = left; + pixels->piece[1].rect.height = height; + + pixels->piece[2].rect.x = left + width; + pixels->piece[2].rect.y = top; + pixels->piece[2].rect.width = right; + pixels->piece[2].rect.height = height; + + pixels->piece[3].rect.x = 0; + pixels->piece[3].rect.y = top + height; + pixels->piece[3].rect.width = left + width + right; + pixels->piece[3].rect.height = bottom; + + for (i = 0; i < 4; i++) + { + CachedFramePiece *piece = &pixels->piece[i]; + if (!piece->pixmap) + piece->pixmap = generate_pixmap (frames, frame, piece->rect); + } + + if (frames->invalidate_cache_timeout_id) + g_source_remove (frames->invalidate_cache_timeout_id); + + frames->invalidate_cache_timeout_id = g_timeout_add (1000, invalidate_cache_timeout, frames); + + if (!g_list_find (frames->invalidate_frames, frame)) + frames->invalidate_frames = + g_list_prepend (frames->invalidate_frames, frame); +} + +static void +clip_to_screen (GdkRegion *region, MetaUIFrame *frame) +{ + GdkRectangle frame_area; + GdkRectangle screen_area = { 0, 0, 0, 0 }; + GdkRegion *tmp_region; + + /* Chop off stuff outside the screen; this optimization + * is crucial to handle huge client windows, + * like "xterm -geometry 1000x1000" + */ + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_X, &frame_area.x, + META_CORE_GET_FRAME_Y, &frame_area.y, + META_CORE_GET_FRAME_WIDTH, &frame_area.width, + META_CORE_GET_FRAME_HEIGHT, &frame_area.height, + META_CORE_GET_SCREEN_WIDTH, &screen_area.height, + META_CORE_GET_SCREEN_HEIGHT, &screen_area.height, + META_CORE_GET_END); + + gdk_region_offset (region, frame_area.x, frame_area.y); + + tmp_region = gdk_region_rectangle (&frame_area); + gdk_region_intersect (region, tmp_region); + gdk_region_destroy (tmp_region); + + gdk_region_offset (region, - frame_area.x, - frame_area.y); +} + +static void +subtract_from_region (GdkRegion *region, GdkDrawable *drawable, + gint x, gint y) +{ + GdkRectangle rect; + GdkRegion *reg_rect; + + #if GTK_CHECK_VERSION(3, 0, 0) + rect.width = gdk_window_get_width(GDK_WINDOW(drawable)); + rect.height = gdk_window_get_height(GDK_WINDOW(drawable)); + #else + gdk_drawable_get_size (drawable, &rect.width, &rect.height); + #endif + + rect.x = x; + rect.y = y; + + reg_rect = gdk_region_rectangle (&rect); + gdk_region_subtract (region, reg_rect); + gdk_region_destroy (reg_rect); +} + +static void +cached_pixels_draw (CachedPixels *pixels, + GdkWindow *window, + GdkRegion *region) +{ + cairo_t *cr; + int i; + + cr = gdk_cairo_create (window); + + for (i = 0; i < 4; i++) + { + CachedFramePiece *piece; + piece = &pixels->piece[i]; + + if (piece->pixmap) + { + gdk_cairo_set_source_pixmap (cr, piece->pixmap, + piece->rect.x, piece->rect.y); + cairo_paint (cr); + subtract_from_region (region, piece->pixmap, + piece->rect.x, piece->rect.y); + } + } + + cairo_destroy (cr); +} + +static gboolean +meta_frames_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + MetaUIFrame *frame; + MetaFrames *frames; + GdkRegion *region; + CachedPixels *pixels; + + frames = META_FRAMES (widget); + + frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window)); + if (frame == NULL) + return FALSE; + + if (frames->expose_delay_count > 0) + { + /* Redraw this entire frame later */ + frame->expose_delayed = TRUE; + return TRUE; + } + + populate_cache (frames, frame); + + region = gdk_region_copy (event->region); + + pixels = get_cache (frames, frame); + + cached_pixels_draw (pixels, frame->window, region); + + clip_to_screen (region, frame); + meta_frames_paint_to_drawable (frames, frame, frame->window, region, 0, 0); + + gdk_region_destroy (region); + + return TRUE; +} + +/* How far off the screen edge the window decorations should + * be drawn. Used only in meta_frames_paint_to_drawable, below. + */ +#define DECORATING_BORDER 100 + +static void +meta_frames_paint_to_drawable (MetaFrames *frames, + MetaUIFrame *frame, + GdkDrawable *drawable, + GdkRegion *region, + int x_offset, + int y_offset) +{ + GtkWidget *widget; + MetaFrameFlags flags; + MetaFrameType type; + GdkPixbuf *mini_icon; + GdkPixbuf *icon; + int w, h; + MetaButtonState button_states[META_BUTTON_TYPE_LAST]; + Window grab_frame; + int i; + MetaButtonLayout button_layout; + MetaGrabOp grab_op; + + widget = GTK_WIDGET (frames); + + for (i = 0; i < META_BUTTON_TYPE_LAST; i++) + button_states[i] = META_BUTTON_STATE_NORMAL; + + grab_frame = meta_core_get_grab_frame (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + grab_op = meta_core_get_grab_op (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + if (grab_frame != frame->xwindow) + grab_op = META_GRAB_OP_NONE; + + /* Set prelight state */ + switch (frame->prelit_control) + { + case META_FRAME_CONTROL_MENU: + if (grab_op == META_GRAB_OP_CLICKING_MENU) + button_states[META_BUTTON_TYPE_MENU] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_MENU] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_MINIMIZE: + if (grab_op == META_GRAB_OP_CLICKING_MINIMIZE) + button_states[META_BUTTON_TYPE_MINIMIZE] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_MINIMIZE] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_MAXIMIZE: + if (grab_op == META_GRAB_OP_CLICKING_MAXIMIZE) + button_states[META_BUTTON_TYPE_MAXIMIZE] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_MAXIMIZE] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_UNMAXIMIZE: + if (grab_op == META_GRAB_OP_CLICKING_UNMAXIMIZE) + button_states[META_BUTTON_TYPE_MAXIMIZE] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_MAXIMIZE] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_SHADE: + if (grab_op == META_GRAB_OP_CLICKING_SHADE) + button_states[META_BUTTON_TYPE_SHADE] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_SHADE] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_UNSHADE: + if (grab_op == META_GRAB_OP_CLICKING_UNSHADE) + button_states[META_BUTTON_TYPE_UNSHADE] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_UNSHADE] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_ABOVE: + if (grab_op == META_GRAB_OP_CLICKING_ABOVE) + button_states[META_BUTTON_TYPE_ABOVE] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_ABOVE] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_UNABOVE: + if (grab_op == META_GRAB_OP_CLICKING_UNABOVE) + button_states[META_BUTTON_TYPE_UNABOVE] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_UNABOVE] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_STICK: + if (grab_op == META_GRAB_OP_CLICKING_STICK) + button_states[META_BUTTON_TYPE_STICK] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_STICK] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_UNSTICK: + if (grab_op == META_GRAB_OP_CLICKING_UNSTICK) + button_states[META_BUTTON_TYPE_UNSTICK] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_UNSTICK] = META_BUTTON_STATE_PRELIGHT; + break; + case META_FRAME_CONTROL_DELETE: + if (grab_op == META_GRAB_OP_CLICKING_DELETE) + button_states[META_BUTTON_TYPE_CLOSE] = META_BUTTON_STATE_PRESSED; + else + button_states[META_BUTTON_TYPE_CLOSE] = META_BUTTON_STATE_PRELIGHT; + break; + default: + break; + } + + /* Map button function states to button position states */ + button_states[META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND] = + button_states[META_BUTTON_TYPE_MENU]; + button_states[META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND] = + META_BUTTON_STATE_NORMAL; + button_states[META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND] = + META_BUTTON_STATE_NORMAL; + button_states[META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND] = + button_states[META_BUTTON_TYPE_MINIMIZE]; + button_states[META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND] = + button_states[META_BUTTON_TYPE_MAXIMIZE]; + button_states[META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND] = + button_states[META_BUTTON_TYPE_CLOSE]; + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_FRAME_TYPE, &type, + META_CORE_GET_MINI_ICON, &mini_icon, + META_CORE_GET_ICON, &icon, + META_CORE_GET_CLIENT_WIDTH, &w, + META_CORE_GET_CLIENT_HEIGHT, &h, + META_CORE_GET_END); + + meta_frames_ensure_layout (frames, frame); + + meta_prefs_get_button_layout (&button_layout); + + if (G_LIKELY (GDK_IS_WINDOW (drawable))) + { + /* A window; happens about 2/3 of the time */ + + GdkRectangle area, *areas; + int n_areas; + int screen_width, screen_height; + GdkRegion *edges, *tmp_region; + int top, bottom, left, right; + + /* Repaint each side of the frame */ + + meta_theme_get_frame_borders (meta_theme_get_current (), + type, frame->text_height, flags, + &top, &bottom, &left, &right); + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_SCREEN_WIDTH, &screen_width, + META_CORE_GET_SCREEN_HEIGHT, &screen_height, + META_CORE_GET_END); + + edges = gdk_region_copy (region); + + /* Punch out the client area */ + + area.x = left; + area.y = top; + area.width = w; + area.height = h; + tmp_region = gdk_region_rectangle (&area); + gdk_region_subtract (edges, tmp_region); + gdk_region_destroy (tmp_region); + + /* Now draw remaining portion of region */ + + gdk_region_get_rectangles (edges, &areas, &n_areas); + + for (i = 0; i < n_areas; i++) + { + /* Bug 399529: clamp areas[i] so that it doesn't go too far + * off the edge of the screen. This works around a GDK bug + * which makes gdk_window_begin_paint_rect cause an X error + * if the window is insanely huge. If the client is a GDK program + * and does this, it will still probably cause an X error in that + * program, but the last thing we want is for Marco to crash + * because it attempted to decorate the silly window. + */ + + areas[i].x = MAX (areas[i].x, -DECORATING_BORDER); + areas[i].y = MAX (areas[i].y, -DECORATING_BORDER); + if (areas[i].x+areas[i].width > screen_width + DECORATING_BORDER) + areas[i].width = MIN (0, screen_width - areas[i].x); + if (areas[i].y+areas[i].height > screen_height + DECORATING_BORDER) + areas[i].height = MIN (0, screen_height - areas[i].y); + + /* Okay, so let's start painting. */ + + gdk_window_begin_paint_rect (drawable, &areas[i]); + + meta_theme_draw_frame_with_style (meta_theme_get_current (), + frame->style, + widget, + drawable, + NULL, /* &areas[i], */ + x_offset, y_offset, + type, + flags, + w, h, + frame->layout, + frame->text_height, + &button_layout, + button_states, + mini_icon, icon); + + gdk_window_end_paint (drawable); + } + + g_free (areas); + gdk_region_destroy (edges); + + } + else + { + /* Not a window; happens about 1/3 of the time */ + + meta_theme_draw_frame_with_style (meta_theme_get_current (), + frame->style, + widget, + drawable, + NULL, + x_offset, y_offset, + type, + flags, + w, h, + frame->layout, + frame->text_height, + &button_layout, + button_states, + mini_icon, icon); + } + +} + +static void +meta_frames_set_window_background (MetaFrames *frames, + MetaUIFrame *frame) +{ + MetaFrameFlags flags; + MetaFrameType type; + MetaFrameStyle *style; + gboolean frame_exists; + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_WINDOW_HAS_FRAME, &frame_exists, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_FRAME_TYPE, &type, + META_CORE_GET_END); + + if (frame_exists) + { + style = meta_theme_get_frame_style (meta_theme_get_current (), + type, flags); + } + + if (frame_exists && style->window_background_color != NULL) + { + GdkColor color; + GdkVisual *visual; + + meta_color_spec_render (style->window_background_color, + GTK_WIDGET (frames), + &color); + + /* Set A in ARGB to window_background_alpha, if we have ARGB */ + + visual = gtk_widget_get_visual (GTK_WIDGET (frames)); + if (visual->depth == 32) /* we have ARGB */ + { + color.pixel = (color.pixel & 0xffffff) & + style->window_background_alpha << 24; + } + + gdk_window_set_background (frame->window, &color); + } + else + { + gtk_style_set_background (frame->style, + frame->window, GTK_STATE_NORMAL); + } + } + +static gboolean +meta_frames_enter_notify_event (GtkWidget *widget, + GdkEventCrossing *event) +{ + MetaUIFrame *frame; + MetaFrames *frames; + MetaFrameControl control; + + frames = META_FRAMES (widget); + + frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window)); + if (frame == NULL) + return FALSE; + + control = get_control (frames, frame, event->x, event->y); + meta_frames_update_prelit_control (frames, frame, control); + + return TRUE; +} + +static gboolean +meta_frames_leave_notify_event (GtkWidget *widget, + GdkEventCrossing *event) +{ + MetaUIFrame *frame; + MetaFrames *frames; + + frames = META_FRAMES (widget); + + frame = meta_frames_lookup_window (frames, GDK_WINDOW_XID (event->window)); + if (frame == NULL) + return FALSE; + + meta_frames_update_prelit_control (frames, frame, META_FRAME_CONTROL_NONE); + + clear_tip (frames); + + return TRUE; +} + +static GdkRectangle* +control_rect (MetaFrameControl control, + MetaFrameGeometry *fgeom) +{ + GdkRectangle *rect; + + rect = NULL; + switch (control) + { + case META_FRAME_CONTROL_TITLE: + rect = &fgeom->title_rect; + break; + case META_FRAME_CONTROL_DELETE: + rect = &fgeom->close_rect.visible; + break; + case META_FRAME_CONTROL_MENU: + rect = &fgeom->menu_rect.visible; + break; + case META_FRAME_CONTROL_MINIMIZE: + rect = &fgeom->min_rect.visible; + break; + case META_FRAME_CONTROL_MAXIMIZE: + case META_FRAME_CONTROL_UNMAXIMIZE: + rect = &fgeom->max_rect.visible; + break; + case META_FRAME_CONTROL_SHADE: + rect = &fgeom->shade_rect.visible; + break; + case META_FRAME_CONTROL_UNSHADE: + rect = &fgeom->unshade_rect.visible; + break; + case META_FRAME_CONTROL_ABOVE: + rect = &fgeom->above_rect.visible; + break; + case META_FRAME_CONTROL_UNABOVE: + rect = &fgeom->unabove_rect.visible; + break; + case META_FRAME_CONTROL_STICK: + rect = &fgeom->stick_rect.visible; + break; + case META_FRAME_CONTROL_UNSTICK: + rect = &fgeom->unstick_rect.visible; + break; + case META_FRAME_CONTROL_RESIZE_SE: + break; + case META_FRAME_CONTROL_RESIZE_S: + break; + case META_FRAME_CONTROL_RESIZE_SW: + break; + case META_FRAME_CONTROL_RESIZE_N: + break; + case META_FRAME_CONTROL_RESIZE_NE: + break; + case META_FRAME_CONTROL_RESIZE_NW: + break; + case META_FRAME_CONTROL_RESIZE_W: + break; + case META_FRAME_CONTROL_RESIZE_E: + break; + case META_FRAME_CONTROL_NONE: + break; + case META_FRAME_CONTROL_CLIENT_AREA: + break; + } + + return rect; +} + +#define RESIZE_EXTENDS 15 +#define TOP_RESIZE_HEIGHT 2 +static MetaFrameControl +get_control (MetaFrames *frames, + MetaUIFrame *frame, + int x, int y) +{ + MetaFrameGeometry fgeom; + MetaFrameFlags flags; + gboolean has_vert, has_horiz; + GdkRectangle client; + + meta_frames_calc_geometry (frames, frame, &fgeom); + + client.x = fgeom.left_width; + client.y = fgeom.top_height; + client.width = fgeom.width - fgeom.left_width - fgeom.right_width; + client.height = fgeom.height - fgeom.top_height - fgeom.bottom_height; + + if (POINT_IN_RECT (x, y, client)) + return META_FRAME_CONTROL_CLIENT_AREA; + + if (POINT_IN_RECT (x, y, fgeom.close_rect.clickable)) + return META_FRAME_CONTROL_DELETE; + + if (POINT_IN_RECT (x, y, fgeom.min_rect.clickable)) + return META_FRAME_CONTROL_MINIMIZE; + + if (POINT_IN_RECT (x, y, fgeom.menu_rect.clickable)) + return META_FRAME_CONTROL_MENU; + + meta_core_get (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), frame->xwindow, + META_CORE_GET_FRAME_FLAGS, &flags, + META_CORE_GET_END); + + has_vert = (flags & META_FRAME_ALLOWS_VERTICAL_RESIZE) != 0; + has_horiz = (flags & META_FRAME_ALLOWS_HORIZONTAL_RESIZE) != 0; + + if (POINT_IN_RECT (x, y, fgeom.title_rect)) + { + if (has_vert && y <= TOP_RESIZE_HEIGHT) + return META_FRAME_CONTROL_RESIZE_N; + else + return META_FRAME_CONTROL_TITLE; + } + + if (POINT_IN_RECT (x, y, fgeom.max_rect.clickable)) + { + if (flags & META_FRAME_MAXIMIZED) + return META_FRAME_CONTROL_UNMAXIMIZE; + else + return META_FRAME_CONTROL_MAXIMIZE; + } + + if (POINT_IN_RECT (x, y, fgeom.shade_rect.clickable)) + { + return META_FRAME_CONTROL_SHADE; + } + + if (POINT_IN_RECT (x, y, fgeom.unshade_rect.clickable)) + { + return META_FRAME_CONTROL_UNSHADE; + } + + if (POINT_IN_RECT (x, y, fgeom.above_rect.clickable)) + { + return META_FRAME_CONTROL_ABOVE; + } + + if (POINT_IN_RECT (x, y, fgeom.unabove_rect.clickable)) + { + return META_FRAME_CONTROL_UNABOVE; + } + + if (POINT_IN_RECT (x, y, fgeom.stick_rect.clickable)) + { + return META_FRAME_CONTROL_STICK; + } + + if (POINT_IN_RECT (x, y, fgeom.unstick_rect.clickable)) + { + return META_FRAME_CONTROL_UNSTICK; + } + + /* South resize always has priority over north resize, + * in case of overlap. + */ + + if (y >= (fgeom.height - fgeom.bottom_height - RESIZE_EXTENDS) && + x >= (fgeom.width - fgeom.right_width - RESIZE_EXTENDS)) + { + if (has_vert && has_horiz) + return META_FRAME_CONTROL_RESIZE_SE; + else if (has_vert) + return META_FRAME_CONTROL_RESIZE_S; + else if (has_horiz) + return META_FRAME_CONTROL_RESIZE_E; + } + else if (y >= (fgeom.height - fgeom.bottom_height - RESIZE_EXTENDS) && + x <= (fgeom.left_width + RESIZE_EXTENDS)) + { + if (has_vert && has_horiz) + return META_FRAME_CONTROL_RESIZE_SW; + else if (has_vert) + return META_FRAME_CONTROL_RESIZE_S; + else if (has_horiz) + return META_FRAME_CONTROL_RESIZE_W; + } + else if (y < (fgeom.top_height + RESIZE_EXTENDS) && + x < RESIZE_EXTENDS) + { + if (has_vert && has_horiz) + return META_FRAME_CONTROL_RESIZE_NW; + else if (has_vert) + return META_FRAME_CONTROL_RESIZE_N; + else if (has_horiz) + return META_FRAME_CONTROL_RESIZE_W; + } + else if (y < (fgeom.top_height + RESIZE_EXTENDS) && + x >= (fgeom.width - RESIZE_EXTENDS)) + { + if (has_vert && has_horiz) + return META_FRAME_CONTROL_RESIZE_NE; + else if (has_vert) + return META_FRAME_CONTROL_RESIZE_N; + else if (has_horiz) + return META_FRAME_CONTROL_RESIZE_E; + } + else if (y >= (fgeom.height - fgeom.bottom_height - RESIZE_EXTENDS)) + { + if (has_vert) + return META_FRAME_CONTROL_RESIZE_S; + } + else if (y <= TOP_RESIZE_HEIGHT) + { + if (has_vert) + return META_FRAME_CONTROL_RESIZE_N; + else if (has_horiz) + return META_FRAME_CONTROL_TITLE; + } + else if (x <= fgeom.left_width) + { + if (has_horiz) + return META_FRAME_CONTROL_RESIZE_W; + } + else if (x >= (fgeom.width - fgeom.right_width)) + { + if (has_horiz) + return META_FRAME_CONTROL_RESIZE_E; + } + + if (y >= fgeom.top_height) + return META_FRAME_CONTROL_NONE; + else + return META_FRAME_CONTROL_TITLE; +} + +void +meta_frames_push_delay_exposes (MetaFrames *frames) +{ + if (frames->expose_delay_count == 0) + { + /* Make sure we've repainted things */ + gdk_window_process_all_updates (); + XFlush (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + } + + frames->expose_delay_count += 1; +} + +static void +queue_pending_exposes_func (gpointer key, gpointer value, gpointer data) +{ + MetaUIFrame *frame; + MetaFrames *frames; + + frames = META_FRAMES (data); + frame = value; + + if (frame->expose_delayed) + { + invalidate_whole_window (frames, frame); + frame->expose_delayed = FALSE; + } +} + +void +meta_frames_pop_delay_exposes (MetaFrames *frames) +{ + g_return_if_fail (frames->expose_delay_count > 0); + + frames->expose_delay_count -= 1; + + if (frames->expose_delay_count == 0) + { + g_hash_table_foreach (frames->frames, + queue_pending_exposes_func, + frames); + } +} + +static void +invalidate_whole_window (MetaFrames *frames, + MetaUIFrame *frame) +{ + gdk_window_invalidate_rect (frame->window, NULL, FALSE); + invalidate_cache (frames, frame); +} diff --git a/src/ui/frames.h b/src/ui/frames.h new file mode 100644 index 00000000..e9da4714 --- /dev/null +++ b/src/ui/frames.h @@ -0,0 +1,163 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco window frame manager widget */ + +/* + * Copyright (C) 2001 Havoc Pennington + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef META_FRAMES_H +#define META_FRAMES_H + +#include +#include +#include "common.h" +#include "theme.h" + +typedef enum +{ + META_FRAME_CONTROL_NONE, + META_FRAME_CONTROL_TITLE, + META_FRAME_CONTROL_DELETE, + META_FRAME_CONTROL_MENU, + META_FRAME_CONTROL_MINIMIZE, + META_FRAME_CONTROL_MAXIMIZE, + META_FRAME_CONTROL_UNMAXIMIZE, + META_FRAME_CONTROL_SHADE, + META_FRAME_CONTROL_UNSHADE, + META_FRAME_CONTROL_ABOVE, + META_FRAME_CONTROL_UNABOVE, + META_FRAME_CONTROL_STICK, + META_FRAME_CONTROL_UNSTICK, + META_FRAME_CONTROL_RESIZE_SE, + META_FRAME_CONTROL_RESIZE_S, + META_FRAME_CONTROL_RESIZE_SW, + META_FRAME_CONTROL_RESIZE_N, + META_FRAME_CONTROL_RESIZE_NE, + META_FRAME_CONTROL_RESIZE_NW, + META_FRAME_CONTROL_RESIZE_W, + META_FRAME_CONTROL_RESIZE_E, + META_FRAME_CONTROL_CLIENT_AREA +} MetaFrameControl; + +/* This is one widget that manages all the window frames + * as subwindows. + */ + +#define META_TYPE_FRAMES (meta_frames_get_type ()) +#define META_FRAMES(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_FRAMES, MetaFrames)) +#define META_FRAMES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), META_TYPE_FRAMES, MetaFramesClass)) +#define META_IS_FRAMES(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), META_TYPE_FRAMES)) +#define META_IS_FRAMES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), META_TYPE_FRAMES)) +#define META_FRAMES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), META_TYPE_FRAMES, MetaFramesClass)) + +typedef struct _MetaFrames MetaFrames; +typedef struct _MetaFramesClass MetaFramesClass; + +typedef struct _MetaUIFrame MetaUIFrame; + +struct _MetaUIFrame +{ + Window xwindow; + GdkWindow *window; + GtkStyle *style; + MetaFrameStyle *cache_style; + PangoLayout *layout; + int text_height; + char *title; /* NULL once we have a layout */ + guint expose_delayed : 1; + guint shape_applied : 1; + + /* FIXME get rid of this, it can just be in the MetaFrames struct */ + MetaFrameControl prelit_control; +}; + +struct _MetaFrames +{ + GtkWindow parent_instance; + + GHashTable *text_heights; + + GHashTable *frames; + + guint tooltip_timeout; + MetaUIFrame *last_motion_frame; + + int expose_delay_count; + + int invalidate_cache_timeout_id; + GList *invalidate_frames; + GHashTable *cache; +}; + +struct _MetaFramesClass +{ + GtkWindowClass parent_class; + +}; + +GType meta_frames_get_type (void) G_GNUC_CONST; + +MetaFrames *meta_frames_new (int screen_number); + +void meta_frames_manage_window (MetaFrames *frames, + Window xwindow, + GdkWindow *window); +void meta_frames_unmanage_window (MetaFrames *frames, + Window xwindow); +void meta_frames_set_title (MetaFrames *frames, + Window xwindow, + const char *title); + +void meta_frames_repaint_frame (MetaFrames *frames, + Window xwindow); + +void meta_frames_get_geometry (MetaFrames *frames, + Window xwindow, + int *top_height, int *bottom_height, + int *left_width, int *right_width); + +void meta_frames_reset_bg (MetaFrames *frames, + Window xwindow); +void meta_frames_unflicker_bg (MetaFrames *frames, + Window xwindow, + int target_width, + int target_height); + +void meta_frames_apply_shapes (MetaFrames *frames, + Window xwindow, + int new_window_width, + int new_window_height, + gboolean window_has_shape); +void meta_frames_move_resize_frame (MetaFrames *frames, + Window xwindow, + int x, + int y, + int width, + int height); +void meta_frames_queue_draw (MetaFrames *frames, + Window xwindow); + +void meta_frames_notify_menu_hide (MetaFrames *frames); + +Window meta_frames_get_moving_frame (MetaFrames *frames); + +void meta_frames_push_delay_exposes (MetaFrames *frames); +void meta_frames_pop_delay_exposes (MetaFrames *frames); + +#endif diff --git a/src/ui/gradient.c b/src/ui/gradient.c new file mode 100644 index 00000000..0e739817 --- /dev/null +++ b/src/ui/gradient.c @@ -0,0 +1,842 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco gradient rendering */ + +/* + * Copyright (C) 2001 Havoc Pennington, 99% copied from wrlib in + * WindowMaker, Copyright (C) 1997-2000 Dan Pascu and Alfredo Kojima + * Copyright (C) 2005 Elijah Newren + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. */ + +#include "gradient.h" +#include "util.h" +#include + +/* 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>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>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; ired / 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>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 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>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>16); + *ptr++ = (unsigned char)(g>>16); + *ptr++ = (unsigned char)(b>>16); + } + + /* copy the first line to the other lines */ + for (i=1; i 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>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>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 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 0); + + if (n_alphas == 1) + { + /* Optimize this */ + simple_multiply_alpha (pixbuf, alphas[0]); + return; + } + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + + gradient = g_new (unsigned char, width); + gradient_end = gradient + width; + + if (n_alphas > width) + n_alphas = width; + + if (n_alphas > 1) + width2 = width / (n_alphas - 1); + else + width2 = width; + + a = alphas[0] << 8; + gradient_p = gradient; + + /* render the gradient into an array */ + for (i = 1; i < n_alphas; i++) + { + da = (((int)(alphas[i] - (int) alphas[i-1])) << 8) / (int) width2; + + for (j = 0; j < width2; j++) + { + *gradient_p++ = (a >> 8); + + a += da; + } + + a = alphas[i] << 8; + } + + /* get leftover pixels */ + while (gradient_p != gradient_end) + { + *gradient_p++ = a >> 8; + } + + /* Now for each line of the pixbuf, fill in with the gradient */ + pixels = gdk_pixbuf_get_pixels (pixbuf); + rowstride = gdk_pixbuf_get_rowstride (pixbuf); + + p = pixels; + i = 0; + while (i < height) + { + unsigned char *row_end = p + rowstride; + gradient_p = gradient; + + p += 3; + while (gradient_p != gradient_end) + { + /* multiply the two alpha channels. not sure this is right. + * but some end cases are that if the pixbuf contains 255, + * then it should be modified to contain "alpha"; if the + * pixbuf contains 0, it should remain 0. + */ + /* ((*p / 255.0) * (alpha / 255.0)) * 255; */ + *p = (guchar) (((int) *p * (int) *gradient_p) / (int) 255); + + p += 4; + ++gradient_p; + } + + p = row_end; + ++i; + } + + g_free (gradient); +} + +void +meta_gradient_add_alpha (GdkPixbuf *pixbuf, + const guchar *alphas, + int n_alphas, + MetaGradientType type) +{ + g_return_if_fail (GDK_IS_PIXBUF (pixbuf)); + g_return_if_fail (gdk_pixbuf_get_has_alpha (pixbuf)); + g_return_if_fail (n_alphas > 0); + + switch (type) + { + case META_GRADIENT_HORIZONTAL: + meta_gradient_add_alpha_horizontal (pixbuf, alphas, n_alphas); + break; + + case META_GRADIENT_VERTICAL: + g_printerr ("marco: vertical alpha channel gradient not implemented yet\n"); + break; + + case META_GRADIENT_DIAGONAL: + g_printerr ("marco: diagonal alpha channel gradient not implemented yet\n"); + break; + + case META_GRADIENT_LAST: + g_assert_not_reached (); + break; + } +} diff --git a/src/ui/gradient.h b/src/ui/gradient.h new file mode 100644 index 00000000..196fd6a0 --- /dev/null +++ b/src/ui/gradient.h @@ -0,0 +1,65 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco gradient rendering */ + +/* + * Copyright (C) 2001 Havoc Pennington, 99% copied from wrlib in + * WindowMaker, Copyright (C) 1997-2000 Dan Pascu and Alfredo Kojima + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. */ + +#ifndef META_GRADIENT_H +#define META_GRADIENT_H + +#include +#include + +typedef enum +{ + META_GRADIENT_VERTICAL, + META_GRADIENT_HORIZONTAL, + META_GRADIENT_DIAGONAL, + META_GRADIENT_LAST +} MetaGradientType; + +GdkPixbuf* meta_gradient_create_simple (int width, + int height, + const GdkColor *from, + const GdkColor *to, + MetaGradientType style); +GdkPixbuf* meta_gradient_create_multi (int width, + int height, + const GdkColor *colors, + int n_colors, + MetaGradientType style); +GdkPixbuf* meta_gradient_create_interwoven (int width, + int height, + const GdkColor colors1[2], + int thickness1, + const GdkColor colors2[2], + int thickness2); + + +/* Generate an alpha gradient and multiply it with the existing alpha + * channel of the given pixbuf + */ +void meta_gradient_add_alpha (GdkPixbuf *pixbuf, + const guchar *alphas, + int n_alphas, + MetaGradientType type); + + +#endif diff --git a/src/ui/menu.c b/src/ui/menu.c new file mode 100644 index 00000000..5207876f --- /dev/null +++ b/src/ui/menu.c @@ -0,0 +1,509 @@ +/* Marco window menu */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2004 Rob Adams + * Copyright (C) 2005 Elijah Newren + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include +#include +#include +#include "menu.h" +#include "main.h" +#include "util.h" +#include "core.h" +#include "themewidget.h" +#include "metaaccellabel.h" +#include "ui.h" + +typedef struct _MenuItem MenuItem; +typedef struct _MenuData MenuData; + +typedef enum { + MENU_ITEM_SEPARATOR = 0, + MENU_ITEM_NORMAL, + MENU_ITEM_IMAGE, + MENU_ITEM_CHECKBOX, + MENU_ITEM_RADIOBUTTON, + MENU_ITEM_WORKSPACE_LIST, +} MetaMenuItemType; + +struct _MenuItem { + MetaMenuOp op; + MetaMenuItemType type; + const char* stock_id; + const gboolean checked; + const char* label; +}; + + +struct _MenuData { + MetaWindowMenu* menu; + MetaMenuOp op; +}; + +static void activate_cb(GtkWidget* menuitem, gpointer data); + +static MenuItem menuitems[] = { + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_MINIMIZE, MENU_ITEM_IMAGE, MARCO_STOCK_MINIMIZE, FALSE, N_("Mi_nimize")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_MAXIMIZE, MENU_ITEM_IMAGE, MARCO_STOCK_MAXIMIZE, FALSE, N_("Ma_ximize")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_UNMAXIMIZE, MENU_ITEM_IMAGE, MARCO_STOCK_RESTORE, FALSE, N_("Unma_ximize")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_SHADE, MENU_ITEM_NORMAL, NULL, FALSE, N_("Roll _Up")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_UNSHADE, MENU_ITEM_NORMAL, NULL, FALSE, N_("_Unroll")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_MOVE, MENU_ITEM_NORMAL, NULL, FALSE, N_("_Move") }, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_RESIZE, MENU_ITEM_NORMAL, NULL, FALSE, N_("_Resize")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_RECOVER, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move Titlebar On_screen")}, + {META_MENU_OP_WORKSPACES, MENU_ITEM_SEPARATOR, NULL, FALSE, NULL}, /* separator */ + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_ABOVE, MENU_ITEM_CHECKBOX, NULL, FALSE, N_("Always on _Top")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_UNABOVE, MENU_ITEM_CHECKBOX, NULL, TRUE, N_("Always on _Top")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_STICK, MENU_ITEM_RADIOBUTTON, NULL, FALSE, N_("_Always on Visible Workspace")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_UNSTICK, MENU_ITEM_RADIOBUTTON, NULL, FALSE, N_("_Only on This Workspace")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_MOVE_LEFT, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move to Workspace _Left")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_MOVE_RIGHT, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move to Workspace R_ight")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_MOVE_UP, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move to Workspace _Up")}, + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_MOVE_DOWN, MENU_ITEM_NORMAL, NULL, FALSE, N_("Move to Workspace _Down")}, + {0, MENU_ITEM_WORKSPACE_LIST, NULL, FALSE, NULL}, + {0, MENU_ITEM_SEPARATOR, NULL, FALSE, NULL}, /* separator */ + /* Translators: Translate this string the same way as you do in libwnck! */ + {META_MENU_OP_DELETE, MENU_ITEM_IMAGE, MARCO_STOCK_DELETE, FALSE, N_("_Close")} +}; + +static void popup_position_func(GtkMenu* menu, gint* x, gint* y, gboolean* push_in, gpointer user_data) +{ + GtkRequisition req; + GdkPoint* pos; + + pos = user_data; + + gtk_widget_size_request(GTK_WIDGET(menu), &req); + + *x = pos->x; + *y = pos->y; + + if (meta_ui_get_direction() == META_UI_DIRECTION_RTL) + { + *x = MAX (0, *x - req.width); + } + + /* Ensure onscreen */ + *x = CLAMP (*x, 0, MAX(0, gdk_screen_width() - req.width)); + *y = CLAMP (*y, 0, MAX(0, gdk_screen_height() - req.height)); +} + +static void menu_closed(GtkMenu* widget, gpointer data) +{ + MetaWindowMenu *menu; + + menu = data; + + meta_frames_notify_menu_hide (menu->frames); + + (*menu->func)( + menu, + GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), + menu->client_xwindow, + gtk_get_current_event_time (), + 0, 0, + menu->data); + + /* menu may now be freed */ +} + +static void activate_cb(GtkWidget* menuitem, gpointer data) +{ + MenuData* md; + + g_return_if_fail (GTK_IS_WIDGET (menuitem)); + + md = data; + + meta_frames_notify_menu_hide(md->menu->frames); + + (*md->menu->func)( + md->menu, + GDK_DISPLAY_XDISPLAY (gdk_display_get_default()), + md->menu->client_xwindow, + gtk_get_current_event_time(), + md->op, + GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem), "workspace")), + md->menu->data); + + /* menu may now be freed */ +} + +/* + * Given a Display and an index, get the workspace name and add any + * accelerators. At the moment this means adding a _ if the name is of + * the form "Workspace n" where n is less than 10, and escaping any + * other '_'s so they do not create inadvertant accelerators. + * + * The calling code owns the string, and is reponsible to free the + * memory after use. + * + * See also http://mail.gnome.org/archives/mate-i18n/2008-March/msg00380.html + * which discusses possible i18n concerns. + */ +static char* +get_workspace_name_with_accel (Display *display, + Window xroot, + int index) +{ + const char *name; + int number; + int charcount=0; + + name = meta_core_get_workspace_name_with_index (display, xroot, index); + + g_assert (name != NULL); + + /* + * If the name is of the form "Workspace x" where x is an unsigned + * integer, insert a '_' before the number if it is less than 10 and + * return it + */ + number = 0; + if (sscanf (name, _("Workspace %d%n"), &number, &charcount) != 0 && + *(name + charcount)=='\0') + { + char *new_name; + + /* + * Above name is a pointer into the Workspace struct. Here we make + * a copy copy so we can have our wicked way with it. + */ + if (number == 10) + new_name = g_strdup_printf (_("Workspace 1_0")); + else + new_name = g_strdup_printf (_("Workspace %s%d"), + number < 10 ? "_" : "", + number); + return new_name; + } + else + { + /* + * Otherwise this is just a normal name. Escape any _ characters so that + * the user's workspace names do not get mangled. If the number is less + * than 10 we provide an accelerator. + */ + char *new_name; + const char *source; + char *dest; + + /* + * Assume the worst case, that every character is a _. We also + * provide memory for " (_#)" + */ + new_name = g_malloc0 (strlen (name) * 2 + 6 + 1); + + /* + * Now iterate down the strings, adding '_' to escape as we go + */ + dest = new_name; + source = name; + while (*source != '\0') + { + if (*source == '_') + *dest++ = '_'; + *dest++ = *source++; + } + + /* People don't start at workspace 0, but workspace 1 */ + if (index < 9) + { + g_snprintf (dest, 6, " (_%d)", index + 1); + } + else if (index == 9) + { + g_snprintf (dest, 6, " (_0)"); + } + + return new_name; + } +} + +static GtkWidget* menu_item_new(MenuItem* menuitem, int workspace_id) +{ + unsigned int key; + MetaVirtualModifier mods; + const char* i18n_label; + GtkWidget* mi; + GtkWidget* accel_label; + + if (menuitem->type == MENU_ITEM_NORMAL) + { + mi = gtk_menu_item_new (); + } + else if (menuitem->type == MENU_ITEM_IMAGE) + { + GtkWidget* image = gtk_image_new_from_icon_name(menuitem->stock_id, GTK_ICON_SIZE_MENU); + + mi = gtk_image_menu_item_new(); + + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), image); + gtk_widget_show(image); + } + else if (menuitem->type == MENU_ITEM_CHECKBOX) + { + mi = gtk_check_menu_item_new (); + + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(mi), menuitem->checked); + } + else if (menuitem->type == MENU_ITEM_RADIOBUTTON) + { + mi = gtk_check_menu_item_new (); + + gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (mi), TRUE); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mi), menuitem->checked); + } + else if (menuitem->type == MENU_ITEM_WORKSPACE_LIST) + { + return NULL; + } + else + { + return gtk_separator_menu_item_new(); + } + + i18n_label = _(menuitem->label); + meta_core_get_menu_accelerator (menuitem->op, workspace_id, &key, &mods); + + accel_label = meta_accel_label_new_with_mnemonic (i18n_label); + gtk_misc_set_alignment (GTK_MISC (accel_label), 0.0, 0.5); + + gtk_container_add (GTK_CONTAINER (mi), accel_label); + gtk_widget_show (accel_label); + + meta_accel_label_set_accelerator (META_ACCEL_LABEL (accel_label), key, mods); + + return mi; +} + +MetaWindowMenu* +meta_window_menu_new (MetaFrames *frames, + MetaMenuOp ops, + MetaMenuOp insensitive, + Window client_xwindow, + unsigned long active_workspace, + int n_workspaces, + MetaWindowMenuFunc func, + gpointer data) +{ + int i; + MetaWindowMenu *menu; + + /* FIXME: Modifications to 'ops' should happen in meta_window_show_menu */ + if (n_workspaces < 2) + ops &= ~(META_MENU_OP_STICK | META_MENU_OP_UNSTICK | META_MENU_OP_WORKSPACES); + else if (n_workspaces == 2) + /* #151183: If we only have two workspaces, disable the menu listing them. */ + ops &= ~(META_MENU_OP_WORKSPACES); + + menu = g_new (MetaWindowMenu, 1); + menu->frames = frames; + menu->client_xwindow = client_xwindow; + menu->func = func; + menu->data = data; + menu->ops = ops; + menu->insensitive = insensitive; + + menu->menu = gtk_menu_new (); + + gtk_menu_set_screen (GTK_MENU (menu->menu), + gtk_widget_get_screen (GTK_WIDGET (frames))); + + for (i = 0; i < (int) G_N_ELEMENTS (menuitems); i++) + { + MenuItem menuitem = menuitems[i]; + if (ops & menuitem.op || menuitem.op == 0) + { + GtkWidget *mi; + MenuData *md; + unsigned int key; + MetaVirtualModifier mods; + + mi = menu_item_new (&menuitem, -1); + + /* Set the activeness of radiobuttons. */ + switch (menuitem.op) + { + case META_MENU_OP_STICK: + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mi), + active_workspace == 0xFFFFFFFF); + break; + case META_MENU_OP_UNSTICK: + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mi), + active_workspace != 0xFFFFFFFF); + break; + default: + break; + } + + if (menuitem.type == MENU_ITEM_WORKSPACE_LIST) + { + if (ops & META_MENU_OP_WORKSPACES) + { + Display *display; + Window xroot; + GdkScreen *screen; + GtkWidget *submenu; + int j; + + MenuItem to_another_workspace = { + 0, MENU_ITEM_NORMAL, + NULL, FALSE, + N_("Move to Another _Workspace") + }; + + meta_verbose ("Creating %d-workspace menu current space %lu\n", + n_workspaces, active_workspace); + + display = gdk_x11_drawable_get_xdisplay (GTK_WIDGET (frames)->window); + + screen = gdk_drawable_get_screen (GTK_WIDGET (frames)->window); + xroot = GDK_DRAWABLE_XID (gdk_screen_get_root_window (screen)); + + submenu = gtk_menu_new (); + + g_assert (mi==NULL); + mi = menu_item_new (&to_another_workspace, -1); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), submenu); + + for (j = 0; j < n_workspaces; j++) + { + char *label; + MenuData *md; + unsigned int key; + MetaVirtualModifier mods; + MenuItem moveitem; + GtkWidget *submi; + + meta_core_get_menu_accelerator (META_MENU_OP_WORKSPACES, + j + 1, + &key, &mods); + + label = get_workspace_name_with_accel (display, xroot, j); + + moveitem.type = MENU_ITEM_NORMAL; + moveitem.op = META_MENU_OP_WORKSPACES; + moveitem.label = label; + submi = menu_item_new (&moveitem, j + 1); + + g_free (label); + + if ((active_workspace == (unsigned)j) && (ops & META_MENU_OP_UNSTICK)) + gtk_widget_set_sensitive (submi, FALSE); + + md = g_new (MenuData, 1); + + md->menu = menu; + md->op = META_MENU_OP_WORKSPACES; + + g_object_set_data (G_OBJECT (submi), + "workspace", + GINT_TO_POINTER (j)); + + gtk_signal_connect_full (GTK_OBJECT (submi), + "activate", + G_CALLBACK (activate_cb), + NULL, + md, + g_free, FALSE, FALSE); + + gtk_menu_shell_append (GTK_MENU_SHELL (submenu), submi); + + gtk_widget_show (submi); + } + } + else + meta_verbose ("not creating workspace menu\n"); + } + else if (menuitem.type != MENU_ITEM_SEPARATOR) + { + meta_core_get_menu_accelerator (menuitems[i].op, -1, + &key, &mods); + + if (insensitive & menuitem.op) + gtk_widget_set_sensitive (mi, FALSE); + + md = g_new (MenuData, 1); + + md->menu = menu; + md->op = menuitem.op; + + gtk_signal_connect_full (GTK_OBJECT (mi), + "activate", + G_CALLBACK (activate_cb), + NULL, + md, + g_free, FALSE, FALSE); + } + + if (mi) + { + gtk_menu_shell_append (GTK_MENU_SHELL (menu->menu), mi); + + gtk_widget_show (mi); + } + } + } + + g_signal_connect (menu->menu, "selection_done", G_CALLBACK(menu_closed), menu); + + return menu; +} + +void meta_window_menu_popup(MetaWindowMenu* menu, int root_x, int root_y, int button, guint32 timestamp) +{ + GdkPoint* pt = g_new(GdkPoint, 1); + + g_object_set_data_full(G_OBJECT(menu->menu), "destroy-point", pt, g_free); + + pt->x = root_x; + pt->y = root_y; + + gtk_menu_popup(GTK_MENU (menu->menu), NULL, NULL, popup_position_func, pt, button, timestamp); + + if (!GTK_MENU_SHELL(menu->menu)->have_xgrab) + { + meta_warning("GtkMenu failed to grab the pointer\n"); + } +} + +void meta_window_menu_free(MetaWindowMenu* menu) +{ + gtk_widget_destroy(menu->menu); + g_free(menu); +} diff --git a/src/ui/menu.h b/src/ui/menu.h new file mode 100644 index 00000000..29e85a5b --- /dev/null +++ b/src/ui/menu.h @@ -0,0 +1,51 @@ +/* Marco window menu */ + +/* + * Copyright (C) 2001 Havoc Pennington + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef META_MENU_H +#define META_MENU_H + +#include +#include "frames.h" + +/* Stock icons */ +#define MARCO_STOCK_DELETE "window-close" +#define MARCO_STOCK_RESTORE "view-restore" +#define MARCO_STOCK_MINIMIZE "go-down" +#define MARCO_STOCK_MAXIMIZE "view-fullscreen" + + + +struct _MetaWindowMenu { + MetaFrames* frames; + Window client_xwindow; + GtkWidget* menu; + MetaWindowMenuFunc func; + gpointer data; + MetaMenuOp ops; + MetaMenuOp insensitive; +}; + +MetaWindowMenu* meta_window_menu_new(MetaFrames* frames, MetaMenuOp ops, MetaMenuOp insensitive, Window client_xwindow, unsigned long active_workspace, int n_workspaces, MetaWindowMenuFunc func, gpointer data); +void meta_window_menu_popup(MetaWindowMenu* menu, int root_x, int root_y, int button, guint32 timestamp); +void meta_window_menu_free(MetaWindowMenu* menu); + + +#endif diff --git a/src/ui/metaaccellabel.c b/src/ui/metaaccellabel.c new file mode 100644 index 00000000..5efd7d2e --- /dev/null +++ b/src/ui/metaaccellabel.c @@ -0,0 +1,456 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco hacked-up GtkAccelLabel */ +/* Copyright (C) 2002 Red Hat, Inc. */ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * MetaAccelLabel: GtkLabel with accelerator monitoring facilities. + * Copyright (C) 1998 Tim Janik + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * Modified by the GTK+ Team and others 1997-2001. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +#include +#include "metaaccellabel.h" +#include +#include +#include "util.h" + +static void meta_accel_label_class_init (MetaAccelLabelClass *klass); +static void meta_accel_label_init (MetaAccelLabel *accel_label); +static void meta_accel_label_destroy (GtkObject *object); +static void meta_accel_label_finalize (GObject *object); +static void meta_accel_label_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static gboolean meta_accel_label_expose_event (GtkWidget *widget, + GdkEventExpose *event); + +static void meta_accel_label_update (MetaAccelLabel *accel_label); +static int meta_accel_label_get_accel_width (MetaAccelLabel *accel_label); + + +static GtkLabelClass *parent_class = NULL; + + +GType +meta_accel_label_get_type (void) +{ + static GType accel_label_type = 0; + + if (!accel_label_type) + { + static const GtkTypeInfo accel_label_info = + { + "MetaAccelLabel", + sizeof (MetaAccelLabel), + sizeof (MetaAccelLabelClass), + (GtkClassInitFunc) meta_accel_label_class_init, + (GtkObjectInitFunc) meta_accel_label_init, + /* reserved_1 */ NULL, + /* reserved_2 */ NULL, + (GtkClassInitFunc) NULL, + }; + + accel_label_type = gtk_type_unique (GTK_TYPE_LABEL, &accel_label_info); + } + + return accel_label_type; +} + +static void +meta_accel_label_class_init (MetaAccelLabelClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + GtkObjectClass *object_class = GTK_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + parent_class = g_type_class_peek_parent (class); + + gobject_class->finalize = meta_accel_label_finalize; + + object_class->destroy = meta_accel_label_destroy; + + widget_class->size_request = meta_accel_label_size_request; + widget_class->expose_event = meta_accel_label_expose_event; + + class->signal_quote1 = g_strdup ("<:"); + class->signal_quote2 = g_strdup (":>"); + /* This is the text that should appear next to menu accelerators + * that use the shift key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_shift = g_strdup (_("Shift")); + /* This is the text that should appear next to menu accelerators + * that use the control key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_control = g_strdup (_("Ctrl")); + /* This is the text that should appear next to menu accelerators + * that use the alt key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_alt = g_strdup (_("Alt")); + /* This is the text that should appear next to menu accelerators + * that use the meta key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_meta = g_strdup (_("Meta")); + /* This is the text that should appear next to menu accelerators + * that use the super key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_super = g_strdup (_("Super")); + /* This is the text that should appear next to menu accelerators + * that use the hyper key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_hyper = g_strdup (_("Hyper")); + /* This is the text that should appear next to menu accelerators + * that use the mod2 key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_mod2 = g_strdup (_("Mod2")); + /* This is the text that should appear next to menu accelerators + * that use the mod3 key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_mod3 = g_strdup (_("Mod3")); + /* This is the text that should appear next to menu accelerators + * that use the mod4 key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_mod4 = g_strdup (_("Mod4")); + /* This is the text that should appear next to menu accelerators + * that use the mod5 key. If the text on this key isn't typically + * translated on keyboards used for your language, don't translate + * this. + */ + class->mod_name_mod5 = g_strdup (_("Mod5")); + + class->mod_separator = g_strdup ("+"); + class->accel_seperator = g_strdup (" / "); + class->latin1_to_char = TRUE; +} + +static void +meta_accel_label_init (MetaAccelLabel *accel_label) +{ + accel_label->accel_padding = 3; + accel_label->accel_string = NULL; + + meta_accel_label_update (accel_label); +} + +GtkWidget* +meta_accel_label_new_with_mnemonic (const gchar *string) +{ + MetaAccelLabel *accel_label; + + g_return_val_if_fail (string != NULL, NULL); + + accel_label = g_object_new (META_TYPE_ACCEL_LABEL, NULL); + + gtk_label_set_text_with_mnemonic (GTK_LABEL (accel_label), string); + + return GTK_WIDGET (accel_label); +} + +static void +meta_accel_label_destroy (GtkObject *object) +{ + MetaAccelLabel *accel_label = META_ACCEL_LABEL (object); + + + g_free (accel_label->accel_string); + accel_label->accel_string = NULL; + + accel_label->accel_mods = 0; + accel_label->accel_key = 0; + + GTK_OBJECT_CLASS (parent_class)->destroy (object); +} + +static void +meta_accel_label_finalize (GObject *object) +{ + MetaAccelLabel *accel_label = META_ACCEL_LABEL (object); + + g_free (accel_label->accel_string); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +void +meta_accel_label_set_accelerator (MetaAccelLabel *accel_label, + guint accelerator_key, + MetaVirtualModifier accelerator_mods) +{ + g_return_if_fail (META_IS_ACCEL_LABEL (accel_label)); + + if (accelerator_key != accel_label->accel_key || + accelerator_mods != accel_label->accel_mods) + { + accel_label->accel_mods = accelerator_mods; + accel_label->accel_key = accelerator_key; + + meta_accel_label_update (accel_label); + } +} + +static int +meta_accel_label_get_accel_width (MetaAccelLabel *accel_label) +{ + g_return_val_if_fail (META_IS_ACCEL_LABEL (accel_label), 0); + + return (accel_label->accel_string_width + + (accel_label->accel_string_width ? accel_label->accel_padding : 0)); +} + +static void +meta_accel_label_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + MetaAccelLabel *accel_label = META_ACCEL_LABEL (widget); + PangoLayout *layout; + gint width; + + if (GTK_WIDGET_CLASS (parent_class)->size_request) + GTK_WIDGET_CLASS (parent_class)->size_request (widget, requisition); + + layout = gtk_widget_create_pango_layout (widget, accel_label->accel_string); + pango_layout_get_pixel_size (layout, &width, NULL); + accel_label->accel_string_width = width; + + g_object_unref (G_OBJECT (layout)); +} + +static gboolean +meta_accel_label_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + MetaAccelLabel *accel_label = META_ACCEL_LABEL (widget); + GtkMisc *misc = GTK_MISC (accel_label); + PangoLayout *layout; + + if (GTK_WIDGET_DRAWABLE (accel_label)) + { + int ac_width; + + ac_width = meta_accel_label_get_accel_width (accel_label); + + if (widget->allocation.width >= widget->requisition.width + ac_width) + { + GtkTextDirection direction = gtk_widget_get_direction (widget); + gint x; + gint y; + + if (direction == GTK_TEXT_DIR_RTL) + { + widget->allocation.x += ac_width; + } + widget->allocation.width -= ac_width; + + if (GTK_WIDGET_CLASS (parent_class)->expose_event) + GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); + + if (direction == GTK_TEXT_DIR_RTL) + { + widget->allocation.x -= ac_width; + } + widget->allocation.width += ac_width; + + if (direction == GTK_TEXT_DIR_RTL) + { + x = widget->allocation.x + misc->xpad; + } + else + { + x = widget->allocation.x + widget->allocation.width - misc->xpad - ac_width; + } + + y = (widget->allocation.y * (1.0 - misc->yalign) + + (widget->allocation.y + widget->allocation.height - + (widget->requisition.height - misc->ypad * 2)) * + misc->yalign) + 1.5; + + layout = gtk_widget_create_pango_layout (widget, accel_label->accel_string); + + gtk_paint_layout (widget->style, + widget->window, + GTK_WIDGET_STATE (widget), + FALSE, + &event->area, + widget, + "accellabel", + x, y, + layout); + + g_object_unref (G_OBJECT (layout)); + } + else + { + if (GTK_WIDGET_CLASS (parent_class)->expose_event) + GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); + } + } + + return FALSE; +} + +static void +meta_accel_label_update (MetaAccelLabel *accel_label) +{ + MetaAccelLabelClass *class; + GString *gstring; + gboolean seen_mod = FALSE; + gunichar ch; + + g_return_if_fail (META_IS_ACCEL_LABEL (accel_label)); + + class = META_ACCEL_LABEL_GET_CLASS (accel_label); + + g_free (accel_label->accel_string); + accel_label->accel_string = NULL; + + gstring = g_string_new (accel_label->accel_string); + g_string_append (gstring, gstring->len ? class->accel_seperator : " "); + + if (accel_label->accel_mods & META_VIRTUAL_SHIFT_MASK) + { + g_string_append (gstring, class->mod_name_shift); + seen_mod = TRUE; + } + if (accel_label->accel_mods & META_VIRTUAL_CONTROL_MASK) + { + if (seen_mod) + g_string_append (gstring, class->mod_separator); + g_string_append (gstring, class->mod_name_control); + seen_mod = TRUE; + } + if (accel_label->accel_mods & META_VIRTUAL_ALT_MASK) + { + if (seen_mod) + g_string_append (gstring, class->mod_separator); + g_string_append (gstring, class->mod_name_alt); + seen_mod = TRUE; + } + if (accel_label->accel_mods & META_VIRTUAL_META_MASK) + { + if (seen_mod) + g_string_append (gstring, class->mod_separator); + g_string_append (gstring, class->mod_name_meta); + seen_mod = TRUE; + } + if (accel_label->accel_mods & META_VIRTUAL_SUPER_MASK) + { + if (seen_mod) + g_string_append (gstring, class->mod_separator); + g_string_append (gstring, class->mod_name_super); + seen_mod = TRUE; + } + if (accel_label->accel_mods & META_VIRTUAL_HYPER_MASK) + { + if (seen_mod) + g_string_append (gstring, class->mod_separator); + g_string_append (gstring, class->mod_name_hyper); + seen_mod = TRUE; + } + if (accel_label->accel_mods & META_VIRTUAL_MOD2_MASK) + { + if (seen_mod) + g_string_append (gstring, class->mod_separator); + g_string_append (gstring, class->mod_name_mod2); + seen_mod = TRUE; + } + if (accel_label->accel_mods & META_VIRTUAL_MOD3_MASK) + { + if (seen_mod) + g_string_append (gstring, class->mod_separator); + g_string_append (gstring, class->mod_name_mod3); + seen_mod = TRUE; + } + if (accel_label->accel_mods & META_VIRTUAL_MOD4_MASK) + { + if (seen_mod) + g_string_append (gstring, class->mod_separator); + g_string_append (gstring, class->mod_name_mod4); + seen_mod = TRUE; + } + if (accel_label->accel_mods & META_VIRTUAL_MOD5_MASK) + { + if (seen_mod) + g_string_append (gstring, class->mod_separator); + g_string_append (gstring, class->mod_name_mod5); + seen_mod = TRUE; + } + + if (seen_mod) + g_string_append (gstring, class->mod_separator); + + ch = gdk_keyval_to_unicode (accel_label->accel_key); + if (ch && (g_unichar_isgraph (ch) || ch == ' ') && + (ch < 0x80 || class->latin1_to_char)) + { + switch (ch) + { + case ' ': + g_string_append (gstring, "Space"); + break; + case '\\': + g_string_append (gstring, "Backslash"); + break; + default: + g_string_append_unichar (gstring, g_unichar_toupper (ch)); + break; + } + } + else + { + gchar *tmp; + + tmp = gtk_accelerator_name (accel_label->accel_key, 0); + if (tmp[0] != 0 && tmp[1] == 0) + tmp[0] = g_ascii_toupper (tmp[0]); + g_string_append (gstring, tmp); + g_free (tmp); + } + + g_free (accel_label->accel_string); + accel_label->accel_string = gstring->str; + g_string_free (gstring, FALSE); + + g_assert (accel_label->accel_string); + /* accel_label->accel_string = g_strdup ("-/-"); */ + + gtk_widget_queue_resize (GTK_WIDGET (accel_label)); +} diff --git a/src/ui/metaaccellabel.h b/src/ui/metaaccellabel.h new file mode 100644 index 00000000..34914c92 --- /dev/null +++ b/src/ui/metaaccellabel.h @@ -0,0 +1,106 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco hacked-up GtkAccelLabel */ +/* Copyright (C) 2002 Red Hat, Inc. */ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * MetaAccelLabel: GtkLabel with accelerator monitoring facilities. + * Copyright (C) 1998 Tim Janik + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * Modified by the GTK+ Team and others 1997-2001. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __META_ACCEL_LABEL_H__ +#define __META_ACCEL_LABEL_H__ + +#include +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#define META_TYPE_ACCEL_LABEL (meta_accel_label_get_type ()) +#define META_ACCEL_LABEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_ACCEL_LABEL, MetaAccelLabel)) +#define META_ACCEL_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), META_TYPE_ACCEL_LABEL, MetaAccelLabelClass)) +#define META_IS_ACCEL_LABEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), META_TYPE_ACCEL_LABEL)) +#define META_IS_ACCEL_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), META_TYPE_ACCEL_LABEL)) +#define META_ACCEL_LABEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), META_TYPE_ACCEL_LABEL, MetaAccelLabelClass)) + + +typedef struct _MetaAccelLabel MetaAccelLabel; +typedef struct _MetaAccelLabelClass MetaAccelLabelClass; + +struct _MetaAccelLabel +{ + GtkLabel label; + + MetaVirtualModifier accel_mods; + guint accel_key; + guint accel_padding; + gchar *accel_string; + guint16 accel_string_width; +}; + +struct _MetaAccelLabelClass +{ + GtkLabelClass parent_class; + + gchar *signal_quote1; + gchar *signal_quote2; + gchar *mod_name_shift; + gchar *mod_name_control; + gchar *mod_name_alt; + gchar *mod_name_meta; + gchar *mod_name_super; + gchar *mod_name_hyper; + gchar *mod_name_mod2; + gchar *mod_name_mod3; + gchar *mod_name_mod4; + gchar *mod_name_mod5; + gchar *mod_separator; + gchar *accel_seperator; + guint latin1_to_char : 1; + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); +}; + +GType meta_accel_label_get_type (void) G_GNUC_CONST; +GtkWidget* meta_accel_label_new_with_mnemonic (const gchar *string); +void meta_accel_label_set_accelerator (MetaAccelLabel *accel_label, + guint accelerator_key, + MetaVirtualModifier accelerator_mods); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __META_ACCEL_LABEL_H__ */ diff --git a/src/ui/preview-widget.c b/src/ui/preview-widget.c new file mode 100644 index 00000000..8fcd2517 --- /dev/null +++ b/src/ui/preview-widget.c @@ -0,0 +1,601 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco theme preview widget */ + +/* + * Copyright (C) 2002 Havoc Pennington + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#define _GNU_SOURCE +#define _XOPEN_SOURCE 600 /* for the maths routines over floats */ + +#include +#include +#include "preview-widget.h" + +static void meta_preview_class_init (MetaPreviewClass *klass); +static void meta_preview_init (MetaPreview *preview); +static void meta_preview_size_request (GtkWidget *widget, + GtkRequisition *req); +static void meta_preview_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static gboolean meta_preview_expose (GtkWidget *widget, + GdkEventExpose *event); +static void meta_preview_finalize (GObject *object); + +static GtkWidgetClass *parent_class; + +GType +meta_preview_get_type (void) +{ + static GType preview_type = 0; + + if (!preview_type) + { + static const GtkTypeInfo preview_info = + { + "MetaPreview", + sizeof (MetaPreview), + sizeof (MetaPreviewClass), + (GtkClassInitFunc) meta_preview_class_init, + (GtkObjectInitFunc) meta_preview_init, + /* reserved_1 */ NULL, + /* reserved_2 */ NULL, + (GtkClassInitFunc) NULL, + }; + + preview_type = gtk_type_unique (GTK_TYPE_BIN, &preview_info); + } + + return preview_type; +} + +static void +meta_preview_class_init (MetaPreviewClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class; + + widget_class = (GtkWidgetClass*) class; + parent_class = g_type_class_peek (GTK_TYPE_BIN); + + gobject_class->finalize = meta_preview_finalize; + + widget_class->expose_event = meta_preview_expose; + widget_class->size_request = meta_preview_size_request; + widget_class->size_allocate = meta_preview_size_allocate; +} + +static void +meta_preview_init (MetaPreview *preview) +{ + int i; + + gtk_widget_set_has_window (GTK_WIDGET (preview), FALSE); + + i = 0; + while (i < MAX_BUTTONS_PER_CORNER) + { + preview->button_layout.left_buttons[i] = META_BUTTON_FUNCTION_LAST; + preview->button_layout.right_buttons[i] = META_BUTTON_FUNCTION_LAST; + ++i; + } + + preview->button_layout.left_buttons[0] = META_BUTTON_FUNCTION_MENU; + + preview->button_layout.right_buttons[0] = META_BUTTON_FUNCTION_MINIMIZE; + preview->button_layout.right_buttons[1] = META_BUTTON_FUNCTION_MAXIMIZE; + preview->button_layout.right_buttons[2] = META_BUTTON_FUNCTION_CLOSE; + + preview->type = META_FRAME_TYPE_NORMAL; + preview->flags = + META_FRAME_ALLOWS_DELETE | + META_FRAME_ALLOWS_MENU | + META_FRAME_ALLOWS_MINIMIZE | + META_FRAME_ALLOWS_MAXIMIZE | + META_FRAME_ALLOWS_VERTICAL_RESIZE | + META_FRAME_ALLOWS_HORIZONTAL_RESIZE | + META_FRAME_HAS_FOCUS | + META_FRAME_ALLOWS_SHADE | + META_FRAME_ALLOWS_MOVE; + + preview->left_width = -1; + preview->right_width = -1; + preview->top_height = -1; + preview->bottom_height = -1; +} + +GtkWidget* +meta_preview_new (void) +{ + MetaPreview *preview; + + preview = gtk_type_new (META_TYPE_PREVIEW); + + return GTK_WIDGET (preview); +} + +static void +meta_preview_finalize (GObject *object) +{ + MetaPreview *preview; + + preview = META_PREVIEW (object); + + g_free (preview->title); + preview->title = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +ensure_info (MetaPreview *preview) +{ + GtkWidget *widget; + + widget = GTK_WIDGET (preview); + + if (preview->layout == NULL) + { + PangoFontDescription *font_desc; + double scale; + PangoAttrList *attrs; + PangoAttribute *attr; + + if (preview->theme) + scale = meta_theme_get_title_scale (preview->theme, + preview->type, + preview->flags); + else + scale = 1.0; + + preview->layout = gtk_widget_create_pango_layout (widget, + preview->title); + + font_desc = meta_gtk_widget_get_font_desc (widget, scale, NULL); + + preview->text_height = + meta_pango_font_desc_get_text_height (font_desc, + gtk_widget_get_pango_context (widget)); + + attrs = pango_attr_list_new (); + + attr = pango_attr_size_new (pango_font_description_get_size (font_desc)); + attr->start_index = 0; + attr->end_index = G_MAXINT; + + pango_attr_list_insert (attrs, attr); + + pango_layout_set_attributes (preview->layout, attrs); + + pango_attr_list_unref (attrs); + + pango_font_description_free (font_desc); + } + + if (preview->top_height < 0) + { + if (preview->theme) + { + meta_theme_get_frame_borders (preview->theme, + preview->type, + preview->text_height, + preview->flags, + &preview->top_height, + &preview->bottom_height, + &preview->left_width, + &preview->right_width); + } + else + { + preview->top_height = 0; + preview->bottom_height = 0; + preview->left_width = 0; + preview->right_width = 0; + } + } +} + +static gboolean +meta_preview_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + MetaPreview *preview; + GtkAllocation allocation; + int border_width; + int client_width; + int client_height; + MetaButtonState button_states[META_BUTTON_TYPE_LAST] = + { + META_BUTTON_STATE_NORMAL, + META_BUTTON_STATE_NORMAL, + META_BUTTON_STATE_NORMAL, + META_BUTTON_STATE_NORMAL + }; + + g_return_val_if_fail (META_IS_PREVIEW (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + preview = META_PREVIEW (widget); + + ensure_info (preview); + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + + gtk_widget_get_allocation (widget, &allocation); + client_width = allocation.width - preview->left_width - preview->right_width - border_width * 2; + client_height = allocation.height - preview->top_height - preview->bottom_height - border_width * 2; + + if (client_width < 0) + client_width = 1; + if (client_height < 0) + client_height = 1; + + if (preview->theme) + { + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + + meta_theme_draw_frame (preview->theme, + widget, + gtk_widget_get_window (widget), + &event->area, + allocation.x + border_width, + allocation.y + border_width, + preview->type, + preview->flags, + client_width, client_height, + preview->layout, + preview->text_height, + &preview->button_layout, + button_states, + meta_preview_get_mini_icon (), + meta_preview_get_icon ()); + } + + /* draw child */ + return GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); +} + +static void +meta_preview_size_request (GtkWidget *widget, + GtkRequisition *req) +{ + MetaPreview *preview; + GtkWidget *child; + guint border_width; + + preview = META_PREVIEW (widget); + + ensure_info (preview); + + req->width = preview->left_width + preview->right_width; + req->height = preview->top_height + preview->bottom_height; + + child = gtk_bin_get_child (GTK_BIN (preview)); + if (child && + gtk_widget_get_visible (child)) + { + GtkRequisition child_requisition; + + gtk_widget_size_request (child, &child_requisition); + + req->width += child_requisition.width; + req->height += child_requisition.height; + } + else + { +#define NO_CHILD_WIDTH 80 +#define NO_CHILD_HEIGHT 20 + req->width += NO_CHILD_WIDTH; + req->height += NO_CHILD_HEIGHT; + } + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + req->width += border_width * 2; + req->height += border_width * 2; +} + +static void +meta_preview_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + MetaPreview *preview; + int border_width; + GtkAllocation child_allocation; + GtkWidget *child; + + preview = META_PREVIEW (widget); + + ensure_info (preview); + + widget->allocation = *allocation; + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + + child = gtk_bin_get_child (GTK_BIN (widget)); + if (child && + gtk_widget_get_visible (child)) + { + child_allocation.x = widget->allocation.x + border_width + preview->left_width; + child_allocation.y = widget->allocation.y + border_width + preview->top_height; + + child_allocation.width = MAX (1, widget->allocation.width - border_width * 2 - preview->left_width - preview->right_width); + child_allocation.height = MAX (1, widget->allocation.height - border_width * 2 - preview->top_height - preview->bottom_height); + + gtk_widget_size_allocate (gtk_bin_get_child (GTK_BIN (widget)), &child_allocation); + } +} + +static void +clear_cache (MetaPreview *preview) +{ + if (preview->layout) + { + g_object_unref (G_OBJECT (preview->layout)); + preview->layout = NULL; + } + + preview->left_width = -1; + preview->right_width = -1; + preview->top_height = -1; + preview->bottom_height = -1; +} + +void +meta_preview_set_theme (MetaPreview *preview, + MetaTheme *theme) +{ + g_return_if_fail (META_IS_PREVIEW (preview)); + + preview->theme = theme; + + clear_cache (preview); + + gtk_widget_queue_resize (GTK_WIDGET (preview)); +} + +void +meta_preview_set_title (MetaPreview *preview, + const char *title) +{ + g_return_if_fail (META_IS_PREVIEW (preview)); + + g_free (preview->title); + preview->title = g_strdup (title); + + clear_cache (preview); + + gtk_widget_queue_resize (GTK_WIDGET (preview)); +} + +void +meta_preview_set_frame_type (MetaPreview *preview, + MetaFrameType type) +{ + g_return_if_fail (META_IS_PREVIEW (preview)); + + preview->type = type; + + clear_cache (preview); + + gtk_widget_queue_resize (GTK_WIDGET (preview)); +} + +void +meta_preview_set_frame_flags (MetaPreview *preview, + MetaFrameFlags flags) +{ + g_return_if_fail (META_IS_PREVIEW (preview)); + + preview->flags = flags; + + clear_cache (preview); + + gtk_widget_queue_resize (GTK_WIDGET (preview)); +} + +void +meta_preview_set_button_layout (MetaPreview *preview, + const MetaButtonLayout *button_layout) +{ + g_return_if_fail (META_IS_PREVIEW (preview)); + + preview->button_layout = *button_layout; + + gtk_widget_queue_draw (GTK_WIDGET (preview)); +} + +GdkPixbuf* +meta_preview_get_icon (void) +{ + static GdkPixbuf *default_icon = NULL; + + if (default_icon == NULL) + { + GtkIconTheme *theme; + gboolean icon_exists; + + theme = gtk_icon_theme_get_default (); + + icon_exists = gtk_icon_theme_has_icon (theme, META_DEFAULT_ICON_NAME); + + if (icon_exists) + default_icon = gtk_icon_theme_load_icon (theme, + META_DEFAULT_ICON_NAME, + META_ICON_WIDTH, + 0, + NULL); + else + default_icon = gtk_icon_theme_load_icon (theme, + "gtk-missing-image", + META_ICON_WIDTH, + 0, + NULL); + + g_assert (default_icon); + } + + return default_icon; +} + +GdkPixbuf* +meta_preview_get_mini_icon (void) +{ + static GdkPixbuf *default_icon = NULL; + + if (default_icon == NULL) + { + GtkIconTheme *theme; + gboolean icon_exists; + + theme = gtk_icon_theme_get_default (); + + icon_exists = gtk_icon_theme_has_icon (theme, META_DEFAULT_ICON_NAME); + + if (icon_exists) + default_icon = gtk_icon_theme_load_icon (theme, + META_DEFAULT_ICON_NAME, + META_MINI_ICON_WIDTH, + 0, + NULL); + else + default_icon = gtk_icon_theme_load_icon (theme, + "gtk-missing-image", + META_MINI_ICON_WIDTH, + 0, + NULL); + + g_assert (default_icon); + } + + return default_icon; +} + +GdkRegion * +meta_preview_get_clip_region (MetaPreview *preview, gint new_window_width, gint new_window_height) +{ + GdkRectangle xrect; + GdkRegion *corners_xregion, *window_xregion; + gint flags; + MetaFrameLayout *fgeom; + MetaFrameStyle *frame_style; + + g_return_val_if_fail (META_IS_PREVIEW (preview), NULL); + + flags = (META_PREVIEW (preview)->flags); + + window_xregion = gdk_region_new (); + + xrect.x = 0; + xrect.y = 0; + xrect.width = new_window_width; + xrect.height = new_window_height; + + gdk_region_union_with_rect (window_xregion, &xrect); + + if (preview->theme == NULL) + return window_xregion; + + /* Otherwise, we do have a theme, so calculate the corners */ + frame_style = meta_theme_get_frame_style (preview->theme, + META_FRAME_TYPE_NORMAL, flags); + + fgeom = frame_style->layout; + + corners_xregion = gdk_region_new (); + + if (fgeom->top_left_corner_rounded_radius != 0) + { + const int corner = fgeom->top_left_corner_rounded_radius; + const float radius = sqrt(corner) + corner; + int i; + + for (i=0; itop_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; ibottom_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; ibottom_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 + +#ifndef META_PREVIEW_WIDGET_H +#define META_PREVIEW_WIDGET_H + +#define META_TYPE_PREVIEW (meta_preview_get_type ()) +#define META_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_PREVIEW, MetaPreview)) +#define META_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), META_TYPE_PREVIEW, MetaPreviewClass)) +#define META_IS_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), META_TYPE_PREVIEW)) +#define META_IS_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), META_TYPE_PREVIEW)) +#define META_PREVIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), META_TYPE_PREVIEW, MetaPreviewClass)) + +typedef struct _MetaPreview MetaPreview; +typedef struct _MetaPreviewClass MetaPreviewClass; + +struct _MetaPreview +{ + GtkBin bin; + + MetaTheme *theme; + char *title; + MetaFrameType type; + MetaFrameFlags flags; + + PangoLayout *layout; + int text_height; + + int left_width; + int right_width; + int top_height; + int bottom_height; + + MetaButtonLayout button_layout; +}; + +struct _MetaPreviewClass +{ + GtkBinClass parent_class; +}; + + +GType meta_preview_get_type (void) G_GNUC_CONST; +GtkWidget* meta_preview_new (void); + +void meta_preview_set_theme (MetaPreview *preview, + MetaTheme *theme); +void meta_preview_set_title (MetaPreview *preview, + const char *title); +void meta_preview_set_frame_type (MetaPreview *preview, + MetaFrameType type); +void meta_preview_set_frame_flags (MetaPreview *preview, + MetaFrameFlags flags); +void meta_preview_set_button_layout (MetaPreview *preview, + const MetaButtonLayout *button_layout); + +GdkRegion * meta_preview_get_clip_region (MetaPreview *preview, + gint new_window_width, + gint new_window_height); + +GdkPixbuf* meta_preview_get_icon (void); +GdkPixbuf* meta_preview_get_mini_icon (void); + +#endif diff --git a/src/ui/resizepopup.c b/src/ui/resizepopup.c new file mode 100644 index 00000000..67f39505 --- /dev/null +++ b/src/ui/resizepopup.c @@ -0,0 +1,217 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco resizing-terminal-window feedback */ + +/* + * Copyright (C) 2001 Havoc Pennington + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include +#include "resizepopup.h" +#include "util.h" +#include +#include + +struct _MetaResizePopup +{ + GtkWidget *size_window; + GtkWidget *size_label; + Display *display; + int screen_number; + + int vertical_size; + int horizontal_size; + + gboolean showing; + + MetaRectangle rect; +}; + +MetaResizePopup* +meta_ui_resize_popup_new (Display *display, + int screen_number) +{ + MetaResizePopup *popup; + + popup = g_new0 (MetaResizePopup, 1); + + popup->display = display; + popup->screen_number = screen_number; + + return popup; +} + +void +meta_ui_resize_popup_free (MetaResizePopup *popup) +{ + g_return_if_fail (popup != NULL); + + if (popup->size_window) + gtk_widget_destroy (popup->size_window); + + g_free (popup); +} + +static void +ensure_size_window (MetaResizePopup *popup) +{ + GtkWidget *frame; + + if (popup->size_window) + return; + + popup->size_window = gtk_window_new (GTK_WINDOW_POPUP); + + gtk_window_set_screen (GTK_WINDOW (popup->size_window), + gdk_display_get_screen (gdk_x11_lookup_xdisplay (popup->display), + popup->screen_number)); + + /* never shrink the size window */ + gtk_window_set_resizable (GTK_WINDOW (popup->size_window), + TRUE); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + + gtk_container_add (GTK_CONTAINER (popup->size_window), frame); + + popup->size_label = gtk_label_new (""); + gtk_misc_set_padding (GTK_MISC (popup->size_label), 3, 3); + + gtk_container_add (GTK_CONTAINER (frame), popup->size_label); + + gtk_widget_show_all (frame); +} + +static void +update_size_window (MetaResizePopup *popup) +{ + char *str; + int x, y; + int width, height; + + g_return_if_fail (popup->size_window != NULL); + + /* Translators: This represents the size of a window. The first number is + * the width of the window and the second is the height. + */ + str = g_strdup_printf (_("%d x %d"), + popup->horizontal_size, + popup->vertical_size); + + gtk_label_set_text (GTK_LABEL (popup->size_label), str); + + g_free (str); + + gtk_window_get_size (GTK_WINDOW (popup->size_window), &width, &height); + + x = popup->rect.x + (popup->rect.width - width) / 2; + y = popup->rect.y + (popup->rect.height - height) / 2; + + if (GTK_WIDGET_REALIZED (popup->size_window)) + { + /* using move_resize to avoid jumpiness */ + gdk_window_move_resize (popup->size_window->window, + x, y, + width, height); + } + else + { + gtk_window_move (GTK_WINDOW (popup->size_window), + x, y); + } +} + +static void +sync_showing (MetaResizePopup *popup) +{ + if (popup->showing) + { + if (popup->size_window) + gtk_widget_show (popup->size_window); + + if (popup->size_window && GTK_WIDGET_REALIZED (popup->size_window)) + gdk_window_raise (popup->size_window->window); + } + else + { + if (popup->size_window) + gtk_widget_hide (popup->size_window); + } +} + +void +meta_ui_resize_popup_set (MetaResizePopup *popup, + MetaRectangle rect, + int base_width, + int base_height, + int width_inc, + int height_inc) +{ + gboolean need_update_size; + int display_w, display_h; + + g_return_if_fail (popup != NULL); + + need_update_size = FALSE; + + display_w = rect.width - base_width; + if (width_inc > 0) + display_w /= width_inc; + + display_h = rect.height - base_height; + if (height_inc > 0) + display_h /= height_inc; + + if (!meta_rectangle_equal(&popup->rect, &rect) || + display_w != popup->horizontal_size || + display_h != popup->vertical_size) + need_update_size = TRUE; + + popup->rect = rect; + popup->vertical_size = display_h; + popup->horizontal_size = display_w; + + if (need_update_size) + { + ensure_size_window (popup); + update_size_window (popup); + } + + sync_showing (popup); +} + +void +meta_ui_resize_popup_set_showing (MetaResizePopup *popup, + gboolean showing) +{ + g_return_if_fail (popup != NULL); + + if (showing == popup->showing) + return; + + popup->showing = !!showing; + + if (popup->showing) + { + ensure_size_window (popup); + update_size_window (popup); + } + + sync_showing (popup); +} diff --git a/src/ui/tabpopup.c b/src/ui/tabpopup.c new file mode 100644 index 00000000..e0a55447 --- /dev/null +++ b/src/ui/tabpopup.c @@ -0,0 +1,967 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco popup window thing showing windows you can tab to */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2002 Red Hat, Inc. + * Copyright (C) 2005 Elijah Newren + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include + +#include "util.h" +#include "core.h" +#include "tabpopup.h" +/* FIXME these two includes are 100% broken ... + */ +#include "../core/workspace.h" +#include "../core/frame-private.h" +#include "draw-workspace.h" +#include +#include + +#define OUTSIDE_SELECT_RECT 2 +#define INSIDE_SELECT_RECT 2 + +typedef struct _TabEntry TabEntry; + +struct _TabEntry +{ + MetaTabEntryKey key; + char *title; + GdkPixbuf *icon, *dimmed_icon; + GtkWidget *widget; + GdkRectangle rect; + GdkRectangle inner_rect; + guint blank : 1; +}; + +struct _MetaTabPopup +{ + GtkWidget *window; + GtkWidget *label; + GList *current; + GList *entries; + TabEntry *current_selected_entry; + GtkWidget *outline_window; + gboolean outline; +}; + +static GtkWidget* selectable_image_new (GdkPixbuf *pixbuf); +static void select_image (GtkWidget *widget); +static void unselect_image (GtkWidget *widget); + +static GtkWidget* selectable_workspace_new (MetaWorkspace *workspace); +static void select_workspace (GtkWidget *widget); +static void unselect_workspace (GtkWidget *widget); + +static gboolean +outline_window_expose (GtkWidget *widget, + GdkEventExpose *event, + gpointer data) +{ + MetaTabPopup *popup; + TabEntry *te; + GtkStyle *style; + GdkWindow *window; + cairo_t *cr; + + popup = data; + + if (!popup->outline || popup->current_selected_entry == NULL) + return FALSE; + + te = popup->current_selected_entry; + window = gtk_widget_get_window (widget); + style = gtk_widget_get_style (widget); + cr = gdk_cairo_create (window); + + cairo_set_line_width (cr, 1.0); + gdk_cairo_set_source_color (cr, &style->white); + + cairo_rectangle (cr, + 0.5, 0.5, + te->rect.width - 1, + te->rect.height - 1); + cairo_stroke (cr); + + cairo_rectangle (cr, + te->inner_rect.x - 0.5, te->inner_rect.y - 0.5, + te->inner_rect.width + 1, + te->inner_rect.height + 1); + cairo_stroke (cr); + + cairo_destroy (cr); + + return FALSE; +} + +static GdkPixbuf* +dimm_icon (GdkPixbuf *pixbuf) +{ + int x, y, pixel_stride, row_stride; + guchar *row, *pixels; + int w, h; + GdkPixbuf *dimmed_pixbuf; + + if (gdk_pixbuf_get_has_alpha (pixbuf)) + { + dimmed_pixbuf = gdk_pixbuf_copy (pixbuf); + } + else + { + dimmed_pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0); + } + + w = gdk_pixbuf_get_width (dimmed_pixbuf); + h = gdk_pixbuf_get_height (dimmed_pixbuf); + + pixel_stride = 4; + + row = gdk_pixbuf_get_pixels (dimmed_pixbuf); + row_stride = gdk_pixbuf_get_rowstride (dimmed_pixbuf); + + for (y = 0; y < h; y++) + { + pixels = row; + for (x = 0; x < w; x++) + { + pixels[3] /= 2; + pixels += pixel_stride; + } + row += row_stride; + } + return dimmed_pixbuf; +} + +static TabEntry* +tab_entry_new (const MetaTabEntry *entry, + gint screen_width, + gboolean outline) +{ + TabEntry *te; + + te = g_new (TabEntry, 1); + te->key = entry->key; + te->title = NULL; + if (entry->title) + { + gchar *str; + gchar *tmp; + gchar *formatter = "%s"; + + str = meta_g_utf8_strndup (entry->title, 4096); + + if (entry->hidden) + { + formatter = "[%s]"; + } + + tmp = g_markup_printf_escaped (formatter, str); + g_free (str); + str = tmp; + + if (entry->demands_attention) + { + /* Escape the whole line of text then markup the text and + * copy it back into the original buffer. + */ + tmp = g_strdup_printf ("%s", str); + g_free (str); + str = tmp; + } + + te->title=g_strdup(str); + + g_free (str); + } + te->widget = NULL; + te->icon = entry->icon; + te->blank = entry->blank; + te->dimmed_icon = NULL; + if (te->icon) + { + g_object_ref (G_OBJECT (te->icon)); + if (entry->hidden) + te->dimmed_icon = dimm_icon (entry->icon); + } + + if (outline) + { + te->rect.x = entry->rect.x; + te->rect.y = entry->rect.y; + te->rect.width = entry->rect.width; + te->rect.height = entry->rect.height; + + te->inner_rect.x = entry->inner_rect.x; + te->inner_rect.y = entry->inner_rect.y; + te->inner_rect.width = entry->inner_rect.width; + te->inner_rect.height = entry->inner_rect.height; + } + return te; +} + +MetaTabPopup* +meta_ui_tab_popup_new (const MetaTabEntry *entries, + int screen_number, + int entry_count, + int width, + gboolean outline) +{ + MetaTabPopup *popup; + int i, left, right, top, bottom; + int height; + GtkWidget *table; + GtkWidget *vbox; + GtkWidget *align; + GList *tmp; + GtkWidget *frame; + int max_label_width; /* the actual max width of the labels we create */ + AtkObject *obj; + GdkScreen *screen; + int screen_width; + + popup = g_new (MetaTabPopup, 1); + + popup->outline_window = gtk_window_new (GTK_WINDOW_POPUP); + + screen = gdk_display_get_screen (gdk_display_get_default (), + screen_number); + gtk_window_set_screen (GTK_WINDOW (popup->outline_window), + screen); + + gtk_widget_set_app_paintable (popup->outline_window, TRUE); + gtk_widget_realize (popup->outline_window); + + g_signal_connect (G_OBJECT (popup->outline_window), "expose_event", + G_CALLBACK (outline_window_expose), popup); + + popup->window = gtk_window_new (GTK_WINDOW_POPUP); + + gtk_window_set_screen (GTK_WINDOW (popup->window), + screen); + + gtk_window_set_position (GTK_WINDOW (popup->window), + GTK_WIN_POS_CENTER_ALWAYS); + /* enable resizing, to get never-shrink behavior */ + gtk_window_set_resizable (GTK_WINDOW (popup->window), + TRUE); + popup->current = NULL; + popup->entries = NULL; + popup->current_selected_entry = NULL; + popup->outline = outline; + + screen_width = gdk_screen_get_width (screen); + for (i = 0; i < entry_count; ++i) + { + TabEntry* new_entry = tab_entry_new (&entries[i], screen_width, outline); + popup->entries = g_list_prepend (popup->entries, new_entry); + } + + popup->entries = g_list_reverse (popup->entries); + + g_assert (width > 0); + height = i / width; + if (i % width) + height += 1; + + table = gtk_table_new (height, width, FALSE); + vbox = gtk_vbox_new (FALSE, 0); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + gtk_container_set_border_width (GTK_CONTAINER (table), 1); + gtk_container_add (GTK_CONTAINER (popup->window), + frame); + gtk_container_add (GTK_CONTAINER (frame), + vbox); + + align = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); + + gtk_box_pack_start (GTK_BOX (vbox), align, TRUE, TRUE, 0); + + gtk_container_add (GTK_CONTAINER (align), + table); + + popup->label = gtk_label_new (""); + + /* Set the accessible role of the label to a status bar so it + * will emit name changed events that can be used by screen + * readers. + */ + obj = gtk_widget_get_accessible (popup->label); + atk_object_set_role (obj, ATK_ROLE_STATUSBAR); + + gtk_misc_set_padding (GTK_MISC (popup->label), 3, 3); + + gtk_box_pack_end (GTK_BOX (vbox), popup->label, FALSE, FALSE, 0); + + max_label_width = 0; + top = 0; + bottom = 1; + tmp = popup->entries; + + while (tmp && top < height) + { + left = 0; + right = 1; + + while (tmp && left < width) + { + GtkWidget *image; + GtkRequisition req; + + TabEntry *te; + + te = tmp->data; + + if (te->blank) + { + /* just stick a widget here to avoid special cases */ + image = gtk_alignment_new (0.0, 0.0, 0.0, 0.0); + } + else if (outline) + { + if (te->dimmed_icon) + { + image = selectable_image_new (te->dimmed_icon); + } + else + { + image = selectable_image_new (te->icon); + } + + gtk_misc_set_padding (GTK_MISC (image), + INSIDE_SELECT_RECT + OUTSIDE_SELECT_RECT + 1, + INSIDE_SELECT_RECT + OUTSIDE_SELECT_RECT + 1); + gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.5); + } + else + { + image = selectable_workspace_new ((MetaWorkspace *) te->key); + } + + te->widget = image; + + gtk_table_attach (GTK_TABLE (table), + te->widget, + left, right, top, bottom, + 0, 0, + 0, 0); + + /* Efficiency rules! */ + gtk_label_set_markup (GTK_LABEL (popup->label), + te->title); + gtk_widget_size_request (popup->label, &req); + max_label_width = MAX (max_label_width, req.width); + + tmp = tmp->next; + + ++left; + ++right; + } + + ++top; + ++bottom; + } + + /* remove all the temporary text */ + gtk_label_set_text (GTK_LABEL (popup->label), ""); + /* Make it so that we ellipsize if the text is too long */ + gtk_label_set_ellipsize (GTK_LABEL (popup->label), PANGO_ELLIPSIZE_END); + + /* Limit the window size to no bigger than screen_width/4 */ + if (max_label_width>(screen_width/4)) + { + max_label_width = screen_width/4; + } + + max_label_width += 20; /* add random padding */ + + gtk_window_set_default_size (GTK_WINDOW (popup->window), + max_label_width, + -1); + + return popup; +} + +static void +free_tab_entry (gpointer data, gpointer user_data) +{ + TabEntry *te; + + te = data; + + g_free (te->title); + if (te->icon) + g_object_unref (G_OBJECT (te->icon)); + if (te->dimmed_icon) + g_object_unref (G_OBJECT (te->dimmed_icon)); + + g_free (te); +} + +void +meta_ui_tab_popup_free (MetaTabPopup *popup) +{ + meta_verbose ("Destroying tab popup window\n"); + + gtk_widget_destroy (popup->outline_window); + gtk_widget_destroy (popup->window); + + g_list_foreach (popup->entries, free_tab_entry, NULL); + + g_list_free (popup->entries); + + g_free (popup); +} + +void +meta_ui_tab_popup_set_showing (MetaTabPopup *popup, + gboolean showing) +{ + if (showing) + { + gtk_widget_show_all (popup->window); + } + else + { + if (GTK_WIDGET_VISIBLE (popup->window)) + { + meta_verbose ("Hiding tab popup window\n"); + gtk_widget_hide (popup->window); + meta_core_increment_event_serial (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + } + } +} + +static void +display_entry (MetaTabPopup *popup, + TabEntry *te) +{ + GdkRectangle rect; + GdkRegion *region; + GdkRegion *inner_region; + + + if (popup->current_selected_entry) + { + if (popup->outline) + unselect_image (popup->current_selected_entry->widget); + else + unselect_workspace (popup->current_selected_entry->widget); + } + + gtk_label_set_markup (GTK_LABEL (popup->label), te->title); + + if (popup->outline) + select_image (te->widget); + else + select_workspace (te->widget); + + if (popup->outline) + { + /* Do stuff behind gtk's back */ + gdk_window_hide (popup->outline_window->window); + meta_core_increment_event_serial (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ())); + + rect = te->rect; + rect.x = 0; + rect.y = 0; + + gdk_window_move_resize (popup->outline_window->window, + te->rect.x, te->rect.y, + te->rect.width, te->rect.height); + + gdk_window_set_background (popup->outline_window->window, + &popup->outline_window->style->black); + + region = gdk_region_rectangle (&rect); + inner_region = gdk_region_rectangle (&te->inner_rect); + gdk_region_subtract (region, inner_region); + gdk_region_destroy (inner_region); + + gdk_window_shape_combine_region (popup->outline_window->window, + region, + 0, 0); + + gdk_region_destroy (region); + + /* This should piss off gtk a bit, but we don't want to raise + * above the tab popup. So, instead of calling gtk_widget_show, + * we manually set the window as mapped and then manually map it + * with gdk functions. + */ + GTK_WIDGET_SET_FLAGS (popup->outline_window, GTK_MAPPED); + gdk_window_show_unraised (popup->outline_window->window); + } + + /* Must be before we handle an expose for the outline window */ + popup->current_selected_entry = te; +} + +void +meta_ui_tab_popup_forward (MetaTabPopup *popup) +{ + if (popup->current != NULL) + popup->current = popup->current->next; + + if (popup->current == NULL) + popup->current = popup->entries; + + if (popup->current != NULL) + { + TabEntry *te; + + te = popup->current->data; + + display_entry (popup, te); + } +} + +void +meta_ui_tab_popup_backward (MetaTabPopup *popup) +{ + if (popup->current != NULL) + popup->current = popup->current->prev; + + if (popup->current == NULL) + popup->current = g_list_last (popup->entries); + + if (popup->current != NULL) + { + TabEntry *te; + + te = popup->current->data; + + display_entry (popup, te); + } +} + +MetaTabEntryKey +meta_ui_tab_popup_get_selected (MetaTabPopup *popup) +{ + if (popup->current) + { + TabEntry *te; + + te = popup->current->data; + + return te->key; + } + else + return (MetaTabEntryKey)None; +} + +void +meta_ui_tab_popup_select (MetaTabPopup *popup, + MetaTabEntryKey key) +{ + GList *tmp; + + /* Note, "key" may not be in the list of entries; other code assumes + * it's OK to pass in a key that isn't. + */ + + tmp = popup->entries; + while (tmp != NULL) + { + TabEntry *te; + + te = tmp->data; + + if (te->key == key) + { + popup->current = tmp; + + display_entry (popup, te); + + return; + } + + tmp = tmp->next; + } +} + +#define META_TYPE_SELECT_IMAGE (meta_select_image_get_type ()) +#define META_SELECT_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_SELECT_IMAGE, MetaSelectImage)) + +typedef struct _MetaSelectImage MetaSelectImage; +typedef struct _MetaSelectImageClass MetaSelectImageClass; + +struct _MetaSelectImage +{ + GtkImage parent_instance; + guint selected : 1; +}; + +struct _MetaSelectImageClass +{ + GtkImageClass parent_class; +}; + + +static GType meta_select_image_get_type (void) G_GNUC_CONST; + +static GtkWidget* +selectable_image_new (GdkPixbuf *pixbuf) +{ + GtkWidget *w; + + w = g_object_new (meta_select_image_get_type (), NULL); + gtk_image_set_from_pixbuf (GTK_IMAGE (w), pixbuf); + + return w; +} + +static void +select_image (GtkWidget *widget) +{ + META_SELECT_IMAGE (widget)->selected = TRUE; + gtk_widget_queue_draw (widget); +} + +static void +unselect_image (GtkWidget *widget) +{ + META_SELECT_IMAGE (widget)->selected = FALSE; + gtk_widget_queue_draw (widget); +} + +static void meta_select_image_class_init (MetaSelectImageClass *klass); +static gboolean meta_select_image_expose_event (GtkWidget *widget, + GdkEventExpose *event); + +static GtkImageClass *parent_class; + +GType +meta_select_image_get_type (void) +{ + static GType image_type = 0; + + if (!image_type) + { + static const GTypeInfo image_info = + { + sizeof (MetaSelectImageClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) meta_select_image_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (MetaSelectImage), + 16, /* n_preallocs */ + (GInstanceInitFunc) NULL, + }; + + image_type = g_type_register_static (GTK_TYPE_IMAGE, "MetaSelectImage", &image_info, 0); + } + + return image_type; +} + +static void +meta_select_image_class_init (MetaSelectImageClass *klass) +{ + GtkWidgetClass *widget_class; + + parent_class = g_type_class_peek (gtk_image_get_type ()); + + widget_class = GTK_WIDGET_CLASS (klass); + + widget_class->expose_event = meta_select_image_expose_event; +} + +static gboolean +meta_select_image_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + if (META_SELECT_IMAGE (widget)->selected) + { + int x, y, w, h; + GtkMisc *misc; + GtkStyle *style; + GtkStateType state; + cairo_t *cr; + + misc = GTK_MISC (widget); + + x = (widget->allocation.x * (1.0 - misc->xalign) + + (widget->allocation.x + widget->allocation.width + - (widget->requisition.width - misc->xpad * 2)) * + misc->xalign) + 0.5; + y = (widget->allocation.y * (1.0 - misc->yalign) + + (widget->allocation.y + widget->allocation.height + - (widget->requisition.height - misc->ypad * 2)) * + misc->yalign) + 0.5; + + x -= INSIDE_SELECT_RECT + 1; + y -= INSIDE_SELECT_RECT + 1; + + w = widget->requisition.width - OUTSIDE_SELECT_RECT * 2 - 1; + h = widget->requisition.height - OUTSIDE_SELECT_RECT * 2 - 1; + + style = gtk_widget_get_style (widget); + state = gtk_widget_get_state (widget); + cr = gdk_cairo_create (widget->window); + + cairo_set_line_width (cr, 2.0); + gdk_cairo_set_source_color (cr, &style->fg[state]); + + cairo_rectangle (cr, x, y, w + 1, h + 1); + cairo_stroke (cr); + + cairo_set_line_width (cr, 1.0); +#if 0 + gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_SELECTED]); + cairo_rectangle (cr, x, y, w, h); + cairo_fill (cr); +#endif + +#if 0 + gtk_paint_focus (widget->style, widget->window, + &event->area, widget, "meta-tab-image", + x, y, w, h); +#endif + + cairo_destroy (cr); + } + + return GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); +} + +#define META_TYPE_SELECT_WORKSPACE (meta_select_workspace_get_type ()) +#define META_SELECT_WORKSPACE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_SELECT_WORKSPACE, MetaSelectWorkspace)) + +typedef struct _MetaSelectWorkspace MetaSelectWorkspace; +typedef struct _MetaSelectWorkspaceClass MetaSelectWorkspaceClass; + +struct _MetaSelectWorkspace +{ + GtkDrawingArea parent_instance; + MetaWorkspace *workspace; + guint selected : 1; +}; + +struct _MetaSelectWorkspaceClass +{ + GtkDrawingAreaClass parent_class; +}; + + +static GType meta_select_workspace_get_type (void) G_GNUC_CONST; + +#define SELECT_OUTLINE_WIDTH 2 +#define MINI_WORKSPACE_WIDTH 48 + +static GtkWidget* +selectable_workspace_new (MetaWorkspace *workspace) +{ + GtkWidget *widget; + double screen_aspect; + + widget = g_object_new (meta_select_workspace_get_type (), NULL); + + screen_aspect = (double) workspace->screen->rect.height / + (double) workspace->screen->rect.width; + + /* account for select rect */ + gtk_widget_set_size_request (widget, + MINI_WORKSPACE_WIDTH + SELECT_OUTLINE_WIDTH * 2, + MINI_WORKSPACE_WIDTH * screen_aspect + SELECT_OUTLINE_WIDTH * 2); + + META_SELECT_WORKSPACE (widget)->workspace = workspace; + + return widget; +} + +static void +select_workspace (GtkWidget *widget) +{ + META_SELECT_WORKSPACE(widget)->selected = TRUE; + gtk_widget_queue_draw (widget); +} + +static void +unselect_workspace (GtkWidget *widget) +{ + META_SELECT_WORKSPACE (widget)->selected = FALSE; + gtk_widget_queue_draw (widget); +} + +static void meta_select_workspace_class_init (MetaSelectWorkspaceClass *klass); + +static gboolean meta_select_workspace_expose_event (GtkWidget *widget, + GdkEventExpose *event); + +GType +meta_select_workspace_get_type (void) +{ + static GType workspace_type = 0; + + if (!workspace_type) + { + static const GTypeInfo workspace_info = + { + sizeof (MetaSelectWorkspaceClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) meta_select_workspace_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (MetaSelectWorkspace), + 16, /* n_preallocs */ + (GInstanceInitFunc) NULL, + }; + + workspace_type = g_type_register_static (GTK_TYPE_DRAWING_AREA, + "MetaSelectWorkspace", + &workspace_info, + 0); + } + + return workspace_type; +} + +static void +meta_select_workspace_class_init (MetaSelectWorkspaceClass *klass) +{ + GtkWidgetClass *widget_class; + + widget_class = GTK_WIDGET_CLASS (klass); + + widget_class->expose_event = meta_select_workspace_expose_event; +} + +/** + * meta_convert_meta_to_wnck() converts a MetaWindow to a + * WnckWindowDisplayInfo window that is used to build a thumbnail of a + * workspace. + **/ +static WnckWindowDisplayInfo +meta_convert_meta_to_wnck (MetaWindow *window, MetaScreen *screen) +{ + WnckWindowDisplayInfo wnck_window; + wnck_window.icon = window->icon; + wnck_window.mini_icon = window->mini_icon; + + wnck_window.is_active = FALSE; + if (window == window->display->expected_focus_window) + wnck_window.is_active = TRUE; + + if (window->frame) + { + wnck_window.x = window->frame->rect.x; + wnck_window.y = window->frame->rect.y; + wnck_window.width = window->frame->rect.width; + wnck_window.height = window->frame->rect.height; + } + else + { + wnck_window.x = window->rect.x; + wnck_window.y = window->rect.y; + wnck_window.width = window->rect.width; + wnck_window.height = window->rect.height; + } + return wnck_window; +} + + +static gboolean +meta_select_workspace_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + MetaWorkspace *workspace; + WnckWindowDisplayInfo *windows; + GtkStyle *style; + cairo_t *cr; + int i, n_windows; + GList *tmp, *list; + + workspace = META_SELECT_WORKSPACE (widget)->workspace; + + list = meta_stack_list_windows (workspace->screen->stack, workspace); + n_windows = g_list_length (list); + windows = g_new (WnckWindowDisplayInfo, n_windows); + + tmp = list; + i = 0; + while (tmp != NULL) + { + MetaWindow *window; + gboolean ignoreable_sticky; + + window = tmp->data; + + ignoreable_sticky = window->on_all_workspaces && + workspace != workspace->screen->active_workspace; + + if (window->skip_pager || + !meta_window_showing_on_its_workspace (window) || + window->unmaps_pending || + ignoreable_sticky) + { + --n_windows; + } + else + { + windows[i] = meta_convert_meta_to_wnck (window, workspace->screen); + i++; + } + tmp = tmp->next; + } + + g_list_free (list); + + wnck_draw_workspace (widget, + widget->window, + SELECT_OUTLINE_WIDTH, + SELECT_OUTLINE_WIDTH, + widget->allocation.width - SELECT_OUTLINE_WIDTH * 2, + widget->allocation.height - SELECT_OUTLINE_WIDTH * 2, + workspace->screen->rect.width, + workspace->screen->rect.height, + NULL, + (workspace->screen->active_workspace == workspace), + windows, + n_windows); + + g_free (windows); + + if (META_SELECT_WORKSPACE (widget)->selected) + { + style = gtk_widget_get_style (widget); + cr = gdk_cairo_create (widget->window); + + gdk_cairo_set_source_color (cr, + &style->fg[gtk_widget_get_state (widget)]); + cairo_set_line_width (cr, SELECT_OUTLINE_WIDTH); + + cairo_rectangle (cr, + SELECT_OUTLINE_WIDTH / 2.0, SELECT_OUTLINE_WIDTH / 2.0, + widget->allocation.width - SELECT_OUTLINE_WIDTH, + widget->allocation.height - SELECT_OUTLINE_WIDTH); + cairo_stroke (cr); + + cairo_destroy (cr); + } + + return TRUE; +} + diff --git a/src/ui/testgradient.c b/src/ui/testgradient.c new file mode 100644 index 00000000..fd1b09c7 --- /dev/null +++ b/src/ui/testgradient.c @@ -0,0 +1,336 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco gradient test program */ + +/* + * Copyright (C) 2002 Havoc Pennington + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. */ + +#include "gradient.h" +#include + +typedef void (* RenderGradientFunc) (GdkDrawable *drawable, + cairo_t *cr, + int width, + int height); + +static void +draw_checkerboard (GdkDrawable *drawable, + int width, + int height) +{ + gint i, j, xcount, ycount; + GdkColor color1, color2; + cairo_t *cr; + +#define CHECK_SIZE 10 +#define SPACING 2 + + color1.red = 30000; + color1.green = 30000; + color1.blue = 30000; + + color2.red = 50000; + color2.green = 50000; + color2.blue = 50000; + + cr = gdk_cairo_create (drawable); + + xcount = 0; + i = SPACING; + while (i < width) + { + j = SPACING; + ycount = xcount % 2; /* start with even/odd depending on row */ + while (j < height) + { + if (ycount % 2) + gdk_cairo_set_source_color (cr, &color1); + else + gdk_cairo_set_source_color (cr, &color2); + + /* If we're outside event->area, this will do nothing. + * It might be mildly more efficient if we handled + * the clipping ourselves, but again we're feeling lazy. + */ + cairo_rectangle (cr, i, j, CHECK_SIZE, CHECK_SIZE); + cairo_fill (cr); + + j += CHECK_SIZE + SPACING; + ++ycount; + } + + i += CHECK_SIZE + SPACING; + ++xcount; + } + + cairo_destroy (cr); +} + +static void +render_simple (GdkDrawable *drawable, + cairo_t *cr, + int width, int height, + MetaGradientType type, + gboolean with_alpha) +{ + GdkPixbuf *pixbuf; + GdkColor from, to; + + gdk_color_parse ("blue", &from); + gdk_color_parse ("green", &to); + + pixbuf = meta_gradient_create_simple (width, height, + &from, &to, + type); + + if (with_alpha) + { + const unsigned char alphas[] = { 0xff, 0xaa, 0x2f, 0x0, 0xcc, 0xff, 0xff }; + + if (!gdk_pixbuf_get_has_alpha (pixbuf)) + { + GdkPixbuf *new_pixbuf; + + new_pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0); + g_object_unref (G_OBJECT (pixbuf)); + pixbuf = new_pixbuf; + } + + meta_gradient_add_alpha (pixbuf, + alphas, G_N_ELEMENTS (alphas), + META_GRADIENT_HORIZONTAL); + + draw_checkerboard (drawable, width, height); + } + + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + cairo_rectangle (cr, 0, 0, width, height); + cairo_fill (cr); + + g_object_unref (G_OBJECT (pixbuf)); +} + +static void +render_vertical_func (GdkDrawable *drawable, + cairo_t *cr, + int width, int height) +{ + render_simple (drawable, cr, width, height, META_GRADIENT_VERTICAL, FALSE); +} + +static void +render_horizontal_func (GdkDrawable *drawable, + cairo_t *cr, + int width, int height) +{ + render_simple (drawable, cr, width, height, META_GRADIENT_HORIZONTAL, FALSE); +} + +static void +render_diagonal_func (GdkDrawable *drawable, + cairo_t *cr, + int width, int height) +{ + render_simple (drawable, cr, width, height, META_GRADIENT_DIAGONAL, FALSE); +} + +static void +render_diagonal_alpha_func (GdkDrawable *drawable, + cairo_t *cr, + int width, int height) +{ + render_simple (drawable, cr, width, height, META_GRADIENT_DIAGONAL, TRUE); +} + +static void +render_multi (GdkDrawable *drawable, + cairo_t *cr, + int width, int height, + MetaGradientType type) +{ + GdkPixbuf *pixbuf; +#define N_COLORS 5 + GdkColor colors[N_COLORS]; + + gdk_color_parse ("red", &colors[0]); + gdk_color_parse ("blue", &colors[1]); + gdk_color_parse ("orange", &colors[2]); + gdk_color_parse ("pink", &colors[3]); + gdk_color_parse ("green", &colors[4]); + + pixbuf = meta_gradient_create_multi (width, height, + colors, N_COLORS, + type); + + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + cairo_rectangle (cr, 0, 0, width, height); + cairo_fill (cr); + + g_object_unref (G_OBJECT (pixbuf)); +#undef N_COLORS +} + +static void +render_vertical_multi_func (GdkDrawable *drawable, + cairo_t *cr, + int width, int height) +{ + render_multi (drawable, cr, width, height, META_GRADIENT_VERTICAL); +} + +static void +render_horizontal_multi_func (GdkDrawable *drawable, + cairo_t *cr, + int width, int height) +{ + render_multi (drawable, cr, width, height, META_GRADIENT_HORIZONTAL); +} + +static void +render_diagonal_multi_func (GdkDrawable *drawable, + cairo_t *cr, + int width, int height) +{ + render_multi (drawable, cr, width, height, META_GRADIENT_DIAGONAL); +} + +static void +render_interwoven_func (GdkDrawable *drawable, + cairo_t *cr, + int width, int height) +{ + GdkPixbuf *pixbuf; +#define N_COLORS 4 + GdkColor colors[N_COLORS]; + + gdk_color_parse ("red", &colors[0]); + gdk_color_parse ("blue", &colors[1]); + gdk_color_parse ("pink", &colors[2]); + gdk_color_parse ("green", &colors[3]); + + pixbuf = meta_gradient_create_interwoven (width, height, + colors, height / 10, + colors + 2, height / 14); + + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + cairo_rectangle (cr, 0, 0, width, height); + cairo_fill (cr); + + g_object_unref (G_OBJECT (pixbuf)); +} + +static gboolean +expose_callback (GtkWidget *widget, + GdkEventExpose *event, + gpointer data) +{ + RenderGradientFunc func = data; + GdkWindow *window; + GtkAllocation allocation; + GtkStyle *style; + cairo_t *cr; + + style = gtk_widget_get_style (widget); + gtk_widget_get_allocation (widget, &allocation); + + window = gtk_widget_get_window (widget); + cr = gdk_cairo_create (window); + gdk_cairo_set_source_color (cr, &style->fg[gtk_widget_get_state (widget)]); + + (* func) (window, + cr, + allocation.width, + allocation.height); + + cairo_destroy (cr); + + return TRUE; +} + +static GtkWidget* +create_gradient_window (const char *title, + RenderGradientFunc func) +{ + GtkWidget *window; + GtkWidget *drawing_area; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_title (GTK_WINDOW (window), title); + + drawing_area = gtk_drawing_area_new (); + + gtk_widget_set_size_request (drawing_area, 1, 1); + + gtk_window_set_default_size (GTK_WINDOW (window), 175, 175); + + g_signal_connect (G_OBJECT (drawing_area), + "expose_event", + G_CALLBACK (expose_callback), + func); + + gtk_container_add (GTK_CONTAINER (window), drawing_area); + + gtk_widget_show_all (window); + + return window; +} + +static void +meta_gradient_test (void) +{ + GtkWidget *window; + + window = create_gradient_window ("Simple vertical", + render_vertical_func); + + window = create_gradient_window ("Simple horizontal", + render_horizontal_func); + + window = create_gradient_window ("Simple diagonal", + render_diagonal_func); + + window = create_gradient_window ("Multi vertical", + render_vertical_multi_func); + + window = create_gradient_window ("Multi horizontal", + render_horizontal_multi_func); + + window = create_gradient_window ("Multi diagonal", + render_diagonal_multi_func); + + window = create_gradient_window ("Interwoven", + render_interwoven_func); + + window = create_gradient_window ("Simple diagonal with horizontal multi alpha", + render_diagonal_alpha_func); + +} + +int +main (int argc, char **argv) +{ + gtk_init (&argc, &argv); + + meta_gradient_test (); + + gtk_main (); + + return 0; +} + diff --git a/src/ui/theme-parser.c b/src/ui/theme-parser.c new file mode 100644 index 00000000..12f01061 --- /dev/null +++ b/src/ui/theme-parser.c @@ -0,0 +1,4212 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco theme parsing */ + +/* + * Copyright (C) 2001 Havoc Pennington + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include +#include "theme-parser.h" +#include "util.h" +#include +#include + +/* This is a hack for Ubuntu's metacity themes + * Its safe for unable or not. + * I am using this define to know what code was added */ +#define USE_UBUNTU_CODE 1 + +typedef enum +{ + STATE_START, + STATE_THEME, + /* info section */ + STATE_INFO, + STATE_NAME, + STATE_AUTHOR, + STATE_COPYRIGHT, + STATE_DATE, + STATE_DESCRIPTION, + /* constants */ + STATE_CONSTANT, + /* geometry */ + STATE_FRAME_GEOMETRY, + STATE_DISTANCE, + STATE_BORDER, + STATE_ASPECT_RATIO, + /* draw ops */ + STATE_DRAW_OPS, + STATE_LINE, + STATE_RECTANGLE, + STATE_ARC, + STATE_CLIP, + STATE_TINT, + STATE_GRADIENT, + STATE_IMAGE, + STATE_GTK_ARROW, + STATE_GTK_BOX, + STATE_GTK_VLINE, + STATE_ICON, + STATE_TITLE, + STATE_INCLUDE, /* include another draw op list */ + STATE_TILE, /* tile another draw op list */ + /* sub-parts of gradient */ + STATE_COLOR, + /* frame style */ + STATE_FRAME_STYLE, + STATE_PIECE, + STATE_BUTTON, +#ifdef USE_UBUNTU_CODE + STATE_SHADOW, + STATE_PADDING, +#endif + /* style set */ + STATE_FRAME_STYLE_SET, + STATE_FRAME, + /* assigning style sets to windows */ + STATE_WINDOW, + /* things we don't use any more but we can still parse: */ + STATE_MENU_ICON, + STATE_FALLBACK +} ParseState; + +typedef struct +{ + GSList *states; + + const char *theme_name; /* name of theme (directory it's in) */ + char *theme_file; /* theme filename */ + char *theme_dir; /* dir the theme is inside */ + MetaTheme *theme; /* theme being parsed */ + guint format_version; /* version of format of theme file */ + char *name; /* name of named thing being parsed */ + MetaFrameLayout *layout; /* layout being parsed if any */ + MetaDrawOpList *op_list; /* op list being parsed if any */ + MetaDrawOp *op; /* op being parsed if any */ + MetaFrameStyle *style; /* frame style being parsed if any */ + MetaFrameStyleSet *style_set; /* frame style set being parsed if any */ + MetaFramePiece piece; /* position of piece being parsed */ + MetaButtonType button_type; /* type of button/menuitem being parsed */ + MetaButtonState button_state; /* state of button being parsed */ +} ParseInfo; + +static void set_error (GError **err, + GMarkupParseContext *context, + int error_domain, + int error_code, + const char *format, + ...) G_GNUC_PRINTF (5, 6); + +static void add_context_to_error (GError **err, + GMarkupParseContext *context); + +static void parse_info_init (ParseInfo *info); +static void parse_info_free (ParseInfo *info); + +static void push_state (ParseInfo *info, + ParseState state); +static void pop_state (ParseInfo *info); +static ParseState peek_state (ParseInfo *info); + + +static void parse_toplevel_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +static void parse_info_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +static void parse_geometry_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +static void parse_draw_op_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +static void parse_gradient_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +static void parse_style_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +static void parse_style_set_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); + +static void parse_piece_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); + +static void parse_button_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); + +#ifdef USE_UBUNTU_CODE +static void parse_shadow_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); + +static void parse_padding_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); +#endif +static void parse_menu_icon_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error); + +static void start_element_handler (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error); +static void end_element_handler (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error); +static void text_handler (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error); + +/* Translators: This means that an attribute which should have been found + * on an XML element was not in fact found. + */ +#define ATTRIBUTE_NOT_FOUND _("No \"%s\" attribute on element <%s>") + +static GMarkupParser marco_theme_parser = { + start_element_handler, + end_element_handler, + text_handler, + NULL, + NULL +}; + +static void +set_error (GError **err, + GMarkupParseContext *context, + int error_domain, + int error_code, + const char *format, + ...) +{ + int line, ch; + va_list args; + char *str; + + g_markup_parse_context_get_position (context, &line, &ch); + + va_start (args, format); + str = g_strdup_vprintf (format, args); + va_end (args); + + g_set_error (err, error_domain, error_code, + _("Line %d character %d: %s"), + line, ch, str); + + g_free (str); +} + +static void +add_context_to_error (GError **err, + GMarkupParseContext *context) +{ + int line, ch; + char *str; + + if (err == NULL || *err == NULL) + return; + + g_markup_parse_context_get_position (context, &line, &ch); + + str = g_strdup_printf (_("Line %d character %d: %s"), + line, ch, (*err)->message); + g_free ((*err)->message); + (*err)->message = str; +} + +static void +parse_info_init (ParseInfo *info) +{ + info->theme_file = NULL; + info->states = g_slist_prepend (NULL, GINT_TO_POINTER (STATE_START)); + info->theme = NULL; + info->name = NULL; + info->layout = NULL; + info->op_list = NULL; + info->op = NULL; + info->style = NULL; + info->style_set = NULL; + info->piece = META_FRAME_PIECE_LAST; + info->button_type = META_BUTTON_TYPE_LAST; + info->button_state = META_BUTTON_STATE_LAST; +} + +static void +parse_info_free (ParseInfo *info) +{ + g_free (info->theme_file); + g_free (info->theme_dir); + + g_slist_free (info->states); + + if (info->theme) + meta_theme_free (info->theme); + + if (info->layout) + meta_frame_layout_unref (info->layout); + + if (info->op_list) + meta_draw_op_list_unref (info->op_list); + + if (info->op) + meta_draw_op_free (info->op); + + if (info->style) + meta_frame_style_unref (info->style); + + if (info->style_set) + meta_frame_style_set_unref (info->style_set); +} + +static void +push_state (ParseInfo *info, + ParseState state) +{ + info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state)); +} + +static void +pop_state (ParseInfo *info) +{ + g_return_if_fail (info->states != NULL); + + info->states = g_slist_remove (info->states, info->states->data); +} + +static ParseState +peek_state (ParseInfo *info) +{ + g_return_val_if_fail (info->states != NULL, STATE_START); + + return GPOINTER_TO_INT (info->states->data); +} + +#define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0) + +typedef struct +{ + const char *name; + const char **retloc; + gboolean required; +} LocateAttr; + +/* Attribute names can have a leading '!' to indicate that they are + * required. + */ +static gboolean +locate_attributes (GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + GError **error, + const char *first_attribute_name, + const char **first_attribute_retloc, + ...) +{ + va_list args; + const char *name; + const char **retloc; + int n_attrs; +#define MAX_ATTRS 24 + LocateAttr attrs[MAX_ATTRS]; + gboolean retval; + int i; + + g_return_val_if_fail (first_attribute_name != NULL, FALSE); + g_return_val_if_fail (first_attribute_retloc != NULL, FALSE); + + retval = TRUE; + + /* FIXME: duplicated code; refactor loop */ + n_attrs = 1; + attrs[0].name = first_attribute_name; + attrs[0].retloc = first_attribute_retloc; + attrs[0].required = attrs[0].name[0]=='!'; + if (attrs[0].required) + attrs[0].name++; /* skip past it */ + *first_attribute_retloc = NULL; + + va_start (args, first_attribute_retloc); + + name = va_arg (args, const char*); + retloc = va_arg (args, const char**); + + while (name != NULL) + { + g_return_val_if_fail (retloc != NULL, FALSE); + + g_assert (n_attrs < MAX_ATTRS); + + attrs[n_attrs].name = name; + attrs[n_attrs].retloc = retloc; + attrs[n_attrs].required = attrs[n_attrs].name[0]=='!'; + if (attrs[n_attrs].required) + attrs[n_attrs].name++; /* skip past it */ + + n_attrs += 1; + *retloc = NULL; + + name = va_arg (args, const char*); + retloc = va_arg (args, const char**); + } + + va_end (args); + + i = 0; + while (attribute_names[i]) + { + int j; + gboolean found; + + found = FALSE; + j = 0; + while (j < n_attrs) + { + if (strcmp (attrs[j].name, attribute_names[i]) == 0) + { + retloc = attrs[j].retloc; + + if (*retloc != NULL) + { + + set_error (error, context, + G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Attribute \"%s\" repeated twice on the same <%s> element"), + attrs[j].name, element_name); + retval = FALSE; + goto out; + } + + *retloc = attribute_values[i]; + found = TRUE; + } + + ++j; + } + + if (!found) + { + j = 0; + while (j < n_attrs) + { + g_warning ("It could have been %s.\n", attrs[j++].name); + } + + set_error (error, context, + G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Attribute \"%s\" is invalid on <%s> element in this context"), + attribute_names[i], element_name); + retval = FALSE; + goto out; + } + + ++i; + } + + /* Did we catch them all? */ + i = 0; + while (i < n_attrs) + { + if (attrs[i].required && *(attrs[i].retloc)==NULL) + { + set_error (error, context, + G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + ATTRIBUTE_NOT_FOUND, + attrs[i].name, element_name); + retval = FALSE; + goto out; + } + + ++i; + } + + out: + return retval; +} + +static gboolean +check_no_attributes (GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + GError **error) +{ + if (attribute_names[0] != NULL) + { + set_error (error, context, + G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Attribute \"%s\" is invalid on <%s> element in this context"), + attribute_names[0], element_name); + return FALSE; + } + + return TRUE; +} + +#define MAX_REASONABLE 4096 +static gboolean +parse_positive_integer (const char *str, + int *val, + GMarkupParseContext *context, + MetaTheme *theme, + GError **error) +{ + char *end; + long l; + int j; + + *val = 0; + + end = NULL; + + /* Is str a constant? */ + + if (META_THEME_ALLOWS (theme, META_THEME_UBIQUITOUS_CONSTANTS) && + meta_theme_lookup_int_constant (theme, str, &j)) + { + /* Yes. */ + l = j; + } + else + { + /* No. Let's try parsing it instead. */ + + l = strtol (str, &end, 10); + + if (end == NULL || end == str) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Could not parse \"%s\" as an integer"), + str); + return FALSE; + } + + if (*end != '\0') + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand trailing characters \"%s\" in string \"%s\""), + end, str); + return FALSE; + } + } + + if (l < 0) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Integer %ld must be positive"), l); + return FALSE; + } + + if (l > MAX_REASONABLE) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Integer %ld is too large, current max is %d"), + l, MAX_REASONABLE); + return FALSE; + } + + *val = (int) l; + + return TRUE; +} + +static gboolean +parse_double (const char *str, + double *val, + GMarkupParseContext *context, + GError **error) +{ + char *end; + + *val = 0; + + end = NULL; + + *val = g_ascii_strtod (str, &end); + + if (end == NULL || end == str) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Could not parse \"%s\" as a floating point number"), + str); + return FALSE; + } + + if (*end != '\0') + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand trailing characters \"%s\" in string \"%s\""), + end, str); + return FALSE; + } + + return TRUE; +} + +static gboolean +parse_boolean (const char *str, + gboolean *val, + GMarkupParseContext *context, + GError **error) +{ + if (strcmp ("true", str) == 0) + *val = TRUE; + else if (strcmp ("false", str) == 0) + *val = FALSE; + else + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Boolean values must be \"true\" or \"false\" not \"%s\""), + str); + return FALSE; + } + + return TRUE; +} + +static gboolean +parse_rounding (const char *str, + guint *val, + GMarkupParseContext *context, + MetaTheme *theme, + GError **error) +{ + if (strcmp ("true", str) == 0) + *val = 5; /* historical "true" value */ + else if (strcmp ("false", str) == 0) + *val = 0; + else + { + int tmp; + gboolean result; + if (!META_THEME_ALLOWS (theme, META_THEME_VARIED_ROUND_CORNERS)) + { + /* Not known in this version, so bail. */ + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Boolean values must be \"true\" or \"false\" not \"%s\""), + str); + return FALSE; + } + + result = parse_positive_integer (str, &tmp, context, theme, error); + + *val = tmp; + + return result; + } + + return TRUE; +} + +static gboolean +parse_angle (const char *str, + double *val, + GMarkupParseContext *context, + GError **error) +{ + if (!parse_double (str, val, context, error)) + return FALSE; + + if (*val < (0.0 - 1e6) || *val > (360.0 + 1e6)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Angle must be between 0.0 and 360.0, was %g\n"), + *val); + return FALSE; + } + + return TRUE; +} + +static gboolean +parse_alpha (const char *str, + MetaAlphaGradientSpec **spec_ret, + GMarkupParseContext *context, + GError **error) +{ + char **split; + int i; + int n_alphas; + MetaAlphaGradientSpec *spec; + + *spec_ret = NULL; + + split = g_strsplit (str, ":", -1); + + i = 0; + while (split[i]) + ++i; + + if (i == 0) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Could not parse \"%s\" as a floating point number"), + str); + + g_strfreev (split); + + return FALSE; + } + + n_alphas = i; + + /* FIXME allow specifying horizontal/vertical/diagonal in theme format, + * once we implement vertical/diagonal in gradient.c + */ + spec = meta_alpha_gradient_spec_new (META_GRADIENT_HORIZONTAL, + n_alphas); + + i = 0; + while (i < n_alphas) + { + double v; + + if (!parse_double (split[i], &v, context, error)) + { + /* clear up, but don't set error: it was set by parse_double */ + g_strfreev (split); + meta_alpha_gradient_spec_free (spec); + + return FALSE; + } + + if (v < (0.0 - 1e-6) || v > (1.0 + 1e-6)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Alpha must be between 0.0 (invisible) and 1.0 (fully opaque), was %g\n"), + v); + + g_strfreev (split); + meta_alpha_gradient_spec_free (spec); + + return FALSE; + } + + spec->alphas[i] = (unsigned char) (v * 255); + + ++i; + } + + g_strfreev (split); + + *spec_ret = spec; + + return TRUE; +} + +static MetaColorSpec* +parse_color (MetaTheme *theme, + const char *str, + GError **err) +{ + char* referent; + + if (META_THEME_ALLOWS (theme, META_THEME_COLOR_CONSTANTS) && + meta_theme_lookup_color_constant (theme, str, &referent)) + { + if (referent) + return meta_color_spec_new_from_string (referent, err); + + /* no need to free referent: it's a pointer into the actual hash table */ + } + + return meta_color_spec_new_from_string (str, err); +} + +static gboolean +parse_title_scale (const char *str, + double *val, + GMarkupParseContext *context, + GError **error) +{ + double factor; + + if (strcmp (str, "xx-small") == 0) + factor = PANGO_SCALE_XX_SMALL; + else if (strcmp (str, "x-small") == 0) + factor = PANGO_SCALE_X_SMALL; + else if (strcmp (str, "small") == 0) + factor = PANGO_SCALE_SMALL; + else if (strcmp (str, "medium") == 0) + factor = PANGO_SCALE_MEDIUM; + else if (strcmp (str, "large") == 0) + factor = PANGO_SCALE_LARGE; + else if (strcmp (str, "x-large") == 0) + factor = PANGO_SCALE_X_LARGE; + else if (strcmp (str, "xx-large") == 0) + factor = PANGO_SCALE_XX_LARGE; + else + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Invalid title scale \"%s\" (must be one of xx-small,x-small,small,medium,large,x-large,xx-large)\n"), + str); + return FALSE; + } + + *val = factor; + + return TRUE; +} + +static void +parse_toplevel_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_THEME); + + if (ELEMENT_IS ("info")) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_INFO); + } + else if (ELEMENT_IS ("constant")) + { + const char *name; + const char *value; + int ival = 0; + double dval = 0.0; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, "!value", &value, + NULL)) + return; + + /* We don't know how a a constant is going to be used, so we have guess its + * type from its contents: + * + * - Starts like a number and contains a '.': float constant + * - Starts like a number and doesn't contain a '.': int constant + * - Starts with anything else: a color constant. + * (colors always start with # or a letter) + */ + if (value[0] == '.' || value[0] == '+' || value[0] == '-' || (value[0] >= '0' && value[0] <= '9')) + { + if (strchr (value, '.')) + { + if (!parse_double (value, &dval, context, error)) + return; + + if (!meta_theme_define_float_constant (info->theme, + name, + dval, + error)) + { + add_context_to_error (error, context); + return; + } + } + else + { + if (!parse_positive_integer (value, &ival, context, info->theme, error)) + return; + + if (!meta_theme_define_int_constant (info->theme, + name, + ival, + error)) + { + add_context_to_error (error, context); + return; + } + } + } + else + { + if (!meta_theme_define_color_constant (info->theme, + name, + value, + error)) + { + add_context_to_error (error, context); + return; + } + } + + push_state (info, STATE_CONSTANT); + } + else if (ELEMENT_IS ("frame_geometry")) + { + const char *name = NULL; + const char *parent = NULL; + const char *has_title = NULL; + const char *title_scale = NULL; + const char *rounded_top_left = NULL; + const char *rounded_top_right = NULL; + const char *rounded_bottom_left = NULL; + const char *rounded_bottom_right = NULL; + const char *hide_buttons = NULL; + gboolean has_title_val; + guint rounded_top_left_val; + guint rounded_top_right_val; + guint rounded_bottom_left_val; + guint rounded_bottom_right_val; + gboolean hide_buttons_val; + double title_scale_val; + MetaFrameLayout *parent_layout; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, "parent", &parent, + "has_title", &has_title, "title_scale", &title_scale, + "rounded_top_left", &rounded_top_left, + "rounded_top_right", &rounded_top_right, + "rounded_bottom_left", &rounded_bottom_left, + "rounded_bottom_right", &rounded_bottom_right, + "hide_buttons", &hide_buttons, + NULL)) + return; + + has_title_val = TRUE; + if (has_title && !parse_boolean (has_title, &has_title_val, context, error)) + return; + + hide_buttons_val = FALSE; + if (hide_buttons && !parse_boolean (hide_buttons, &hide_buttons_val, context, error)) + return; + + rounded_top_left_val = 0; + rounded_top_right_val = 0; + rounded_bottom_left_val = 0; + rounded_bottom_right_val = 0; + + if (rounded_top_left && !parse_rounding (rounded_top_left, &rounded_top_left_val, context, info->theme, error)) + return; + if (rounded_top_right && !parse_rounding (rounded_top_right, &rounded_top_right_val, context, info->theme, error)) + return; + if (rounded_bottom_left && !parse_rounding (rounded_bottom_left, &rounded_bottom_left_val, context, info->theme, error)) + return; + if (rounded_bottom_right && !parse_rounding (rounded_bottom_right, &rounded_bottom_right_val, context, info->theme, error)) + return; + + title_scale_val = 1.0; + if (title_scale && !parse_title_scale (title_scale, &title_scale_val, context, error)) + return; + + if (meta_theme_lookup_layout (info->theme, name)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> name \"%s\" used a second time"), + element_name, name); + return; + } + + parent_layout = NULL; + if (parent) + { + parent_layout = meta_theme_lookup_layout (info->theme, parent); + if (parent_layout == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> parent \"%s\" has not been defined"), + element_name, parent); + return; + } + } + + g_assert (info->layout == NULL); + + if (parent_layout) + info->layout = meta_frame_layout_copy (parent_layout); + else + info->layout = meta_frame_layout_new (); + + if (has_title) /* only if explicit, otherwise inherit */ + info->layout->has_title = has_title_val; + + if (META_THEME_ALLOWS (info->theme, META_THEME_HIDDEN_BUTTONS) && hide_buttons_val) + info->layout->hide_buttons = hide_buttons_val; + + if (title_scale) + info->layout->title_scale = title_scale_val; + + if (rounded_top_left) + info->layout->top_left_corner_rounded_radius = rounded_top_left_val; + + if (rounded_top_right) + info->layout->top_right_corner_rounded_radius = rounded_top_right_val; + + if (rounded_bottom_left) + info->layout->bottom_left_corner_rounded_radius = rounded_bottom_left_val; + + if (rounded_bottom_right) + info->layout->bottom_right_corner_rounded_radius = rounded_bottom_right_val; + + meta_theme_insert_layout (info->theme, name, info->layout); + + push_state (info, STATE_FRAME_GEOMETRY); + } + else if (ELEMENT_IS ("draw_ops")) + { + const char *name = NULL; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, + NULL)) + return; + + if (meta_theme_lookup_draw_op_list (info->theme, name)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> name \"%s\" used a second time"), + element_name, name); + return; + } + + g_assert (info->op_list == NULL); + info->op_list = meta_draw_op_list_new (2); + + meta_theme_insert_draw_op_list (info->theme, name, info->op_list); + + push_state (info, STATE_DRAW_OPS); + } + else if (ELEMENT_IS ("frame_style")) + { + const char *name = NULL; + const char *parent = NULL; + const char *geometry = NULL; + const char *background = NULL; + const char *alpha = NULL; + MetaFrameStyle *parent_style; + MetaFrameLayout *layout; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, "parent", &parent, + "geometry", &geometry, + "background", &background, + "alpha", &alpha, + NULL)) + return; + + if (meta_theme_lookup_style (info->theme, name)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> name \"%s\" used a second time"), + element_name, name); + return; + } + + parent_style = NULL; + if (parent) + { + parent_style = meta_theme_lookup_style (info->theme, parent); + if (parent_style == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> parent \"%s\" has not been defined"), + element_name, parent); + return; + } + } + + layout = NULL; + if (geometry) + { + layout = meta_theme_lookup_layout (info->theme, geometry); + if (layout == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> geometry \"%s\" has not been defined"), + element_name, geometry); + return; + } + } + else if (parent_style) + { + layout = parent_style->layout; + } + + if (layout == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> must specify either a geometry or a parent that has a geometry"), + element_name); + return; + } + + g_assert (info->style == NULL); + + info->style = meta_frame_style_new (parent_style); + g_assert (info->style->layout == NULL); + meta_frame_layout_ref (layout); + info->style->layout = layout; + + if (background != NULL && META_THEME_ALLOWS (info->theme, META_THEME_FRAME_BACKGROUNDS)) + { + info->style->window_background_color = meta_color_spec_new_from_string (background, error); + if (!info->style->window_background_color) + return; + + if (alpha != NULL) + { + + gboolean success; + MetaAlphaGradientSpec *alpha_vector; + + g_clear_error (error); + /* fortunately, we already have a routine to parse alpha values, + * though it produces a vector of them, which is a superset of + * what we want. + */ + success = parse_alpha (alpha, &alpha_vector, context, error); + if (!success) + return; + + /* alpha_vector->alphas must contain at least one element */ + info->style->window_background_alpha = alpha_vector->alphas[0]; + + meta_alpha_gradient_spec_free (alpha_vector); + } + } + else if (alpha != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("You must specify a background for an alpha value to be meaningful")); + return; + } + + meta_theme_insert_style (info->theme, name, info->style); + + push_state (info, STATE_FRAME_STYLE); + } + else if (ELEMENT_IS ("frame_style_set")) + { + const char *name = NULL; + const char *parent = NULL; + MetaFrameStyleSet *parent_set; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, "parent", &parent, + NULL)) + return; + + if (meta_theme_lookup_style_set (info->theme, name)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> name \"%s\" used a second time"), + element_name, name); + return; + } + + parent_set = NULL; + if (parent) + { + parent_set = meta_theme_lookup_style_set (info->theme, parent); + if (parent_set == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("<%s> parent \"%s\" has not been defined"), + element_name, parent); + return; + } + } + + g_assert (info->style_set == NULL); + + info->style_set = meta_frame_style_set_new (parent_set); + + meta_theme_insert_style_set (info->theme, name, info->style_set); + + push_state (info, STATE_FRAME_STYLE_SET); + } + else if (ELEMENT_IS ("window")) + { + const char *type_name = NULL; + const char *style_set_name = NULL; + MetaFrameStyleSet *style_set; + MetaFrameType type; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!type", &type_name, "!style_set", &style_set_name, + NULL)) + return; + + type = meta_frame_type_from_string (type_name); + + if (type == META_FRAME_TYPE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Unknown type \"%s\" on <%s> element"), + type_name, element_name); + return; + } + + style_set = meta_theme_lookup_style_set (info->theme, + style_set_name); + + if (style_set == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Unknown style_set \"%s\" on <%s> element"), + style_set_name, element_name); + return; + } + + if (info->theme->style_sets_by_type[type] != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Window type \"%s\" has already been assigned a style set"), + type_name); + return; + } + + meta_frame_style_set_ref (style_set); + info->theme->style_sets_by_type[type] = style_set; + + push_state (info, STATE_WINDOW); + } + else if (ELEMENT_IS ("menu_icon")) + { + /* Not supported any more, but we have to parse it if they include it, + * for backwards compatibility. + */ + g_assert (info->op_list == NULL); + + push_state (info, STATE_MENU_ICON); + } + else if (ELEMENT_IS ("fallback")) + { + /* Not supported any more, but we have to parse it if they include it, + * for backwards compatibility. + */ + push_state (info, STATE_FALLBACK); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "metacity_theme"); + } +} + +static void +parse_info_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_INFO); + + if (ELEMENT_IS ("name")) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_NAME); + } + else if (ELEMENT_IS ("author")) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_AUTHOR); + } + else if (ELEMENT_IS ("copyright")) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_COPYRIGHT); + } + else if (ELEMENT_IS ("description")) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_DESCRIPTION); + } + else if (ELEMENT_IS ("date")) + { + if (!check_no_attributes (context, element_name, + attribute_names, attribute_values, + error)) + return; + + push_state (info, STATE_DATE); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "info"); + } +} + +static void +parse_distance (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + const char *name; + const char *value; + int val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, "!value", &value, + NULL)) + return; + + val = 0; + if (!parse_positive_integer (value, &val, context, info->theme, error)) + return; + + g_assert (val >= 0); /* yeah, "non-negative" not "positive" get over it */ + g_assert (info->layout); + + if (strcmp (name, "left_width") == 0) + info->layout->left_width = val; + else if (strcmp (name, "right_width") == 0) + info->layout->right_width = val; + else if (strcmp (name, "bottom_height") == 0) + info->layout->bottom_height = val; + else if (strcmp (name, "title_vertical_pad") == 0) + info->layout->title_vertical_pad = val; + else if (strcmp (name, "right_titlebar_edge") == 0) + info->layout->right_titlebar_edge = val; + else if (strcmp (name, "left_titlebar_edge") == 0) + info->layout->left_titlebar_edge = val; + else if (strcmp (name, "button_width") == 0) + { + info->layout->button_width = val; + + if (!(info->layout->button_sizing == META_BUTTON_SIZING_LAST || + info->layout->button_sizing == META_BUTTON_SIZING_FIXED)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Cannot specify both \"button_width\"/\"button_height\" and \"aspect_ratio\" for buttons")); + return; + } + + info->layout->button_sizing = META_BUTTON_SIZING_FIXED; + } + else if (strcmp (name, "button_height") == 0) + { + info->layout->button_height = val; + + if (!(info->layout->button_sizing == META_BUTTON_SIZING_LAST || + info->layout->button_sizing == META_BUTTON_SIZING_FIXED)) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Cannot specify both \"button_width\"/\"button_height\" and \"aspect_ratio\" for buttons")); + return; + } + + info->layout->button_sizing = META_BUTTON_SIZING_FIXED; + } + else + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Distance \"%s\" is unknown"), name); + return; + } +} + +static void +parse_aspect_ratio (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + const char *name; + const char *value; + double val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, "!value", &value, + NULL)) + return; + + val = 0; + if (!parse_double (value, &val, context, error)) + return; + + g_assert (info->layout); + + if (strcmp (name, "button") == 0) + { + info->layout->button_aspect = val; + + if (info->layout->button_sizing != META_BUTTON_SIZING_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Cannot specify both \"button_width\"/\"button_height\" and \"aspect_ratio\" for buttons")); + return; + } + + info->layout->button_sizing = META_BUTTON_SIZING_ASPECT; + } + else + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Aspect ratio \"%s\" is unknown"), name); + return; + } +} + +static void +parse_border (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + const char *name; + const char *top; + const char *bottom; + const char *left; + const char *right; + int top_val; + int bottom_val; + int left_val; + int right_val; + GtkBorder *border; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!name", &name, + "!top", &top, + "!bottom", &bottom, + "!left", &left, + "!right", &right, + NULL)) + return; + + top_val = 0; + if (!parse_positive_integer (top, &top_val, context, info->theme, error)) + return; + + bottom_val = 0; + if (!parse_positive_integer (bottom, &bottom_val, context, info->theme, error)) + return; + + left_val = 0; + if (!parse_positive_integer (left, &left_val, context, info->theme, error)) + return; + + right_val = 0; + if (!parse_positive_integer (right, &right_val, context, info->theme, error)) + return; + + g_assert (info->layout); + + border = NULL; + + if (strcmp (name, "title_border") == 0) + border = &info->layout->title_border; + else if (strcmp (name, "button_border") == 0) + border = &info->layout->button_border; + + if (border == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Border \"%s\" is unknown"), name); + return; + } + + border->top = top_val; + border->bottom = bottom_val; + border->left = left_val; + border->right = right_val; +} + +static void +parse_geometry_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_FRAME_GEOMETRY); + + if (ELEMENT_IS ("distance")) + { + parse_distance (context, element_name, + attribute_names, attribute_values, + info, error); + push_state (info, STATE_DISTANCE); + } + else if (ELEMENT_IS ("border")) + { + parse_border (context, element_name, + attribute_names, attribute_values, + info, error); + push_state (info, STATE_BORDER); + } + else if (ELEMENT_IS ("aspect_ratio")) + { + parse_aspect_ratio (context, element_name, + attribute_names, attribute_values, + info, error); + + push_state (info, STATE_ASPECT_RATIO); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "frame_geometry"); + } +} + +#if 0 +static gboolean +check_expression (PosToken *tokens, + int n_tokens, + gboolean has_object, + MetaTheme *theme, + GMarkupParseContext *context, + GError **error) +{ + MetaPositionExprEnv env; + int x, y; + + /* We set it all to 0 to try and catch divide-by-zero screwups. + * it's possible we should instead guarantee that widths and heights + * are at least 1. + */ + + env.rect = meta_rect (0, 0, 0, 0); + if (has_object) + { + env.object_width = 0; + env.object_height = 0; + } + else + { + env.object_width = -1; + env.object_height = -1; + } + + env.left_width = 0; + env.right_width = 0; + env.top_height = 0; + env.bottom_height = 0; + env.title_width = 0; + env.title_height = 0; + + env.icon_width = 0; + env.icon_height = 0; + env.mini_icon_width = 0; + env.mini_icon_height = 0; + env.theme = theme; + + if (!meta_parse_position_expression (tokens, n_tokens, + &env, + &x, &y, + error)) + { + add_context_to_error (error, context); + return FALSE; + } + + return TRUE; +} +#endif + +static void +parse_draw_op_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_DRAW_OPS); + + if (ELEMENT_IS ("line")) + { + MetaDrawOp *op; + const char *color; + const char *x1; + const char *y1; + const char *x2; + const char *y2; + const char *dash_on_length; + const char *dash_off_length; + const char *width; + MetaColorSpec *color_spec; + int dash_on_val; + int dash_off_val; + int width_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!color", &color, + "!x1", &x1, "!y1", &y1, + "!x2", &x2, "!y2", &y2, + "dash_on_length", &dash_on_length, + "dash_off_length", &dash_off_length, + "width", &width, + NULL)) + return; + +#if 0 + if (!check_expression (x1, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y1, FALSE, info->theme, context, error)) + return; + + if (!check_expression (x2, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y2, FALSE, info->theme, context, error)) + return; +#endif + + dash_on_val = 0; + if (dash_on_length && + !parse_positive_integer (dash_on_length, &dash_on_val, context, info->theme, error)) + return; + + dash_off_val = 0; + if (dash_off_length && + !parse_positive_integer (dash_off_length, &dash_off_val, context, info->theme, error)) + return; + + width_val = 0; + if (width && + !parse_positive_integer (width, &width_val, context, info->theme, error)) + return; + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = parse_color (info->theme, color, error); + if (color_spec == NULL) + { + add_context_to_error (error, context); + return; + } + + op = meta_draw_op_new (META_DRAW_LINE); + + op->data.line.color_spec = color_spec; + + op->data.line.x1 = meta_draw_spec_new (info->theme, x1, NULL); + op->data.line.y1 = meta_draw_spec_new (info->theme, y1, NULL); + + if (strcmp(x1, x2)==0) + op->data.line.x2 = NULL; + else + op->data.line.x2 = meta_draw_spec_new (info->theme, x2, NULL); + + if (strcmp(y1, y2)==0) + op->data.line.y2 = NULL; + else + op->data.line.y2 = meta_draw_spec_new (info->theme, y2, NULL); + + op->data.line.width = width_val; + op->data.line.dash_on_length = dash_on_val; + op->data.line.dash_off_length = dash_off_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_LINE); + } + else if (ELEMENT_IS ("rectangle")) + { + MetaDrawOp *op; + const char *color; + const char *x; + const char *y; + const char *width; + const char *height; + const char *filled; + gboolean filled_val; + MetaColorSpec *color_spec; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!color", &color, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + "filled", &filled, + NULL)) + return; + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; +#endif + + filled_val = FALSE; + if (filled && !parse_boolean (filled, &filled_val, context, error)) + return; + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = parse_color (info->theme, color, error); + if (color_spec == NULL) + { + add_context_to_error (error, context); + return; + } + + op = meta_draw_op_new (META_DRAW_RECTANGLE); + + op->data.rectangle.color_spec = color_spec; + op->data.rectangle.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.rectangle.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.rectangle.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.rectangle.height = meta_draw_spec_new (info->theme, + height, NULL); + + op->data.rectangle.filled = filled_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_RECTANGLE); + } + else if (ELEMENT_IS ("arc")) + { + MetaDrawOp *op; + const char *color; + const char *x; + const char *y; + const char *width; + const char *height; + const char *filled; + const char *start_angle; + const char *extent_angle; + const char *from; + const char *to; + gboolean filled_val; + double start_angle_val; + double extent_angle_val; + MetaColorSpec *color_spec; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!color", &color, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + "filled", &filled, + "start_angle", &start_angle, + "extent_angle", &extent_angle, + "from", &from, + "to", &to, + NULL)) + return; + + if (META_THEME_ALLOWS (info->theme, META_THEME_DEGREES_IN_ARCS) ) + { + if (start_angle == NULL && from == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"start_angle\" or \"from\" attribute on element <%s>"), element_name); + return; + } + + if (extent_angle == NULL && to == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No \"extent_angle\" or \"to\" attribute on element <%s>"), element_name); + return; + } + } + else + { + if (start_angle == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + ATTRIBUTE_NOT_FOUND, "start_angle", element_name); + return; + } + + if (extent_angle == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + ATTRIBUTE_NOT_FOUND, "extent_angle", element_name); + return; + } + } + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; +#endif + + if (start_angle == NULL) + { + if (!parse_angle (from, &start_angle_val, context, error)) + return; + + start_angle_val = (180-start_angle_val)/360.0; + } + else + { + if (!parse_angle (start_angle, &start_angle_val, context, error)) + return; + } + + if (extent_angle == NULL) + { + if (!parse_angle (to, &extent_angle_val, context, error)) + return; + + extent_angle_val = ((180-extent_angle_val)/360.0) - start_angle_val; + } + else + { + if (!parse_angle (extent_angle, &extent_angle_val, context, error)) + return; + } + + filled_val = FALSE; + if (filled && !parse_boolean (filled, &filled_val, context, error)) + return; + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = parse_color (info->theme, color, error); + if (color_spec == NULL) + { + add_context_to_error (error, context); + return; + } + + op = meta_draw_op_new (META_DRAW_ARC); + + op->data.arc.color_spec = color_spec; + + op->data.arc.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.arc.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.arc.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.arc.height = meta_draw_spec_new (info->theme, height, NULL); + + op->data.arc.filled = filled_val; + op->data.arc.start_angle = start_angle_val; + op->data.arc.extent_angle = extent_angle_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_ARC); + } + else if (ELEMENT_IS ("clip")) + { + MetaDrawOp *op; + const char *x; + const char *y; + const char *width; + const char *height; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + NULL)) + return; + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; +#endif + op = meta_draw_op_new (META_DRAW_CLIP); + + op->data.clip.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.clip.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.clip.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.clip.height = meta_draw_spec_new (info->theme, height, NULL); + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_CLIP); + } + else if (ELEMENT_IS ("tint")) + { + MetaDrawOp *op; + const char *color; + const char *x; + const char *y; + const char *width; + const char *height; + const char *alpha; + MetaAlphaGradientSpec *alpha_spec; + MetaColorSpec *color_spec; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!color", &color, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + "!alpha", &alpha, + NULL)) + return; + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; +#endif + alpha_spec = NULL; + if (!parse_alpha (alpha, &alpha_spec, context, error)) + return; + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = parse_color (info->theme, color, error); + if (color_spec == NULL) + { + if (alpha_spec) + meta_alpha_gradient_spec_free (alpha_spec); + + add_context_to_error (error, context); + return; + } + + op = meta_draw_op_new (META_DRAW_TINT); + + op->data.tint.color_spec = color_spec; + op->data.tint.alpha_spec = alpha_spec; + + op->data.tint.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.tint.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.tint.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.tint.height = meta_draw_spec_new (info->theme, height, NULL); + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_TINT); + } + else if (ELEMENT_IS ("gradient")) + { + const char *x; + const char *y; + const char *width; + const char *height; + const char *type; + const char *alpha; + MetaAlphaGradientSpec *alpha_spec; + MetaGradientType type_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!type", &type, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + "alpha", &alpha, + NULL)) + return; + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; +#endif + + type_val = meta_gradient_type_from_string (type); + if (type_val == META_GRADIENT_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Did not understand value \"%s\" for type of gradient"), + type); + return; + } + + alpha_spec = NULL; + if (alpha && !parse_alpha (alpha, &alpha_spec, context, error)) + return; + + g_assert (info->op == NULL); + info->op = meta_draw_op_new (META_DRAW_GRADIENT); + + info->op->data.gradient.x = meta_draw_spec_new (info->theme, x, NULL); + info->op->data.gradient.y = meta_draw_spec_new (info->theme, y, NULL); + info->op->data.gradient.width = meta_draw_spec_new (info->theme, + width, NULL); + info->op->data.gradient.height = meta_draw_spec_new (info->theme, + height, NULL); + + info->op->data.gradient.gradient_spec = meta_gradient_spec_new (type_val); + + info->op->data.gradient.alpha_spec = alpha_spec; + + push_state (info, STATE_GRADIENT); + + /* op gets appended on close tag */ + } + else if (ELEMENT_IS ("image")) + { + MetaDrawOp *op; + const char *filename; + const char *x; + const char *y; + const char *width; + const char *height; + const char *alpha; + const char *colorize; + const char *fill_type; + MetaAlphaGradientSpec *alpha_spec; + GdkPixbuf *pixbuf; + MetaColorSpec *colorize_spec = NULL; + MetaImageFillType fill_type_val; + int h, w, c; + int pixbuf_width, pixbuf_height, pixbuf_n_channels, pixbuf_rowstride; + guchar *pixbuf_pixels; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + "alpha", &alpha, "!filename", &filename, + "colorize", &colorize, + "fill_type", &fill_type, + NULL)) + return; + +#if 0 + if (!check_expression (x, TRUE, info->theme, context, error)) + return; + + if (!check_expression (y, TRUE, info->theme, context, error)) + return; + + if (!check_expression (width, TRUE, info->theme, context, error)) + return; + + if (!check_expression (height, TRUE, info->theme, context, error)) + return; +#endif + fill_type_val = META_IMAGE_FILL_SCALE; + if (fill_type) + { + fill_type_val = meta_image_fill_type_from_string (fill_type); + + if (((int) fill_type_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand fill type \"%s\" for <%s> element"), + fill_type, element_name); + } + } + + /* Check last so we don't have to free it when other + * stuff fails. + * + * If it's a theme image, ask for it at 64px, which is + * the largest possible. We scale it anyway. + */ + pixbuf = meta_theme_load_image (info->theme, filename, 64, error); + + if (pixbuf == NULL) + { + add_context_to_error (error, context); + return; + } + + if (colorize) + { + colorize_spec = parse_color (info->theme, colorize, error); + + if (colorize_spec == NULL) + { + add_context_to_error (error, context); + g_object_unref (G_OBJECT (pixbuf)); + return; + } + } + + alpha_spec = NULL; + if (alpha && !parse_alpha (alpha, &alpha_spec, context, error)) + { + g_object_unref (G_OBJECT (pixbuf)); + return; + } + + op = meta_draw_op_new (META_DRAW_IMAGE); + + op->data.image.pixbuf = pixbuf; + op->data.image.colorize_spec = colorize_spec; + + op->data.image.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.image.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.image.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.image.height = meta_draw_spec_new (info->theme, height, NULL); + + op->data.image.alpha_spec = alpha_spec; + op->data.image.fill_type = fill_type_val; + + /* Check for vertical & horizontal stripes */ + pixbuf_n_channels = gdk_pixbuf_get_n_channels(pixbuf); + pixbuf_width = gdk_pixbuf_get_width(pixbuf); + pixbuf_height = gdk_pixbuf_get_height(pixbuf); + pixbuf_rowstride = gdk_pixbuf_get_rowstride(pixbuf); + pixbuf_pixels = gdk_pixbuf_get_pixels(pixbuf); + + /* Check for horizontal stripes */ + for (h = 0; h < pixbuf_height; h++) + { + for (w = 1; w < pixbuf_width; w++) + { + for (c = 0; c < pixbuf_n_channels; c++) + { + if (pixbuf_pixels[(h * pixbuf_rowstride) + c] != + pixbuf_pixels[(h * pixbuf_rowstride) + w + c]) + break; + } + if (c < pixbuf_n_channels) + break; + } + if (w < pixbuf_width) + break; + } + + if (h >= pixbuf_height) + { + op->data.image.horizontal_stripes = TRUE; + } + else + { + op->data.image.horizontal_stripes = FALSE; + } + + /* Check for vertical stripes */ + for (w = 0; w < pixbuf_width; w++) + { + for (h = 1; h < pixbuf_height; h++) + { + for (c = 0; c < pixbuf_n_channels; c++) + { + if (pixbuf_pixels[w + c] != + pixbuf_pixels[(h * pixbuf_rowstride) + w + c]) + break; + } + if (c < pixbuf_n_channels) + break; + } + if (h < pixbuf_height) + break; + } + + if (w >= pixbuf_width) + { + op->data.image.vertical_stripes = TRUE; + } + else + { + op->data.image.vertical_stripes = FALSE; + } + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_IMAGE); + } + else if (ELEMENT_IS ("gtk_arrow")) + { + MetaDrawOp *op; + const char *state; + const char *shadow; + const char *arrow; + const char *x; + const char *y; + const char *width; + const char *height; + const char *filled; + gboolean filled_val; + GtkStateType state_val; + GtkShadowType shadow_val; + GtkArrowType arrow_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!state", &state, + "!shadow", &shadow, + "!arrow", &arrow, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + "filled", &filled, + NULL)) + return; + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; +#endif + filled_val = TRUE; + if (filled && !parse_boolean (filled, &filled_val, context, error)) + return; + + state_val = meta_gtk_state_from_string (state); + if (((int) state_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand state \"%s\" for <%s> element"), + state, element_name); + return; + } + + shadow_val = meta_gtk_shadow_from_string (shadow); + if (((int) shadow_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand shadow \"%s\" for <%s> element"), + shadow, element_name); + return; + } + + arrow_val = meta_gtk_arrow_from_string (arrow); + if (((int) arrow_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand arrow \"%s\" for <%s> element"), + arrow, element_name); + return; + } + + op = meta_draw_op_new (META_DRAW_GTK_ARROW); + + op->data.gtk_arrow.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.gtk_arrow.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.gtk_arrow.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.gtk_arrow.height = meta_draw_spec_new (info->theme, + height, NULL); + + op->data.gtk_arrow.filled = filled_val; + op->data.gtk_arrow.state = state_val; + op->data.gtk_arrow.shadow = shadow_val; + op->data.gtk_arrow.arrow = arrow_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_GTK_ARROW); + } + else if (ELEMENT_IS ("gtk_box")) + { + MetaDrawOp *op; + const char *state; + const char *shadow; + const char *x; + const char *y; + const char *width; + const char *height; + GtkStateType state_val; + GtkShadowType shadow_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!state", &state, + "!shadow", &shadow, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + NULL)) + return; + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; +#endif + state_val = meta_gtk_state_from_string (state); + if (((int) state_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand state \"%s\" for <%s> element"), + state, element_name); + return; + } + + shadow_val = meta_gtk_shadow_from_string (shadow); + if (((int) shadow_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand shadow \"%s\" for <%s> element"), + shadow, element_name); + return; + } + + op = meta_draw_op_new (META_DRAW_GTK_BOX); + + op->data.gtk_box.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.gtk_box.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.gtk_box.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.gtk_box.height = meta_draw_spec_new (info->theme, height, NULL); + + op->data.gtk_box.state = state_val; + op->data.gtk_box.shadow = shadow_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_GTK_BOX); + } + else if (ELEMENT_IS ("gtk_vline")) + { + MetaDrawOp *op; + const char *state; + const char *x; + const char *y1; + const char *y2; + GtkStateType state_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!state", &state, + "!x", &x, "!y1", &y1, "!y2", &y2, + NULL)) + return; + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y1, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y2, FALSE, info->theme, context, error)) + return; +#endif + + state_val = meta_gtk_state_from_string (state); + if (((int) state_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand state \"%s\" for <%s> element"), + state, element_name); + return; + } + + op = meta_draw_op_new (META_DRAW_GTK_VLINE); + + op->data.gtk_vline.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.gtk_vline.y1 = meta_draw_spec_new (info->theme, y1, NULL); + op->data.gtk_vline.y2 = meta_draw_spec_new (info->theme, y2, NULL); + + op->data.gtk_vline.state = state_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_GTK_VLINE); + } + else if (ELEMENT_IS ("icon")) + { + MetaDrawOp *op; + const char *x; + const char *y; + const char *width; + const char *height; + const char *alpha; + const char *fill_type; + MetaAlphaGradientSpec *alpha_spec; + MetaImageFillType fill_type_val; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!x", &x, "!y", &y, + "!width", &width, "!height", &height, + "alpha", &alpha, + "fill_type", &fill_type, + NULL)) + return; + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; + + if (!check_expression (width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (height, FALSE, info->theme, context, error)) + return; +#endif + fill_type_val = META_IMAGE_FILL_SCALE; + if (fill_type) + { + fill_type_val = meta_image_fill_type_from_string (fill_type); + + if (((int) fill_type_val) == -1) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Did not understand fill type \"%s\" for <%s> element"), + fill_type, element_name); + } + } + + alpha_spec = NULL; + if (alpha && !parse_alpha (alpha, &alpha_spec, context, error)) + return; + + op = meta_draw_op_new (META_DRAW_ICON); + + op->data.icon.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.icon.y = meta_draw_spec_new (info->theme, y, NULL); + op->data.icon.width = meta_draw_spec_new (info->theme, width, NULL); + op->data.icon.height = meta_draw_spec_new (info->theme, height, NULL); + + op->data.icon.alpha_spec = alpha_spec; + op->data.icon.fill_type = fill_type_val; + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_ICON); + } + else if (ELEMENT_IS ("title")) + { + MetaDrawOp *op; + const char *color; + const char *x; + const char *y; + MetaColorSpec *color_spec; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!color", &color, + "!x", &x, "!y", &y, + NULL)) + return; + +#if 0 + if (!check_expression (x, FALSE, info->theme, context, error)) + return; + + if (!check_expression (y, FALSE, info->theme, context, error)) + return; +#endif + + /* Check last so we don't have to free it when other + * stuff fails + */ + color_spec = parse_color (info->theme, color, error); + if (color_spec == NULL) + { + add_context_to_error (error, context); + return; + } + + op = meta_draw_op_new (META_DRAW_TITLE); + + op->data.title.color_spec = color_spec; + + op->data.title.x = meta_draw_spec_new (info->theme, x, NULL); + op->data.title.y = meta_draw_spec_new (info->theme, y, NULL); + + g_assert (info->op_list); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_TITLE); + } + else if (ELEMENT_IS ("include")) + { + MetaDrawOp *op; + const char *name; + const char *x; + const char *y; + const char *width; + const char *height; + MetaDrawOpList *op_list; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "x", &x, "y", &y, + "width", &width, "height", &height, + "!name", &name, + NULL)) + return; + + /* x/y/width/height default to 0,0,width,height - should + * probably do this for all the draw ops + */ +#if 0 + if (x && !check_expression (x, FALSE, info->theme, context, error)) + return; + + if (y && !check_expression (y, FALSE, info->theme, context, error)) + return; + + if (width && !check_expression (width, FALSE, info->theme, context, error)) + return; + + if (height && !check_expression (height, FALSE, info->theme, context, error)) + return; +#endif + + op_list = meta_theme_lookup_draw_op_list (info->theme, + name); + if (op_list == NULL) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("No called \"%s\" has been defined"), + name); + return; + } + + g_assert (info->op_list); + + if (op_list == info->op_list || + meta_draw_op_list_contains (op_list, info->op_list)) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Including draw_ops \"%s\" here would create a circular reference"), + name); + return; + } + + op = meta_draw_op_new (META_DRAW_OP_LIST); + + meta_draw_op_list_ref (op_list); + op->data.op_list.op_list = op_list; + + op->data.op_list.x = meta_draw_spec_new (info->theme, x ? x : "0", NULL); + op->data.op_list.y = meta_draw_spec_new (info->theme, y ? y : "0", NULL); + op->data.op_list.width = meta_draw_spec_new (info->theme, + width ? width : "width", + NULL); + op->data.op_list.height = meta_draw_spec_new (info->theme, + height ? height : "height", + NULL); + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_INCLUDE); + } + else if (ELEMENT_IS ("tile")) + { + MetaDrawOp *op; + const char *name; + const char *x; + const char *y; + const char *width; + const char *height; + const char *tile_xoffset; + const char *tile_yoffset; + const char *tile_width; + const char *tile_height; + MetaDrawOpList *op_list; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "x", &x, "y", &y, + "width", &width, "height", &height, + "!name", &name, + "tile_xoffset", &tile_xoffset, + "tile_yoffset", &tile_yoffset, + "!tile_width", &tile_width, + "!tile_height", &tile_height, + NULL)) + return; + + /* These default to 0 */ +#if 0 + if (tile_xoffset && !check_expression (tile_xoffset, FALSE, info->theme, context, error)) + return; + + if (tile_yoffset && !check_expression (tile_yoffset, FALSE, info->theme, context, error)) + return; + + /* x/y/width/height default to 0,0,width,height - should + * probably do this for all the draw ops + */ + if (x && !check_expression (x, FALSE, info->theme, context, error)) + return; + + if (y && !check_expression (y, FALSE, info->theme, context, error)) + return; + + if (width && !check_expression (width, FALSE, info->theme, context, error)) + return; + + if (height && !check_expression (height, FALSE, info->theme, context, error)) + return; + + if (!check_expression (tile_width, FALSE, info->theme, context, error)) + return; + + if (!check_expression (tile_height, FALSE, info->theme, context, error)) + return; +#endif + op_list = meta_theme_lookup_draw_op_list (info->theme, + name); + if (op_list == NULL) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("No called \"%s\" has been defined"), + name); + return; + } + + g_assert (info->op_list); + + if (op_list == info->op_list || + meta_draw_op_list_contains (op_list, info->op_list)) + { + set_error (error, context, G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + _("Including draw_ops \"%s\" here would create a circular reference"), + name); + return; + } + + op = meta_draw_op_new (META_DRAW_TILE); + + meta_draw_op_list_ref (op_list); + + op->data.tile.x = meta_draw_spec_new (info->theme, x ? x : "0", NULL); + op->data.tile.y = meta_draw_spec_new (info->theme, y ? y : "0", NULL); + op->data.tile.width = meta_draw_spec_new (info->theme, + width ? width : "width", + NULL); + op->data.tile.height = meta_draw_spec_new (info->theme, + height ? height : "height", + NULL); + op->data.tile.tile_xoffset = meta_draw_spec_new (info->theme, + tile_xoffset ? tile_xoffset : "0", + NULL); + op->data.tile.tile_yoffset = meta_draw_spec_new (info->theme, + tile_yoffset ? tile_yoffset : "0", + NULL); + op->data.tile.tile_width = meta_draw_spec_new (info->theme, tile_width, NULL); + op->data.tile.tile_height = meta_draw_spec_new (info->theme, tile_height, NULL); + + op->data.tile.op_list = op_list; + + meta_draw_op_list_append (info->op_list, op); + + push_state (info, STATE_TILE); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "draw_ops"); + } +} + +static void +parse_gradient_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_GRADIENT); + + if (ELEMENT_IS ("color")) + { + const char *value = NULL; + MetaColorSpec *color_spec; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!value", &value, + NULL)) + return; + + color_spec = parse_color (info->theme, value, error); + if (color_spec == NULL) + { + add_context_to_error (error, context); + return; + } + + g_assert (info->op); + g_assert (info->op->type == META_DRAW_GRADIENT); + g_assert (info->op->data.gradient.gradient_spec != NULL); + info->op->data.gradient.gradient_spec->color_specs = + g_slist_append (info->op->data.gradient.gradient_spec->color_specs, + color_spec); + + push_state (info, STATE_COLOR); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "gradient"); + } +} + +static void +parse_style_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_FRAME_STYLE); + + g_assert (info->style); + + if (ELEMENT_IS ("piece")) + { + const char *position = NULL; + const char *draw_ops = NULL; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!position", &position, + "draw_ops", &draw_ops, + NULL)) + return; + + info->piece = meta_frame_piece_from_string (position); + if (info->piece == META_FRAME_PIECE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Unknown position \"%s\" for frame piece"), + position); + return; + } + + if (info->style->pieces[info->piece] != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Frame style already has a piece at position %s"), + position); + return; + } + + g_assert (info->op_list == NULL); + + if (draw_ops) + { + MetaDrawOpList *op_list; + + op_list = meta_theme_lookup_draw_op_list (info->theme, + draw_ops); + + if (op_list == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No with the name \"%s\" has been defined"), + draw_ops); + return; + } + + meta_draw_op_list_ref (op_list); + info->op_list = op_list; + } + + push_state (info, STATE_PIECE); + } + else if (ELEMENT_IS ("button")) + { + const char *function = NULL; + const char *state = NULL; + const char *draw_ops = NULL; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!function", &function, + "!state", &state, + "draw_ops", &draw_ops, + NULL)) + return; + + info->button_type = meta_button_type_from_string (function, info->theme); + if (info->button_type == META_BUTTON_TYPE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Unknown function \"%s\" for button"), + function); + return; + } + + if (meta_theme_earliest_version_with_button (info->button_type) > + info->theme->format_version) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Button function \"%s\" does not exist in this version (%d, need %d)"), + function, + info->theme->format_version, + meta_theme_earliest_version_with_button (info->button_type) + ); + return; + } + + info->button_state = meta_button_state_from_string (state); + if (info->button_state == META_BUTTON_STATE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Unknown state \"%s\" for button"), + state); + return; + } + + if (info->style->buttons[info->button_type][info->button_state] != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Frame style already has a button for function %s state %s"), + function, state); + return; + } + + g_assert (info->op_list == NULL); + + if (draw_ops) + { + MetaDrawOpList *op_list; + + op_list = meta_theme_lookup_draw_op_list (info->theme, + draw_ops); + + if (op_list == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("No with the name \"%s\" has been defined"), + draw_ops); + return; + } + + meta_draw_op_list_ref (op_list); + info->op_list = op_list; + } + + push_state (info, STATE_BUTTON); + } +#ifdef USE_UBUNTU_CODE + else if (ELEMENT_IS ("shadow")) + { + push_state (info, STATE_SHADOW); + } + else if (ELEMENT_IS ("padding")) + { + push_state (info, STATE_PADDING); + } +#endif + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "frame_style"); + } +} + +static void +parse_style_set_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_FRAME_STYLE_SET); + + if (ELEMENT_IS ("frame")) + { + const char *focus = NULL; + const char *state = NULL; + const char *resize = NULL; + const char *style = NULL; + MetaFrameFocus frame_focus; + MetaFrameState frame_state; + MetaFrameResize frame_resize; + MetaFrameStyle *frame_style; + + if (!locate_attributes (context, element_name, attribute_names, attribute_values, + error, + "!focus", &focus, + "!state", &state, + "resize", &resize, + "!style", &style, + NULL)) + return; + + frame_focus = meta_frame_focus_from_string (focus); + if (frame_focus == META_FRAME_FOCUS_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("\"%s\" is not a valid value for focus attribute"), + focus); + return; + } + + frame_state = meta_frame_state_from_string (state); + if (frame_state == META_FRAME_STATE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("\"%s\" is not a valid value for state attribute"), + focus); + return; + } + + frame_style = meta_theme_lookup_style (info->theme, style); + + if (frame_style == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("A style called \"%s\" has not been defined"), + style); + return; + } + + switch (frame_state) + { + case META_FRAME_STATE_NORMAL: + if (resize == NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + ATTRIBUTE_NOT_FOUND, + "resize", element_name); + return; + } + + + frame_resize = meta_frame_resize_from_string (resize); + if (frame_resize == META_FRAME_RESIZE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("\"%s\" is not a valid value for resize attribute"), + focus); + return; + } + + break; + + case META_FRAME_STATE_SHADED: + if (META_THEME_ALLOWS (info->theme, META_THEME_UNRESIZABLE_SHADED_STYLES)) + { + if (resize == NULL) + /* In state="normal" we would complain here. But instead we accept + * not having a resize attribute and default to resize="both", since + * that most closely mimics what we did in v1, and thus people can + * upgrade a theme to v2 without as much hassle. + */ + frame_resize = META_FRAME_RESIZE_BOTH; + else + { + frame_resize = meta_frame_resize_from_string (resize); + if (frame_resize == META_FRAME_RESIZE_LAST) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("\"%s\" is not a valid value for resize attribute"), + focus); + return; + } + } + } + else /* v1 theme */ + { + if (resize != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Should not have \"resize\" attribute on <%s> element for maximized/shaded states"), + element_name); + return; + } + + /* resize="both" is equivalent to the old behaviour */ + frame_resize = META_FRAME_RESIZE_BOTH; + } + break; + + default: + if (resize != NULL) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Should not have \"resize\" attribute on <%s> element for maximized states"), + element_name); + return; + } + + frame_resize = META_FRAME_RESIZE_LAST; + } + + switch (frame_state) + { + case META_FRAME_STATE_NORMAL: + if (info->style_set->normal_styles[frame_resize][frame_focus]) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Style has already been specified for state %s resize %s focus %s"), + state, resize, focus); + return; + } + meta_frame_style_ref (frame_style); + info->style_set->normal_styles[frame_resize][frame_focus] = frame_style; + break; + case META_FRAME_STATE_MAXIMIZED: + if (info->style_set->maximized_styles[frame_focus]) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Style has already been specified for state %s focus %s"), + state, focus); + return; + } + meta_frame_style_ref (frame_style); + info->style_set->maximized_styles[frame_focus] = frame_style; + break; + case META_FRAME_STATE_SHADED: + if (info->style_set->shaded_styles[frame_resize][frame_focus]) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Style has already been specified for state %s resize %s focus %s"), + state, resize, focus); + return; + } + meta_frame_style_ref (frame_style); + info->style_set->shaded_styles[frame_resize][frame_focus] = frame_style; + break; + case META_FRAME_STATE_MAXIMIZED_AND_SHADED: + if (info->style_set->maximized_and_shaded_styles[frame_focus]) + { + set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Style has already been specified for state %s focus %s"), + state, focus); + return; + } + meta_frame_style_ref (frame_style); + info->style_set->maximized_and_shaded_styles[frame_focus] = frame_style; + break; + case META_FRAME_STATE_LAST: + g_assert_not_reached (); + break; + } + + push_state (info, STATE_FRAME); + } + else + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Element <%s> is not allowed below <%s>"), + element_name, "frame_style_set"); + } +} + +static void +parse_piece_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + ParseInfo *info, + GError **error) +{ + g_return_if_fail (peek_state (info) == STATE_PIECE); + + if (ELEMENT_IS ("draw_ops")) + { + if (info->op_list) + { + set_error (error, context, + G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, + _("Can't have a two draw_ops for a element (theme specified a draw_ops attribute and also a 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