1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 
3 /* Marco Theme Rendering */
4 
5 /*
6  * Copyright (C) 2001 Havoc Pennington
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License as
10  * published by the Free Software Foundation; either version 2 of the
11  * License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301, USA.
22  */
23 
24 /**
25  * \file theme.c    Making Marco look pretty
26  *
27  * The window decorations drawn by Marco are described by files on disk
28  * known internally as "themes" (externally as "window border themes" on
29  * http://art.gnome.org/themes/marco/ or "Marco themes"). This file
30  * contains most of the code necessary to support themes; it does not
31  * contain the XML parser, which is in theme-parser.c.
32  *
33  * \bug This is a big file with lots of different subsystems, which might
34  * be better split out into separate files.
35  */
36 
37 /**
38  * \defgroup tokenizer   The theme expression tokenizer
39  *
40  * Themes can use a simple expression language to represent the values of
41  * things. This is the tokeniser used for that language.
42  *
43  * \bug We could remove almost all this code by using GScanner instead,
44  * but we would also have to find every expression in every existing theme
45  * we could and make sure the parse trees were the same.
46  */
47 
48 /**
49  * \defgroup parser  The theme expression parser
50  *
51  * Themes can use a simple expression language to represent the values of
52  * things. This is the parser used for that language.
53  */
54 
55 #include <config.h>
56 #include <glib/gi18n-lib.h>
57 
58 #include "prefs.h"
59 #include "theme.h"
60 #include "theme-parser.h"
61 #include "util.h"
62 #include "gradient.h"
63 #include <gtk/gtk.h>
64 #include <string.h>
65 #include <stdlib.h>
66 #define __USE_XOPEN
67 #include <math.h>
68 
69 #define GDK_COLOR_RGBA(color)                                           \
70                          ((guint32) (0xff                         |     \
71                                      ((int)((color).red * 255) << 24)   |    \
72                                      ((int)((color).green * 255) << 16) |    \
73                                      ((int)((color).blue * 255) << 8)))
74 
75 #define GDK_COLOR_RGB(color)                                            \
76                          ((guint32) (((int)((color).red * 255) << 16)   |    \
77                                      ((int)((color).green * 255) << 8)  |    \
78                                      ((int)((color).blue * 255))))
79 
80 #define DEBUG_FILL_STRUCT(s) memset ((s), 0xef, sizeof (*(s)))
81 #define CLAMP_UCHAR(v) ((guchar) (CLAMP (((int)v), (int)0, (int)255)))
82 #define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11)
83 
84 static void gtk_style_shade		(GdkRGBA	 *a,
85 					 GdkRGBA	 *b,
86 					 gdouble	  k);
87 static void rgb_to_hls			(gdouble	 *r,
88 					 gdouble	 *g,
89 					 gdouble	 *b);
90 static void hls_to_rgb			(gdouble	 *h,
91 					 gdouble	 *l,
92 					 gdouble	 *s);
93 
94 /**
95  * The current theme. (Themes are singleton.)
96  */
97 static MetaTheme *meta_current_theme = NULL;
98 
99 static cairo_surface_t *
scale_surface(cairo_surface_t * surface,gdouble old_width,gdouble old_height,gdouble new_width,gdouble new_height,gboolean vertical_stripes,gboolean horizontal_stripes)100 scale_surface (cairo_surface_t *surface,
101                gdouble          old_width,
102                gdouble          old_height,
103                gdouble          new_width,
104                gdouble          new_height,
105                gboolean         vertical_stripes,
106                gboolean         horizontal_stripes)
107 {
108   gdouble scale_x;
109   gdouble scale_y;
110   cairo_content_t content;
111   gint width;
112   gint height;
113   cairo_surface_t *scaled;
114   cairo_t *cr;
115 
116   scale_x = new_width / old_width;
117   scale_y = new_height / old_height;
118 
119   if (horizontal_stripes && !vertical_stripes)
120     {
121       new_width = old_width;
122       scale_x = 1.0;
123     }
124   else if (vertical_stripes && !horizontal_stripes)
125     {
126       new_height = old_height;
127       scale_y = 1.0;
128     }
129 
130   content = CAIRO_CONTENT_COLOR_ALPHA;
131   width = ceil (new_width);
132   height = ceil (new_height);
133 
134   scaled = cairo_surface_create_similar (surface, content, width, height);
135   cr = cairo_create (scaled);
136 
137   cairo_scale (cr, scale_x, scale_y);
138   cairo_set_source_surface (cr, surface, 0, 0);
139 
140   cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_PAD);
141 
142   cairo_paint (cr);
143   cairo_destroy (cr);
144 
145   return scaled;
146 }
147 
148 static cairo_surface_t *
get_surface_from_pixbuf(GdkPixbuf * pixbuf,MetaImageFillType fill_type,gdouble width,gdouble height,gboolean vertical_stripes,gboolean horizontal_stripes)149 get_surface_from_pixbuf (GdkPixbuf         *pixbuf,
150                          MetaImageFillType  fill_type,
151                          gdouble            width,
152                          gdouble            height,
153                          gboolean           vertical_stripes,
154                          gboolean           horizontal_stripes)
155 {
156   gdouble pixbuf_width;
157   gdouble pixbuf_height;
158   cairo_surface_t *surface;
159   cairo_content_t content;
160   cairo_surface_t *copy;
161   cairo_t *cr;
162 
163   pixbuf_width = gdk_pixbuf_get_width (pixbuf);
164   pixbuf_height = gdk_pixbuf_get_height (pixbuf);
165   surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, 1, NULL);
166 
167   if (pixbuf_width == width && pixbuf_height == height)
168     {
169       return surface;
170     }
171 
172   if (fill_type != META_IMAGE_FILL_TILE)
173     {
174       cairo_surface_t *scaled;
175 
176       scaled = scale_surface (surface, pixbuf_width, pixbuf_height,
177                               width, height, vertical_stripes,
178                               horizontal_stripes);
179 
180       cairo_surface_destroy (surface);
181       surface = scaled;
182     }
183 
184   content = CAIRO_CONTENT_COLOR_ALPHA;
185   width = ceil (width);
186   height = ceil (height);
187 
188   copy = cairo_surface_create_similar (surface, content, width, height);
189   cr = cairo_create (copy);
190 
191   cairo_set_source_surface (cr, surface, 0, 0);
192 
193   if (fill_type == META_IMAGE_FILL_TILE ||
194       vertical_stripes || horizontal_stripes)
195     {
196       cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
197     }
198 
199   cairo_paint (cr);
200   cairo_destroy (cr);
201 
202   cairo_surface_destroy (surface);
203 
204   return copy;
205 }
206 
207 static GdkPixbuf *
colorize_pixbuf(GdkPixbuf * orig,GdkRGBA * new_color)208 colorize_pixbuf (GdkPixbuf *orig,
209                  GdkRGBA   *new_color)
210 {
211   GdkPixbuf *pixbuf;
212   double intensity;
213   int x, y;
214   const guchar *src;
215   guchar *dest;
216   int orig_rowstride;
217   int dest_rowstride;
218   int width, height;
219   gboolean has_alpha;
220   const guchar *src_pixels;
221   guchar *dest_pixels;
222 
223   pixbuf = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (orig), gdk_pixbuf_get_has_alpha (orig),
224 			   gdk_pixbuf_get_bits_per_sample (orig),
225 			   gdk_pixbuf_get_width (orig), gdk_pixbuf_get_height (orig));
226 
227   if (pixbuf == NULL)
228     return NULL;
229 
230   orig_rowstride = gdk_pixbuf_get_rowstride (orig);
231   dest_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
232   width = gdk_pixbuf_get_width (pixbuf);
233   height = gdk_pixbuf_get_height (pixbuf);
234   has_alpha = gdk_pixbuf_get_has_alpha (orig);
235   src_pixels = gdk_pixbuf_get_pixels (orig);
236   dest_pixels = gdk_pixbuf_get_pixels (pixbuf);
237 
238   for (y = 0; y < height; y++)
239     {
240       src = src_pixels + y * orig_rowstride;
241       dest = dest_pixels + y * dest_rowstride;
242 
243       for (x = 0; x < width; x++)
244         {
245           double dr, dg, db;
246 
247           intensity = INTENSITY (src[0], src[1], src[2]) / 255.0;
248 
249           if (intensity <= 0.5)
250             {
251               /* Go from black at intensity = 0.0 to new_color at intensity = 0.5 */
252               dr = new_color->red * intensity * 2.0;
253               dg = new_color->green * intensity * 2.0;
254               db = new_color->blue * intensity * 2.0;
255             }
256           else
257             {
258               /* Go from new_color at intensity = 0.5 to white at intensity = 1.0 */
259               dr = new_color->red + (1.0 - new_color->red) * (intensity - 0.5) * 2.0;
260               dg = new_color->green + (1.0 - new_color->green) * (intensity - 0.5) * 2.0;
261               db = new_color->blue + (1.0 - new_color->blue) * (intensity - 0.5) * 2.0;
262             }
263 
264           dest[0] = CLAMP_UCHAR (255 * dr);
265           dest[1] = CLAMP_UCHAR (255 * dg);
266           dest[2] = CLAMP_UCHAR (255 * db);
267 
268           if (has_alpha)
269             {
270               dest[3] = src[3];
271               src += 4;
272               dest += 4;
273             }
274           else
275             {
276               src += 3;
277               dest += 3;
278             }
279         }
280     }
281 
282   return pixbuf;
283 }
284 
285 static void
color_composite(const GdkRGBA * bg,const GdkRGBA * fg,double alpha,GdkRGBA * color)286 color_composite (const GdkRGBA *bg,
287                  const GdkRGBA *fg,
288                  double         alpha,
289                  GdkRGBA       *color)
290 {
291   *color = *bg;
292   color->red = color->red + (fg->red - color->red) * alpha;
293   color->green = color->green + (fg->green - color->green) * alpha;
294   color->blue = color->blue + (fg->blue - color->blue) * alpha;
295 }
296 
297 /**
298  * Sets all the fields of a border to dummy values.
299  *
300  * \param border The border whose fields should be reset.
301  */
302 static void
init_border(GtkBorder * border)303 init_border (GtkBorder *border)
304 {
305   border->top = -1;
306   border->bottom = -1;
307   border->left = -1;
308   border->right = -1;
309 }
310 
311 /**
312  * Creates a new, empty MetaFrameLayout. The fields will be set to dummy
313  * values.
314  *
315  * \return The newly created MetaFrameLayout.
316  */
317 MetaFrameLayout*
meta_frame_layout_new(void)318 meta_frame_layout_new  (void)
319 {
320   MetaFrameLayout *layout;
321 
322   layout = g_new0 (MetaFrameLayout, 1);
323 
324   layout->refcount = 1;
325 
326   /* Fill with -1 values to detect invalid themes */
327   layout->left_width = -1;
328   layout->right_width = -1;
329   layout->bottom_height = -1;
330 
331   layout->invisible_border.left = 10;
332   layout->invisible_border.right = 10;
333   layout->invisible_border.bottom = 10;
334   layout->invisible_border.top = 10;
335 
336   init_border (&layout->title_border);
337 
338   layout->title_vertical_pad = -1;
339 
340   layout->right_titlebar_edge = -1;
341   layout->left_titlebar_edge = -1;
342 
343   layout->button_sizing = META_BUTTON_SIZING_LAST;
344   layout->button_aspect = 1.0;
345   layout->button_width = -1;
346   layout->button_height = -1;
347 
348   layout->has_title = TRUE;
349   layout->title_scale = 1.0;
350 
351   init_border (&layout->button_border);
352 
353   return layout;
354 }
355 
356 /**
357  *
358  */
359 static gboolean
validate_border(const GtkBorder * border,const char ** bad)360 validate_border (const GtkBorder *border,
361                  const char     **bad)
362 {
363   *bad = NULL;
364 
365   if (border->top < 0)
366     *bad = _("top");
367   else if (border->bottom < 0)
368     *bad = _("bottom");
369   else if (border->left < 0)
370     *bad = _("left");
371   else if (border->right < 0)
372     *bad = _("right");
373 
374   return *bad == NULL;
375 }
376 
377 /**
378  * Ensures that the theme supplied a particular dimension. When a
379  * MetaFrameLayout is created, all its integer fields are set to -1
380  * by meta_frame_layout_new(). After an instance of this type
381  * should have been initialised, this function checks that
382  * a given field is not still at -1. It is never called directly, but
383  * rather via the CHECK_GEOMETRY_VALUE and CHECK_GEOMETRY_BORDER
384  * macros.
385  *
386  * \param      val    The value to check
387  * \param      name   The name to use in the error message
388  * \param[out] error  Set to an error if val was not initialised
389  */
390 static gboolean
validate_geometry_value(int val,const char * name,GError ** error)391 validate_geometry_value (int         val,
392                          const char *name,
393                          GError    **error)
394 {
395   if (val < 0)
396     {
397       g_set_error (error, META_THEME_ERROR,
398                    META_THEME_ERROR_FRAME_GEOMETRY,
399                    _("frame geometry does not specify \"%s\" dimension"),
400                    name);
401       return FALSE;
402     }
403   else
404     return TRUE;
405 }
406 
407 static gboolean
validate_geometry_border(const GtkBorder * border,const char * name,GError ** error)408 validate_geometry_border (const GtkBorder *border,
409                           const char      *name,
410                           GError         **error)
411 {
412   const char *bad;
413 
414   if (!validate_border (border, &bad))
415     {
416       g_set_error (error, META_THEME_ERROR,
417                    META_THEME_ERROR_FRAME_GEOMETRY,
418                    _("frame geometry does not specify dimension \"%s\" for border \"%s\""),
419                    bad, name);
420       return FALSE;
421     }
422   else
423     return TRUE;
424 }
425 
426 gboolean
meta_frame_layout_validate(const MetaFrameLayout * layout,GError ** error)427 meta_frame_layout_validate (const MetaFrameLayout *layout,
428                             GError               **error)
429 {
430   g_return_val_if_fail (layout != NULL, FALSE);
431 
432 #define CHECK_GEOMETRY_VALUE(vname) if (!validate_geometry_value (layout->vname, #vname, error)) return FALSE
433 
434 #define CHECK_GEOMETRY_BORDER(bname) if (!validate_geometry_border (&layout->bname, #bname, error)) return FALSE
435 
436   CHECK_GEOMETRY_VALUE (left_width);
437   CHECK_GEOMETRY_VALUE (right_width);
438   CHECK_GEOMETRY_VALUE (bottom_height);
439 
440   CHECK_GEOMETRY_BORDER (title_border);
441 
442   CHECK_GEOMETRY_VALUE (title_vertical_pad);
443 
444   CHECK_GEOMETRY_VALUE (right_titlebar_edge);
445   CHECK_GEOMETRY_VALUE (left_titlebar_edge);
446 
447   switch (layout->button_sizing)
448     {
449     case META_BUTTON_SIZING_ASPECT:
450       if (layout->button_aspect < (0.1) ||
451           layout->button_aspect > (15.0))
452         {
453           g_set_error (error, META_THEME_ERROR,
454                        META_THEME_ERROR_FRAME_GEOMETRY,
455                        _("Button aspect ratio %g is not reasonable"),
456                        layout->button_aspect);
457           return FALSE;
458         }
459       break;
460     case META_BUTTON_SIZING_FIXED:
461       CHECK_GEOMETRY_VALUE (button_width);
462       CHECK_GEOMETRY_VALUE (button_height);
463       break;
464     case META_BUTTON_SIZING_LAST:
465       g_set_error (error, META_THEME_ERROR,
466                    META_THEME_ERROR_FRAME_GEOMETRY,
467                    _("Frame geometry does not specify size of buttons"));
468       return FALSE;
469     }
470 
471   CHECK_GEOMETRY_BORDER (button_border);
472 
473   return TRUE;
474 }
475 
476 MetaFrameLayout*
meta_frame_layout_copy(const MetaFrameLayout * src)477 meta_frame_layout_copy (const MetaFrameLayout *src)
478 {
479   MetaFrameLayout *layout;
480 
481   layout = g_new0 (MetaFrameLayout, 1);
482 
483   *layout = *src;
484 
485   layout->refcount = 1;
486 
487   return layout;
488 }
489 
490 void
meta_frame_layout_ref(MetaFrameLayout * layout)491 meta_frame_layout_ref (MetaFrameLayout *layout)
492 {
493   g_return_if_fail (layout != NULL);
494 
495   layout->refcount += 1;
496 }
497 
498 void
meta_frame_layout_unref(MetaFrameLayout * layout)499 meta_frame_layout_unref (MetaFrameLayout *layout)
500 {
501   g_return_if_fail (layout != NULL);
502   g_return_if_fail (layout->refcount > 0);
503 
504   layout->refcount -= 1;
505 
506   if (layout->refcount == 0)
507     {
508       DEBUG_FILL_STRUCT (layout);
509       g_free (layout);
510     }
511 }
512 
513 void
meta_frame_layout_get_borders(const MetaFrameLayout * layout,int text_height,MetaFrameFlags flags,MetaFrameBorders * borders)514 meta_frame_layout_get_borders (const MetaFrameLayout *layout,
515                                int                    text_height,
516                                MetaFrameFlags         flags,
517                                MetaFrameBorders      *borders)
518 {
519   int buttons_height, title_height;
520 
521   meta_frame_borders_clear (borders);
522 
523   /* For a full-screen window, we don't have any borders, visible or not. */
524   if (flags & META_FRAME_FULLSCREEN)
525     return;
526 
527   g_return_if_fail (layout != NULL);
528 
529   if (!layout->has_title)
530     text_height = 0;
531 
532   buttons_height = layout->button_height +
533     layout->button_border.top + layout->button_border.bottom;
534   title_height = text_height +
535     layout->title_vertical_pad +
536     layout->title_border.top + layout->title_border.bottom;
537 
538   borders->visible.top = MAX (buttons_height, title_height);
539   borders->visible.left = layout->left_width;
540   borders->visible.right = layout->right_width;
541   borders->visible.bottom = layout->bottom_height;
542 
543   if (flags & META_FRAME_ALLOWS_HORIZONTAL_RESIZE)
544     {
545       borders->invisible.left = layout->invisible_border.left;
546       borders->invisible.right = layout->invisible_border.right;
547     }
548 
549   if (flags & META_FRAME_ALLOWS_VERTICAL_RESIZE)
550     {
551       borders->invisible.bottom = layout->invisible_border.bottom;
552       borders->invisible.top = layout->invisible_border.top;
553     }
554 
555   if (flags & META_FRAME_SHADED)
556     borders->visible.bottom = borders->invisible.bottom = 0;
557 
558   borders->total.left = borders->invisible.left + borders->visible.left;
559   borders->total.right = borders->invisible.right + borders->visible.right;
560   borders->total.bottom = borders->invisible.bottom + borders->visible.bottom;
561   borders->total.top = borders->invisible.top + borders->visible.top;
562 }
563 
564 static MetaButtonType
map_button_function_to_type(MetaButtonFunction function)565 map_button_function_to_type (MetaButtonFunction  function)
566 {
567   switch (function)
568     {
569     case META_BUTTON_FUNCTION_SHADE:
570       return META_BUTTON_TYPE_SHADE;
571     case META_BUTTON_FUNCTION_ABOVE:
572       return META_BUTTON_TYPE_ABOVE;
573     case META_BUTTON_FUNCTION_STICK:
574       return META_BUTTON_TYPE_STICK;
575     case META_BUTTON_FUNCTION_UNSHADE:
576       return META_BUTTON_TYPE_UNSHADE;
577     case META_BUTTON_FUNCTION_UNABOVE:
578       return META_BUTTON_TYPE_UNABOVE;
579     case META_BUTTON_FUNCTION_UNSTICK:
580       return META_BUTTON_TYPE_UNSTICK;
581     case META_BUTTON_FUNCTION_MENU:
582       return META_BUTTON_TYPE_MENU;
583     case META_BUTTON_FUNCTION_APPMENU:
584       return META_BUTTON_TYPE_APPMENU;
585     case META_BUTTON_FUNCTION_MINIMIZE:
586       return META_BUTTON_TYPE_MINIMIZE;
587     case META_BUTTON_FUNCTION_MAXIMIZE:
588       return META_BUTTON_TYPE_MAXIMIZE;
589     case META_BUTTON_FUNCTION_CLOSE:
590       return META_BUTTON_TYPE_CLOSE;
591     case META_BUTTON_FUNCTION_LAST:
592       return META_BUTTON_TYPE_LAST;
593     }
594 
595   return META_BUTTON_TYPE_LAST;
596 }
597 
598 static MetaButtonSpace*
rect_for_function(MetaFrameGeometry * fgeom,MetaFrameFlags flags,MetaButtonFunction function,MetaTheme * theme)599 rect_for_function (MetaFrameGeometry *fgeom,
600                    MetaFrameFlags     flags,
601                    MetaButtonFunction function,
602                    MetaTheme         *theme)
603 {
604 
605   /* Firstly, check version-specific things. */
606 
607   if (META_THEME_ALLOWS(theme, META_THEME_SHADE_STICK_ABOVE_BUTTONS))
608     {
609       switch (function)
610         {
611         case META_BUTTON_FUNCTION_SHADE:
612           if ((flags & META_FRAME_ALLOWS_SHADE) && !(flags & META_FRAME_SHADED))
613             return &fgeom->shade_rect;
614           else
615             return NULL;
616         case META_BUTTON_FUNCTION_ABOVE:
617           if (!(flags & META_FRAME_ABOVE))
618             return &fgeom->above_rect;
619           else
620             return NULL;
621         case META_BUTTON_FUNCTION_STICK:
622           if (!(flags & META_FRAME_STUCK))
623             return &fgeom->stick_rect;
624           else
625             return NULL;
626         case META_BUTTON_FUNCTION_UNSHADE:
627           if ((flags & META_FRAME_ALLOWS_SHADE) && (flags & META_FRAME_SHADED))
628             return &fgeom->unshade_rect;
629           else
630             return NULL;
631         case META_BUTTON_FUNCTION_UNABOVE:
632           if (flags & META_FRAME_ABOVE)
633             return &fgeom->unabove_rect;
634           else
635             return NULL;
636         case META_BUTTON_FUNCTION_UNSTICK:
637           if (flags & META_FRAME_STUCK)
638             return &fgeom->unstick_rect;
639         default:
640           /* just go on to the next switch block */;
641         }
642     }
643 
644   /* now consider the buttons which exist in all versions */
645 
646   switch (function)
647     {
648     case META_BUTTON_FUNCTION_MENU:
649       if (flags & META_FRAME_ALLOWS_MENU)
650         return &fgeom->menu_rect;
651       else
652         return NULL;
653     case META_BUTTON_FUNCTION_APPMENU:
654       if (flags & META_FRAME_ALLOWS_APPMENU)
655         return &fgeom->appmenu_rect;
656       else
657         return NULL;
658     case META_BUTTON_FUNCTION_MINIMIZE:
659       if (flags & META_FRAME_ALLOWS_MINIMIZE)
660         return &fgeom->min_rect;
661       else
662         return NULL;
663     case META_BUTTON_FUNCTION_MAXIMIZE:
664       if (flags & META_FRAME_ALLOWS_MAXIMIZE)
665         return &fgeom->max_rect;
666       else
667         return NULL;
668     case META_BUTTON_FUNCTION_CLOSE:
669       if (flags & META_FRAME_ALLOWS_DELETE)
670         return &fgeom->close_rect;
671       else
672         return NULL;
673     case META_BUTTON_FUNCTION_STICK:
674     case META_BUTTON_FUNCTION_SHADE:
675     case META_BUTTON_FUNCTION_ABOVE:
676     case META_BUTTON_FUNCTION_UNSTICK:
677     case META_BUTTON_FUNCTION_UNSHADE:
678     case META_BUTTON_FUNCTION_UNABOVE:
679       /* we are being asked for a >v1 button which hasn't been handled yet,
680        * so obviously we're not in a theme which supports that version.
681        * therefore, we don't show the button. return NULL and all will
682        * be well.
683        */
684       return NULL;
685 
686     case META_BUTTON_FUNCTION_LAST:
687       return NULL;
688     }
689 
690   return NULL;
691 }
692 
693 static gboolean
strip_button(MetaButtonSpace * func_rects[MAX_BUTTONS_PER_CORNER],GdkRectangle * bg_rects[MAX_BUTTONS_PER_CORNER],int * n_rects,MetaButtonSpace * to_strip)694 strip_button (MetaButtonSpace *func_rects[MAX_BUTTONS_PER_CORNER],
695               GdkRectangle    *bg_rects[MAX_BUTTONS_PER_CORNER],
696               int             *n_rects,
697               MetaButtonSpace *to_strip)
698 {
699   int i;
700 
701   i = 0;
702   while (i < *n_rects)
703     {
704       if (func_rects[i] == to_strip)
705         {
706           *n_rects -= 1;
707 
708           /* shift the other rects back in the array */
709           while (i < *n_rects)
710             {
711               func_rects[i] = func_rects[i+1];
712               bg_rects[i] = bg_rects[i+1];
713 
714               ++i;
715             }
716 
717           func_rects[i] = NULL;
718           bg_rects[i] = NULL;
719 
720           return TRUE;
721         }
722 
723       ++i;
724     }
725 
726   return FALSE; /* did not strip anything */
727 }
728 
729 void
meta_frame_layout_calc_geometry(const MetaFrameLayout * layout,int text_height,MetaFrameFlags flags,int client_width,int client_height,const MetaButtonLayout * button_layout,MetaFrameGeometry * fgeom,MetaTheme * theme)730 meta_frame_layout_calc_geometry (const MetaFrameLayout  *layout,
731                                  int                     text_height,
732                                  MetaFrameFlags          flags,
733                                  int                     client_width,
734                                  int                     client_height,
735                                  const MetaButtonLayout *button_layout,
736                                  MetaFrameGeometry      *fgeom,
737                                  MetaTheme              *theme)
738 {
739   int i, n_left, n_right, n_left_spacers, n_right_spacers;
740   int x;
741   int button_y;
742   int title_right_edge;
743   int width, height;
744   int button_width, button_height;
745   int min_size_for_rounding;
746 
747   /* the left/right rects in order; the max # of rects
748    * is the number of button functions
749    */
750   MetaButtonSpace *left_func_rects[MAX_BUTTONS_PER_CORNER];
751   MetaButtonSpace *right_func_rects[MAX_BUTTONS_PER_CORNER];
752   GdkRectangle *left_bg_rects[MAX_BUTTONS_PER_CORNER];
753   gboolean left_buttons_has_spacer[MAX_BUTTONS_PER_CORNER];
754   GdkRectangle *right_bg_rects[MAX_BUTTONS_PER_CORNER];
755   gboolean right_buttons_has_spacer[MAX_BUTTONS_PER_CORNER];
756 
757   MetaFrameBorders borders;
758 
759   meta_frame_layout_get_borders (layout, text_height,
760                                  flags,
761                                  &borders);
762 
763   fgeom->borders = borders;
764 
765   width = client_width + borders.total.left + borders.total.right;
766 
767   height = ((flags & META_FRAME_SHADED) ? 0: client_height) +
768     borders.total.top + borders.total.bottom;
769 
770   fgeom->width = width;
771   fgeom->height = height;
772 
773   fgeom->top_titlebar_edge = layout->title_border.top;
774   fgeom->bottom_titlebar_edge = layout->title_border.bottom;
775   fgeom->left_titlebar_edge = layout->left_titlebar_edge;
776   fgeom->right_titlebar_edge = layout->right_titlebar_edge;
777 
778   switch (layout->button_sizing)
779     {
780     case META_BUTTON_SIZING_ASPECT:
781       button_height = borders.visible.top - layout->button_border.top - layout->button_border.bottom;
782       button_width = button_height / layout->button_aspect;
783       break;
784     case META_BUTTON_SIZING_FIXED:
785       button_width = layout->button_width;
786       button_height = layout->button_height;
787       break;
788     case META_BUTTON_SIZING_LAST:
789       g_assert_not_reached ();
790     default:
791       button_width = -1;
792       button_height = -1;
793     }
794 
795   /* FIXME all this code sort of pretends that duplicate buttons
796    * with the same function are allowed, but that breaks the
797    * code in frames.c, so isn't really allowed right now.
798    * Would need left_close_rect, right_close_rect, etc.
799    */
800 
801   /* Init all button rects to 0, lame hack */
802   memset (ADDRESS_OF_BUTTON_RECTS (fgeom), '\0',
803           LENGTH_OF_BUTTON_RECTS);
804 
805   n_left = 0;
806   n_right = 0;
807   n_left_spacers = 0;
808   n_right_spacers = 0;
809 
810   if (!layout->hide_buttons)
811     {
812       /* Try to fill in rects */
813       for (i = 0; i < MAX_BUTTONS_PER_CORNER && button_layout->left_buttons[i] != META_BUTTON_FUNCTION_LAST; i++)
814         {
815           left_func_rects[n_left] = rect_for_function (fgeom, flags,
816                                                        button_layout->left_buttons[i],
817                                                        theme);
818           if (left_func_rects[n_left] != NULL)
819             {
820               left_buttons_has_spacer[n_left] = button_layout->left_buttons_has_spacer[i];
821               if (button_layout->left_buttons_has_spacer[i])
822                 ++n_left_spacers;
823 
824               ++n_left;
825             }
826         }
827 
828       for (i = 0; i < MAX_BUTTONS_PER_CORNER && button_layout->right_buttons[i] != META_BUTTON_FUNCTION_LAST; i++)
829         {
830           right_func_rects[n_right] = rect_for_function (fgeom, flags,
831                                                          button_layout->right_buttons[i],
832                                                          theme);
833           if (right_func_rects[n_right] != NULL)
834             {
835               right_buttons_has_spacer[n_right] = button_layout->right_buttons_has_spacer[i];
836               if (button_layout->right_buttons_has_spacer[i])
837                 ++n_right_spacers;
838 
839               ++n_right;
840             }
841         }
842     }
843 
844   for (i = 0; i < MAX_BUTTONS_PER_CORNER; i++)
845     {
846       left_bg_rects[i] = NULL;
847       right_bg_rects[i] = NULL;
848     }
849 
850   for (i = 0; i < n_left; i++)
851     {
852       if (n_left == 1)
853         left_bg_rects[i] = &fgeom->left_single_background;
854       else if (i == 0)
855         left_bg_rects[i] = &fgeom->left_left_background;
856       else if (i == (n_left - 1))
857         left_bg_rects[i] = &fgeom->left_right_background;
858       else
859         left_bg_rects[i] = &fgeom->left_middle_backgrounds[i - 1];
860     }
861 
862   for (i = 0; i < n_right; i++)
863     {
864       if (n_right == 1)
865         right_bg_rects[i] = &fgeom->right_single_background;
866       else if (i == (n_right - 1))
867         right_bg_rects[i] = &fgeom->right_right_background;
868       else if (i == 0)
869         right_bg_rects[i] = &fgeom->right_left_background;
870       else
871         right_bg_rects[i] = &fgeom->right_middle_backgrounds[i - 1];
872     }
873 
874   /* Be sure buttons fit */
875   while (n_left > 0 || n_right > 0)
876     {
877       int space_used_by_buttons;
878       int space_available;
879 
880       space_available = fgeom->width - layout->left_titlebar_edge - layout->right_titlebar_edge;
881 
882       space_used_by_buttons = 0;
883 
884       space_used_by_buttons += button_width * n_left;
885       space_used_by_buttons += (button_width * 0.75) * n_left_spacers;
886       space_used_by_buttons += layout->button_border.left * n_left;
887       space_used_by_buttons += layout->button_border.right * n_left;
888 
889       space_used_by_buttons += button_width * n_right;
890       space_used_by_buttons += (button_width * 0.75) * n_right_spacers;
891       space_used_by_buttons += layout->button_border.left * n_right;
892       space_used_by_buttons += layout->button_border.right * n_right;
893 
894       if (space_used_by_buttons <= space_available)
895         break; /* Everything fits, bail out */
896 
897       /* First try to remove separators */
898       if (n_left_spacers > 0)
899         {
900           left_buttons_has_spacer[--n_left_spacers] = FALSE;
901           continue;
902         }
903       else if (n_right_spacers > 0)
904         {
905           right_buttons_has_spacer[--n_right_spacers] = FALSE;
906           continue;
907         }
908 
909       /* Otherwise we need to shave out a button. Shave
910        * above, stick, shade, min, max, close, then menu (menu is most useful);
911        * prefer the default button locations.
912        */
913       if (strip_button (left_func_rects, left_bg_rects,
914                         &n_left, &fgeom->above_rect))
915         continue;
916       else if (strip_button (right_func_rects, right_bg_rects,
917                              &n_right, &fgeom->above_rect))
918         continue;
919       else if (strip_button (left_func_rects, left_bg_rects,
920                         &n_left, &fgeom->stick_rect))
921         continue;
922       else if (strip_button (right_func_rects, right_bg_rects,
923                              &n_right, &fgeom->stick_rect))
924         continue;
925       else if (strip_button (left_func_rects, left_bg_rects,
926                         &n_left, &fgeom->shade_rect))
927         continue;
928       else if (strip_button (right_func_rects, right_bg_rects,
929                              &n_right, &fgeom->shade_rect))
930         continue;
931       else if (strip_button (left_func_rects, left_bg_rects,
932                         &n_left, &fgeom->min_rect))
933         continue;
934       else if (strip_button (right_func_rects, right_bg_rects,
935                              &n_right, &fgeom->min_rect))
936         continue;
937       else if (strip_button (left_func_rects, left_bg_rects,
938                              &n_left, &fgeom->max_rect))
939         continue;
940       else if (strip_button (right_func_rects, right_bg_rects,
941                              &n_right, &fgeom->max_rect))
942         continue;
943       else if (strip_button (left_func_rects, left_bg_rects,
944                              &n_left, &fgeom->close_rect))
945         continue;
946       else if (strip_button (right_func_rects, right_bg_rects,
947                              &n_right, &fgeom->close_rect))
948         continue;
949       else if (strip_button (right_func_rects, right_bg_rects,
950                              &n_right, &fgeom->menu_rect))
951         continue;
952       else if (strip_button (left_func_rects, left_bg_rects,
953                              &n_left, &fgeom->menu_rect))
954         continue;
955       else if (strip_button (right_func_rects, right_bg_rects,
956                              &n_right, &fgeom->appmenu_rect))
957         continue;
958       else if (strip_button (left_func_rects, left_bg_rects,
959                              &n_left, &fgeom->appmenu_rect))
960         continue;
961       else
962         {
963           meta_bug ("Could not find a button to strip. n_left = %d n_right = %d\n",
964                     n_left, n_right);
965         }
966     }
967 
968   /* Save the button layout */
969   fgeom->button_layout = *button_layout;
970   fgeom->n_left_buttons = n_left;
971   fgeom->n_right_buttons = n_right;
972 
973   /* center buttons vertically */
974   button_y = (borders.visible.top -
975               (button_height + layout->button_border.top + layout->button_border.bottom)) / 2 + layout->button_border.top + borders.invisible.top;
976 
977   /* right edge of farthest-right button */
978   x = width - layout->right_titlebar_edge - borders.invisible.right;
979 
980   i = n_right - 1;
981   while (i >= 0)
982     {
983       MetaButtonSpace *rect;
984 
985       if (x < 0) /* if we go negative, leave the buttons we don't get to as 0-width */
986         break;
987 
988       rect = right_func_rects[i];
989       rect->visible.x = x - layout->button_border.right - button_width;
990       if (right_buttons_has_spacer[i])
991         rect->visible.x -= (button_width * 0.75);
992 
993       rect->visible.y = button_y;
994       rect->visible.width = button_width;
995       rect->visible.height = button_height;
996 
997       if (flags & META_FRAME_MAXIMIZED ||
998           flags & META_FRAME_TILED_LEFT ||
999           flags & META_FRAME_TILED_RIGHT)
1000         {
1001           rect->clickable.x = rect->visible.x;
1002           rect->clickable.y = rect->visible.y;
1003           rect->clickable.width = button_width;
1004           rect->clickable.height = button_height;
1005 
1006           if (i == n_right - 1)
1007             rect->clickable.width += layout->right_titlebar_edge + layout->right_width + layout->button_border.right;
1008 
1009         }
1010       else
1011         memmove (&(rect->clickable), &(rect->visible), sizeof(rect->clickable));
1012 
1013       *(right_bg_rects[i]) = rect->visible;
1014 
1015       x = rect->visible.x - layout->button_border.left;
1016 
1017       --i;
1018     }
1019 
1020   /* save right edge of titlebar for later use */
1021   title_right_edge = x - layout->title_border.right;
1022 
1023   /* Now x changes to be position from the left and we go through
1024    * the left-side buttons
1025    */
1026   x = layout->left_titlebar_edge + borders.invisible.left;
1027   for (i = 0; i < n_left; i++)
1028     {
1029       MetaButtonSpace *rect;
1030 
1031       rect = left_func_rects[i];
1032 
1033       rect->visible.x = x + layout->button_border.left;
1034       rect->visible.y = button_y;
1035       rect->visible.width = button_width;
1036       rect->visible.height = button_height;
1037 
1038       if (flags & META_FRAME_MAXIMIZED)
1039         {
1040           rect->clickable.x = rect->visible.x;
1041           rect->clickable.y = rect->visible.y;
1042           rect->clickable.width = button_width;
1043           rect->clickable.height = button_height;
1044         }
1045       else
1046         memmove (&(rect->clickable), &(rect->visible), sizeof(rect->clickable));
1047 
1048       x = rect->visible.x + rect->visible.width + layout->button_border.right;
1049       if (left_buttons_has_spacer[i])
1050         x += (button_width * 0.75);
1051 
1052       *(left_bg_rects[i]) = rect->visible;
1053     }
1054 
1055   /* We always fill as much vertical space as possible with title rect,
1056    * rather than centering it like the buttons
1057    */
1058   fgeom->title_rect.x = x + layout->title_border.left;
1059   fgeom->title_rect.y = layout->title_border.top + borders.invisible.top;
1060   fgeom->title_rect.width = title_right_edge - fgeom->title_rect.x;
1061   fgeom->title_rect.height = borders.visible.top - layout->title_border.top - layout->title_border.bottom;
1062 
1063   /* Nuke title if it won't fit */
1064   if (fgeom->title_rect.width < 0 ||
1065       fgeom->title_rect.height < 0)
1066     {
1067       fgeom->title_rect.width = 0;
1068       fgeom->title_rect.height = 0;
1069     }
1070 
1071   if (flags & META_FRAME_SHADED)
1072     min_size_for_rounding = 0;
1073   else
1074     min_size_for_rounding = 5;
1075 
1076   fgeom->top_left_corner_rounded_radius = 0;
1077   fgeom->top_right_corner_rounded_radius = 0;
1078   fgeom->bottom_left_corner_rounded_radius = 0;
1079   fgeom->bottom_right_corner_rounded_radius = 0;
1080 
1081   if (borders.visible.top + borders.visible.left >= min_size_for_rounding)
1082     fgeom->top_left_corner_rounded_radius = layout->top_left_corner_rounded_radius;
1083   if (borders.visible.top + borders.visible.right >= min_size_for_rounding)
1084     fgeom->top_right_corner_rounded_radius = layout->top_right_corner_rounded_radius;
1085 
1086   if (borders.visible.bottom + borders.visible.left >= min_size_for_rounding)
1087     fgeom->bottom_left_corner_rounded_radius = layout->bottom_left_corner_rounded_radius;
1088   if (borders.visible.bottom + borders.visible.right >= min_size_for_rounding)
1089     fgeom->bottom_right_corner_rounded_radius = layout->bottom_right_corner_rounded_radius;
1090 }
1091 
1092 MetaGradientSpec*
meta_gradient_spec_new(MetaGradientType type)1093 meta_gradient_spec_new (MetaGradientType type)
1094 {
1095   MetaGradientSpec *spec;
1096 
1097   spec = g_new (MetaGradientSpec, 1);
1098 
1099   spec->type = type;
1100   spec->color_specs = NULL;
1101 
1102   return spec;
1103 }
1104 
1105 static cairo_pattern_t *
create_cairo_pattern_from_gradient_spec(const MetaGradientSpec * spec,const MetaAlphaGradientSpec * alpha_spec,GtkStyleContext * context)1106 create_cairo_pattern_from_gradient_spec (const MetaGradientSpec      *spec,
1107                                          const MetaAlphaGradientSpec *alpha_spec,
1108                                          GtkStyleContext             *context)
1109 {
1110   gint n_colors;
1111   cairo_pattern_t *pattern;
1112   GSList *tmp;
1113   gint i;
1114 
1115   n_colors = g_slist_length (spec->color_specs);
1116   if (n_colors == 0)
1117     return NULL;
1118 
1119   if (alpha_spec != NULL && alpha_spec->n_alphas != 1)
1120     g_assert (n_colors == alpha_spec->n_alphas);
1121 
1122   if (spec->type == META_GRADIENT_HORIZONTAL)
1123     pattern = cairo_pattern_create_linear (0, 0, 1, 0);
1124   else if (spec->type == META_GRADIENT_VERTICAL)
1125     pattern = cairo_pattern_create_linear (0, 0, 0, 1);
1126   else if (spec->type == META_GRADIENT_DIAGONAL)
1127     pattern = cairo_pattern_create_linear (0, 0, 1, 1);
1128   else
1129     g_assert_not_reached ();
1130 
1131   i = 0;
1132   tmp = spec->color_specs;
1133   while (tmp != NULL)
1134     {
1135       GdkRGBA color;
1136 
1137       meta_color_spec_render (tmp->data, context, &color);
1138 
1139       if (alpha_spec != NULL)
1140         {
1141           gdouble alpha;
1142 
1143           if (alpha_spec->n_alphas == 1)
1144             alpha = alpha_spec->alphas[0] / 255.0;
1145           else
1146             alpha = alpha_spec->alphas[i] / 255.0;
1147 
1148           cairo_pattern_add_color_stop_rgba (pattern, i / (gfloat) (n_colors - 1),
1149                                              color.red, color.green, color.blue,
1150                                              alpha);
1151         }
1152       else
1153         cairo_pattern_add_color_stop_rgb (pattern, i / (gfloat) (n_colors - 1),
1154                                           color.red, color.green, color.blue);
1155 
1156       tmp = tmp->next;
1157       ++i;
1158     }
1159 
1160   if (cairo_pattern_status (pattern) != CAIRO_STATUS_SUCCESS)
1161     {
1162       cairo_pattern_destroy (pattern);
1163       return NULL;
1164     }
1165 
1166   return pattern;
1167 }
1168 
1169 static void
free_color_spec(gpointer spec,gpointer user_data)1170 free_color_spec (gpointer spec, gpointer user_data)
1171 {
1172   meta_color_spec_free (spec);
1173 }
1174 
1175 void
meta_gradient_spec_free(MetaGradientSpec * spec)1176 meta_gradient_spec_free (MetaGradientSpec *spec)
1177 {
1178   g_return_if_fail (spec != NULL);
1179 
1180   g_slist_foreach (spec->color_specs, free_color_spec, NULL);
1181   g_slist_free (spec->color_specs);
1182 
1183   DEBUG_FILL_STRUCT (spec);
1184   g_free (spec);
1185 }
1186 
1187 void
meta_gradient_spec_render(const MetaGradientSpec * spec,const MetaAlphaGradientSpec * alpha_spec,cairo_t * cr,GtkStyleContext * context,gint x,gint y,gint width,gint height)1188 meta_gradient_spec_render (const MetaGradientSpec      *spec,
1189                            const MetaAlphaGradientSpec *alpha_spec,
1190                            cairo_t                     *cr,
1191                            GtkStyleContext             *context,
1192                            gint                         x,
1193                            gint                         y,
1194                            gint                         width,
1195                            gint                         height)
1196 {
1197   cairo_pattern_t *pattern;
1198 
1199   pattern = create_cairo_pattern_from_gradient_spec (spec, alpha_spec, context);
1200   if (pattern == NULL)
1201     return;
1202 
1203   cairo_save (cr);
1204 
1205   cairo_rectangle (cr, x, y, width, height);
1206 
1207   cairo_translate (cr, x, y);
1208   cairo_scale (cr, width, height);
1209 
1210   cairo_set_source (cr, pattern);
1211   cairo_fill (cr);
1212   cairo_pattern_destroy (pattern);
1213 
1214   cairo_restore (cr);
1215 }
1216 
1217 gboolean
meta_gradient_spec_validate(MetaGradientSpec * spec,GError ** error)1218 meta_gradient_spec_validate (MetaGradientSpec *spec,
1219                              GError          **error)
1220 {
1221   g_return_val_if_fail (spec != NULL, FALSE);
1222 
1223   if (g_slist_length (spec->color_specs) < 2)
1224     {
1225       g_set_error (error, META_THEME_ERROR,
1226                    META_THEME_ERROR_FAILED,
1227                    _("Gradients should have at least two colors"));
1228       return FALSE;
1229     }
1230 
1231   return TRUE;
1232 }
1233 
1234 MetaAlphaGradientSpec*
meta_alpha_gradient_spec_new(MetaGradientType type,int n_alphas)1235 meta_alpha_gradient_spec_new (MetaGradientType       type,
1236                               int                    n_alphas)
1237 {
1238   MetaAlphaGradientSpec *spec;
1239 
1240   g_return_val_if_fail (n_alphas > 0, NULL);
1241 
1242   spec = g_new0 (MetaAlphaGradientSpec, 1);
1243 
1244   spec->type = type;
1245   spec->alphas = g_new0 (unsigned char, n_alphas);
1246   spec->n_alphas = n_alphas;
1247 
1248   return spec;
1249 }
1250 
1251 void
meta_alpha_gradient_spec_free(MetaAlphaGradientSpec * spec)1252 meta_alpha_gradient_spec_free (MetaAlphaGradientSpec *spec)
1253 {
1254   g_return_if_fail (spec != NULL);
1255 
1256   g_free (spec->alphas);
1257   g_free (spec);
1258 }
1259 
1260 cairo_pattern_t *
meta_alpha_gradient_spec_get_mask(const MetaAlphaGradientSpec * spec)1261 meta_alpha_gradient_spec_get_mask (const MetaAlphaGradientSpec *spec)
1262 {
1263   gint n_alphas;
1264   cairo_pattern_t *pattern;
1265   gint i;
1266 
1267   /* Hardcoded in theme-parser.c */
1268   g_assert (spec->type == META_GRADIENT_HORIZONTAL);
1269 
1270   n_alphas = spec->n_alphas;
1271   if (n_alphas == 0)
1272     return NULL;
1273 
1274   if (n_alphas == 1)
1275     return cairo_pattern_create_rgba (0, 0, 0, spec->alphas[0] / 255.0);
1276 
1277   pattern = cairo_pattern_create_linear (0, 0, 1, 0);
1278 
1279   for (i = 0; i < n_alphas; i++)
1280     cairo_pattern_add_color_stop_rgba (pattern, i / (gfloat) (n_alphas - 1),
1281                                        0, 0, 0, spec->alphas[i] / 255.0);
1282 
1283   if (cairo_pattern_status (pattern) != CAIRO_STATUS_SUCCESS)
1284     {
1285       cairo_pattern_destroy (pattern);
1286       return NULL;
1287     }
1288 
1289   return pattern;
1290 }
1291 
1292 MetaColorSpec*
meta_color_spec_new(MetaColorSpecType type)1293 meta_color_spec_new (MetaColorSpecType type)
1294 {
1295   MetaColorSpec *spec;
1296   MetaColorSpec dummy;
1297   int size;
1298 
1299   size = G_STRUCT_OFFSET (MetaColorSpec, data);
1300 
1301   switch (type)
1302     {
1303     case META_COLOR_SPEC_BASIC:
1304       size += sizeof (dummy.data.basic);
1305       break;
1306 
1307     case META_COLOR_SPEC_GTK:
1308       size += sizeof (dummy.data.gtk);
1309       break;
1310 
1311     case META_COLOR_SPEC_GTK_CUSTOM:
1312       size += sizeof (dummy.data.gtkcustom);
1313       break;
1314 
1315     case META_COLOR_SPEC_BLEND:
1316       size += sizeof (dummy.data.blend);
1317       break;
1318 
1319     case META_COLOR_SPEC_SHADE:
1320       size += sizeof (dummy.data.shade);
1321       break;
1322     }
1323 
1324   spec = g_malloc0 (size);
1325 
1326   spec->type = type;
1327 
1328   return spec;
1329 }
1330 
1331 void
meta_color_spec_free(MetaColorSpec * spec)1332 meta_color_spec_free (MetaColorSpec *spec)
1333 {
1334   g_return_if_fail (spec != NULL);
1335 
1336   switch (spec->type)
1337     {
1338     case META_COLOR_SPEC_BASIC:
1339       DEBUG_FILL_STRUCT (&spec->data.basic);
1340       break;
1341 
1342     case META_COLOR_SPEC_GTK:
1343       DEBUG_FILL_STRUCT (&spec->data.gtk);
1344       break;
1345 
1346     case META_COLOR_SPEC_GTK_CUSTOM:
1347       if (spec->data.gtkcustom.color_name)
1348         g_free (spec->data.gtkcustom.color_name);
1349       if (spec->data.gtkcustom.fallback)
1350         meta_color_spec_free (spec->data.gtkcustom.fallback);
1351       DEBUG_FILL_STRUCT (&spec->data.gtkcustom);
1352       break;
1353 
1354     case META_COLOR_SPEC_BLEND:
1355       if (spec->data.blend.foreground)
1356         meta_color_spec_free (spec->data.blend.foreground);
1357       if (spec->data.blend.background)
1358         meta_color_spec_free (spec->data.blend.background);
1359       DEBUG_FILL_STRUCT (&spec->data.blend);
1360       break;
1361 
1362     case META_COLOR_SPEC_SHADE:
1363       if (spec->data.shade.base)
1364         meta_color_spec_free (spec->data.shade.base);
1365       DEBUG_FILL_STRUCT (&spec->data.shade);
1366       break;
1367     }
1368 
1369   g_free (spec);
1370 }
1371 
1372 MetaColorSpec*
meta_color_spec_new_from_string(const char * str,GError ** err)1373 meta_color_spec_new_from_string (const char *str,
1374                                  GError    **err)
1375 {
1376   MetaColorSpec *spec;
1377 
1378   spec = NULL;
1379 
1380   if (strncmp (str, "gtk:custom", 10) == 0)
1381     {
1382       const char *color_name_start, *fallback_str_start, *end;
1383       char *color_name;
1384       MetaColorSpec *fallback = NULL;
1385       static gboolean debug, debug_set = FALSE;
1386 
1387       if (!debug_set)
1388         {
1389           debug = g_getenv ("MARCO_DISABLE_FALLBACK_COLOR") != NULL;
1390           debug_set = TRUE;
1391         }
1392 
1393       if (str[10] != '(')
1394         {
1395           g_set_error (err, META_THEME_ERROR,
1396                        META_THEME_ERROR_FAILED,
1397                        _("GTK custom color specification must have color name and fallback in parentheses, e.g. gtk:custom(foo,bar); could not parse \"%s\""),
1398                        str);
1399           return NULL;
1400         }
1401 
1402       color_name_start = str + 11;
1403 
1404       fallback_str_start = color_name_start;
1405       while (*fallback_str_start && *fallback_str_start != ',')
1406         {
1407           if (!(g_ascii_isalnum (*fallback_str_start)
1408                 || *fallback_str_start == '-'
1409                 || *fallback_str_start == '_'))
1410             {
1411               g_set_error (err, META_THEME_ERROR,
1412                            META_THEME_ERROR_FAILED,
1413                            _("Invalid character '%c' in color_name parameter of gtk:custom, only A-Za-z0-9-_ are valid"),
1414                            *fallback_str_start);
1415               return NULL;
1416             }
1417           fallback_str_start++;
1418         }
1419       fallback_str_start++;
1420 
1421       end = strrchr (str, ')');
1422 
1423       if (color_name_start == NULL || fallback_str_start == NULL || end == NULL)
1424         {
1425           g_set_error (err, META_THEME_ERROR,
1426                        META_THEME_ERROR_FAILED,
1427                        _("Gtk:custom format is \"gtk:custom(color_name,fallback)\", \"%s\" does not fit the format"),
1428                        str);
1429           return NULL;
1430         }
1431 
1432       if (!debug)
1433         {
1434           char *fallback_str;
1435           fallback_str = g_strndup (fallback_str_start,
1436                                     end - fallback_str_start);
1437           fallback = meta_color_spec_new_from_string (fallback_str, err);
1438           g_free (fallback_str);
1439         }
1440       else
1441         {
1442           fallback = meta_color_spec_new_from_string ("pink", err);
1443         }
1444 
1445       if (fallback == NULL)
1446         return NULL;
1447 
1448       color_name = g_strndup (color_name_start, fallback_str_start - color_name_start - 1);
1449 
1450       spec = meta_color_spec_new (META_COLOR_SPEC_GTK_CUSTOM);
1451       spec->data.gtkcustom.color_name = color_name;
1452       spec->data.gtkcustom.fallback = fallback;
1453     }
1454   else if (strncmp (str, "gtk:", 4) == 0)
1455     {
1456       /* GTK color */
1457       const char *bracket;
1458       const char *end_bracket;
1459       char *tmp;
1460       GtkStateFlags state;
1461       MetaGtkColorComponent component;
1462 
1463       bracket = str;
1464       while (*bracket && *bracket != '[')
1465         ++bracket;
1466 
1467       if (*bracket == '\0')
1468         {
1469           g_set_error (err, META_THEME_ERROR,
1470                        META_THEME_ERROR_FAILED,
1471                        _("GTK color specification must have the state in brackets, e.g. gtk:fg[NORMAL] where NORMAL is the state; could not parse \"%s\""),
1472                        str);
1473           return NULL;
1474         }
1475 
1476       end_bracket = bracket;
1477       ++end_bracket;
1478       while (*end_bracket && *end_bracket != ']')
1479         ++end_bracket;
1480 
1481       if (*end_bracket == '\0')
1482         {
1483           g_set_error (err, META_THEME_ERROR,
1484                        META_THEME_ERROR_FAILED,
1485                        _("GTK color specification must have a close bracket after the state, e.g. gtk:fg[NORMAL] where NORMAL is the state; could not parse \"%s\""),
1486                        str);
1487           return NULL;
1488         }
1489 
1490       tmp = g_strndup (bracket + 1, end_bracket - bracket - 1);
1491       state = meta_gtk_state_from_string (tmp);
1492       if (((int) state) == -1)
1493         {
1494           g_set_error (err, META_THEME_ERROR,
1495                        META_THEME_ERROR_FAILED,
1496                        _("Did not understand state \"%s\" in color specification"),
1497                        tmp);
1498           g_free (tmp);
1499           return NULL;
1500         }
1501       g_free (tmp);
1502 
1503       tmp = g_strndup (str + 4, bracket - str - 4);
1504       component = meta_color_component_from_string (tmp);
1505       if (component == META_GTK_COLOR_LAST)
1506         {
1507           g_set_error (err, META_THEME_ERROR,
1508                        META_THEME_ERROR_FAILED,
1509                        _("Did not understand color component \"%s\" in color specification"),
1510                        tmp);
1511           g_free (tmp);
1512           return NULL;
1513         }
1514       g_free (tmp);
1515 
1516       spec = meta_color_spec_new (META_COLOR_SPEC_GTK);
1517       spec->data.gtk.state = state;
1518       spec->data.gtk.component = component;
1519       g_assert (spec->data.gtk.component < META_GTK_COLOR_LAST);
1520     }
1521   else if (strncmp (str, "blend/", 6) == 0)
1522     {
1523       /* blend */
1524       char **split;
1525       double alpha;
1526       char *end;
1527       MetaColorSpec *fg;
1528       MetaColorSpec *bg;
1529 
1530       split = g_strsplit (str, "/", 4);
1531 
1532       if (split[0] == NULL || split[1] == NULL ||
1533           split[2] == NULL || split[3] == NULL)
1534         {
1535           g_set_error (err, META_THEME_ERROR,
1536                        META_THEME_ERROR_FAILED,
1537                        _("Blend format is \"blend/bg_color/fg_color/alpha\", \"%s\" does not fit the format"),
1538                        str);
1539           g_strfreev (split);
1540           return NULL;
1541         }
1542 
1543       alpha = g_ascii_strtod (split[3], &end);
1544       if (end == split[3])
1545         {
1546           g_set_error (err, META_THEME_ERROR,
1547                        META_THEME_ERROR_FAILED,
1548                        _("Could not parse alpha value \"%s\" in blended color"),
1549                        split[3]);
1550           g_strfreev (split);
1551           return NULL;
1552         }
1553 
1554       if (alpha < (0.0 - 1e6) || alpha > (1.0 + 1e6))
1555         {
1556           g_set_error (err, META_THEME_ERROR,
1557                        META_THEME_ERROR_FAILED,
1558                        _("Alpha value \"%s\" in blended color is not between 0.0 and 1.0"),
1559                        split[3]);
1560           g_strfreev (split);
1561           return NULL;
1562         }
1563 
1564       fg = NULL;
1565       bg = NULL;
1566 
1567       bg = meta_color_spec_new_from_string (split[1], err);
1568       if (bg == NULL)
1569         {
1570           g_strfreev (split);
1571           return NULL;
1572         }
1573 
1574       fg = meta_color_spec_new_from_string (split[2], err);
1575       if (fg == NULL)
1576         {
1577           meta_color_spec_free (bg);
1578           g_strfreev (split);
1579           return NULL;
1580         }
1581 
1582       g_strfreev (split);
1583 
1584       spec = meta_color_spec_new (META_COLOR_SPEC_BLEND);
1585       spec->data.blend.alpha = alpha;
1586       spec->data.blend.background = bg;
1587       spec->data.blend.foreground = fg;
1588     }
1589   else if (strncmp (str, "shade/", 6) == 0)
1590     {
1591       /* shade */
1592       char **split;
1593       double factor;
1594       char *end;
1595       MetaColorSpec *base;
1596 
1597       split = g_strsplit (str, "/", 3);
1598 
1599       if (split[0] == NULL || split[1] == NULL ||
1600           split[2] == NULL)
1601         {
1602           g_set_error (err, META_THEME_ERROR,
1603                        META_THEME_ERROR_FAILED,
1604                        _("Shade format is \"shade/base_color/factor\", \"%s\" does not fit the format"),
1605                        str);
1606           g_strfreev (split);
1607           return NULL;
1608         }
1609 
1610       factor = g_ascii_strtod (split[2], &end);
1611       if (end == split[2])
1612         {
1613           g_set_error (err, META_THEME_ERROR,
1614                        META_THEME_ERROR_FAILED,
1615                        _("Could not parse shade factor \"%s\" in shaded color"),
1616                        split[2]);
1617           g_strfreev (split);
1618           return NULL;
1619         }
1620 
1621       if (factor < (0.0 - 1e6))
1622         {
1623           g_set_error (err, META_THEME_ERROR,
1624                        META_THEME_ERROR_FAILED,
1625                        _("Shade factor \"%s\" in shaded color is negative"),
1626                        split[2]);
1627           g_strfreev (split);
1628           return NULL;
1629         }
1630 
1631       base = NULL;
1632 
1633       base = meta_color_spec_new_from_string (split[1], err);
1634       if (base == NULL)
1635         {
1636           g_strfreev (split);
1637           return NULL;
1638         }
1639 
1640       g_strfreev (split);
1641 
1642       spec = meta_color_spec_new (META_COLOR_SPEC_SHADE);
1643       spec->data.shade.factor = factor;
1644       spec->data.shade.base = base;
1645     }
1646   else
1647     {
1648       spec = meta_color_spec_new (META_COLOR_SPEC_BASIC);
1649 
1650       if (!gdk_rgba_parse (&spec->data.basic.color, str))
1651         {
1652           g_set_error (err, META_THEME_ERROR,
1653                        META_THEME_ERROR_FAILED,
1654                        _("Could not parse color \"%s\""),
1655                        str);
1656           meta_color_spec_free (spec);
1657           return NULL;
1658         }
1659     }
1660 
1661   g_assert (spec);
1662 
1663   return spec;
1664 }
1665 
1666 MetaColorSpec*
meta_color_spec_new_gtk(MetaGtkColorComponent component,GtkStateFlags state)1667 meta_color_spec_new_gtk (MetaGtkColorComponent component,
1668                          GtkStateFlags         state)
1669 {
1670   MetaColorSpec *spec;
1671 
1672   spec = meta_color_spec_new (META_COLOR_SPEC_GTK);
1673 
1674   spec->data.gtk.component = component;
1675   spec->data.gtk.state = state;
1676 
1677   return spec;
1678 }
1679 
1680 static void
get_background_color_real(GtkStyleContext * context,GtkStateFlags state,GdkRGBA * color)1681 get_background_color_real (GtkStyleContext *context,
1682                            GtkStateFlags    state,
1683                            GdkRGBA         *color)
1684 {
1685   GdkRGBA *c;
1686 
1687   g_return_if_fail (color != NULL);
1688   g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
1689 
1690   gtk_style_context_get (context,
1691                          state,
1692                          "background-color", &c,
1693                          NULL);
1694 
1695   *color = *c;
1696   gdk_rgba_free (c);
1697 }
1698 
1699 static void
get_background_color(GtkStyleContext * context,GtkStateFlags state,GdkRGBA * color)1700 get_background_color (GtkStyleContext *context,
1701                       GtkStateFlags    state,
1702                       GdkRGBA         *color)
1703 {
1704   GdkRGBA empty = { 0.0, 0.0, 0.0, 0.0 };
1705   GdkRGBA rgba;
1706 
1707   get_background_color_real (context, state, &rgba);
1708 
1709   if (gdk_rgba_equal (&rgba, &empty))
1710     {
1711       GtkWidget *toplevel;
1712       GtkStyleContext *tmp;
1713 
1714       toplevel = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1715       tmp = gtk_widget_get_style_context (toplevel);
1716 
1717       get_background_color_real (tmp, state, &rgba);
1718 
1719       gtk_widget_destroy (toplevel);
1720     }
1721 
1722   *color = rgba;
1723 }
1724 
1725 /* Based on set_color() in gtkstyle.c */
1726 #define LIGHTNESS_MULT 1.3
1727 #define DARKNESS_MULT  0.7
1728 void
meta_gtk_style_get_light_color(GtkStyleContext * style,GtkStateFlags state,GdkRGBA * color)1729 meta_gtk_style_get_light_color (GtkStyleContext *style,
1730                                 GtkStateFlags    state,
1731                                 GdkRGBA         *color)
1732 {
1733   get_background_color (style, state, color);
1734   gtk_style_shade (color, color, LIGHTNESS_MULT);
1735 }
1736 
1737 void
meta_gtk_style_get_dark_color(GtkStyleContext * style,GtkStateFlags state,GdkRGBA * color)1738 meta_gtk_style_get_dark_color (GtkStyleContext *style,
1739                                GtkStateFlags    state,
1740                                GdkRGBA         *color)
1741 {
1742   get_background_color (style, state, color);
1743   gtk_style_shade (color, color, DARKNESS_MULT);
1744 }
1745 
1746 static void
meta_set_color_from_style(GdkRGBA * color,GtkStyleContext * context,GtkStateFlags state,MetaGtkColorComponent component)1747 meta_set_color_from_style (GdkRGBA               *color,
1748                            GtkStyleContext       *context,
1749                            GtkStateFlags          state,
1750                            MetaGtkColorComponent  component)
1751 {
1752   GdkRGBA other;
1753 
1754   /* Add background class to context to get the correct colors from the GTK+
1755      theme instead of white text over black background. */
1756   gtk_style_context_add_class (context, GTK_STYLE_CLASS_BACKGROUND);
1757 
1758   switch (component)
1759     {
1760     case META_GTK_COLOR_BG:
1761     case META_GTK_COLOR_BASE:
1762       get_background_color (context, state, color);
1763       break;
1764     case META_GTK_COLOR_FG:
1765     case META_GTK_COLOR_TEXT:
1766       gtk_style_context_get_color (context, state, color);
1767       break;
1768     case META_GTK_COLOR_TEXT_AA:
1769       gtk_style_context_get_color (context, state, color);
1770       meta_set_color_from_style (&other, context, state, META_GTK_COLOR_BASE);
1771 
1772       color->red = (color->red + other.red) / 2;
1773       color->green = (color->green + other.green) / 2;
1774       color->blue = (color->blue + other.blue) / 2;
1775       break;
1776     case META_GTK_COLOR_MID:
1777       meta_gtk_style_get_light_color (context, state, color);
1778       meta_gtk_style_get_dark_color (context, state, &other);
1779 
1780       color->red = (color->red + other.red) / 2;
1781       color->green = (color->green + other.green) / 2;
1782       color->blue = (color->blue + other.blue) / 2;
1783       break;
1784     case META_GTK_COLOR_LIGHT:
1785       meta_gtk_style_get_light_color (context, state, color);
1786       break;
1787     case META_GTK_COLOR_DARK:
1788       meta_gtk_style_get_dark_color (context, state, color);
1789       break;
1790     case META_GTK_COLOR_LAST:
1791       g_assert_not_reached ();
1792       break;
1793     }
1794 }
1795 
1796 static void
meta_set_custom_color_from_style(GdkRGBA * color,GtkStyleContext * context,char * color_name,MetaColorSpec * fallback)1797 meta_set_custom_color_from_style (GdkRGBA         *color,
1798                                   GtkStyleContext *context,
1799                                   char            *color_name,
1800                                   MetaColorSpec   *fallback)
1801 {
1802   if (!gtk_style_context_lookup_color (context, color_name, color))
1803     meta_color_spec_render (fallback, context, color);
1804 }
1805 
1806 void
meta_color_spec_render(MetaColorSpec * spec,GtkStyleContext * style,GdkRGBA * color)1807 meta_color_spec_render (MetaColorSpec *spec,
1808                         GtkStyleContext *style,
1809                         GdkRGBA         *color)
1810 {
1811   g_return_if_fail (spec != NULL);
1812 
1813   g_return_if_fail (GTK_IS_STYLE_CONTEXT (style));
1814 
1815   switch (spec->type)
1816     {
1817     case META_COLOR_SPEC_BASIC:
1818       *color = spec->data.basic.color;
1819       break;
1820 
1821     case META_COLOR_SPEC_GTK:
1822       meta_set_color_from_style (color,
1823                                  style,
1824                                  spec->data.gtk.state,
1825                                  spec->data.gtk.component);
1826       break;
1827 
1828     case META_COLOR_SPEC_GTK_CUSTOM:
1829       meta_set_custom_color_from_style (color,
1830                                         style,
1831                                         spec->data.gtkcustom.color_name,
1832                                         spec->data.gtkcustom.fallback);
1833       break;
1834 
1835     case META_COLOR_SPEC_BLEND:
1836       {
1837         GdkRGBA bg, fg;
1838 
1839         meta_color_spec_render (spec->data.blend.background, style, &bg);
1840         meta_color_spec_render (spec->data.blend.foreground, style, &fg);
1841 
1842         color_composite (&bg, &fg, spec->data.blend.alpha,
1843                          &spec->data.blend.color);
1844 
1845         *color = spec->data.blend.color;
1846       }
1847       break;
1848 
1849     case META_COLOR_SPEC_SHADE:
1850       {
1851         meta_color_spec_render (spec->data.shade.base, style,
1852                                 &spec->data.shade.color);
1853 
1854         gtk_style_shade (&spec->data.shade.color,
1855                          &spec->data.shade.color, spec->data.shade.factor);
1856 
1857         *color = spec->data.shade.color;
1858       }
1859       break;
1860     }
1861 }
1862 
1863 /**
1864  * Represents an operation as a string.
1865  *
1866  * \param type  an operation, such as addition
1867  * \return  a string, such as "+"
1868  */
1869 static const char*
op_name(PosOperatorType type)1870 op_name (PosOperatorType type)
1871 {
1872   switch (type)
1873     {
1874     case POS_OP_ADD:
1875       return "+";
1876     case POS_OP_SUBTRACT:
1877       return "-";
1878     case POS_OP_MULTIPLY:
1879       return "*";
1880     case POS_OP_DIVIDE:
1881       return "/";
1882     case POS_OP_MOD:
1883       return "%";
1884     case POS_OP_MAX:
1885       return "`max`";
1886     case POS_OP_MIN:
1887       return "`min`";
1888     case POS_OP_NONE:
1889       break;
1890     }
1891 
1892   return "<unknown>";
1893 }
1894 
1895 /**
1896  * Parses a string and returns an operation.
1897  *
1898  * \param p  a pointer into a string representing an operation; part of an
1899  *           expression somewhere, so not null-terminated
1900  * \param len  set to the length of the string found. Set to 0 if none is.
1901  * \return  the operation found. If none was, returns POS_OP_NONE.
1902  */
1903 static PosOperatorType
op_from_string(const char * p,int * len)1904 op_from_string (const char *p,
1905                 int        *len)
1906 {
1907   *len = 0;
1908 
1909   switch (*p)
1910     {
1911     case '+':
1912       *len = 1;
1913       return POS_OP_ADD;
1914     case '-':
1915       *len = 1;
1916       return POS_OP_SUBTRACT;
1917     case '*':
1918       *len = 1;
1919       return POS_OP_MULTIPLY;
1920     case '/':
1921       *len = 1;
1922       return POS_OP_DIVIDE;
1923     case '%':
1924       *len = 1;
1925       return POS_OP_MOD;
1926 
1927     case '`':
1928       if (strncmp (p, "`max`", 5) == 0)
1929         {
1930           *len = 5;
1931           return POS_OP_MAX;
1932         }
1933       else if (strncmp (p, "`min`", 5) == 0)
1934         {
1935           *len = 5;
1936           return POS_OP_MIN;
1937         }
1938     }
1939 
1940   return POS_OP_NONE;
1941 }
1942 
1943 /**
1944  * Frees an array of tokens. All the tokens and their associated memory
1945  * will be freed.
1946  *
1947  * \param tokens  an array of tokens to be freed
1948  * \param n_tokens  how many tokens are in the array.
1949  */
1950 static void
free_tokens(PosToken * tokens,int n_tokens)1951 free_tokens (PosToken *tokens,
1952              int       n_tokens)
1953 {
1954   int i;
1955 
1956   /* n_tokens can be 0 since tokens may have been allocated more than
1957    * it was initialized
1958    */
1959 
1960   for (i = 0; i < n_tokens; i++)
1961     if (tokens[i].type == POS_TOKEN_VARIABLE)
1962       g_free (tokens[i].d.v.name);
1963 
1964   g_free (tokens);
1965 }
1966 
1967 /**
1968  * Tokenises a number in an expression.
1969  *
1970  * \param p  a pointer into a string representing an operation; part of an
1971  *           expression somewhere, so not null-terminated
1972  * \param end_return  set to a pointer to the end of the number found; but
1973  *                    not updated if no number was found at all
1974  * \param next  set to either an integer or a float token
1975  * \param[out] err  set to the problem if there was a problem
1976  * \return TRUE if a valid number was found, FALSE otherwise (and "err" will
1977  *         have been set)
1978  *
1979  * \bug The "while (*start)..." part: what's wrong with strchr-ish things?
1980  * \bug The name is wrong: it doesn't parse anything.
1981  * \ingroup tokenizer
1982  */
1983 static gboolean
parse_number(const char * p,const char ** end_return,PosToken * next,GError ** err)1984 parse_number (const char  *p,
1985               const char **end_return,
1986               PosToken    *next,
1987               GError     **err)
1988 {
1989   const char *start = p;
1990   char *end;
1991   gboolean is_float;
1992   char *num_str;
1993 
1994   while (*p && (*p == '.' || g_ascii_isdigit (*p)))
1995     ++p;
1996 
1997   if (p == start)
1998     {
1999       char buf[7] = { '\0' };
2000       buf[g_unichar_to_utf8 (g_utf8_get_char (p), buf)] = '\0';
2001       g_set_error (err, META_THEME_ERROR,
2002                    META_THEME_ERROR_BAD_CHARACTER,
2003                    _("Coordinate expression contains character '%s' which is not allowed"),
2004                    buf);
2005       return FALSE;
2006     }
2007 
2008   *end_return = p;
2009 
2010   /* we need this to exclude floats like "1e6" */
2011   num_str = g_strndup (start, p - start);
2012   start = num_str;
2013   is_float = FALSE;
2014   while (*start)
2015     {
2016       if (*start == '.')
2017         is_float = TRUE;
2018       ++start;
2019     }
2020 
2021   if (is_float)
2022     {
2023       next->type = POS_TOKEN_DOUBLE;
2024       next->d.d.val = g_ascii_strtod (num_str, &end);
2025 
2026       if (end == num_str)
2027         {
2028           g_set_error (err, META_THEME_ERROR,
2029                        META_THEME_ERROR_FAILED,
2030                        _("Coordinate expression contains floating point number '%s' which could not be parsed"),
2031                        num_str);
2032           g_free (num_str);
2033           return FALSE;
2034         }
2035     }
2036   else
2037     {
2038       next->type = POS_TOKEN_INT;
2039       next->d.i.val = strtol (num_str, &end, 10);
2040       if (end == num_str)
2041         {
2042           g_set_error (err, META_THEME_ERROR,
2043                        META_THEME_ERROR_FAILED,
2044                        _("Coordinate expression contains integer '%s' which could not be parsed"),
2045                        num_str);
2046           g_free (num_str);
2047           return FALSE;
2048         }
2049     }
2050 
2051   g_free (num_str);
2052 
2053   g_assert (next->type == POS_TOKEN_INT || next->type == POS_TOKEN_DOUBLE);
2054 
2055   return TRUE;
2056 }
2057 
2058 /**
2059  * Whether a variable can validly appear as part of the name of a variable.
2060  */
2061 #define IS_VARIABLE_CHAR(c) (g_ascii_isalpha ((c)) || (c) == '_')
2062 
2063 #if 0
2064 static void
2065 debug_print_tokens (PosToken *tokens,
2066                     int       n_tokens)
2067 {
2068   int i;
2069 
2070   for (i = 0; i < n_tokens; i++)
2071     {
2072       PosToken *t = &tokens[i];
2073 
2074       g_print (" ");
2075 
2076       switch (t->type)
2077         {
2078         case POS_TOKEN_INT:
2079           g_print ("\"%d\"", t->d.i.val);
2080           break;
2081         case POS_TOKEN_DOUBLE:
2082           g_print ("\"%g\"", t->d.d.val);
2083           break;
2084         case POS_TOKEN_OPEN_PAREN:
2085           g_print ("\"(\"");
2086           break;
2087         case POS_TOKEN_CLOSE_PAREN:
2088           g_print ("\")\"");
2089           break;
2090         case POS_TOKEN_VARIABLE:
2091           g_print ("\"%s\"", t->d.v.name);
2092           break;
2093         case POS_TOKEN_OPERATOR:
2094           g_print ("\"%s\"", op_name (t->d.o.op));
2095           break;
2096         }
2097     }
2098 
2099   g_print ("\n");
2100 }
2101 #endif
2102 
2103 /**
2104  * Tokenises an expression.
2105  *
2106  * \param      expr        The expression
2107  * \param[out] tokens_p    The resulting tokens
2108  * \param[out] n_tokens_p  The number of resulting tokens
2109  * \param[out] err  set to the problem if there was a problem
2110  *
2111  * \return  True if the expression was successfully tokenised; false otherwise.
2112  *
2113  * \ingroup tokenizer
2114  */
2115 static gboolean
pos_tokenize(const char * expr,PosToken ** tokens_p,int * n_tokens_p,GError ** err)2116 pos_tokenize (const char  *expr,
2117               PosToken   **tokens_p,
2118               int         *n_tokens_p,
2119               GError     **err)
2120 {
2121   PosToken *tokens;
2122   int n_tokens;
2123   int allocated;
2124   const char *p;
2125 
2126   *tokens_p = NULL;
2127   *n_tokens_p = 0;
2128 
2129   allocated = 3;
2130   n_tokens = 0;
2131   tokens = g_new (PosToken, allocated);
2132 
2133   p = expr;
2134   while (*p)
2135     {
2136       PosToken *next;
2137       int len;
2138 
2139       if (n_tokens == allocated)
2140         {
2141           allocated *= 2;
2142           tokens = g_renew (PosToken, tokens, allocated);
2143         }
2144 
2145       next = &tokens[n_tokens];
2146 
2147       switch (*p)
2148         {
2149         case '*':
2150         case '/':
2151         case '+':
2152         case '-': /* negative numbers aren't allowed so this is easy */
2153         case '%':
2154         case '`':
2155           next->type = POS_TOKEN_OPERATOR;
2156           next->d.o.op = op_from_string (p, &len);
2157           if (next->d.o.op != POS_OP_NONE)
2158             {
2159               ++n_tokens;
2160               p = p + (len - 1); /* -1 since we ++p later */
2161             }
2162           else
2163             {
2164               g_set_error (err, META_THEME_ERROR,
2165                            META_THEME_ERROR_FAILED,
2166                            _("Coordinate expression contained unknown operator at the start of this text: \"%s\""),
2167                            p);
2168 
2169               goto error;
2170             }
2171           break;
2172 
2173         case '(':
2174           next->type = POS_TOKEN_OPEN_PAREN;
2175           ++n_tokens;
2176           break;
2177 
2178         case ')':
2179           next->type = POS_TOKEN_CLOSE_PAREN;
2180           ++n_tokens;
2181           break;
2182 
2183         case ' ':
2184         case '\t':
2185         case '\n':
2186           break;
2187 
2188         default:
2189           if (IS_VARIABLE_CHAR (*p))
2190             {
2191               /* Assume variable */
2192               const char *start = p;
2193               while (*p && IS_VARIABLE_CHAR (*p))
2194                 ++p;
2195               g_assert (p != start);
2196               next->type = POS_TOKEN_VARIABLE;
2197               next->d.v.name = g_strndup (start, p - start);
2198               ++n_tokens;
2199               --p; /* since we ++p again at the end of while loop */
2200             }
2201           else
2202             {
2203               /* Assume number */
2204               const char *end;
2205 
2206               if (!parse_number (p, &end, next, err))
2207                 goto error;
2208 
2209               ++n_tokens;
2210               p = end - 1; /* -1 since we ++p again at the end of while loop */
2211             }
2212 
2213           break;
2214         }
2215 
2216       ++p;
2217     }
2218 
2219   if (n_tokens == 0)
2220     {
2221       g_set_error (err, META_THEME_ERROR,
2222                    META_THEME_ERROR_FAILED,
2223                    _("Coordinate expression was empty or not understood"));
2224 
2225       goto error;
2226     }
2227 
2228   *tokens_p = tokens;
2229   *n_tokens_p = n_tokens;
2230 
2231   return TRUE;
2232 
2233  error:
2234   g_assert (err == NULL || *err != NULL);
2235 
2236   free_tokens (tokens, n_tokens);
2237   return FALSE;
2238 }
2239 
2240 /**
2241  * The type of a PosExpr: either integer, double, or an operation.
2242  * \ingroup parser
2243  */
2244 typedef enum
2245 {
2246   POS_EXPR_INT,
2247   POS_EXPR_DOUBLE,
2248   POS_EXPR_OPERATOR
2249 } PosExprType;
2250 
2251 /**
2252  * Type and value of an expression in a parsed sequence. We don't
2253  * keep expressions in a tree; if this is of type POS_EXPR_OPERATOR,
2254  * the arguments of the operator will be in the array positions
2255  * immediately preceding and following this operator; they cannot
2256  * themselves be operators.
2257  *
2258  * \bug operator is char; it should really be of PosOperatorType.
2259  * \ingroup parser
2260  */
2261 typedef struct
2262 {
2263   PosExprType type;
2264   union
2265   {
2266     double double_val;
2267     int int_val;
2268     char operator;
2269   } d;
2270 } PosExpr;
2271 
2272 #if 0
2273 static void
2274 debug_print_exprs (PosExpr *exprs,
2275                    int      n_exprs)
2276 {
2277   int i;
2278 
2279   for (i = 0; i < n_exprs; i++)
2280     {
2281       switch (exprs[i].type)
2282         {
2283         case POS_EXPR_INT:
2284           g_print (" %d", exprs[i].d.int_val);
2285           break;
2286         case POS_EXPR_DOUBLE:
2287           g_print (" %g", exprs[i].d.double_val);
2288           break;
2289         case POS_EXPR_OPERATOR:
2290           g_print (" %s", op_name (exprs[i].d.operator));
2291           break;
2292         }
2293     }
2294   g_print ("\n");
2295 }
2296 #endif
2297 
2298 static gboolean
do_operation(PosExpr * a,PosExpr * b,PosOperatorType op,GError ** err)2299 do_operation (PosExpr *a,
2300               PosExpr *b,
2301               PosOperatorType op,
2302               GError **err)
2303 {
2304   /* Promote types to double if required */
2305   if (a->type == POS_EXPR_DOUBLE ||
2306       b->type == POS_EXPR_DOUBLE)
2307     {
2308       if (a->type != POS_EXPR_DOUBLE)
2309         {
2310           a->type = POS_EXPR_DOUBLE;
2311           a->d.double_val = a->d.int_val;
2312         }
2313       if (b->type != POS_EXPR_DOUBLE)
2314         {
2315           b->type = POS_EXPR_DOUBLE;
2316           b->d.double_val = b->d.int_val;
2317         }
2318     }
2319 
2320   g_assert (a->type == b->type);
2321 
2322   if (a->type == POS_EXPR_INT)
2323     {
2324       switch (op)
2325         {
2326         case POS_OP_MULTIPLY:
2327           a->d.int_val = a->d.int_val * b->d.int_val;
2328           break;
2329         case POS_OP_DIVIDE:
2330           if (b->d.int_val == 0)
2331             {
2332               g_set_error (err, META_THEME_ERROR,
2333                            META_THEME_ERROR_DIVIDE_BY_ZERO,
2334                            _("Coordinate expression results in division by zero"));
2335               return FALSE;
2336             }
2337           a->d.int_val = a->d.int_val / b->d.int_val;
2338           break;
2339         case POS_OP_MOD:
2340           if (b->d.int_val == 0)
2341             {
2342               g_set_error (err, META_THEME_ERROR,
2343                            META_THEME_ERROR_DIVIDE_BY_ZERO,
2344                            _("Coordinate expression results in division by zero"));
2345               return FALSE;
2346             }
2347           a->d.int_val = a->d.int_val % b->d.int_val;
2348           break;
2349         case POS_OP_ADD:
2350           a->d.int_val = a->d.int_val + b->d.int_val;
2351           break;
2352         case POS_OP_SUBTRACT:
2353           a->d.int_val = a->d.int_val - b->d.int_val;
2354           break;
2355         case POS_OP_MAX:
2356           a->d.int_val = MAX (a->d.int_val, b->d.int_val);
2357           break;
2358         case POS_OP_MIN:
2359           a->d.int_val = MIN (a->d.int_val, b->d.int_val);
2360           break;
2361         case POS_OP_NONE:
2362           g_assert_not_reached ();
2363           break;
2364         }
2365     }
2366   else if (a->type == POS_EXPR_DOUBLE)
2367     {
2368       switch (op)
2369         {
2370         case POS_OP_MULTIPLY:
2371           a->d.double_val = a->d.double_val * b->d.double_val;
2372           break;
2373         case POS_OP_DIVIDE:
2374           if (b->d.double_val == 0.0)
2375             {
2376               g_set_error (err, META_THEME_ERROR,
2377                            META_THEME_ERROR_DIVIDE_BY_ZERO,
2378                            _("Coordinate expression results in division by zero"));
2379               return FALSE;
2380             }
2381           a->d.double_val = a->d.double_val / b->d.double_val;
2382           break;
2383         case POS_OP_MOD:
2384           g_set_error (err, META_THEME_ERROR,
2385                        META_THEME_ERROR_MOD_ON_FLOAT,
2386                        _("Coordinate expression tries to use mod operator on a floating-point number"));
2387           return FALSE;
2388         case POS_OP_ADD:
2389           a->d.double_val = a->d.double_val + b->d.double_val;
2390           break;
2391         case POS_OP_SUBTRACT:
2392           a->d.double_val = a->d.double_val - b->d.double_val;
2393           break;
2394         case POS_OP_MAX:
2395           a->d.double_val = MAX (a->d.double_val, b->d.double_val);
2396           break;
2397         case POS_OP_MIN:
2398           a->d.double_val = MIN (a->d.double_val, b->d.double_val);
2399           break;
2400         case POS_OP_NONE:
2401           g_assert_not_reached ();
2402           break;
2403         }
2404     }
2405   else
2406     g_assert_not_reached ();
2407 
2408   return TRUE;
2409 }
2410 
2411 static gboolean
do_operations(PosExpr * exprs,int * n_exprs,int precedence,GError ** err)2412 do_operations (PosExpr *exprs,
2413                int     *n_exprs,
2414                int      precedence,
2415                GError **err)
2416 {
2417   int i;
2418 
2419 #if 0
2420   g_print ("Doing prec %d ops on %d exprs\n", precedence, *n_exprs);
2421   debug_print_exprs (exprs, *n_exprs);
2422 #endif
2423 
2424   i = 1;
2425   while (i < *n_exprs)
2426     {
2427       gboolean compress;
2428 
2429       /* exprs[i-1] first operand
2430        * exprs[i]   operator
2431        * exprs[i+1] second operand
2432        *
2433        * we replace first operand with result of mul/div/mod,
2434        * or skip over operator and second operand if we have
2435        * an add/subtract
2436        */
2437 
2438       if (exprs[i-1].type == POS_EXPR_OPERATOR)
2439         {
2440           g_set_error (err, META_THEME_ERROR,
2441                        META_THEME_ERROR_FAILED,
2442                        _("Coordinate expression has an operator \"%s\" where an operand was expected"),
2443                        op_name (exprs[i-1].d.operator));
2444           return FALSE;
2445         }
2446 
2447       if (exprs[i].type != POS_EXPR_OPERATOR)
2448         {
2449           g_set_error (err, META_THEME_ERROR,
2450                        META_THEME_ERROR_FAILED,
2451                        _("Coordinate expression had an operand where an operator was expected"));
2452           return FALSE;
2453         }
2454 
2455       if (i == (*n_exprs - 1))
2456         {
2457           g_set_error (err, META_THEME_ERROR,
2458                        META_THEME_ERROR_FAILED,
2459                        _("Coordinate expression ended with an operator instead of an operand"));
2460           return FALSE;
2461         }
2462 
2463       g_assert ((i+1) < *n_exprs);
2464 
2465       if (exprs[i+1].type == POS_EXPR_OPERATOR)
2466         {
2467           g_set_error (err, META_THEME_ERROR,
2468                        META_THEME_ERROR_FAILED,
2469                        _("Coordinate expression has operator \"%c\" following operator \"%c\" with no operand in between"),
2470                        exprs[i+1].d.operator,
2471                        exprs[i].d.operator);
2472           return FALSE;
2473         }
2474 
2475       compress = FALSE;
2476 
2477       switch (precedence)
2478         {
2479         case 2:
2480           switch (exprs[i].d.operator)
2481             {
2482             case POS_OP_DIVIDE:
2483             case POS_OP_MOD:
2484             case POS_OP_MULTIPLY:
2485               compress = TRUE;
2486               if (!do_operation (&exprs[i-1], &exprs[i+1],
2487                                  exprs[i].d.operator,
2488                                  err))
2489                 return FALSE;
2490               break;
2491             }
2492           break;
2493         case 1:
2494           switch (exprs[i].d.operator)
2495             {
2496             case POS_OP_ADD:
2497             case POS_OP_SUBTRACT:
2498               compress = TRUE;
2499               if (!do_operation (&exprs[i-1], &exprs[i+1],
2500                                  exprs[i].d.operator,
2501                                  err))
2502                 return FALSE;
2503               break;
2504             }
2505           break;
2506           /* I have no rationale at all for making these low-precedence */
2507         case 0:
2508           switch (exprs[i].d.operator)
2509             {
2510             case POS_OP_MAX:
2511             case POS_OP_MIN:
2512               compress = TRUE;
2513               if (!do_operation (&exprs[i-1], &exprs[i+1],
2514                                  exprs[i].d.operator,
2515                                  err))
2516                 return FALSE;
2517               break;
2518             }
2519           break;
2520         }
2521 
2522       if (compress)
2523         {
2524           /* exprs[i-1] first operand (now result)
2525            * exprs[i]   operator
2526            * exprs[i+1] second operand
2527            * exprs[i+2] new operator
2528            *
2529            * we move new operator just after first operand
2530            */
2531           if ((i+2) < *n_exprs)
2532             {
2533               memmove (&exprs[i], &exprs[i+2],
2534                          sizeof (PosExpr) * (*n_exprs - i - 2));
2535             }
2536 
2537           *n_exprs -= 2;
2538         }
2539       else
2540         {
2541           /* Skip operator and next operand */
2542           i += 2;
2543         }
2544     }
2545 
2546   return TRUE;
2547 }
2548 
2549 /**
2550  * There is a predefined set of variables which can appear in an expression.
2551  * Here we take a token representing a variable, and return the current value
2552  * of that variable in a particular environment.
2553  * (The value is always an integer.)
2554  *
2555  * There are supposedly some circumstances in which this function can be
2556  * called from outside Marco, in which case env->theme will be NULL, and
2557  * therefore we can't use it to find out quark values, so we do the comparison
2558  * using strcmp, which is slower.
2559  *
2560  * \param t  The token representing a variable
2561  * \param[out] result  The value of that variable; not set if the token did
2562  *                     not represent a known variable
2563  * \param env  The environment within which t should be evaluated
2564  * \param[out] err  set to the problem if there was a problem
2565  *
2566  * \return true if we found the variable asked for, false if we didn't
2567  *
2568  * \bug shouldn't t be const?
2569  * \bug we should perhaps consider some sort of lookup arrangement into an
2570  *      array; also, the duplication of code is unlovely; perhaps using glib
2571  *      string hashes instead of quarks would fix both problems?
2572  * \ingroup parser
2573  */
2574 static gboolean
pos_eval_get_variable(PosToken * t,int * result,const MetaPositionExprEnv * env,GError ** err)2575 pos_eval_get_variable (PosToken                  *t,
2576                        int                       *result,
2577                        const MetaPositionExprEnv *env,
2578                        GError                   **err)
2579 {
2580   if (env->theme)
2581     {
2582       if (t->d.v.name_quark == env->theme->quark_width)
2583         *result = env->rect.width;
2584       else if (t->d.v.name_quark == env->theme->quark_height)
2585         *result = env->rect.height;
2586       else if (env->object_width >= 0 &&
2587                t->d.v.name_quark == env->theme->quark_object_width)
2588         *result = env->object_width;
2589       else if (env->object_height >= 0 &&
2590                t->d.v.name_quark == env->theme->quark_object_height)
2591         *result = env->object_height;
2592       else if (t->d.v.name_quark == env->theme->quark_left_width)
2593         *result = env->left_width;
2594       else if (t->d.v.name_quark == env->theme->quark_right_width)
2595         *result = env->right_width;
2596       else if (t->d.v.name_quark == env->theme->quark_top_height)
2597         *result = env->top_height;
2598       else if (t->d.v.name_quark == env->theme->quark_bottom_height)
2599         *result = env->bottom_height;
2600       else if (t->d.v.name_quark == env->theme->quark_mini_icon_width)
2601         *result = env->mini_icon_width;
2602       else if (t->d.v.name_quark == env->theme->quark_mini_icon_height)
2603         *result = env->mini_icon_height;
2604       else if (t->d.v.name_quark == env->theme->quark_icon_width)
2605         *result = env->icon_width;
2606       else if (t->d.v.name_quark == env->theme->quark_icon_height)
2607         *result = env->icon_height;
2608       else if (t->d.v.name_quark == env->theme->quark_title_width)
2609         *result = env->title_width;
2610       else if (t->d.v.name_quark == env->theme->quark_title_height)
2611         *result = env->title_height;
2612       else if (t->d.v.name_quark == env->theme->quark_frame_x_center)
2613         *result = env->frame_x_center;
2614       else if (t->d.v.name_quark == env->theme->quark_frame_y_center)
2615         *result = env->frame_y_center;
2616       else
2617         {
2618           g_set_error (err, META_THEME_ERROR,
2619                        META_THEME_ERROR_UNKNOWN_VARIABLE,
2620                        _("Coordinate expression had unknown variable or constant \"%s\""),
2621                        t->d.v.name);
2622           return FALSE;
2623         }
2624     }
2625   else
2626     {
2627       if (strcmp (t->d.v.name, "width") == 0)
2628         *result = env->rect.width;
2629       else if (strcmp (t->d.v.name, "height") == 0)
2630         *result = env->rect.height;
2631       else if (env->object_width >= 0 &&
2632                strcmp (t->d.v.name, "object_width") == 0)
2633         *result = env->object_width;
2634       else if (env->object_height >= 0 &&
2635                strcmp (t->d.v.name, "object_height") == 0)
2636         *result = env->object_height;
2637       else if (strcmp (t->d.v.name, "left_width") == 0)
2638         *result = env->left_width;
2639       else if (strcmp (t->d.v.name, "right_width") == 0)
2640         *result = env->right_width;
2641       else if (strcmp (t->d.v.name, "top_height") == 0)
2642         *result = env->top_height;
2643       else if (strcmp (t->d.v.name, "bottom_height") == 0)
2644         *result = env->bottom_height;
2645       else if (strcmp (t->d.v.name, "mini_icon_width") == 0)
2646         *result = env->mini_icon_width;
2647       else if (strcmp (t->d.v.name, "mini_icon_height") == 0)
2648         *result = env->mini_icon_height;
2649       else if (strcmp (t->d.v.name, "icon_width") == 0)
2650         *result = env->icon_width;
2651       else if (strcmp (t->d.v.name, "icon_height") == 0)
2652         *result = env->icon_height;
2653       else if (strcmp (t->d.v.name, "title_width") == 0)
2654         *result = env->title_width;
2655       else if (strcmp (t->d.v.name, "title_height") == 0)
2656         *result = env->title_height;
2657       else if (strcmp (t->d.v.name, "frame_x_center") == 0)
2658         *result = env->frame_x_center;
2659       else if (strcmp (t->d.v.name, "frame_y_center") == 0)
2660         *result = env->frame_y_center;
2661       else
2662         {
2663           g_set_error (err, META_THEME_ERROR,
2664                        META_THEME_ERROR_UNKNOWN_VARIABLE,
2665                        _("Coordinate expression had unknown variable or constant \"%s\""),
2666                        t->d.v.name);
2667           return FALSE;
2668         }
2669     }
2670 
2671   return TRUE;
2672 }
2673 
2674 /**
2675  * Evaluates a sequence of tokens within a particular environment context,
2676  * and returns the current value. May recur if parantheses are found.
2677  *
2678  * \param tokens  A list of tokens to evaluate.
2679  * \param n_tokens  How many tokens are in the list.
2680  * \param env  The environment context in which to evaluate the expression.
2681  * \param[out] result  The current value of the expression
2682  *
2683  * \bug Yes, we really do reparse the expression every time it's evaluated.
2684  *      We should keep the parse tree around all the time and just
2685  *      run the new values through it.
2686  * \ingroup parser
2687  */
2688 static gboolean
pos_eval_helper(PosToken * tokens,int n_tokens,const MetaPositionExprEnv * env,PosExpr * result,GError ** err)2689 pos_eval_helper (PosToken                   *tokens,
2690                  int                         n_tokens,
2691                  const MetaPositionExprEnv  *env,
2692                  PosExpr                    *result,
2693                  GError                    **err)
2694 {
2695   /* Lazy-ass hardcoded limit on number of terms in expression */
2696 #define MAX_EXPRS 32
2697   int paren_level;
2698   int first_paren;
2699   int i;
2700   PosExpr exprs[MAX_EXPRS];
2701   int n_exprs;
2702   int precedence;
2703 
2704   /* Our first goal is to get a list of PosExpr, essentially
2705    * substituting variables and handling parentheses.
2706    */
2707 
2708   first_paren = 0;
2709   paren_level = 0;
2710   n_exprs = 0;
2711   for (i = 0; i < n_tokens; i++)
2712     {
2713       PosToken *t = &tokens[i];
2714 
2715       if (n_exprs >= MAX_EXPRS)
2716         {
2717           g_set_error (err, META_THEME_ERROR,
2718                        META_THEME_ERROR_FAILED,
2719                        _("Coordinate expression parser overflowed its buffer."));
2720           return FALSE;
2721         }
2722 
2723       if (paren_level == 0)
2724         {
2725           switch (t->type)
2726             {
2727             case POS_TOKEN_INT:
2728               exprs[n_exprs].type = POS_EXPR_INT;
2729               exprs[n_exprs].d.int_val = t->d.i.val;
2730               ++n_exprs;
2731               break;
2732 
2733             case POS_TOKEN_DOUBLE:
2734               exprs[n_exprs].type = POS_EXPR_DOUBLE;
2735               exprs[n_exprs].d.double_val = t->d.d.val;
2736               ++n_exprs;
2737               break;
2738 
2739             case POS_TOKEN_OPEN_PAREN:
2740               ++paren_level;
2741               if (paren_level == 1)
2742                 first_paren = i;
2743               break;
2744 
2745             case POS_TOKEN_CLOSE_PAREN:
2746               g_set_error (err, META_THEME_ERROR,
2747                            META_THEME_ERROR_BAD_PARENS,
2748                            _("Coordinate expression had a close parenthesis with no open parenthesis"));
2749               return FALSE;
2750 
2751             case POS_TOKEN_VARIABLE:
2752               exprs[n_exprs].type = POS_EXPR_INT;
2753 
2754               /* FIXME we should just dump all this crap
2755                * in a hash, maybe keep width/height out
2756                * for optimization purposes
2757                */
2758               if (!pos_eval_get_variable (t, &exprs[n_exprs].d.int_val, env, err))
2759                 return FALSE;
2760 
2761               ++n_exprs;
2762               break;
2763 
2764             case POS_TOKEN_OPERATOR:
2765               exprs[n_exprs].type = POS_EXPR_OPERATOR;
2766               exprs[n_exprs].d.operator = t->d.o.op;
2767               ++n_exprs;
2768               break;
2769             }
2770         }
2771       else
2772         {
2773           g_assert (paren_level > 0);
2774 
2775           switch (t->type)
2776             {
2777             case POS_TOKEN_INT:
2778             case POS_TOKEN_DOUBLE:
2779             case POS_TOKEN_VARIABLE:
2780             case POS_TOKEN_OPERATOR:
2781               break;
2782 
2783             case POS_TOKEN_OPEN_PAREN:
2784               ++paren_level;
2785               break;
2786 
2787             case POS_TOKEN_CLOSE_PAREN:
2788               if (paren_level == 1)
2789                 {
2790                   /* We closed a toplevel paren group, so recurse */
2791                   if (!pos_eval_helper (&tokens[first_paren+1],
2792                                         i - first_paren - 1,
2793                                         env,
2794                                         &exprs[n_exprs],
2795                                         err))
2796                     return FALSE;
2797 
2798                   ++n_exprs;
2799                 }
2800 
2801               --paren_level;
2802               break;
2803 
2804             }
2805         }
2806     }
2807 
2808   if (paren_level > 0)
2809     {
2810       g_set_error (err, META_THEME_ERROR,
2811                    META_THEME_ERROR_BAD_PARENS,
2812                    _("Coordinate expression had an open parenthesis with no close parenthesis"));
2813       return FALSE;
2814     }
2815 
2816   /* Now we have no parens and no vars; so we just do all the multiplies
2817    * and divides, then all the add and subtract.
2818    */
2819   if (n_exprs == 0)
2820     {
2821       g_set_error (err, META_THEME_ERROR,
2822                    META_THEME_ERROR_FAILED,
2823                    _("Coordinate expression doesn't seem to have any operators or operands"));
2824       return FALSE;
2825     }
2826 
2827   /* precedence 1 ops */
2828   precedence = 2;
2829   while (precedence >= 0)
2830     {
2831       if (!do_operations (exprs, &n_exprs, precedence, err))
2832         return FALSE;
2833       --precedence;
2834     }
2835 
2836   g_assert (n_exprs == 1);
2837 
2838   *result = *exprs;
2839 
2840   return TRUE;
2841 }
2842 
2843 /*
2844  *   expr = int | double | expr * expr | expr / expr |
2845  *          expr + expr | expr - expr | (expr)
2846  *
2847  *   so very not worth fooling with bison, yet so very painful by hand.
2848  */
2849 /**
2850  * Evaluates an expression.
2851  *
2852  * \param spec  The expression to evaluate.
2853  * \param env   The environment context to evaluate the expression in.
2854  * \param[out] val_p  The integer value of the expression; if the expression
2855  *                    is of type float, this will be rounded. If we return
2856  *                    FALSE because the expression is invalid, this will be
2857  *                    zero.
2858  * \param[out] err    The error, if anything went wrong.
2859  *
2860  * \return  True if we evaluated the expression successfully; false otherwise.
2861  *
2862  * \bug Shouldn't spec be const?
2863  * \ingroup parser
2864  */
2865 static gboolean
pos_eval(MetaDrawSpec * spec,const MetaPositionExprEnv * env,int * val_p,GError ** err)2866 pos_eval (MetaDrawSpec              *spec,
2867           const MetaPositionExprEnv *env,
2868           int                       *val_p,
2869           GError                   **err)
2870 {
2871   PosExpr expr;
2872 
2873   *val_p = 0;
2874 
2875   if (pos_eval_helper (spec->tokens, spec->n_tokens, env, &expr, err))
2876     {
2877       switch (expr.type)
2878         {
2879         case POS_EXPR_INT:
2880           *val_p = expr.d.int_val;
2881           break;
2882         case POS_EXPR_DOUBLE:
2883           *val_p = expr.d.double_val;
2884           break;
2885         case POS_EXPR_OPERATOR:
2886           g_assert_not_reached ();
2887           break;
2888         }
2889       return TRUE;
2890     }
2891   else
2892     {
2893       return FALSE;
2894     }
2895 }
2896 
2897 /* We always return both X and Y, but only one will be meaningful in
2898  * most contexts.
2899  */
2900 
2901 gboolean
meta_parse_position_expression(MetaDrawSpec * spec,const MetaPositionExprEnv * env,int * x_return,int * y_return,GError ** err)2902 meta_parse_position_expression (MetaDrawSpec              *spec,
2903                                 const MetaPositionExprEnv *env,
2904                                 int                       *x_return,
2905                                 int                       *y_return,
2906                                 GError                   **err)
2907 {
2908   /* All positions are in a coordinate system with x, y at the origin.
2909    * The expression can have -, +, *, / as operators, floating point
2910    * or integer constants, and the variables "width" and "height" and
2911    * optionally "object_width" and object_height". Negative numbers
2912    * aren't allowed.
2913    */
2914   int val;
2915 
2916   if (spec->constant)
2917     val = spec->value;
2918   else
2919     {
2920       if (pos_eval (spec, env, &spec->value, err) == FALSE)
2921         {
2922           g_assert (err == NULL || *err != NULL);
2923           return FALSE;
2924         }
2925 
2926       val = spec->value;
2927     }
2928 
2929   if (x_return)
2930     *x_return = env->rect.x + val;
2931   if (y_return)
2932     *y_return = env->rect.y + val;
2933 
2934   return TRUE;
2935 }
2936 
2937 gboolean
meta_parse_size_expression(MetaDrawSpec * spec,const MetaPositionExprEnv * env,int * val_return,GError ** err)2938 meta_parse_size_expression (MetaDrawSpec              *spec,
2939                             const MetaPositionExprEnv *env,
2940                             int                       *val_return,
2941                             GError                   **err)
2942 {
2943   int val;
2944 
2945   if (spec->constant)
2946     val = spec->value;
2947   else
2948     {
2949       if (pos_eval (spec, env, &spec->value, err) == FALSE)
2950         {
2951           g_assert (err == NULL || *err != NULL);
2952           return FALSE;
2953         }
2954 
2955       val = spec->value;
2956     }
2957 
2958   if (val_return)
2959     *val_return = MAX (val, 1); /* require that sizes be at least 1x1 */
2960 
2961   return TRUE;
2962 }
2963 
2964 /* To do this we tokenize, replace variable tokens
2965  * that are constants, then reassemble. The purpose
2966  * here is to optimize expressions so we don't do hash
2967  * lookups to eval them. Obviously it's a tradeoff that
2968  * slows down theme load times.
2969  */
2970 gboolean
meta_theme_replace_constants(MetaTheme * theme,PosToken * tokens,int n_tokens,GError ** err)2971 meta_theme_replace_constants (MetaTheme   *theme,
2972                               PosToken    *tokens,
2973                               int          n_tokens,
2974                               GError     **err)
2975 {
2976   int i;
2977   double dval;
2978   int ival;
2979   gboolean is_constant = TRUE;
2980 
2981   /* Loop through tokenized string looking for variables to replace */
2982   for (i = 0; i < n_tokens; i++)
2983     {
2984       PosToken *t = &tokens[i];
2985 
2986       if (t->type == POS_TOKEN_VARIABLE)
2987         {
2988           if (meta_theme_lookup_int_constant (theme, t->d.v.name, &ival))
2989             {
2990               g_free (t->d.v.name);
2991               t->type = POS_TOKEN_INT;
2992               t->d.i.val = ival;
2993             }
2994           else if (meta_theme_lookup_float_constant (theme, t->d.v.name, &dval))
2995             {
2996               g_free (t->d.v.name);
2997               t->type = POS_TOKEN_DOUBLE;
2998               t->d.d.val = dval;
2999             }
3000           else
3001             {
3002               /* If we've found a variable that cannot be replaced then the
3003                  expression is not a constant expression and we want to
3004                  replace it with a GQuark */
3005 
3006               t->d.v.name_quark = g_quark_from_string (t->d.v.name);
3007               is_constant = FALSE;
3008             }
3009         }
3010     }
3011 
3012   return is_constant;
3013 }
3014 
3015 static int
parse_x_position_unchecked(MetaDrawSpec * spec,const MetaPositionExprEnv * env)3016 parse_x_position_unchecked (MetaDrawSpec              *spec,
3017                             const MetaPositionExprEnv *env)
3018 {
3019   int retval;
3020   GError *error;
3021 
3022   retval = 0;
3023   error = NULL;
3024   if (!meta_parse_position_expression (spec, env, &retval, NULL, &error))
3025     {
3026       meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
3027                     error->message);
3028 
3029       g_error_free (error);
3030     }
3031 
3032   return retval;
3033 }
3034 
3035 static int
parse_y_position_unchecked(MetaDrawSpec * spec,const MetaPositionExprEnv * env)3036 parse_y_position_unchecked (MetaDrawSpec              *spec,
3037                             const MetaPositionExprEnv *env)
3038 {
3039   int retval;
3040   GError *error;
3041 
3042   retval = 0;
3043   error = NULL;
3044   if (!meta_parse_position_expression (spec, env, NULL, &retval, &error))
3045     {
3046       meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
3047                     error->message);
3048 
3049       g_error_free (error);
3050     }
3051 
3052   return retval;
3053 }
3054 
3055 static int
parse_size_unchecked(MetaDrawSpec * spec,MetaPositionExprEnv * env)3056 parse_size_unchecked (MetaDrawSpec        *spec,
3057                       MetaPositionExprEnv *env)
3058 {
3059   int retval;
3060   GError *error;
3061 
3062   retval = 0;
3063   error = NULL;
3064   if (!meta_parse_size_expression (spec, env, &retval, &error))
3065     {
3066       meta_warning (_("Theme contained an expression that resulted in an error: %s\n"),
3067                     error->message);
3068 
3069       g_error_free (error);
3070     }
3071 
3072   return retval;
3073 }
3074 
3075 void
meta_draw_spec_free(MetaDrawSpec * spec)3076 meta_draw_spec_free (MetaDrawSpec *spec)
3077 {
3078   if (!spec) return;
3079   free_tokens (spec->tokens, spec->n_tokens);
3080   g_slice_free (MetaDrawSpec, spec);
3081 }
3082 
3083 MetaDrawSpec *
meta_draw_spec_new(MetaTheme * theme,const char * expr,GError ** error)3084 meta_draw_spec_new (MetaTheme  *theme,
3085                     const char *expr,
3086                     GError    **error)
3087 {
3088   MetaDrawSpec *spec;
3089 
3090   spec = g_slice_new0 (MetaDrawSpec);
3091 
3092   pos_tokenize (expr, &spec->tokens, &spec->n_tokens, NULL);
3093 
3094   spec->constant = meta_theme_replace_constants (theme, spec->tokens,
3095                                                  spec->n_tokens, NULL);
3096   if (spec->constant)
3097     {
3098       gboolean result;
3099 
3100       result = pos_eval (spec, NULL, &spec->value, error);
3101       if (result == FALSE)
3102         {
3103           meta_draw_spec_free (spec);
3104           return NULL;
3105         }
3106     }
3107 
3108   return spec;
3109 }
3110 
3111 MetaDrawOp*
meta_draw_op_new(MetaDrawType type)3112 meta_draw_op_new (MetaDrawType type)
3113 {
3114   MetaDrawOp *op;
3115   MetaDrawOp dummy;
3116   int size;
3117 
3118   size = G_STRUCT_OFFSET (MetaDrawOp, data);
3119 
3120   switch (type)
3121     {
3122     case META_DRAW_LINE:
3123       size += sizeof (dummy.data.line);
3124       break;
3125 
3126     case META_DRAW_RECTANGLE:
3127       size += sizeof (dummy.data.rectangle);
3128       break;
3129 
3130     case META_DRAW_ARC:
3131       size += sizeof (dummy.data.arc);
3132       break;
3133 
3134     case META_DRAW_CLIP:
3135       size += sizeof (dummy.data.clip);
3136       break;
3137 
3138     case META_DRAW_TINT:
3139       size += sizeof (dummy.data.tint);
3140       break;
3141 
3142     case META_DRAW_GRADIENT:
3143       size += sizeof (dummy.data.gradient);
3144       break;
3145 
3146     case META_DRAW_IMAGE:
3147       size += sizeof (dummy.data.image);
3148       break;
3149 
3150     case META_DRAW_GTK_ARROW:
3151       size += sizeof (dummy.data.gtk_arrow);
3152       break;
3153 
3154     case META_DRAW_GTK_BOX:
3155       size += sizeof (dummy.data.gtk_box);
3156       break;
3157 
3158     case META_DRAW_GTK_VLINE:
3159       size += sizeof (dummy.data.gtk_vline);
3160       break;
3161 
3162     case META_DRAW_ICON:
3163       size += sizeof (dummy.data.icon);
3164       break;
3165 
3166     case META_DRAW_TITLE:
3167       size += sizeof (dummy.data.title);
3168       break;
3169     case META_DRAW_OP_LIST:
3170       size += sizeof (dummy.data.op_list);
3171       break;
3172     case META_DRAW_TILE:
3173       size += sizeof (dummy.data.tile);
3174       break;
3175     }
3176 
3177   op = g_malloc0 (size);
3178 
3179   op->type = type;
3180 
3181   return op;
3182 }
3183 
3184 void
meta_draw_op_free(MetaDrawOp * op)3185 meta_draw_op_free (MetaDrawOp *op)
3186 {
3187   g_return_if_fail (op != NULL);
3188 
3189   switch (op->type)
3190     {
3191     case META_DRAW_LINE:
3192       if (op->data.line.color_spec)
3193         meta_color_spec_free (op->data.line.color_spec);
3194 
3195       meta_draw_spec_free (op->data.line.x1);
3196       meta_draw_spec_free (op->data.line.y1);
3197       meta_draw_spec_free (op->data.line.x2);
3198       meta_draw_spec_free (op->data.line.y2);
3199       break;
3200 
3201     case META_DRAW_RECTANGLE:
3202       if (op->data.rectangle.color_spec)
3203         g_free (op->data.rectangle.color_spec);
3204 
3205       meta_draw_spec_free (op->data.rectangle.x);
3206       meta_draw_spec_free (op->data.rectangle.y);
3207       meta_draw_spec_free (op->data.rectangle.width);
3208       meta_draw_spec_free (op->data.rectangle.height);
3209       break;
3210 
3211     case META_DRAW_ARC:
3212       if (op->data.arc.color_spec)
3213         g_free (op->data.arc.color_spec);
3214 
3215       meta_draw_spec_free (op->data.arc.x);
3216       meta_draw_spec_free (op->data.arc.y);
3217       meta_draw_spec_free (op->data.arc.width);
3218       meta_draw_spec_free (op->data.arc.height);
3219       break;
3220 
3221     case META_DRAW_CLIP:
3222       meta_draw_spec_free (op->data.clip.x);
3223       meta_draw_spec_free (op->data.clip.y);
3224       meta_draw_spec_free (op->data.clip.width);
3225       meta_draw_spec_free (op->data.clip.height);
3226       break;
3227 
3228     case META_DRAW_TINT:
3229       if (op->data.tint.color_spec)
3230         meta_color_spec_free (op->data.tint.color_spec);
3231 
3232       if (op->data.tint.alpha_spec)
3233         meta_alpha_gradient_spec_free (op->data.tint.alpha_spec);
3234 
3235       meta_draw_spec_free (op->data.tint.x);
3236       meta_draw_spec_free (op->data.tint.y);
3237       meta_draw_spec_free (op->data.tint.width);
3238       meta_draw_spec_free (op->data.tint.height);
3239       break;
3240 
3241     case META_DRAW_GRADIENT:
3242       if (op->data.gradient.gradient_spec)
3243         meta_gradient_spec_free (op->data.gradient.gradient_spec);
3244 
3245       if (op->data.gradient.alpha_spec)
3246         meta_alpha_gradient_spec_free (op->data.gradient.alpha_spec);
3247 
3248       meta_draw_spec_free (op->data.gradient.x);
3249       meta_draw_spec_free (op->data.gradient.y);
3250       meta_draw_spec_free (op->data.gradient.width);
3251       meta_draw_spec_free (op->data.gradient.height);
3252       break;
3253 
3254     case META_DRAW_IMAGE:
3255       if (op->data.image.alpha_spec)
3256         meta_alpha_gradient_spec_free (op->data.image.alpha_spec);
3257 
3258       if (op->data.image.pixbuf)
3259         g_object_unref (G_OBJECT (op->data.image.pixbuf));
3260 
3261       if (op->data.image.colorize_spec)
3262 	meta_color_spec_free (op->data.image.colorize_spec);
3263 
3264       if (op->data.image.colorize_cache_pixbuf)
3265         g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf));
3266 
3267       meta_draw_spec_free (op->data.image.x);
3268       meta_draw_spec_free (op->data.image.y);
3269       meta_draw_spec_free (op->data.image.width);
3270       meta_draw_spec_free (op->data.image.height);
3271       break;
3272 
3273     case META_DRAW_GTK_ARROW:
3274       meta_draw_spec_free (op->data.gtk_arrow.x);
3275       meta_draw_spec_free (op->data.gtk_arrow.y);
3276       meta_draw_spec_free (op->data.gtk_arrow.width);
3277       meta_draw_spec_free (op->data.gtk_arrow.height);
3278       break;
3279 
3280     case META_DRAW_GTK_BOX:
3281       meta_draw_spec_free (op->data.gtk_box.x);
3282       meta_draw_spec_free (op->data.gtk_box.y);
3283       meta_draw_spec_free (op->data.gtk_box.width);
3284       meta_draw_spec_free (op->data.gtk_box.height);
3285       break;
3286 
3287     case META_DRAW_GTK_VLINE:
3288       meta_draw_spec_free (op->data.gtk_vline.x);
3289       meta_draw_spec_free (op->data.gtk_vline.y1);
3290       meta_draw_spec_free (op->data.gtk_vline.y2);
3291       break;
3292 
3293     case META_DRAW_ICON:
3294       if (op->data.icon.alpha_spec)
3295         meta_alpha_gradient_spec_free (op->data.icon.alpha_spec);
3296 
3297       meta_draw_spec_free (op->data.icon.x);
3298       meta_draw_spec_free (op->data.icon.y);
3299       meta_draw_spec_free (op->data.icon.width);
3300       meta_draw_spec_free (op->data.icon.height);
3301       break;
3302 
3303     case META_DRAW_TITLE:
3304       if (op->data.title.color_spec)
3305         meta_color_spec_free (op->data.title.color_spec);
3306 
3307       meta_draw_spec_free (op->data.title.x);
3308       meta_draw_spec_free (op->data.title.y);
3309       if (op->data.title.ellipsize_width)
3310         meta_draw_spec_free (op->data.title.ellipsize_width);
3311       break;
3312 
3313     case META_DRAW_OP_LIST:
3314       if (op->data.op_list.op_list)
3315         meta_draw_op_list_unref (op->data.op_list.op_list);
3316 
3317       meta_draw_spec_free (op->data.op_list.x);
3318       meta_draw_spec_free (op->data.op_list.y);
3319       meta_draw_spec_free (op->data.op_list.width);
3320       meta_draw_spec_free (op->data.op_list.height);
3321       break;
3322 
3323     case META_DRAW_TILE:
3324       if (op->data.tile.op_list)
3325         meta_draw_op_list_unref (op->data.tile.op_list);
3326 
3327       meta_draw_spec_free (op->data.tile.x);
3328       meta_draw_spec_free (op->data.tile.y);
3329       meta_draw_spec_free (op->data.tile.width);
3330       meta_draw_spec_free (op->data.tile.height);
3331       meta_draw_spec_free (op->data.tile.tile_xoffset);
3332       meta_draw_spec_free (op->data.tile.tile_yoffset);
3333       meta_draw_spec_free (op->data.tile.tile_width);
3334       meta_draw_spec_free (op->data.tile.tile_height);
3335       break;
3336     }
3337 
3338   g_free (op);
3339 }
3340 
3341 static GdkPixbuf*
apply_alpha(GdkPixbuf * pixbuf,MetaAlphaGradientSpec * spec,gboolean force_copy)3342 apply_alpha (GdkPixbuf             *pixbuf,
3343              MetaAlphaGradientSpec *spec,
3344              gboolean               force_copy)
3345 {
3346   GdkPixbuf *new_pixbuf;
3347   gboolean needs_alpha;
3348 
3349   g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
3350 
3351   needs_alpha = spec && (spec->n_alphas > 1 ||
3352                          spec->alphas[0] != 0xff);
3353 
3354   if (!needs_alpha)
3355     return pixbuf;
3356 
3357   if (!gdk_pixbuf_get_has_alpha (pixbuf))
3358     {
3359       new_pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
3360       g_object_unref (G_OBJECT (pixbuf));
3361       pixbuf = new_pixbuf;
3362     }
3363   else if (force_copy)
3364     {
3365       new_pixbuf = gdk_pixbuf_copy (pixbuf);
3366       g_object_unref (G_OBJECT (pixbuf));
3367       pixbuf = new_pixbuf;
3368     }
3369 
3370   g_assert (gdk_pixbuf_get_has_alpha (pixbuf));
3371 
3372   meta_gradient_add_alpha (pixbuf, spec->alphas, spec->n_alphas, spec->type);
3373 
3374   return pixbuf;
3375 }
3376 
3377 static GdkPixbuf*
pixbuf_tile(GdkPixbuf * tile,int width,int height)3378 pixbuf_tile (GdkPixbuf *tile,
3379              int        width,
3380              int        height)
3381 {
3382   GdkPixbuf *pixbuf;
3383   int tile_width;
3384   int tile_height;
3385   int i, j;
3386 
3387   tile_width = gdk_pixbuf_get_width (tile);
3388   tile_height = gdk_pixbuf_get_height (tile);
3389 
3390   pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
3391                            gdk_pixbuf_get_has_alpha (tile),
3392                            8, width, height);
3393 
3394   i = 0;
3395   while (i < width)
3396     {
3397       j = 0;
3398       while (j < height)
3399         {
3400           int w, h;
3401 
3402           w = MIN (tile_width, width - i);
3403           h = MIN (tile_height, height - j);
3404 
3405           gdk_pixbuf_copy_area (tile,
3406                                 0, 0,
3407                                 w, h,
3408                                 pixbuf,
3409                                 i, j);
3410 
3411           j += tile_height;
3412         }
3413 
3414       i += tile_width;
3415     }
3416 
3417   return pixbuf;
3418 }
3419 
3420 static GdkPixbuf *
replicate_rows(GdkPixbuf * src,int src_x,int src_y,int width,int height)3421 replicate_rows (GdkPixbuf  *src,
3422                 int         src_x,
3423                 int         src_y,
3424                 int         width,
3425                 int         height)
3426 {
3427   unsigned int n_channels = gdk_pixbuf_get_n_channels (src);
3428   unsigned int src_rowstride = gdk_pixbuf_get_rowstride (src);
3429   unsigned char *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x
3430                            * n_channels);
3431   unsigned char *dest_pixels;
3432   GdkPixbuf *result;
3433   unsigned int dest_rowstride;
3434   int i;
3435 
3436   result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
3437                            width, height);
3438   dest_rowstride = gdk_pixbuf_get_rowstride (result);
3439   dest_pixels = gdk_pixbuf_get_pixels (result);
3440 
3441   for (i = 0; i < height; i++)
3442     memcpy (dest_pixels + dest_rowstride * i, pixels, n_channels * width);
3443 
3444   return result;
3445 }
3446 
3447 static GdkPixbuf *
replicate_cols(GdkPixbuf * src,int src_x,int src_y,int width,int height)3448 replicate_cols (GdkPixbuf  *src,
3449                 int         src_x,
3450                 int         src_y,
3451                 int         width,
3452                 int         height)
3453 {
3454   unsigned int n_channels = gdk_pixbuf_get_n_channels (src);
3455   unsigned int src_rowstride = gdk_pixbuf_get_rowstride (src);
3456   unsigned char *pixels = (gdk_pixbuf_get_pixels (src) + src_y * src_rowstride + src_x
3457                            * n_channels);
3458   unsigned char *dest_pixels;
3459   GdkPixbuf *result;
3460   unsigned int dest_rowstride;
3461   int i, j;
3462 
3463   result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, n_channels == 4, 8,
3464                            width, height);
3465   dest_rowstride = gdk_pixbuf_get_rowstride (result);
3466   dest_pixels = gdk_pixbuf_get_pixels (result);
3467 
3468   for (i = 0; i < height; i++)
3469     {
3470       unsigned char *p = dest_pixels + dest_rowstride * i;
3471       unsigned char *q = pixels + src_rowstride * i;
3472 
3473       unsigned char r = *(q++);
3474       unsigned char g = *(q++);
3475       unsigned char b = *(q++);
3476 
3477       if (n_channels == 4)
3478         {
3479           unsigned char a;
3480 
3481           a = *(q++);
3482 
3483           for (j = 0; j < width; j++)
3484             {
3485               *(p++) = r;
3486               *(p++) = g;
3487               *(p++) = b;
3488               *(p++) = a;
3489             }
3490         }
3491       else
3492         {
3493           for (j = 0; j < width; j++)
3494             {
3495               *(p++) = r;
3496               *(p++) = g;
3497               *(p++) = b;
3498             }
3499         }
3500     }
3501 
3502   return result;
3503 }
3504 
3505 static GdkPixbuf*
scale_and_alpha_pixbuf(GdkPixbuf * src,MetaAlphaGradientSpec * alpha_spec,MetaImageFillType fill_type,int width,int height,gboolean vertical_stripes,gboolean horizontal_stripes)3506 scale_and_alpha_pixbuf (GdkPixbuf             *src,
3507                         MetaAlphaGradientSpec *alpha_spec,
3508                         MetaImageFillType      fill_type,
3509                         int                    width,
3510                         int                    height,
3511                         gboolean               vertical_stripes,
3512                         gboolean               horizontal_stripes)
3513 {
3514   GdkPixbuf *pixbuf;
3515   GdkPixbuf *temp_pixbuf;
3516 
3517   pixbuf = NULL;
3518 
3519   pixbuf = src;
3520 
3521   if (gdk_pixbuf_get_width (pixbuf) == width &&
3522       gdk_pixbuf_get_height (pixbuf) == height)
3523     {
3524       g_object_ref (G_OBJECT (pixbuf));
3525     }
3526   else
3527     {
3528       if (fill_type == META_IMAGE_FILL_TILE)
3529         {
3530           pixbuf = pixbuf_tile (pixbuf, width, height);
3531         }
3532       else
3533         {
3534     	  int src_h, src_w, dest_h, dest_w;
3535           src_h = gdk_pixbuf_get_height (src);
3536           src_w = gdk_pixbuf_get_width (src);
3537 
3538           /* prefer to replicate_cols if possible, as that
3539            * is faster (no memory reads)
3540            */
3541           if (horizontal_stripes)
3542             {
3543               dest_w = gdk_pixbuf_get_width (src);
3544               dest_h = height;
3545             }
3546           else if (vertical_stripes)
3547             {
3548               dest_w = width;
3549               dest_h = gdk_pixbuf_get_height (src);
3550             }
3551 
3552           else
3553             {
3554               dest_w = width;
3555               dest_h = height;
3556             }
3557 
3558           if (dest_w == src_w && dest_h == src_h)
3559             {
3560               temp_pixbuf = src;
3561               g_object_ref (G_OBJECT (temp_pixbuf));
3562             }
3563           else
3564             {
3565               temp_pixbuf = gdk_pixbuf_scale_simple (src,
3566                                                      dest_w, dest_h,
3567                                                      GDK_INTERP_BILINEAR);
3568             }
3569 
3570           /* prefer to replicate_cols if possible, as that
3571            * is faster (no memory reads)
3572            */
3573           if (horizontal_stripes)
3574             {
3575               pixbuf = replicate_cols (temp_pixbuf, 0, 0, width, height);
3576               g_object_unref (G_OBJECT (temp_pixbuf));
3577             }
3578           else if (vertical_stripes)
3579             {
3580               pixbuf = replicate_rows (temp_pixbuf, 0, 0, width, height);
3581               g_object_unref (G_OBJECT (temp_pixbuf));
3582             }
3583           else
3584             {
3585               pixbuf = temp_pixbuf;
3586             }
3587         }
3588     }
3589 
3590   if (pixbuf)
3591     pixbuf = apply_alpha (pixbuf, alpha_spec, pixbuf == src);
3592 
3593   return pixbuf;
3594 }
3595 
3596 static GdkPixbuf*
draw_op_as_pixbuf(const MetaDrawOp * op,GtkStyleContext * style,const MetaDrawInfo * info,int width,int height)3597 draw_op_as_pixbuf (const MetaDrawOp    *op,
3598                    GtkStyleContext     *style,
3599                    const MetaDrawInfo  *info,
3600                    int                  width,
3601                    int                  height)
3602 {
3603   /* Try to get the op as a pixbuf, assuming w/h in the op
3604    * matches the width/height passed in. return NULL
3605    * if the op can't be converted to an equivalent pixbuf.
3606    */
3607   GdkPixbuf *pixbuf;
3608 
3609   pixbuf = NULL;
3610 
3611   switch (op->type)
3612     {
3613     case META_DRAW_LINE:
3614       break;
3615 
3616     case META_DRAW_RECTANGLE:
3617       if (op->data.rectangle.filled)
3618         {
3619           GdkRGBA color;
3620 
3621           meta_color_spec_render (op->data.rectangle.color_spec,
3622                                   style,
3623                                   &color);
3624 
3625           pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
3626                                    FALSE,
3627                                    8, width, height);
3628 
3629           gdk_pixbuf_fill (pixbuf, GDK_COLOR_RGBA (color));
3630         }
3631       break;
3632 
3633     case META_DRAW_ARC:
3634       break;
3635 
3636     case META_DRAW_CLIP:
3637       break;
3638 
3639     case META_DRAW_TINT:
3640       {
3641         GdkRGBA color;
3642         guint32 rgba;
3643         gboolean has_alpha;
3644 
3645         meta_color_spec_render (op->data.rectangle.color_spec,
3646                                 style,
3647                                 &color);
3648 
3649         has_alpha =
3650           op->data.tint.alpha_spec &&
3651           (op->data.tint.alpha_spec->n_alphas > 1 ||
3652            op->data.tint.alpha_spec->alphas[0] != 0xff);
3653 
3654         pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
3655                                  has_alpha,
3656                                  8, width, height);
3657 
3658         if (!has_alpha)
3659           {
3660             rgba = GDK_COLOR_RGBA (color);
3661 
3662             gdk_pixbuf_fill (pixbuf, rgba);
3663           }
3664         else if (op->data.tint.alpha_spec->n_alphas == 1)
3665           {
3666             rgba = GDK_COLOR_RGBA (color);
3667             rgba &= ~0xff;
3668             rgba |= op->data.tint.alpha_spec->alphas[0];
3669 
3670             gdk_pixbuf_fill (pixbuf, rgba);
3671           }
3672         else
3673           {
3674             rgba = GDK_COLOR_RGBA (color);
3675 
3676             gdk_pixbuf_fill (pixbuf, rgba);
3677 
3678             meta_gradient_add_alpha (pixbuf,
3679                                      op->data.tint.alpha_spec->alphas,
3680                                      op->data.tint.alpha_spec->n_alphas,
3681                                      op->data.tint.alpha_spec->type);
3682           }
3683       }
3684       break;
3685 
3686     case META_DRAW_IMAGE:
3687       {
3688 	if (op->data.image.colorize_spec)
3689 	  {
3690 	    GdkRGBA color;
3691 
3692             meta_color_spec_render (op->data.image.colorize_spec,
3693                                     style, &color);
3694 
3695             if (op->data.image.colorize_cache_pixbuf == NULL ||
3696                 op->data.image.colorize_cache_pixel != GDK_COLOR_RGB (color))
3697               {
3698                 if (op->data.image.colorize_cache_pixbuf)
3699                   g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf));
3700 
3701                 /* const cast here */
3702                 ((MetaDrawOp*)op)->data.image.colorize_cache_pixbuf =
3703                   colorize_pixbuf (op->data.image.pixbuf,
3704                                    &color);
3705                 ((MetaDrawOp*)op)->data.image.colorize_cache_pixel =
3706                   GDK_COLOR_RGB (color);
3707               }
3708 
3709             if (op->data.image.colorize_cache_pixbuf)
3710               {
3711                 pixbuf = scale_and_alpha_pixbuf (op->data.image.colorize_cache_pixbuf,
3712                                                  op->data.image.alpha_spec,
3713                                                  op->data.image.fill_type,
3714                                                  width, height,
3715                                                  op->data.image.vertical_stripes,
3716                                                  op->data.image.horizontal_stripes);
3717               }
3718 	  }
3719 	else
3720 	  {
3721 	    pixbuf = scale_and_alpha_pixbuf (op->data.image.pixbuf,
3722                                              op->data.image.alpha_spec,
3723                                              op->data.image.fill_type,
3724                                              width, height,
3725                                              op->data.image.vertical_stripes,
3726                                              op->data.image.horizontal_stripes);
3727 	  }
3728         break;
3729       }
3730 
3731     case META_DRAW_GRADIENT:
3732     case META_DRAW_GTK_ARROW:
3733     case META_DRAW_GTK_BOX:
3734     case META_DRAW_GTK_VLINE:
3735       break;
3736 
3737     case META_DRAW_ICON:
3738       if (info->mini_icon &&
3739           width <= gdk_pixbuf_get_width (info->mini_icon) &&
3740           height <= gdk_pixbuf_get_height (info->mini_icon))
3741         pixbuf = scale_and_alpha_pixbuf (info->mini_icon,
3742                                          op->data.icon.alpha_spec,
3743                                          op->data.icon.fill_type,
3744                                          width, height,
3745                                          FALSE, FALSE);
3746       else if (info->icon)
3747         pixbuf = scale_and_alpha_pixbuf (info->icon,
3748                                          op->data.icon.alpha_spec,
3749                                          op->data.icon.fill_type,
3750                                          width, height,
3751                                          FALSE, FALSE);
3752       break;
3753 
3754     case META_DRAW_TITLE:
3755       break;
3756 
3757     case META_DRAW_OP_LIST:
3758       break;
3759 
3760     case META_DRAW_TILE:
3761       break;
3762     }
3763 
3764   return pixbuf;
3765 }
3766 
3767 static cairo_surface_t *
draw_op_as_surface(const MetaDrawOp * op,GtkStyleContext * style,const MetaDrawInfo * info,gdouble width,gdouble height)3768 draw_op_as_surface (const MetaDrawOp   *op,
3769                     GtkStyleContext    *style,
3770                     const MetaDrawInfo *info,
3771                     gdouble             width,
3772                     gdouble             height)
3773 {
3774   cairo_surface_t *surface;
3775 
3776   surface = NULL;
3777 
3778   switch (op->type)
3779     {
3780     case META_DRAW_IMAGE:
3781       {
3782         if (op->data.image.colorize_spec)
3783           {
3784             GdkRGBA color;
3785 
3786             meta_color_spec_render (op->data.image.colorize_spec,
3787                                     style, &color);
3788 
3789             if (op->data.image.colorize_cache_pixbuf == NULL ||
3790                 op->data.image.colorize_cache_pixel != GDK_COLOR_RGB (color))
3791               {
3792                 if (op->data.image.colorize_cache_pixbuf)
3793                   g_object_unref (G_OBJECT (op->data.image.colorize_cache_pixbuf));
3794 
3795                 /* const cast here */
3796                 ((MetaDrawOp*)op)->data.image.colorize_cache_pixbuf =
3797                   colorize_pixbuf (op->data.image.pixbuf,
3798                                    &color);
3799                 ((MetaDrawOp*)op)->data.image.colorize_cache_pixel =
3800                   GDK_COLOR_RGB (color);
3801               }
3802 
3803             if (op->data.image.colorize_cache_pixbuf)
3804               {
3805                 surface = get_surface_from_pixbuf (op->data.image.colorize_cache_pixbuf,
3806                                                    op->data.image.fill_type,
3807                                                    width, height,
3808                                                    op->data.image.vertical_stripes,
3809                                                    op->data.image.horizontal_stripes);
3810               }
3811           }
3812         else
3813           {
3814             surface = get_surface_from_pixbuf (op->data.image.pixbuf,
3815                                                op->data.image.fill_type,
3816                                                width, height,
3817                                                op->data.image.vertical_stripes,
3818                                                op->data.image.horizontal_stripes);
3819           }
3820         break;
3821       }
3822 
3823     case META_DRAW_ICON:
3824       if (info->mini_icon &&
3825           width <= gdk_pixbuf_get_width (info->mini_icon) &&
3826           height <= gdk_pixbuf_get_height (info->mini_icon))
3827         surface = get_surface_from_pixbuf (info->mini_icon, op->data.icon.fill_type,
3828                                            width, height, FALSE, FALSE);
3829       else if (info->icon)
3830         surface = get_surface_from_pixbuf (info->icon, op->data.icon.fill_type,
3831                                            width, height, FALSE, FALSE);
3832       break;
3833 
3834     case META_DRAW_TINT:
3835     case META_DRAW_LINE:
3836     case META_DRAW_RECTANGLE:
3837     case META_DRAW_ARC:
3838     case META_DRAW_CLIP:
3839     case META_DRAW_GRADIENT:
3840     case META_DRAW_GTK_ARROW:
3841     case META_DRAW_GTK_BOX:
3842     case META_DRAW_GTK_VLINE:
3843     case META_DRAW_TITLE:
3844     case META_DRAW_OP_LIST:
3845     case META_DRAW_TILE:
3846       break;
3847 
3848     default:
3849       break;
3850     }
3851 
3852   return surface;
3853 }
3854 
3855 static void
fill_env(MetaPositionExprEnv * env,const MetaDrawInfo * info,MetaRectangle logical_region)3856 fill_env (MetaPositionExprEnv *env,
3857           const MetaDrawInfo  *info,
3858           MetaRectangle        logical_region)
3859 {
3860   /* FIXME this stuff could be raised into draw_op_list_draw() probably
3861    */
3862   env->rect = logical_region;
3863   env->object_width = -1;
3864   env->object_height = -1;
3865   if (info->fgeom)
3866     {
3867       env->left_width = info->fgeom->borders.visible.left;
3868       env->right_width = info->fgeom->borders.visible.right;
3869       env->top_height = info->fgeom->borders.visible.top;
3870       env->bottom_height = info->fgeom->borders.visible.bottom;
3871       env->frame_x_center = info->fgeom->width / 2 - logical_region.x;
3872       env->frame_y_center = info->fgeom->height / 2 - logical_region.y;
3873     }
3874   else
3875     {
3876       env->left_width = 0;
3877       env->right_width = 0;
3878       env->top_height = 0;
3879       env->bottom_height = 0;
3880       env->frame_x_center = 0;
3881       env->frame_y_center = 0;
3882     }
3883 
3884   env->mini_icon_width = info->mini_icon ? gdk_pixbuf_get_width (info->mini_icon) : 0;
3885   env->mini_icon_height = info->mini_icon ? gdk_pixbuf_get_height (info->mini_icon) : 0;
3886   env->icon_width = info->icon ? gdk_pixbuf_get_width (info->icon) : 0;
3887   env->icon_height = info->icon ? gdk_pixbuf_get_height (info->icon) : 0;
3888 
3889   env->title_width = info->title_layout_width;
3890   env->title_height = info->title_layout_height;
3891   env->theme = meta_current_theme;
3892 }
3893 
3894 /* This code was originally rendering anti-aliased using X primitives, and
3895  * now has been switched to draw anti-aliased using cairo. In general, the
3896  * closest correspondence between X rendering and cairo rendering is given
3897  * by offsetting the geometry by 0.5 pixels in both directions before rendering
3898  * with cairo. This is because X samples at the upper left corner of the
3899  * pixel while cairo averages over the entire pixel. However, in the cases
3900  * where the X rendering was an exact rectangle with no "jaggies"
3901  * we need to be a bit careful about applying the offset. We want to produce
3902  * the exact same pixel-aligned rectangle, rather than a rectangle with
3903  * fuzz around the edges.
3904  */
3905 static void
meta_draw_op_draw_with_env(const MetaDrawOp * op,GtkStyleContext * style_gtk,cairo_t * cr,const MetaDrawInfo * info,MetaRectangle rect,MetaPositionExprEnv * env)3906 meta_draw_op_draw_with_env (const MetaDrawOp    *op,
3907                             GtkStyleContext     *style_gtk,
3908                             cairo_t             *cr,
3909                             const MetaDrawInfo  *info,
3910                             MetaRectangle        rect,
3911                             MetaPositionExprEnv *env)
3912 {
3913   GdkRGBA color;
3914 
3915   cairo_save (cr);
3916   gtk_style_context_save (style_gtk);
3917 
3918   cairo_set_line_width (cr, 1.0);
3919 
3920   switch (op->type)
3921     {
3922     case META_DRAW_LINE:
3923       {
3924         int x1, x2, y1, y2;
3925 
3926         meta_color_spec_render (op->data.line.color_spec, style_gtk, &color);
3927         gdk_cairo_set_source_rgba (cr, &color);
3928 
3929         if (op->data.line.width > 0)
3930           cairo_set_line_width (cr, op->data.line.width);
3931 
3932         if (op->data.line.dash_on_length > 0 &&
3933             op->data.line.dash_off_length > 0)
3934           {
3935             double dash_list[2];
3936             dash_list[0] = op->data.line.dash_on_length;
3937             dash_list[1] = op->data.line.dash_off_length;
3938             cairo_set_dash (cr, dash_list, 2, 0);
3939           }
3940 
3941         x1 = parse_x_position_unchecked (op->data.line.x1, env);
3942         y1 = parse_y_position_unchecked (op->data.line.y1, env);
3943 
3944         if (!op->data.line.x2 &&
3945             !op->data.line.y2 &&
3946             op->data.line.width==0)
3947           {
3948             cairo_rectangle (cr, x1, y1, 1, 1);
3949             cairo_fill (cr);
3950           }
3951         else
3952           {
3953             if (op->data.line.x2)
3954               x2 = parse_x_position_unchecked (op->data.line.x2, env);
3955             else
3956               x2 = x1;
3957 
3958             if (op->data.line.y2)
3959               y2 = parse_y_position_unchecked (op->data.line.y2, env);
3960             else
3961               y2 = y1;
3962 
3963             /* This is one of the cases where we are matching the exact
3964              * pixel aligned rectangle produced by X; for zero-width lines
3965              * the generic algorithm produces the right result so we don't
3966              * need to handle them here.
3967              */
3968             if ((y1 == y2 || x1 == x2) && op->data.line.width != 0)
3969               {
3970                 double offset = op->data.line.width % 2 ? .5 : 0;
3971 
3972                 if (y1 == y2)
3973                   {
3974                     cairo_move_to (cr, x1, y1 + offset);
3975                     cairo_line_to (cr, x2, y2 + offset);
3976                   }
3977                 else
3978                   {
3979                     cairo_move_to (cr, x1 + offset, y1);
3980                     cairo_line_to (cr, x2 + offset, y2);
3981                   }
3982               }
3983             else
3984               {
3985                 /* zero-width lines include both end-points in X, unlike wide lines */
3986                 if (op->data.line.width == 0)
3987                   cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
3988 
3989                 cairo_move_to (cr, x1 + .5, y1 + .5);
3990                 cairo_line_to (cr, x2 + .5, y2 + .5);
3991               }
3992             cairo_stroke (cr);
3993           }
3994       }
3995       break;
3996 
3997     case META_DRAW_RECTANGLE:
3998       {
3999         int rx, ry, rwidth, rheight;
4000 
4001         meta_color_spec_render (op->data.rectangle.color_spec, style_gtk, &color);
4002         gdk_cairo_set_source_rgba (cr, &color);
4003 
4004         rx = parse_x_position_unchecked (op->data.rectangle.x, env);
4005         ry = parse_y_position_unchecked (op->data.rectangle.y, env);
4006         rwidth = parse_size_unchecked (op->data.rectangle.width, env);
4007         rheight = parse_size_unchecked (op->data.rectangle.height, env);
4008 
4009         /* Filled and stroked rectangles are the other cases
4010          * we pixel-align to X rasterization
4011          */
4012         if (op->data.rectangle.filled)
4013           {
4014             cairo_rectangle (cr, rx, ry, rwidth, rheight);
4015             cairo_fill (cr);
4016           }
4017         else
4018           {
4019             cairo_rectangle (cr, rx + .5, ry + .5, rwidth, rheight);
4020             cairo_stroke (cr);
4021           }
4022       }
4023       break;
4024 
4025     case META_DRAW_ARC:
4026       {
4027         int rx, ry, rwidth, rheight;
4028         double start_angle, end_angle;
4029         double center_x, center_y;
4030 
4031         meta_color_spec_render (op->data.arc.color_spec, style_gtk, &color);
4032         gdk_cairo_set_source_rgba (cr, &color);
4033 
4034         rx = parse_x_position_unchecked (op->data.arc.x, env);
4035         ry = parse_y_position_unchecked (op->data.arc.y, env);
4036         rwidth = parse_size_unchecked (op->data.arc.width, env);
4037         rheight = parse_size_unchecked (op->data.arc.height, env);
4038 
4039         start_angle = op->data.arc.start_angle * (M_PI / 180.)
4040                       - (.5 * M_PI); /* start at 12 instead of 3 oclock */
4041         end_angle = start_angle + op->data.arc.extent_angle * (M_PI / 180.);
4042         center_x = rx + (double)rwidth / 2. + .5;
4043         center_y = ry + (double)rheight / 2. + .5;
4044 
4045         cairo_save (cr);
4046 
4047         cairo_translate (cr, center_x, center_y);
4048         cairo_scale (cr, (double)rwidth / 2., (double)rheight / 2.);
4049 
4050         if (op->data.arc.extent_angle >= 0)
4051           cairo_arc (cr, 0, 0, 1, start_angle, end_angle);
4052         else
4053           cairo_arc_negative (cr, 0, 0, 1, start_angle, end_angle);
4054 
4055         cairo_restore (cr);
4056 
4057         if (op->data.arc.filled)
4058           {
4059             cairo_line_to (cr, center_x, center_y);
4060             cairo_fill (cr);
4061           }
4062         else
4063           cairo_stroke (cr);
4064       }
4065       break;
4066 
4067     case META_DRAW_CLIP:
4068       break;
4069 
4070     case META_DRAW_TINT:
4071       {
4072         int rx, ry, rwidth, rheight;
4073         gboolean needs_alpha;
4074 
4075         needs_alpha = op->data.tint.alpha_spec &&
4076           (op->data.tint.alpha_spec->n_alphas > 1 ||
4077            op->data.tint.alpha_spec->alphas[0] != 0xff);
4078 
4079         rx = parse_x_position_unchecked (op->data.tint.x, env);
4080         ry = parse_y_position_unchecked (op->data.tint.y, env);
4081         rwidth = parse_size_unchecked (op->data.tint.width, env);
4082         rheight = parse_size_unchecked (op->data.tint.height, env);
4083 
4084         if (!needs_alpha)
4085           {
4086             meta_color_spec_render (op->data.tint.color_spec, style_gtk, &color);
4087             gdk_cairo_set_source_rgba (cr, &color);
4088 
4089             cairo_rectangle (cr, rx, ry, rwidth, rheight);
4090             cairo_fill (cr);
4091           }
4092         else
4093           {
4094             GdkPixbuf *pixbuf;
4095 
4096             pixbuf = draw_op_as_pixbuf (op, style_gtk, info,
4097                                         rwidth, rheight);
4098 
4099             if (pixbuf)
4100               {
4101                 gdk_cairo_set_source_pixbuf (cr, pixbuf, rx, ry);
4102                 cairo_paint (cr);
4103 
4104                 g_object_unref (G_OBJECT (pixbuf));
4105               }
4106           }
4107       }
4108       break;
4109 
4110     case META_DRAW_GRADIENT:
4111       {
4112         int rx, ry, rwidth, rheight;
4113 
4114         rx = parse_x_position_unchecked (op->data.gradient.x, env);
4115         ry = parse_y_position_unchecked (op->data.gradient.y, env);
4116         rwidth = parse_size_unchecked (op->data.gradient.width, env);
4117         rheight = parse_size_unchecked (op->data.gradient.height, env);
4118 
4119         meta_gradient_spec_render (op->data.gradient.gradient_spec,
4120                                    op->data.gradient.alpha_spec,
4121                                    cr, style_gtk, rx, ry, rwidth, rheight);
4122       }
4123       break;
4124 
4125     case META_DRAW_IMAGE:
4126       {
4127         gint scale;
4128         gdouble rx, ry, rwidth, rheight;
4129         cairo_surface_t *surface;
4130 
4131         scale = gdk_window_get_scale_factor (gdk_get_default_root_window ());
4132         cairo_scale (cr, 1.0 / scale, 1.0 / scale);
4133 
4134         if (op->data.image.pixbuf)
4135           {
4136             env->object_width = gdk_pixbuf_get_width (op->data.image.pixbuf) / scale;
4137             env->object_height = gdk_pixbuf_get_height (op->data.image.pixbuf) / scale;
4138           }
4139 
4140         rwidth = parse_size_unchecked (op->data.image.width, env) * scale;
4141         rheight = parse_size_unchecked (op->data.image.height, env) * scale;
4142 
4143         surface = draw_op_as_surface (op, style_gtk, info, rwidth, rheight);
4144 
4145         if (surface)
4146           {
4147             rx = parse_x_position_unchecked (op->data.image.x, env) * scale;
4148             ry = parse_y_position_unchecked (op->data.image.y, env) * scale;
4149 
4150             cairo_set_source_surface (cr, surface, rx, ry);
4151 
4152             if (op->data.image.alpha_spec)
4153               {
4154                 cairo_pattern_t *pattern;
4155 
4156                 cairo_translate (cr, rx, ry);
4157                 cairo_scale (cr, rwidth, rheight);
4158 
4159                 pattern = meta_alpha_gradient_spec_get_mask (op->data.image.alpha_spec);
4160                 cairo_mask (cr, pattern);
4161 
4162                 cairo_pattern_destroy (pattern);
4163               }
4164             else
4165               {
4166                 cairo_paint (cr);
4167               }
4168 
4169             cairo_surface_destroy (surface);
4170           }
4171       }
4172       break;
4173 
4174     case META_DRAW_GTK_ARROW:
4175       {
4176         int rx, ry, rwidth, rheight;
4177 
4178         rx = parse_x_position_unchecked (op->data.gtk_arrow.x, env);
4179         ry = parse_y_position_unchecked (op->data.gtk_arrow.y, env);
4180         rwidth = parse_size_unchecked (op->data.gtk_arrow.width, env);
4181         rheight = parse_size_unchecked (op->data.gtk_arrow.height, env);
4182 
4183         double size = MAX(rwidth, rheight), angle = 0;
4184 
4185         switch (op->data.gtk_arrow.arrow)
4186           {
4187           case GTK_ARROW_UP:
4188             angle = 0;
4189             break;
4190           case GTK_ARROW_RIGHT:
4191             angle = M_PI / 2;
4192             break;
4193           case GTK_ARROW_DOWN:
4194             angle = M_PI;
4195             break;
4196           case GTK_ARROW_LEFT:
4197             angle = 3 * M_PI / 2;
4198             break;
4199           case GTK_ARROW_NONE:
4200             return;
4201           }
4202 
4203         gtk_style_context_set_state (style_gtk, op->data.gtk_arrow.state);
4204         gtk_render_arrow (style_gtk, cr, angle, rx, ry, size);
4205       }
4206       break;
4207 
4208     case META_DRAW_GTK_BOX:
4209       {
4210         int rx, ry, rwidth, rheight;
4211 
4212         rx = parse_x_position_unchecked (op->data.gtk_box.x, env);
4213         ry = parse_y_position_unchecked (op->data.gtk_box.y, env);
4214         rwidth = parse_size_unchecked (op->data.gtk_box.width, env);
4215         rheight = parse_size_unchecked (op->data.gtk_box.height, env);
4216 
4217         gtk_style_context_set_state (style_gtk, op->data.gtk_box.state);
4218         gtk_render_background (style_gtk, cr, rx, ry, rwidth, rheight);
4219         gtk_render_frame (style_gtk, cr, rx, ry, rwidth, rheight);
4220       }
4221       break;
4222 
4223     case META_DRAW_GTK_VLINE:
4224       {
4225         int rx, ry1, ry2;
4226 
4227         rx = parse_x_position_unchecked (op->data.gtk_vline.x, env);
4228         ry1 = parse_y_position_unchecked (op->data.gtk_vline.y1, env);
4229         ry2 = parse_y_position_unchecked (op->data.gtk_vline.y2, env);
4230 
4231         gtk_style_context_set_state (style_gtk, op->data.gtk_vline.state);
4232         gtk_render_line (style_gtk, cr, rx, ry1, rx, ry2);
4233       }
4234       break;
4235 
4236     case META_DRAW_ICON:
4237       {
4238         gint scale;
4239         gdouble rx, ry, rwidth, rheight;
4240         cairo_surface_t *surface;
4241 
4242         scale = gdk_window_get_scale_factor (gdk_get_default_root_window ());
4243         cairo_scale (cr, 1.0 / scale, 1.0 / scale);
4244 
4245         rwidth = parse_size_unchecked (op->data.icon.width, env) * scale;
4246         rheight = parse_size_unchecked (op->data.icon.height, env) * scale;
4247 
4248         surface = draw_op_as_surface (op, style_gtk, info, rwidth, rheight);
4249 
4250         if (surface)
4251           {
4252             rx = parse_x_position_unchecked (op->data.icon.x, env) * scale;
4253             ry = parse_y_position_unchecked (op->data.icon.y, env) * scale;
4254 
4255             cairo_set_source_surface (cr, surface, rx, ry);
4256 
4257             if (op->data.icon.alpha_spec)
4258               {
4259                 cairo_pattern_t *pattern;
4260 
4261                 cairo_translate (cr, rx, ry);
4262                 cairo_scale (cr, rwidth, rheight);
4263 
4264                 pattern = meta_alpha_gradient_spec_get_mask (op->data.icon.alpha_spec);
4265                 cairo_mask (cr, pattern);
4266 
4267                 cairo_pattern_destroy (pattern);
4268               }
4269             else
4270               {
4271                 cairo_paint (cr);
4272               }
4273 
4274             cairo_surface_destroy (surface);
4275           }
4276       }
4277       break;
4278 
4279     case META_DRAW_TITLE:
4280       if (info->title_layout)
4281         {
4282           int rx, ry;
4283           PangoRectangle ink_rect, logical_rect;
4284 
4285           meta_color_spec_render (op->data.title.color_spec, style_gtk, &color);
4286           gdk_cairo_set_source_rgba (cr, &color);
4287 
4288           rx = parse_x_position_unchecked (op->data.title.x, env);
4289           ry = parse_y_position_unchecked (op->data.title.y, env);
4290 
4291           if (op->data.title.ellipsize_width)
4292             {
4293               int ellipsize_width;
4294               int right_bearing;
4295 
4296               ellipsize_width = parse_x_position_unchecked (op->data.title.ellipsize_width, env);
4297               /* HACK: parse_x_position_unchecked adds in env->rect.x, subtract out again */
4298               ellipsize_width -= env->rect.x;
4299 
4300               pango_layout_set_width (info->title_layout, -1);
4301               pango_layout_get_pixel_extents (info->title_layout,
4302                                               &ink_rect, &logical_rect);
4303 
4304               /* Pango's idea of ellipsization is with respect to the logical rect.
4305                * correct for this, by reducing the ellipsization width by the overflow
4306                * of the un-ellipsized text on the right... it's always the visual
4307                * right we want regardless of bidi, since since the X we pass in to
4308                * cairo_move_to() is always the left edge of the line.
4309                */
4310               right_bearing = (ink_rect.x + ink_rect.width) - (logical_rect.x + logical_rect.width);
4311               right_bearing = MAX (right_bearing, 0);
4312 
4313               ellipsize_width -= right_bearing;
4314               ellipsize_width = MAX (ellipsize_width, 0);
4315 
4316               /* Only ellipsizing when necessary is a performance optimization -
4317                * pango_layout_set_width() will force a relayout if it isn't the
4318                * same as the current width of -1.
4319                */
4320               if (ellipsize_width < logical_rect.width)
4321                 pango_layout_set_width (info->title_layout, PANGO_SCALE * ellipsize_width);
4322             }
4323           else if (rx - env->rect.x + env->title_width >= env->rect.width)
4324           {
4325             const double alpha_margin = 30.0;
4326             int text_space = env->rect.x + env->rect.width -
4327                              (rx - env->rect.x) - env->right_width;
4328 
4329             double startalpha = 1.0 - (alpha_margin/((double)text_space));
4330 
4331             cairo_pattern_t *linpat;
4332             linpat = cairo_pattern_create_linear (rx, ry, text_space,
4333                                                   env->title_height);
4334             cairo_pattern_add_color_stop_rgba (linpat, 0, color.red,
4335                                                           color.green,
4336                                                           color.blue,
4337                                                           color.alpha);
4338             cairo_pattern_add_color_stop_rgba (linpat, startalpha,
4339                                                        color.red,
4340                                                        color.green,
4341                                                        color.blue,
4342                                                        color.alpha);
4343             cairo_pattern_add_color_stop_rgba (linpat, 1, color.red,
4344                                                           color.green,
4345                                                           color.blue, 0);
4346             cairo_set_source(cr, linpat);
4347             cairo_pattern_destroy(linpat);
4348           }
4349 
4350           cairo_move_to (cr, rx, ry);
4351           pango_cairo_show_layout (cr, info->title_layout);
4352 
4353           /* Remove any ellipsization we might have set; will short-circuit
4354            * if the width is already -1 */
4355           pango_layout_set_width (info->title_layout, -1);
4356         }
4357       break;
4358 
4359     case META_DRAW_OP_LIST:
4360       {
4361         MetaRectangle d_rect;
4362 
4363         d_rect.x = parse_x_position_unchecked (op->data.op_list.x, env);
4364         d_rect.y = parse_y_position_unchecked (op->data.op_list.y, env);
4365         d_rect.width = parse_size_unchecked (op->data.op_list.width, env);
4366         d_rect.height = parse_size_unchecked (op->data.op_list.height, env);
4367 
4368         meta_draw_op_list_draw_with_style (op->data.op_list.op_list,
4369                                            style_gtk,
4370                                            cr,
4371                                            info, d_rect);
4372       }
4373       break;
4374 
4375     case META_DRAW_TILE:
4376       {
4377         int rx, ry, rwidth, rheight;
4378         int tile_xoffset, tile_yoffset;
4379         MetaRectangle tile;
4380 
4381         rx = parse_x_position_unchecked (op->data.tile.x, env);
4382         ry = parse_y_position_unchecked (op->data.tile.y, env);
4383         rwidth = parse_size_unchecked (op->data.tile.width, env);
4384         rheight = parse_size_unchecked (op->data.tile.height, env);
4385 
4386         cairo_save (cr);
4387 
4388         cairo_rectangle (cr, rx, ry, rwidth, rheight);
4389         cairo_clip (cr);
4390 
4391         tile_xoffset = parse_x_position_unchecked (op->data.tile.tile_xoffset, env);
4392         tile_yoffset = parse_y_position_unchecked (op->data.tile.tile_yoffset, env);
4393         /* tile offset should not include x/y */
4394         tile_xoffset -= rect.x;
4395         tile_yoffset -= rect.y;
4396 
4397         tile.width = parse_size_unchecked (op->data.tile.tile_width, env);
4398         tile.height = parse_size_unchecked (op->data.tile.tile_height, env);
4399 
4400         tile.x = rx - tile_xoffset;
4401 
4402         while (tile.x < (rx + rwidth))
4403           {
4404             tile.y = ry - tile_yoffset;
4405             while (tile.y < (ry + rheight))
4406               {
4407                 meta_draw_op_list_draw_with_style (op->data.tile.op_list,
4408                                                    style_gtk, cr, info,
4409                                                    tile);
4410 
4411                 tile.y += tile.height;
4412               }
4413 
4414             tile.x += tile.width;
4415           }
4416         cairo_restore (cr);
4417       }
4418       break;
4419     }
4420 
4421   cairo_restore (cr);
4422   gtk_style_context_restore (style_gtk);
4423 }
4424 
4425 void
meta_draw_op_draw_with_style(const MetaDrawOp * op,GtkStyleContext * style_gtk,cairo_t * cr,const MetaDrawInfo * info,MetaRectangle logical_region)4426 meta_draw_op_draw_with_style (const MetaDrawOp    *op,
4427                               GtkStyleContext     *style_gtk,
4428                               cairo_t             *cr,
4429                               const MetaDrawInfo  *info,
4430                               MetaRectangle        logical_region)
4431 {
4432   MetaPositionExprEnv env;
4433 
4434   fill_env (&env, info, logical_region);
4435 
4436   meta_draw_op_draw_with_env (op,
4437                               style_gtk,
4438                               cr,
4439                               info, logical_region,
4440                               &env);
4441 
4442 }
4443 
4444 void
meta_draw_op_draw(const MetaDrawOp * op,GtkWidget * widget,cairo_t * cr,const MetaDrawInfo * info,MetaRectangle logical_region)4445 meta_draw_op_draw (const MetaDrawOp    *op,
4446                    GtkWidget           *widget,
4447                    cairo_t             *cr,
4448                    const MetaDrawInfo  *info,
4449                    MetaRectangle        logical_region)
4450 {
4451   meta_draw_op_draw_with_style (op,
4452                                 gtk_widget_get_style_context (widget),
4453                                 cr,
4454                                 info, logical_region);
4455 }
4456 
4457 MetaDrawOpList*
meta_draw_op_list_new(int n_preallocs)4458 meta_draw_op_list_new (int n_preallocs)
4459 {
4460   MetaDrawOpList *op_list;
4461 
4462   g_return_val_if_fail (n_preallocs >= 0, NULL);
4463 
4464   op_list = g_new (MetaDrawOpList, 1);
4465 
4466   op_list->refcount = 1;
4467   op_list->n_allocated = n_preallocs;
4468   op_list->ops = g_new (MetaDrawOp*, op_list->n_allocated);
4469   op_list->n_ops = 0;
4470 
4471   return op_list;
4472 }
4473 
4474 void
meta_draw_op_list_ref(MetaDrawOpList * op_list)4475 meta_draw_op_list_ref (MetaDrawOpList *op_list)
4476 {
4477   g_return_if_fail (op_list != NULL);
4478 
4479   op_list->refcount += 1;
4480 }
4481 
4482 void
meta_draw_op_list_unref(MetaDrawOpList * op_list)4483 meta_draw_op_list_unref (MetaDrawOpList *op_list)
4484 {
4485   g_return_if_fail (op_list != NULL);
4486   g_return_if_fail (op_list->refcount > 0);
4487 
4488   op_list->refcount -= 1;
4489 
4490   if (op_list->refcount == 0)
4491     {
4492       int i;
4493 
4494       for (i = 0; i < op_list->n_ops; i++)
4495         meta_draw_op_free (op_list->ops[i]);
4496 
4497       g_free (op_list->ops);
4498 
4499       DEBUG_FILL_STRUCT (op_list);
4500       g_free (op_list);
4501     }
4502 }
4503 
4504 void
meta_draw_op_list_draw_with_style(const MetaDrawOpList * op_list,GtkStyleContext * style_gtk,cairo_t * cr,const MetaDrawInfo * info,MetaRectangle rect)4505 meta_draw_op_list_draw_with_style  (const MetaDrawOpList *op_list,
4506                                     GtkStyleContext      *style_gtk,
4507                                     cairo_t              *cr,
4508                                     const MetaDrawInfo   *info,
4509                                     MetaRectangle         rect)
4510 {
4511   /* BOOKMARK */
4512 
4513   int i;
4514   MetaPositionExprEnv env;
4515 
4516   if (op_list->n_ops == 0)
4517     return;
4518 
4519   fill_env (&env, info, rect);
4520 
4521   /* FIXME this can be optimized, potentially a lot, by
4522    * compressing multiple ops when possible. For example,
4523    * anything convertible to a pixbuf can be composited
4524    * client-side, and putting a color tint over a pixbuf
4525    * can be done without creating the solid-color pixbuf.
4526    *
4527    * To implement this my plan is to have the idea of a
4528    * compiled draw op (with the string expressions already
4529    * evaluated), we make an array of those, and then fold
4530    * adjacent items when possible.
4531    */
4532 
4533   cairo_save (cr);
4534 
4535   for (i = 0; i < op_list->n_ops; i++)
4536     {
4537       MetaDrawOp *op = op_list->ops[i];
4538 
4539       if (op->type == META_DRAW_CLIP)
4540         {
4541           cairo_restore (cr);
4542 
4543           cairo_rectangle (cr,
4544                            parse_x_position_unchecked (op->data.clip.x, &env),
4545                            parse_y_position_unchecked (op->data.clip.y, &env),
4546                            parse_size_unchecked (op->data.clip.width, &env),
4547                            parse_size_unchecked (op->data.clip.height, &env));
4548           cairo_clip (cr);
4549 
4550           cairo_save (cr);
4551         }
4552       else if (gdk_cairo_get_clip_rectangle (cr, NULL))
4553         {
4554           meta_draw_op_draw_with_env (op, style_gtk, cr, info, rect, &env);
4555         }
4556     }
4557 
4558     cairo_restore (cr);
4559 }
4560 
4561 void
meta_draw_op_list_draw(const MetaDrawOpList * op_list,GtkWidget * widget,cairo_t * cr,const MetaDrawInfo * info,MetaRectangle rect)4562 meta_draw_op_list_draw  (const MetaDrawOpList *op_list,
4563                          GtkWidget            *widget,
4564                          cairo_t              *cr,
4565                          const MetaDrawInfo   *info,
4566                          MetaRectangle         rect)
4567 
4568 {
4569   meta_draw_op_list_draw_with_style (op_list,
4570                                      gtk_widget_get_style_context (widget),
4571                                      cr,
4572                                      info, rect);
4573 }
4574 
4575 void
meta_draw_op_list_append(MetaDrawOpList * op_list,MetaDrawOp * op)4576 meta_draw_op_list_append (MetaDrawOpList       *op_list,
4577                           MetaDrawOp           *op)
4578 {
4579   if (op_list->n_ops == op_list->n_allocated)
4580     {
4581       op_list->n_allocated *= 2;
4582       op_list->ops = g_renew (MetaDrawOp*, op_list->ops, op_list->n_allocated);
4583     }
4584 
4585   op_list->ops[op_list->n_ops] = op;
4586   op_list->n_ops += 1;
4587 }
4588 
4589 gboolean
meta_draw_op_list_validate(MetaDrawOpList * op_list,GError ** error)4590 meta_draw_op_list_validate (MetaDrawOpList    *op_list,
4591                             GError           **error)
4592 {
4593   g_return_val_if_fail (op_list != NULL, FALSE);
4594 
4595   /* empty lists are OK, nothing else to check really */
4596 
4597   return TRUE;
4598 }
4599 
4600 /* This is not done in validate, since we wouldn't know the name
4601  * of the list to report the error. It might be nice to
4602  * store names inside the list sometime.
4603  */
4604 gboolean
meta_draw_op_list_contains(MetaDrawOpList * op_list,MetaDrawOpList * child)4605 meta_draw_op_list_contains (MetaDrawOpList    *op_list,
4606                             MetaDrawOpList    *child)
4607 {
4608   int i;
4609 
4610   /* mmm, huge tree recursion */
4611 
4612   for (i = 0; i < op_list->n_ops; i++)
4613     {
4614       if (op_list->ops[i]->type == META_DRAW_OP_LIST)
4615         {
4616           if (op_list->ops[i]->data.op_list.op_list == child)
4617             return TRUE;
4618 
4619           if (meta_draw_op_list_contains (op_list->ops[i]->data.op_list.op_list,
4620                                           child))
4621             return TRUE;
4622         }
4623       else if (op_list->ops[i]->type == META_DRAW_TILE)
4624         {
4625           if (op_list->ops[i]->data.tile.op_list == child)
4626             return TRUE;
4627 
4628           if (meta_draw_op_list_contains (op_list->ops[i]->data.tile.op_list,
4629                                           child))
4630             return TRUE;
4631         }
4632     }
4633 
4634   return FALSE;
4635 }
4636 
4637 /**
4638  * Constructor for a MetaFrameStyle.
4639  *
4640  * \param parent  The parent style. Data not filled in here will be
4641  *                looked for in the parent style, and in its parent
4642  *                style, and so on.
4643  *
4644  * \return The newly-constructed style.
4645  */
4646 MetaFrameStyle*
meta_frame_style_new(MetaFrameStyle * parent)4647 meta_frame_style_new (MetaFrameStyle *parent)
4648 {
4649   MetaFrameStyle *style;
4650 
4651   style = g_new0 (MetaFrameStyle, 1);
4652 
4653   style->refcount = 1;
4654 
4655   /* Default alpha is fully opaque */
4656   style->window_background_alpha = 255;
4657 
4658   style->parent = parent;
4659   if (parent)
4660     meta_frame_style_ref (parent);
4661 
4662   return style;
4663 }
4664 
4665 /**
4666  * Increases the reference count of a frame style.
4667  * If the style is NULL, this is a no-op.
4668  *
4669  * \param style  The style.
4670  */
4671 void
meta_frame_style_ref(MetaFrameStyle * style)4672 meta_frame_style_ref (MetaFrameStyle *style)
4673 {
4674   g_return_if_fail (style != NULL);
4675 
4676   style->refcount += 1;
4677 }
4678 
4679 static void
free_button_ops(MetaDrawOpList * op_lists[META_BUTTON_TYPE_LAST][META_BUTTON_STATE_LAST])4680 free_button_ops (MetaDrawOpList *op_lists[META_BUTTON_TYPE_LAST][META_BUTTON_STATE_LAST])
4681 {
4682   int i, j;
4683 
4684   for (i = 0; i < META_BUTTON_TYPE_LAST; i++)
4685     for (j = 0; j < META_BUTTON_STATE_LAST; j++)
4686       if (op_lists[i][j])
4687         meta_draw_op_list_unref (op_lists[i][j]);
4688 }
4689 
4690 void
meta_frame_style_unref(MetaFrameStyle * style)4691 meta_frame_style_unref (MetaFrameStyle *style)
4692 {
4693   g_return_if_fail (style != NULL);
4694   g_return_if_fail (style->refcount > 0);
4695 
4696   style->refcount -= 1;
4697 
4698   if (style->refcount == 0)
4699     {
4700       int i;
4701 
4702       free_button_ops (style->buttons);
4703 
4704       for (i = 0; i < META_FRAME_PIECE_LAST; i++)
4705         if (style->pieces[i])
4706           meta_draw_op_list_unref (style->pieces[i]);
4707 
4708       if (style->layout)
4709         meta_frame_layout_unref (style->layout);
4710 
4711       if (style->window_background_color)
4712         meta_color_spec_free (style->window_background_color);
4713 
4714       /* we hold a reference to any parent style */
4715       if (style->parent)
4716         meta_frame_style_unref (style->parent);
4717 
4718       DEBUG_FILL_STRUCT (style);
4719       g_free (style);
4720     }
4721 }
4722 
4723 static MetaButtonState
map_button_state(MetaButtonType button_type,const MetaFrameGeometry * fgeom,int middle_bg_offset,MetaButtonState button_states[META_BUTTON_TYPE_LAST])4724 map_button_state (MetaButtonType           button_type,
4725                   const MetaFrameGeometry *fgeom,
4726                   int                      middle_bg_offset,
4727                   MetaButtonState          button_states[META_BUTTON_TYPE_LAST])
4728 {
4729   MetaButtonFunction function = META_BUTTON_FUNCTION_LAST;
4730 
4731   switch (button_type)
4732     {
4733     /* First handle functions, which map directly */
4734     case META_BUTTON_TYPE_SHADE:
4735     case META_BUTTON_TYPE_ABOVE:
4736     case META_BUTTON_TYPE_STICK:
4737     case META_BUTTON_TYPE_UNSHADE:
4738     case META_BUTTON_TYPE_UNABOVE:
4739     case META_BUTTON_TYPE_UNSTICK:
4740     case META_BUTTON_TYPE_MENU:
4741     case META_BUTTON_TYPE_APPMENU:
4742     case META_BUTTON_TYPE_MINIMIZE:
4743     case META_BUTTON_TYPE_MAXIMIZE:
4744     case META_BUTTON_TYPE_CLOSE:
4745       return button_states[button_type];
4746 
4747     /* Map position buttons to the corresponding function */
4748     case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND:
4749     case META_BUTTON_TYPE_RIGHT_SINGLE_BACKGROUND:
4750       if (fgeom->n_right_buttons > 0)
4751         function = fgeom->button_layout.right_buttons[0];
4752       break;
4753     case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND:
4754       if (fgeom->n_right_buttons > 0)
4755         function = fgeom->button_layout.right_buttons[fgeom->n_right_buttons - 1];
4756       break;
4757     case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND:
4758       if (middle_bg_offset + 1 < fgeom->n_right_buttons)
4759         function = fgeom->button_layout.right_buttons[middle_bg_offset + 1];
4760       break;
4761     case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND:
4762     case META_BUTTON_TYPE_LEFT_SINGLE_BACKGROUND:
4763       if (fgeom->n_left_buttons > 0)
4764         function = fgeom->button_layout.left_buttons[0];
4765       break;
4766     case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND:
4767       if (fgeom->n_left_buttons > 0)
4768         function = fgeom->button_layout.left_buttons[fgeom->n_left_buttons - 1];
4769       break;
4770     case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND:
4771       if (middle_bg_offset + 1 < fgeom->n_left_buttons)
4772         function = fgeom->button_layout.left_buttons[middle_bg_offset + 1];
4773       break;
4774     case META_BUTTON_TYPE_LAST:
4775       break;
4776     }
4777 
4778   if (function != META_BUTTON_FUNCTION_LAST)
4779     return button_states[map_button_function_to_type (function)];
4780 
4781   return META_BUTTON_STATE_LAST;
4782 }
4783 
4784 static MetaDrawOpList*
get_button(MetaFrameStyle * style,MetaButtonType type,MetaButtonState state)4785 get_button (MetaFrameStyle *style,
4786             MetaButtonType  type,
4787             MetaButtonState state)
4788 {
4789   MetaDrawOpList *op_list;
4790   MetaFrameStyle *parent;
4791 
4792   parent = style;
4793   op_list = NULL;
4794   while (parent && op_list == NULL)
4795     {
4796       op_list = parent->buttons[type][state];
4797       parent = parent->parent;
4798     }
4799 
4800   /* We fall back to the side buttons if we don't have
4801    * single button backgrounds, and to middle button
4802    * backgrounds if we don't have the ones on the sides
4803    */
4804 
4805   if (op_list == NULL &&
4806       type == META_BUTTON_TYPE_LEFT_SINGLE_BACKGROUND)
4807     return get_button (style, META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND, state);
4808 
4809   if (op_list == NULL &&
4810       type == META_BUTTON_TYPE_RIGHT_SINGLE_BACKGROUND)
4811     return get_button (style, META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND, state);
4812 
4813   if (op_list == NULL &&
4814       (type == META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND ||
4815        type == META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND))
4816     return get_button (style, META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND,
4817                        state);
4818 
4819   if (op_list == NULL &&
4820       (type == META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND ||
4821        type == META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND))
4822     return get_button (style, META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND,
4823                        state);
4824 
4825   /* We fall back to normal if no prelight */
4826   if (op_list == NULL &&
4827       state == META_BUTTON_STATE_PRELIGHT)
4828     return get_button (style, type, META_BUTTON_STATE_NORMAL);
4829 
4830   return op_list;
4831 }
4832 
4833 gboolean
meta_frame_style_validate(MetaFrameStyle * style,guint current_theme_version,GError ** error)4834 meta_frame_style_validate (MetaFrameStyle    *style,
4835                            guint              current_theme_version,
4836                            GError           **error)
4837 {
4838   int i, j;
4839 
4840   g_return_val_if_fail (style != NULL, FALSE);
4841   g_return_val_if_fail (style->layout != NULL, FALSE);
4842 
4843   for (i = 0; i < META_BUTTON_TYPE_LAST; i++)
4844     {
4845       /* for now the "positional" buttons are optional */
4846       if (i >= META_BUTTON_TYPE_CLOSE)
4847         {
4848           for (j = 0; j < META_BUTTON_STATE_LAST; j++)
4849             {
4850               if (get_button (style, i, j) == NULL &&
4851                   meta_theme_earliest_version_with_button (i) <= current_theme_version
4852                   )
4853                 {
4854                   g_set_error (error, META_THEME_ERROR,
4855                                META_THEME_ERROR_FAILED,
4856                                _("<button function=\"%s\" state=\"%s\" draw_ops=\"whatever\"/> must be specified for this frame style"),
4857                                meta_button_type_to_string (i),
4858                                meta_button_state_to_string (j));
4859                   return FALSE;
4860                 }
4861             }
4862         }
4863     }
4864 
4865   return TRUE;
4866 }
4867 
4868 static void
get_button_rect(MetaButtonType type,const MetaFrameGeometry * fgeom,int middle_background_offset,GdkRectangle * rect)4869 get_button_rect (MetaButtonType           type,
4870                  const MetaFrameGeometry *fgeom,
4871                  int                      middle_background_offset,
4872                  GdkRectangle            *rect)
4873 {
4874   switch (type)
4875     {
4876     case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND:
4877       *rect = fgeom->left_left_background;
4878       break;
4879 
4880     case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND:
4881       *rect = fgeom->left_middle_backgrounds[middle_background_offset];
4882       break;
4883 
4884     case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND:
4885       *rect = fgeom->left_right_background;
4886       break;
4887 
4888     case META_BUTTON_TYPE_LEFT_SINGLE_BACKGROUND:
4889       *rect = fgeom->left_single_background;
4890       break;
4891 
4892     case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND:
4893       *rect = fgeom->right_left_background;
4894       break;
4895 
4896     case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND:
4897       *rect = fgeom->right_middle_backgrounds[middle_background_offset];
4898       break;
4899 
4900     case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND:
4901       *rect = fgeom->right_right_background;
4902       break;
4903 
4904     case META_BUTTON_TYPE_RIGHT_SINGLE_BACKGROUND:
4905       *rect = fgeom->right_single_background;
4906       break;
4907 
4908     case META_BUTTON_TYPE_CLOSE:
4909       *rect = fgeom->close_rect.visible;
4910       break;
4911 
4912     case META_BUTTON_TYPE_SHADE:
4913       *rect = fgeom->shade_rect.visible;
4914       break;
4915 
4916     case META_BUTTON_TYPE_UNSHADE:
4917       *rect = fgeom->unshade_rect.visible;
4918       break;
4919 
4920     case META_BUTTON_TYPE_ABOVE:
4921       *rect = fgeom->above_rect.visible;
4922       break;
4923 
4924     case META_BUTTON_TYPE_UNABOVE:
4925       *rect = fgeom->unabove_rect.visible;
4926       break;
4927 
4928     case META_BUTTON_TYPE_STICK:
4929       *rect = fgeom->stick_rect.visible;
4930       break;
4931 
4932     case META_BUTTON_TYPE_UNSTICK:
4933       *rect = fgeom->unstick_rect.visible;
4934       break;
4935 
4936     case META_BUTTON_TYPE_MAXIMIZE:
4937       *rect = fgeom->max_rect.visible;
4938       break;
4939 
4940     case META_BUTTON_TYPE_MINIMIZE:
4941       *rect = fgeom->min_rect.visible;
4942       break;
4943 
4944     case META_BUTTON_TYPE_MENU:
4945       *rect = fgeom->menu_rect.visible;
4946       break;
4947 
4948     case META_BUTTON_TYPE_APPMENU:
4949       *rect = fgeom->appmenu_rect.visible;
4950       break;
4951 
4952     case META_BUTTON_TYPE_LAST:
4953       g_assert_not_reached ();
4954       break;
4955     }
4956 }
4957 
4958 void
meta_frame_style_draw_with_style(MetaFrameStyle * style,GtkStyleContext * style_gtk,cairo_t * cr,const MetaFrameGeometry * fgeom,int client_width,int client_height,PangoLayout * title_layout,int text_height,MetaButtonState button_states[META_BUTTON_TYPE_LAST],GdkPixbuf * mini_icon,GdkPixbuf * icon)4959 meta_frame_style_draw_with_style (MetaFrameStyle          *style,
4960                                   GtkStyleContext         *style_gtk,
4961                                   cairo_t                 *cr,
4962                                   const MetaFrameGeometry *fgeom,
4963                                   int                      client_width,
4964                                   int                      client_height,
4965                                   PangoLayout             *title_layout,
4966                                   int                      text_height,
4967                                   MetaButtonState          button_states[META_BUTTON_TYPE_LAST],
4968                                   GdkPixbuf               *mini_icon,
4969                                   GdkPixbuf               *icon)
4970 {
4971     /* BOOKMARK */
4972   int i, j;
4973   GdkRectangle visible_rect;
4974   GdkRectangle titlebar_rect;
4975   GdkRectangle left_titlebar_edge;
4976   GdkRectangle right_titlebar_edge;
4977   GdkRectangle bottom_titlebar_edge;
4978   GdkRectangle top_titlebar_edge;
4979   GdkRectangle left_edge, right_edge, bottom_edge;
4980   PangoRectangle extents;
4981   MetaDrawInfo draw_info;
4982   const MetaFrameBorders *borders;
4983 
4984   borders = &fgeom->borders;
4985 
4986   visible_rect.x = borders->invisible.left;
4987   visible_rect.y = borders->invisible.top;
4988   visible_rect.width = fgeom->width - borders->invisible.left - borders->invisible.right;
4989   visible_rect.height = fgeom->height - borders->invisible.top - borders->invisible.bottom;
4990 
4991   titlebar_rect.x = visible_rect.x;
4992   titlebar_rect.y = visible_rect.y;
4993   titlebar_rect.width = visible_rect.width;
4994   titlebar_rect.height = borders->visible.top;
4995 
4996   left_titlebar_edge.x = titlebar_rect.x;
4997   left_titlebar_edge.y = titlebar_rect.y + fgeom->top_titlebar_edge;
4998   left_titlebar_edge.width = fgeom->left_titlebar_edge;
4999   left_titlebar_edge.height = titlebar_rect.height - fgeom->top_titlebar_edge - fgeom->bottom_titlebar_edge;
5000 
5001   right_titlebar_edge.y = left_titlebar_edge.y;
5002   right_titlebar_edge.height = left_titlebar_edge.height;
5003   right_titlebar_edge.width = fgeom->right_titlebar_edge;
5004   right_titlebar_edge.x = titlebar_rect.x + titlebar_rect.width - right_titlebar_edge.width;
5005 
5006   top_titlebar_edge.x = titlebar_rect.x;
5007   top_titlebar_edge.y = titlebar_rect.y;
5008   top_titlebar_edge.width = titlebar_rect.width;
5009   top_titlebar_edge.height = fgeom->top_titlebar_edge;
5010 
5011   bottom_titlebar_edge.x = titlebar_rect.x;
5012   bottom_titlebar_edge.width = titlebar_rect.width;
5013   bottom_titlebar_edge.height = fgeom->bottom_titlebar_edge;
5014   bottom_titlebar_edge.y = titlebar_rect.y + titlebar_rect.height - bottom_titlebar_edge.height;
5015 
5016   left_edge.x = visible_rect.x;
5017   left_edge.y = visible_rect.y + borders->visible.top;
5018   left_edge.width = borders->visible.left;
5019   left_edge.height = visible_rect.height - borders->visible.top - borders->visible.bottom;
5020 
5021   right_edge.x = visible_rect.x + visible_rect.width - borders->visible.right;
5022   right_edge.y = visible_rect.y + borders->visible.top;
5023   right_edge.width = borders->visible.right;
5024   right_edge.height = visible_rect.height - borders->visible.top - borders->visible.bottom;
5025 
5026   bottom_edge.x = visible_rect.x;
5027   bottom_edge.y = visible_rect.y + visible_rect.height - borders->visible.bottom;
5028   bottom_edge.width = visible_rect.width;
5029   bottom_edge.height = borders->visible.bottom;
5030 
5031   if (title_layout)
5032     pango_layout_get_pixel_extents (title_layout,
5033                                     NULL, &extents);
5034 
5035   draw_info.mini_icon = mini_icon;
5036   draw_info.icon = icon;
5037   draw_info.title_layout = title_layout;
5038   draw_info.title_layout_width = title_layout ? extents.width : 0;
5039   draw_info.title_layout_height = title_layout ? extents.height : 0;
5040   draw_info.fgeom = fgeom;
5041 
5042   /* The enum is in the order the pieces should be rendered. */
5043   i = 0;
5044   while (i < META_FRAME_PIECE_LAST)
5045     {
5046       GdkRectangle rect;
5047 
5048       switch ((MetaFramePiece) i)
5049         {
5050         case META_FRAME_PIECE_ENTIRE_BACKGROUND:
5051           rect = visible_rect;
5052           break;
5053 
5054         case META_FRAME_PIECE_TITLEBAR:
5055           rect = titlebar_rect;
5056           break;
5057 
5058         case META_FRAME_PIECE_LEFT_TITLEBAR_EDGE:
5059           rect = left_titlebar_edge;
5060           break;
5061 
5062         case META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE:
5063           rect = right_titlebar_edge;
5064           break;
5065 
5066         case META_FRAME_PIECE_TOP_TITLEBAR_EDGE:
5067           rect = top_titlebar_edge;
5068           break;
5069 
5070         case META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE:
5071           rect = bottom_titlebar_edge;
5072           break;
5073 
5074         case META_FRAME_PIECE_TITLEBAR_MIDDLE:
5075           rect.x = left_titlebar_edge.x + left_titlebar_edge.width;
5076           rect.y = top_titlebar_edge.y + top_titlebar_edge.height;
5077           rect.width = titlebar_rect.width - left_titlebar_edge.width -
5078             right_titlebar_edge.width;
5079           rect.height = titlebar_rect.height - top_titlebar_edge.height - bottom_titlebar_edge.height;
5080           break;
5081 
5082         case META_FRAME_PIECE_TITLE:
5083           rect = fgeom->title_rect;
5084           break;
5085 
5086         case META_FRAME_PIECE_LEFT_EDGE:
5087           rect = left_edge;
5088           break;
5089 
5090         case META_FRAME_PIECE_RIGHT_EDGE:
5091           rect = right_edge;
5092           break;
5093 
5094         case META_FRAME_PIECE_BOTTOM_EDGE:
5095           rect = bottom_edge;
5096           break;
5097 
5098         case META_FRAME_PIECE_OVERLAY:
5099           rect = visible_rect;
5100           break;
5101 
5102         case META_FRAME_PIECE_LAST:
5103           g_assert_not_reached ();
5104           break;
5105         }
5106 
5107       cairo_save (cr);
5108 
5109       gdk_cairo_rectangle (cr, &rect);
5110       cairo_clip (cr);
5111 
5112       if (gdk_cairo_get_clip_rectangle (cr, NULL))
5113         {
5114           MetaDrawOpList *op_list;
5115           MetaFrameStyle *parent;
5116 
5117           parent = style;
5118           op_list = NULL;
5119           while (parent && op_list == NULL)
5120             {
5121               op_list = parent->pieces[i];
5122               parent = parent->parent;
5123             }
5124 
5125           if (op_list)
5126             {
5127               MetaRectangle m_rect;
5128               m_rect = meta_rect (rect.x, rect.y, rect.width, rect.height);
5129               meta_draw_op_list_draw_with_style (op_list,
5130                                                  style_gtk,
5131                                                  cr,
5132                                                  &draw_info,
5133                                                  m_rect);
5134             }
5135         }
5136 
5137       cairo_restore (cr);
5138 
5139       /* Draw buttons just before overlay */
5140       if ((i + 1) == META_FRAME_PIECE_OVERLAY)
5141         {
5142           MetaDrawOpList *op_list;
5143           int middle_bg_offset;
5144 
5145           middle_bg_offset = 0;
5146           j = 0;
5147           while (j < META_BUTTON_TYPE_LAST)
5148             {
5149               MetaButtonState button_state;
5150 
5151               get_button_rect (j, fgeom, middle_bg_offset, &rect);
5152 
5153               button_state = map_button_state (j, fgeom, middle_bg_offset, button_states);
5154               op_list = get_button (style, j, button_state);
5155 
5156               if (op_list)
5157                 {
5158                   cairo_save (cr);
5159                   gdk_cairo_rectangle (cr, &rect);
5160                   cairo_clip (cr);
5161 
5162                   if (gdk_cairo_get_clip_rectangle (cr, NULL))
5163                     {
5164                       MetaRectangle m_rect;
5165 
5166                       m_rect = meta_rect (rect.x, rect.y,
5167                                           rect.width, rect.height);
5168 
5169                       meta_draw_op_list_draw_with_style (op_list,
5170                                                          style_gtk,
5171                                                          cr,
5172                                                          &draw_info,
5173                                                          m_rect);
5174                     }
5175 
5176                   cairo_restore (cr);
5177                 }
5178 
5179               /* MIDDLE_BACKGROUND type may get drawn more than once */
5180               if ((j == META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND ||
5181                    j == META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND) &&
5182                   middle_bg_offset < MAX_MIDDLE_BACKGROUNDS)
5183                 {
5184                   ++middle_bg_offset;
5185                 }
5186               else
5187                 {
5188                   middle_bg_offset = 0;
5189                   ++j;
5190                 }
5191             }
5192         }
5193 
5194       ++i;
5195     }
5196 }
5197 
5198 void
meta_frame_style_draw(MetaFrameStyle * style,GtkWidget * widget,cairo_t * cr,const MetaFrameGeometry * fgeom,int client_width,int client_height,PangoLayout * title_layout,int text_height,MetaButtonState button_states[META_BUTTON_TYPE_LAST],GdkPixbuf * mini_icon,GdkPixbuf * icon)5199 meta_frame_style_draw (MetaFrameStyle          *style,
5200                        GtkWidget               *widget,
5201                        cairo_t                 *cr,
5202                        const MetaFrameGeometry *fgeom,
5203                        int                      client_width,
5204                        int                      client_height,
5205                        PangoLayout             *title_layout,
5206                        int                      text_height,
5207                        MetaButtonState          button_states[META_BUTTON_TYPE_LAST],
5208                        GdkPixbuf               *mini_icon,
5209                        GdkPixbuf               *icon)
5210 {
5211   meta_frame_style_draw_with_style (style,
5212                                     gtk_widget_get_style_context (widget),
5213                                     cr,
5214                                     fgeom, client_width, client_height,
5215                                     title_layout, text_height,
5216                                     button_states, mini_icon, icon);
5217 }
5218 
5219 MetaFrameStyleSet*
meta_frame_style_set_new(MetaFrameStyleSet * parent)5220 meta_frame_style_set_new (MetaFrameStyleSet *parent)
5221 {
5222   MetaFrameStyleSet *style_set;
5223 
5224   style_set = g_new0 (MetaFrameStyleSet, 1);
5225 
5226   style_set->parent = parent;
5227   if (parent)
5228     meta_frame_style_set_ref (parent);
5229 
5230   style_set->refcount = 1;
5231 
5232   return style_set;
5233 }
5234 
5235 static void
free_focus_styles(MetaFrameStyle * focus_styles[META_FRAME_FOCUS_LAST])5236 free_focus_styles (MetaFrameStyle *focus_styles[META_FRAME_FOCUS_LAST])
5237 {
5238   int i;
5239 
5240   for (i = 0; i < META_FRAME_FOCUS_LAST; i++)
5241     if (focus_styles[i])
5242       meta_frame_style_unref (focus_styles[i]);
5243 }
5244 
5245 void
meta_frame_style_set_ref(MetaFrameStyleSet * style_set)5246 meta_frame_style_set_ref (MetaFrameStyleSet *style_set)
5247 {
5248   g_return_if_fail (style_set != NULL);
5249 
5250   style_set->refcount += 1;
5251 }
5252 
5253 void
meta_frame_style_set_unref(MetaFrameStyleSet * style_set)5254 meta_frame_style_set_unref (MetaFrameStyleSet *style_set)
5255 {
5256   g_return_if_fail (style_set != NULL);
5257   g_return_if_fail (style_set->refcount > 0);
5258 
5259   style_set->refcount -= 1;
5260 
5261   if (style_set->refcount == 0)
5262     {
5263       int i;
5264 
5265       for (i = 0; i < META_FRAME_RESIZE_LAST; i++)
5266         {
5267           free_focus_styles (style_set->normal_styles[i]);
5268           free_focus_styles (style_set->shaded_styles[i]);
5269         }
5270 
5271       free_focus_styles (style_set->maximized_styles);
5272       free_focus_styles (style_set->tiled_left_styles);
5273       free_focus_styles (style_set->tiled_right_styles);
5274       free_focus_styles (style_set->maximized_and_shaded_styles);
5275       free_focus_styles (style_set->tiled_left_and_shaded_styles);
5276       free_focus_styles (style_set->tiled_right_and_shaded_styles);
5277 
5278       if (style_set->parent)
5279         meta_frame_style_set_unref (style_set->parent);
5280 
5281       DEBUG_FILL_STRUCT (style_set);
5282       g_free (style_set);
5283     }
5284 }
5285 
5286 static MetaFrameStyle*
get_style(MetaFrameStyleSet * style_set,MetaFrameState state,MetaFrameResize resize,MetaFrameFocus focus)5287 get_style (MetaFrameStyleSet *style_set,
5288            MetaFrameState     state,
5289            MetaFrameResize    resize,
5290            MetaFrameFocus     focus)
5291 {
5292   MetaFrameStyle *style;
5293 
5294   style = NULL;
5295 
5296   switch (state)
5297     {
5298     case META_FRAME_STATE_NORMAL:
5299     case META_FRAME_STATE_SHADED:
5300       {
5301         if (state == META_FRAME_STATE_SHADED)
5302           style = style_set->shaded_styles[resize][focus];
5303         else
5304           style = style_set->normal_styles[resize][focus];
5305 
5306         /* Try parent if we failed here */
5307         if (style == NULL && style_set->parent)
5308           style = get_style (style_set->parent, state, resize, focus);
5309 
5310         /* Allow people to omit the vert/horz/none resize modes */
5311         if (style == NULL &&
5312             resize != META_FRAME_RESIZE_BOTH)
5313           style = get_style (style_set, state, META_FRAME_RESIZE_BOTH, focus);
5314       }
5315       break;
5316     default:
5317       {
5318         MetaFrameStyle **styles;
5319 
5320         styles = NULL;
5321 
5322         switch (state)
5323           {
5324           case META_FRAME_STATE_MAXIMIZED:
5325             styles = style_set->maximized_styles;
5326             break;
5327           case META_FRAME_STATE_TILED_LEFT:
5328             styles = style_set->tiled_left_styles;
5329             break;
5330           case META_FRAME_STATE_TILED_RIGHT:
5331             styles = style_set->tiled_right_styles;
5332             break;
5333           case META_FRAME_STATE_MAXIMIZED_AND_SHADED:
5334             styles = style_set->maximized_and_shaded_styles;
5335             break;
5336           case META_FRAME_STATE_TILED_LEFT_AND_SHADED:
5337             styles = style_set->tiled_left_and_shaded_styles;
5338             break;
5339           case META_FRAME_STATE_TILED_RIGHT_AND_SHADED:
5340             styles = style_set->tiled_right_and_shaded_styles;
5341             break;
5342           case META_FRAME_STATE_NORMAL:
5343           case META_FRAME_STATE_SHADED:
5344           case META_FRAME_STATE_LAST:
5345             g_assert_not_reached ();
5346             break;
5347           }
5348 
5349         style = styles[focus];
5350 
5351         /* Tiled states are optional, try falling back to non-tiled states */
5352         if (style == NULL)
5353           {
5354             if (state == META_FRAME_STATE_TILED_LEFT ||
5355                 state == META_FRAME_STATE_TILED_RIGHT)
5356               style = get_style (style_set, META_FRAME_STATE_NORMAL,
5357                                  resize, focus);
5358             else if (state == META_FRAME_STATE_TILED_LEFT_AND_SHADED ||
5359                      state == META_FRAME_STATE_TILED_RIGHT_AND_SHADED)
5360               style = get_style (style_set, META_FRAME_STATE_SHADED,
5361                                  resize, focus);
5362           }
5363 
5364         /* Try parent if we failed here */
5365         if (style == NULL && style_set->parent)
5366           style = get_style (style_set->parent, state, resize, focus);
5367       }
5368     }
5369 
5370   return style;
5371 }
5372 
5373 static gboolean
check_state(MetaFrameStyleSet * style_set,MetaFrameState state,GError ** error)5374 check_state  (MetaFrameStyleSet *style_set,
5375               MetaFrameState     state,
5376               GError           **error)
5377 {
5378   int i;
5379 
5380   for (i = 0; i < META_FRAME_FOCUS_LAST; i++)
5381     {
5382       if (get_style (style_set, state,
5383                      META_FRAME_RESIZE_NONE, i) == NULL)
5384         {
5385           /* Translators: This error occurs when a <frame> tag is missing
5386            * in theme XML.  The "<frame ...>" is intended as a noun phrase,
5387            * and the "missing" qualifies it.  You should translate "whatever".
5388            */
5389           g_set_error (error, META_THEME_ERROR,
5390                        META_THEME_ERROR_FAILED,
5391                        _("Missing <frame state=\"%s\" resize=\"%s\" focus=\"%s\" style=\"whatever\"/>"),
5392                        meta_frame_state_to_string (state),
5393                        meta_frame_resize_to_string (META_FRAME_RESIZE_NONE),
5394                        meta_frame_focus_to_string (i));
5395           return FALSE;
5396         }
5397     }
5398 
5399   return TRUE;
5400 }
5401 
5402 gboolean
meta_frame_style_set_validate(MetaFrameStyleSet * style_set,GError ** error)5403 meta_frame_style_set_validate  (MetaFrameStyleSet *style_set,
5404                                 GError           **error)
5405 {
5406   int i, j;
5407 
5408   g_return_val_if_fail (style_set != NULL, FALSE);
5409 
5410   for (i = 0; i < META_FRAME_RESIZE_LAST; i++)
5411     for (j = 0; j < META_FRAME_FOCUS_LAST; j++)
5412       if (get_style (style_set, META_FRAME_STATE_NORMAL, i, j) == NULL)
5413         {
5414           g_set_error (error, META_THEME_ERROR,
5415                        META_THEME_ERROR_FAILED,
5416                        _("Missing <frame state=\"%s\" resize=\"%s\" focus=\"%s\" style=\"whatever\"/>"),
5417                        meta_frame_state_to_string (META_FRAME_STATE_NORMAL),
5418                        meta_frame_resize_to_string (i),
5419                        meta_frame_focus_to_string (j));
5420           return FALSE;
5421         }
5422 
5423   if (!check_state (style_set, META_FRAME_STATE_SHADED, error))
5424     return FALSE;
5425 
5426   if (!check_state (style_set, META_FRAME_STATE_MAXIMIZED, error))
5427     return FALSE;
5428 
5429   if (!check_state (style_set, META_FRAME_STATE_MAXIMIZED_AND_SHADED, error))
5430     return FALSE;
5431 
5432   return TRUE;
5433 }
5434 
5435 MetaTheme*
meta_theme_get_current(void)5436 meta_theme_get_current (void)
5437 {
5438   return meta_current_theme;
5439 }
5440 
5441 void
meta_theme_set_current(const char * name,gboolean force_reload)5442 meta_theme_set_current (const char *name,
5443                         gboolean    force_reload)
5444 {
5445   MetaTheme *new_theme;
5446   GError *err;
5447 
5448   meta_topic (META_DEBUG_THEMES, "Setting current theme to \"%s\"\n", name);
5449 
5450   if (!force_reload &&
5451       meta_current_theme &&
5452       strcmp (name, meta_current_theme->name) == 0)
5453     return;
5454 
5455   err = NULL;
5456   new_theme = meta_theme_load (name, &err);
5457 
5458   if (new_theme == NULL)
5459     {
5460       meta_warning (_("Failed to load theme \"%s\": %s\n"),
5461                     name, err->message);
5462       g_error_free (err);
5463     }
5464   else
5465     {
5466       if (meta_current_theme)
5467         meta_theme_free (meta_current_theme);
5468 
5469       meta_current_theme = new_theme;
5470 
5471       meta_topic (META_DEBUG_THEMES, "New theme is \"%s\"\n", meta_current_theme->name);
5472     }
5473 }
5474 
5475 MetaTheme*
meta_theme_new(void)5476 meta_theme_new (void)
5477 {
5478   MetaTheme *theme;
5479 
5480   theme = g_new0 (MetaTheme, 1);
5481 
5482   theme->images_by_filename =
5483     g_hash_table_new_full (g_str_hash,
5484                            g_str_equal,
5485                            g_free,
5486                            g_object_unref);
5487 
5488   theme->layouts_by_name =
5489     g_hash_table_new_full (g_str_hash,
5490                            g_str_equal,
5491                            g_free,
5492                            (GDestroyNotify) meta_frame_layout_unref);
5493 
5494   theme->draw_op_lists_by_name =
5495     g_hash_table_new_full (g_str_hash,
5496                            g_str_equal,
5497                            g_free,
5498                            (GDestroyNotify) meta_draw_op_list_unref);
5499 
5500   theme->styles_by_name =
5501     g_hash_table_new_full (g_str_hash,
5502                            g_str_equal,
5503                            g_free,
5504                            (GDestroyNotify) meta_frame_style_unref);
5505 
5506   theme->style_sets_by_name =
5507     g_hash_table_new_full (g_str_hash,
5508                            g_str_equal,
5509                            g_free,
5510                            (GDestroyNotify) meta_frame_style_set_unref);
5511 
5512   /* Create our variable quarks so we can look up variables without
5513      having to strcmp for the names */
5514   theme->quark_width = g_quark_from_static_string ("width");
5515   theme->quark_height = g_quark_from_static_string ("height");
5516   theme->quark_object_width = g_quark_from_static_string ("object_width");
5517   theme->quark_object_height = g_quark_from_static_string ("object_height");
5518   theme->quark_left_width = g_quark_from_static_string ("left_width");
5519   theme->quark_right_width = g_quark_from_static_string ("right_width");
5520   theme->quark_top_height = g_quark_from_static_string ("top_height");
5521   theme->quark_bottom_height = g_quark_from_static_string ("bottom_height");
5522   theme->quark_mini_icon_width = g_quark_from_static_string ("mini_icon_width");
5523   theme->quark_mini_icon_height = g_quark_from_static_string ("mini_icon_height");
5524   theme->quark_icon_width = g_quark_from_static_string ("icon_width");
5525   theme->quark_icon_height = g_quark_from_static_string ("icon_height");
5526   theme->quark_title_width = g_quark_from_static_string ("title_width");
5527   theme->quark_title_height = g_quark_from_static_string ("title_height");
5528   theme->quark_frame_x_center = g_quark_from_static_string ("frame_x_center");
5529   theme->quark_frame_y_center = g_quark_from_static_string ("frame_y_center");
5530   return theme;
5531 }
5532 
5533 void
meta_theme_free(MetaTheme * theme)5534 meta_theme_free (MetaTheme *theme)
5535 {
5536   int i;
5537 
5538   g_return_if_fail (theme != NULL);
5539 
5540   g_free (theme->name);
5541   g_free (theme->dirname);
5542   g_free (theme->filename);
5543   g_free (theme->readable_name);
5544   g_free (theme->date);
5545   g_free (theme->description);
5546   g_free (theme->author);
5547   g_free (theme->copyright);
5548 
5549   /* be more careful when destroying the theme hash tables,
5550      since they are only constructed as needed, and may be NULL. */
5551   if (theme->integer_constants)
5552     g_hash_table_destroy (theme->integer_constants);
5553   if (theme->images_by_filename)
5554     g_hash_table_destroy (theme->images_by_filename);
5555   if (theme->layouts_by_name)
5556     g_hash_table_destroy (theme->layouts_by_name);
5557   if (theme->draw_op_lists_by_name)
5558     g_hash_table_destroy (theme->draw_op_lists_by_name);
5559   if (theme->styles_by_name)
5560     g_hash_table_destroy (theme->styles_by_name);
5561   if (theme->style_sets_by_name)
5562     g_hash_table_destroy (theme->style_sets_by_name);
5563 
5564   for (i = 0; i < META_FRAME_TYPE_LAST; i++)
5565     if (theme->style_sets_by_type[i])
5566       meta_frame_style_set_unref (theme->style_sets_by_type[i]);
5567 
5568   DEBUG_FILL_STRUCT (theme);
5569   g_free (theme);
5570 }
5571 
5572 gboolean
meta_theme_validate(MetaTheme * theme,GError ** error)5573 meta_theme_validate (MetaTheme *theme,
5574                      GError   **error)
5575 {
5576   int i;
5577 
5578   g_return_val_if_fail (theme != NULL, FALSE);
5579 
5580   /* FIXME what else should be checked? */
5581 
5582   g_assert (theme->name);
5583 
5584   if (theme->readable_name == NULL)
5585     {
5586       /* Translators: This error means that a necessary XML tag (whose name
5587        * is given in angle brackets) was not found in a given theme (whose
5588        * name is given second, in quotation marks).
5589        */
5590       g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
5591                    _("No <%s> set for theme \"%s\""), "name", theme->name);
5592       return FALSE;
5593     }
5594 
5595   if (theme->author == NULL)
5596     {
5597       g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
5598                    _("No <%s> set for theme \"%s\""), "author", theme->name);
5599       return FALSE;
5600     }
5601 
5602   if (theme->date == NULL)
5603     {
5604       g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
5605                    _("No <%s> set for theme \"%s\""), "date", theme->name);
5606       return FALSE;
5607     }
5608 
5609   if (theme->description == NULL)
5610     {
5611       g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
5612                    _("No <%s> set for theme \"%s\""), "description", theme->name);
5613       return FALSE;
5614     }
5615 
5616   if (theme->copyright == NULL)
5617     {
5618       g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
5619                    _("No <%s> set for theme \"%s\""), "copyright", theme->name);
5620       return FALSE;
5621     }
5622 
5623   for (i = 0; i < (int)META_FRAME_TYPE_LAST; i++)
5624     if (i != (int)META_FRAME_TYPE_ATTACHED && theme->style_sets_by_type[i] == NULL)
5625       {
5626         g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
5627                      _("No frame style set for window type \"%s\" in theme \"%s\", add a <window type=\"%s\" style_set=\"whatever\"/> element"),
5628                      meta_frame_type_to_string (i),
5629                      theme->name,
5630                      meta_frame_type_to_string (i));
5631 
5632         return FALSE;
5633       }
5634 
5635   return TRUE;
5636 }
5637 
5638 GdkPixbuf*
meta_theme_load_image(MetaTheme * theme,const char * filename,guint size_of_theme_icons,GError ** error)5639 meta_theme_load_image (MetaTheme  *theme,
5640                        const char *filename,
5641                        guint size_of_theme_icons,
5642                        GError    **error)
5643 {
5644   GdkPixbuf *pixbuf;
5645   int scale;
5646 
5647   pixbuf = g_hash_table_lookup (theme->images_by_filename,
5648                                 filename);
5649 
5650   scale = gdk_window_get_scale_factor (gdk_get_default_root_window ());
5651 
5652   if (pixbuf == NULL)
5653     {
5654 
5655       if (g_str_has_prefix (filename, "theme:") &&
5656           META_THEME_ALLOWS (theme, META_THEME_IMAGES_FROM_ICON_THEMES))
5657         {
5658           pixbuf = gtk_icon_theme_load_icon_for_scale (
5659               gtk_icon_theme_get_default (),
5660               filename+6,
5661               size_of_theme_icons,
5662               scale,
5663               0,
5664               error);
5665           if (pixbuf == NULL) return NULL;
5666          }
5667       else
5668         {
5669           char *full_path;
5670           full_path = g_build_filename (theme->dirname, filename, NULL);
5671 
5672           gint width, height;
5673 
5674           if (gdk_pixbuf_get_file_info (full_path, &width, &height) == NULL)
5675             {
5676               g_free (full_path);
5677               return NULL;
5678             }
5679 
5680           width *= scale;
5681           height *= scale;
5682 
5683           pixbuf = gdk_pixbuf_new_from_file_at_size (full_path, width, height, error);
5684 
5685           if (pixbuf == NULL)
5686             {
5687               g_free (full_path);
5688               return NULL;
5689             }
5690 
5691           g_free (full_path);
5692         }
5693       g_hash_table_replace (theme->images_by_filename,
5694                             g_strdup (filename),
5695                             pixbuf);
5696     }
5697 
5698   g_assert (pixbuf);
5699 
5700   g_object_ref (G_OBJECT (pixbuf));
5701 
5702   return pixbuf;
5703 }
5704 
5705 static MetaFrameStyle*
theme_get_style(MetaTheme * theme,MetaFrameType type,MetaFrameFlags flags)5706 theme_get_style (MetaTheme     *theme,
5707                  MetaFrameType  type,
5708                  MetaFrameFlags flags)
5709 {
5710   MetaFrameState state;
5711   MetaFrameResize resize;
5712   MetaFrameFocus focus;
5713   MetaFrameStyle *style;
5714   MetaFrameStyleSet *style_set;
5715 
5716   style_set = theme->style_sets_by_type[type];
5717 
5718   if (style_set == NULL && type == META_FRAME_TYPE_ATTACHED)
5719     style_set = theme->style_sets_by_type[META_FRAME_TYPE_BORDER];
5720 
5721   /* Right now the parser forces a style set for all other types,
5722    * but this fallback code is here in case I take that out.
5723    */
5724   if (style_set == NULL)
5725     style_set = theme->style_sets_by_type[META_FRAME_TYPE_NORMAL];
5726   if (style_set == NULL)
5727     return NULL;
5728 
5729   switch (flags & (META_FRAME_MAXIMIZED | META_FRAME_SHADED | META_FRAME_TILED_LEFT | META_FRAME_TILED_RIGHT))
5730     {
5731     case 0:
5732       state = META_FRAME_STATE_NORMAL;
5733       break;
5734     case META_FRAME_MAXIMIZED:
5735       state = META_FRAME_STATE_MAXIMIZED;
5736       break;
5737     case META_FRAME_TILED_LEFT:
5738     case (META_FRAME_MAXIMIZED | META_FRAME_TILED_LEFT):
5739       state = META_FRAME_STATE_TILED_LEFT;
5740       break;
5741     case META_FRAME_TILED_RIGHT:
5742     case (META_FRAME_MAXIMIZED | META_FRAME_TILED_RIGHT):
5743       state = META_FRAME_STATE_TILED_RIGHT;
5744       break;
5745     case META_FRAME_SHADED:
5746       state = META_FRAME_STATE_SHADED;
5747       break;
5748     case (META_FRAME_MAXIMIZED | META_FRAME_SHADED):
5749       state = META_FRAME_STATE_MAXIMIZED_AND_SHADED;
5750       break;
5751     case (META_FRAME_TILED_LEFT | META_FRAME_SHADED):
5752       state = META_FRAME_STATE_TILED_LEFT_AND_SHADED;
5753       break;
5754     case (META_FRAME_TILED_RIGHT | META_FRAME_SHADED):
5755       state = META_FRAME_STATE_TILED_RIGHT_AND_SHADED;
5756       break;
5757     default:
5758       g_assert_not_reached ();
5759       state = META_FRAME_STATE_LAST; /* compiler */
5760       break;
5761     }
5762 
5763   switch (flags & (META_FRAME_ALLOWS_VERTICAL_RESIZE | META_FRAME_ALLOWS_HORIZONTAL_RESIZE))
5764     {
5765     case 0:
5766       resize = META_FRAME_RESIZE_NONE;
5767       break;
5768     case META_FRAME_ALLOWS_VERTICAL_RESIZE:
5769       resize = META_FRAME_RESIZE_VERTICAL;
5770       break;
5771     case META_FRAME_ALLOWS_HORIZONTAL_RESIZE:
5772       resize = META_FRAME_RESIZE_HORIZONTAL;
5773       break;
5774     case (META_FRAME_ALLOWS_VERTICAL_RESIZE | META_FRAME_ALLOWS_HORIZONTAL_RESIZE):
5775       resize = META_FRAME_RESIZE_BOTH;
5776       break;
5777     default:
5778       g_assert_not_reached ();
5779       resize = META_FRAME_RESIZE_LAST; /* compiler */
5780       break;
5781     }
5782 
5783   /* re invert the styles used for focus/unfocussed while flashing a frame */
5784   if (((flags & META_FRAME_HAS_FOCUS) && !(flags & META_FRAME_IS_FLASHING))
5785       || (!(flags & META_FRAME_HAS_FOCUS) && (flags & META_FRAME_IS_FLASHING)))
5786     focus = META_FRAME_FOCUS_YES;
5787   else
5788     focus = META_FRAME_FOCUS_NO;
5789 
5790   style = get_style (style_set, state, resize, focus);
5791 
5792   return style;
5793 }
5794 
5795 MetaFrameStyle*
meta_theme_get_frame_style(MetaTheme * theme,MetaFrameType type,MetaFrameFlags flags)5796 meta_theme_get_frame_style (MetaTheme     *theme,
5797                             MetaFrameType  type,
5798                             MetaFrameFlags flags)
5799 {
5800   MetaFrameStyle *style;
5801 
5802   g_return_val_if_fail (type < META_FRAME_TYPE_LAST, NULL);
5803 
5804   style = theme_get_style (theme, type, flags);
5805 
5806   return style;
5807 }
5808 
5809 double
meta_theme_get_title_scale(MetaTheme * theme,MetaFrameType type,MetaFrameFlags flags)5810 meta_theme_get_title_scale (MetaTheme     *theme,
5811                             MetaFrameType  type,
5812                             MetaFrameFlags flags)
5813 {
5814   MetaFrameStyle *style;
5815 
5816   g_return_val_if_fail (type < META_FRAME_TYPE_LAST, 1.0);
5817 
5818   style = theme_get_style (theme, type, flags);
5819 
5820   /* Parser is not supposed to allow this currently */
5821   if (style == NULL)
5822     return 1.0;
5823 
5824   return style->layout->title_scale;
5825 }
5826 
5827 void
meta_theme_draw_frame(MetaTheme * theme,GtkStyleContext * style_gtk,cairo_t * cr,MetaFrameType type,MetaFrameFlags flags,int client_width,int client_height,PangoLayout * title_layout,int text_height,const MetaButtonLayout * button_layout,MetaButtonState button_states[META_BUTTON_TYPE_LAST],GdkPixbuf * mini_icon,GdkPixbuf * icon)5828 meta_theme_draw_frame (MetaTheme              *theme,
5829                        GtkStyleContext        *style_gtk,
5830                        cairo_t                *cr,
5831                        MetaFrameType           type,
5832                        MetaFrameFlags          flags,
5833                        int                     client_width,
5834                        int                     client_height,
5835                        PangoLayout            *title_layout,
5836                        int                     text_height,
5837                        const MetaButtonLayout *button_layout,
5838                        MetaButtonState         button_states[META_BUTTON_TYPE_LAST],
5839                        GdkPixbuf              *mini_icon,
5840                        GdkPixbuf              *icon)
5841 {
5842   MetaFrameGeometry fgeom;
5843   MetaFrameStyle *style;
5844 
5845   g_return_if_fail (type < META_FRAME_TYPE_LAST);
5846 
5847   style = theme_get_style (theme, type, flags);
5848 
5849   /* Parser is not supposed to allow this currently */
5850   if (style == NULL)
5851     return;
5852 
5853   meta_frame_layout_calc_geometry (style->layout,
5854                                    text_height,
5855                                    flags,
5856                                    client_width, client_height,
5857                                    button_layout,
5858                                    &fgeom,
5859                                    theme);
5860 
5861   meta_frame_style_draw_with_style (style,
5862                                     style_gtk,
5863                                     cr,
5864                                     &fgeom,
5865                                     client_width, client_height,
5866                                     title_layout,
5867                                     text_height,
5868                                     button_states,
5869                                     mini_icon, icon);
5870 }
5871 
5872 void
meta_theme_draw_frame_by_name(MetaTheme * theme,GtkWidget * widget,cairo_t * cr,const gchar * style_name,MetaFrameFlags flags,int client_width,int client_height,PangoLayout * title_layout,int text_height,const MetaButtonLayout * button_layout,MetaButtonState button_states[META_BUTTON_TYPE_LAST],GdkPixbuf * mini_icon,GdkPixbuf * icon)5873 meta_theme_draw_frame_by_name (MetaTheme              *theme,
5874                                GtkWidget              *widget,
5875                                cairo_t                *cr,
5876                                const gchar             *style_name,
5877                                MetaFrameFlags          flags,
5878                                int                     client_width,
5879                                int                     client_height,
5880                                PangoLayout            *title_layout,
5881                                int                     text_height,
5882                                const MetaButtonLayout *button_layout,
5883                                MetaButtonState         button_states[META_BUTTON_TYPE_LAST],
5884                                GdkPixbuf              *mini_icon,
5885                                GdkPixbuf              *icon)
5886 {
5887   MetaFrameGeometry fgeom;
5888   MetaFrameStyle *style;
5889 
5890   style = meta_theme_lookup_style (theme, style_name);
5891 
5892   /* Parser is not supposed to allow this currently */
5893   if (style == NULL)
5894     return;
5895 
5896   meta_frame_layout_calc_geometry (style->layout,
5897                                    text_height,
5898                                    flags,
5899                                    client_width, client_height,
5900                                    button_layout,
5901                                    &fgeom,
5902                                    theme);
5903 
5904   meta_frame_style_draw (style,
5905                          widget,
5906                          cr,
5907                          &fgeom,
5908                          client_width, client_height,
5909                          title_layout,
5910                          text_height,
5911                          button_states,
5912                          mini_icon, icon);
5913 }
5914 
5915 void
meta_theme_get_frame_borders(MetaTheme * theme,MetaFrameType type,int text_height,MetaFrameFlags flags,MetaFrameBorders * borders)5916 meta_theme_get_frame_borders (MetaTheme        *theme,
5917                               MetaFrameType     type,
5918                               int               text_height,
5919                               MetaFrameFlags    flags,
5920                               MetaFrameBorders *borders)
5921 {
5922   MetaFrameStyle *style;
5923 
5924   g_return_if_fail (type < META_FRAME_TYPE_LAST);
5925 
5926   style = theme_get_style (theme, type, flags);
5927 
5928   meta_frame_borders_clear (borders);
5929 
5930   /* Parser is not supposed to allow this currently */
5931   if (style == NULL)
5932     return;
5933 
5934   meta_frame_layout_get_borders (style->layout,
5935                                  text_height,
5936                                  flags,
5937                                  borders);
5938 }
5939 
5940 void
meta_theme_calc_geometry(MetaTheme * theme,MetaFrameType type,int text_height,MetaFrameFlags flags,int client_width,int client_height,const MetaButtonLayout * button_layout,MetaFrameGeometry * fgeom)5941 meta_theme_calc_geometry (MetaTheme              *theme,
5942                           MetaFrameType           type,
5943                           int                     text_height,
5944                           MetaFrameFlags          flags,
5945                           int                     client_width,
5946                           int                     client_height,
5947                           const MetaButtonLayout *button_layout,
5948                           MetaFrameGeometry      *fgeom)
5949 {
5950   MetaFrameStyle *style;
5951 
5952   g_return_if_fail (type < META_FRAME_TYPE_LAST);
5953 
5954   style = theme_get_style (theme, type, flags);
5955 
5956   /* Parser is not supposed to allow this currently */
5957   if (style == NULL)
5958     return;
5959 
5960   meta_frame_layout_calc_geometry (style->layout,
5961                                    text_height,
5962                                    flags,
5963                                    client_width, client_height,
5964                                    button_layout,
5965                                    fgeom,
5966                                    theme);
5967 }
5968 
5969 MetaFrameLayout*
meta_theme_lookup_layout(MetaTheme * theme,const char * name)5970 meta_theme_lookup_layout (MetaTheme         *theme,
5971                           const char        *name)
5972 {
5973   return g_hash_table_lookup (theme->layouts_by_name, name);
5974 }
5975 
5976 void
meta_theme_insert_layout(MetaTheme * theme,const char * name,MetaFrameLayout * layout)5977 meta_theme_insert_layout (MetaTheme         *theme,
5978                           const char        *name,
5979                           MetaFrameLayout   *layout)
5980 {
5981   meta_frame_layout_ref (layout);
5982   g_hash_table_replace (theme->layouts_by_name, g_strdup (name), layout);
5983 }
5984 
5985 MetaDrawOpList*
meta_theme_lookup_draw_op_list(MetaTheme * theme,const char * name)5986 meta_theme_lookup_draw_op_list (MetaTheme         *theme,
5987                                 const char        *name)
5988 {
5989   return g_hash_table_lookup (theme->draw_op_lists_by_name, name);
5990 }
5991 
5992 void
meta_theme_insert_draw_op_list(MetaTheme * theme,const char * name,MetaDrawOpList * op_list)5993 meta_theme_insert_draw_op_list (MetaTheme         *theme,
5994                                 const char        *name,
5995                                 MetaDrawOpList    *op_list)
5996 {
5997   meta_draw_op_list_ref (op_list);
5998   g_hash_table_replace (theme->draw_op_lists_by_name, g_strdup (name), op_list);
5999 }
6000 
6001 MetaFrameStyle*
meta_theme_lookup_style(MetaTheme * theme,const char * name)6002 meta_theme_lookup_style (MetaTheme         *theme,
6003                          const char        *name)
6004 {
6005   return g_hash_table_lookup (theme->styles_by_name, name);
6006 }
6007 
6008 void
meta_theme_insert_style(MetaTheme * theme,const char * name,MetaFrameStyle * style)6009 meta_theme_insert_style (MetaTheme         *theme,
6010                          const char        *name,
6011                          MetaFrameStyle    *style)
6012 {
6013   meta_frame_style_ref (style);
6014   g_hash_table_replace (theme->styles_by_name, g_strdup (name), style);
6015 }
6016 
6017 MetaFrameStyleSet*
meta_theme_lookup_style_set(MetaTheme * theme,const char * name)6018 meta_theme_lookup_style_set (MetaTheme         *theme,
6019                              const char        *name)
6020 {
6021   return g_hash_table_lookup (theme->style_sets_by_name, name);
6022 }
6023 
6024 void
meta_theme_insert_style_set(MetaTheme * theme,const char * name,MetaFrameStyleSet * style_set)6025 meta_theme_insert_style_set    (MetaTheme         *theme,
6026                                 const char        *name,
6027                                 MetaFrameStyleSet *style_set)
6028 {
6029   meta_frame_style_set_ref (style_set);
6030   g_hash_table_replace (theme->style_sets_by_name, g_strdup (name), style_set);
6031 }
6032 
6033 static gboolean
first_uppercase(const char * str)6034 first_uppercase (const char *str)
6035 {
6036   return g_ascii_isupper (*str);
6037 }
6038 
6039 gboolean
meta_theme_define_int_constant(MetaTheme * theme,const char * name,int value,GError ** error)6040 meta_theme_define_int_constant (MetaTheme   *theme,
6041                                 const char  *name,
6042                                 int          value,
6043                                 GError     **error)
6044 {
6045   if (theme->integer_constants == NULL)
6046     theme->integer_constants = g_hash_table_new_full (g_str_hash,
6047                                                       g_str_equal,
6048                                                       g_free,
6049                                                       NULL);
6050 
6051   if (!first_uppercase (name))
6052     {
6053       g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
6054                    _("User-defined constants must begin with a capital letter; \"%s\" does not"),
6055                    name);
6056       return FALSE;
6057     }
6058 
6059   if (g_hash_table_lookup_extended (theme->integer_constants, name, NULL, NULL))
6060     {
6061       g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
6062                    _("Constant \"%s\" has already been defined"),
6063                    name);
6064 
6065       return FALSE;
6066     }
6067 
6068   g_hash_table_insert (theme->integer_constants,
6069                        g_strdup (name),
6070                        GINT_TO_POINTER (value));
6071 
6072   return TRUE;
6073 }
6074 
6075 gboolean
meta_theme_lookup_int_constant(MetaTheme * theme,const char * name,int * value)6076 meta_theme_lookup_int_constant (MetaTheme   *theme,
6077                                 const char  *name,
6078                                 int         *value)
6079 {
6080   gpointer old_value;
6081 
6082   *value = 0;
6083 
6084   if (theme->integer_constants == NULL)
6085     return FALSE;
6086 
6087   if (g_hash_table_lookup_extended (theme->integer_constants,
6088                                     name, NULL, &old_value))
6089     {
6090       *value = GPOINTER_TO_INT (old_value);
6091       return TRUE;
6092     }
6093   else
6094     {
6095       return FALSE;
6096     }
6097 }
6098 
6099 gboolean
meta_theme_define_float_constant(MetaTheme * theme,const char * name,double value,GError ** error)6100 meta_theme_define_float_constant (MetaTheme   *theme,
6101                                   const char  *name,
6102                                   double       value,
6103                                   GError     **error)
6104 {
6105   double *d;
6106 
6107   if (theme->float_constants == NULL)
6108     theme->float_constants = g_hash_table_new_full (g_str_hash,
6109                                                     g_str_equal,
6110                                                     g_free,
6111                                                     g_free);
6112 
6113   if (!first_uppercase (name))
6114     {
6115       g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
6116                    _("User-defined constants must begin with a capital letter; \"%s\" does not"),
6117                    name);
6118       return FALSE;
6119     }
6120 
6121   if (g_hash_table_lookup_extended (theme->float_constants, name, NULL, NULL))
6122     {
6123       g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
6124                    _("Constant \"%s\" has already been defined"),
6125                    name);
6126 
6127       return FALSE;
6128     }
6129 
6130   d = g_new (double, 1);
6131   *d = value;
6132 
6133   g_hash_table_insert (theme->float_constants,
6134                        g_strdup (name), d);
6135 
6136   return TRUE;
6137 }
6138 
6139 gboolean
meta_theme_lookup_float_constant(MetaTheme * theme,const char * name,double * value)6140 meta_theme_lookup_float_constant (MetaTheme   *theme,
6141                                   const char  *name,
6142                                   double      *value)
6143 {
6144   double *d;
6145 
6146   *value = 0.0;
6147 
6148   if (theme->float_constants == NULL)
6149     return FALSE;
6150 
6151   d = g_hash_table_lookup (theme->float_constants, name);
6152 
6153   if (d)
6154     {
6155       *value = *d;
6156       return TRUE;
6157     }
6158   else
6159     {
6160       return FALSE;
6161     }
6162 }
6163 
6164 gboolean
meta_theme_define_color_constant(MetaTheme * theme,const char * name,const char * value,GError ** error)6165 meta_theme_define_color_constant (MetaTheme   *theme,
6166                                   const char  *name,
6167                                   const char  *value,
6168                                   GError     **error)
6169 {
6170   if (theme->color_constants == NULL)
6171     theme->color_constants = g_hash_table_new_full (g_str_hash,
6172                                                     g_str_equal,
6173                                                     g_free,
6174                                                     NULL);
6175 
6176   if (!first_uppercase (name))
6177     {
6178       g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
6179                    _("User-defined constants must begin with a capital letter; \"%s\" does not"),
6180                    name);
6181       return FALSE;
6182     }
6183 
6184   if (g_hash_table_lookup_extended (theme->color_constants, name, NULL, NULL))
6185     {
6186       g_set_error (error, META_THEME_ERROR, META_THEME_ERROR_FAILED,
6187                    _("Constant \"%s\" has already been defined"),
6188                    name);
6189 
6190       return FALSE;
6191     }
6192 
6193   g_hash_table_insert (theme->color_constants,
6194                        g_strdup (name),
6195                        g_strdup (value));
6196 
6197   return TRUE;
6198 }
6199 
6200 /**
6201  * Looks up a colour constant.
6202  *
6203  * \param theme  the theme containing the constant
6204  * \param name  the name of the constant
6205  * \param value  [out] the string representation of the colour, or NULL if it
6206  *               doesn't exist
6207  * \return  TRUE if it exists, FALSE otherwise
6208  */
6209 gboolean
meta_theme_lookup_color_constant(MetaTheme * theme,const char * name,char ** value)6210 meta_theme_lookup_color_constant (MetaTheme   *theme,
6211                                   const char  *name,
6212                                   char       **value)
6213 {
6214   char *result;
6215 
6216   *value = NULL;
6217 
6218   if (theme->color_constants == NULL)
6219     return FALSE;
6220 
6221   result = g_hash_table_lookup (theme->color_constants, name);
6222 
6223   if (result)
6224     {
6225       *value = result;
6226       return TRUE;
6227     }
6228   else
6229     {
6230       return FALSE;
6231     }
6232 }
6233 
6234 PangoFontDescription*
meta_gtk_widget_get_font_desc(GtkWidget * widget,double scale,const PangoFontDescription * override)6235 meta_gtk_widget_get_font_desc (GtkWidget *widget,
6236                                double     scale,
6237 			       const PangoFontDescription *override)
6238 {
6239   PangoFontDescription *font_desc;
6240 
6241   GtkStyleContext *style = gtk_widget_get_style_context (widget);
6242   GtkStateFlags state = gtk_widget_get_state_flags (widget);
6243   gtk_style_context_get(style, state, GTK_STYLE_PROPERTY_FONT, &font_desc, NULL);
6244   font_desc = pango_font_description_copy (font_desc);
6245 
6246   if (override)
6247     pango_font_description_merge (font_desc, override, TRUE);
6248 
6249   pango_font_description_set_size (font_desc,
6250                                    MAX (pango_font_description_get_size (font_desc) * scale, 1));
6251 
6252   return font_desc;
6253 }
6254 
6255 /**
6256  * Returns the height of the letters in a particular font.
6257  *
6258  * \param font_desc  the font
6259  * \param context  the context of the font
6260  * \return  the height of the letters
6261  */
6262 int
meta_pango_font_desc_get_text_height(const PangoFontDescription * font_desc,PangoContext * context)6263 meta_pango_font_desc_get_text_height (const PangoFontDescription *font_desc,
6264                                       PangoContext         *context)
6265 {
6266   PangoFontMetrics *metrics;
6267   PangoLanguage *lang;
6268   int retval;
6269 
6270   lang = pango_context_get_language (context);
6271   metrics = pango_context_get_metrics (context, font_desc, lang);
6272 
6273   retval = PANGO_PIXELS (pango_font_metrics_get_ascent (metrics) +
6274                          pango_font_metrics_get_descent (metrics));
6275 
6276   pango_font_metrics_unref (metrics);
6277 
6278   return retval;
6279 }
6280 
6281 MetaGtkColorComponent
meta_color_component_from_string(const char * str)6282 meta_color_component_from_string (const char *str)
6283 {
6284   if (strcmp ("fg", str) == 0)
6285     return META_GTK_COLOR_FG;
6286   else if (strcmp ("bg", str) == 0)
6287     return META_GTK_COLOR_BG;
6288   else if (strcmp ("light", str) == 0)
6289     return META_GTK_COLOR_LIGHT;
6290   else if (strcmp ("dark", str) == 0)
6291     return META_GTK_COLOR_DARK;
6292   else if (strcmp ("mid", str) == 0)
6293     return META_GTK_COLOR_MID;
6294   else if (strcmp ("text", str) == 0)
6295     return META_GTK_COLOR_TEXT;
6296   else if (strcmp ("base", str) == 0)
6297     return META_GTK_COLOR_BASE;
6298   else if (strcmp ("text_aa", str) == 0)
6299     return META_GTK_COLOR_TEXT_AA;
6300   else
6301     return META_GTK_COLOR_LAST;
6302 }
6303 
6304 const char*
meta_color_component_to_string(MetaGtkColorComponent component)6305 meta_color_component_to_string (MetaGtkColorComponent component)
6306 {
6307   switch (component)
6308     {
6309     case META_GTK_COLOR_FG:
6310       return "fg";
6311     case META_GTK_COLOR_BG:
6312       return "bg";
6313     case META_GTK_COLOR_LIGHT:
6314       return "light";
6315     case META_GTK_COLOR_DARK:
6316       return "dark";
6317     case META_GTK_COLOR_MID:
6318       return "mid";
6319     case META_GTK_COLOR_TEXT:
6320       return "text";
6321     case META_GTK_COLOR_BASE:
6322       return "base";
6323     case META_GTK_COLOR_TEXT_AA:
6324       return "text_aa";
6325     case META_GTK_COLOR_LAST:
6326       break;
6327     }
6328 
6329   return "<unknown>";
6330 }
6331 
6332 MetaButtonState
meta_button_state_from_string(const char * str)6333 meta_button_state_from_string (const char *str)
6334 {
6335   if (strcmp ("normal", str) == 0)
6336     return META_BUTTON_STATE_NORMAL;
6337   else if (strcmp ("pressed", str) == 0)
6338     return META_BUTTON_STATE_PRESSED;
6339   else if (strcmp ("prelight", str) == 0)
6340     return META_BUTTON_STATE_PRELIGHT;
6341   else
6342     return META_BUTTON_STATE_LAST;
6343 }
6344 
6345 const char*
meta_button_state_to_string(MetaButtonState state)6346 meta_button_state_to_string (MetaButtonState state)
6347 {
6348   switch (state)
6349     {
6350     case META_BUTTON_STATE_NORMAL:
6351       return "normal";
6352     case META_BUTTON_STATE_PRESSED:
6353       return "pressed";
6354     case META_BUTTON_STATE_PRELIGHT:
6355       return "prelight";
6356     case META_BUTTON_STATE_LAST:
6357       break;
6358     }
6359 
6360   return "<unknown>";
6361 }
6362 
6363 MetaButtonType
meta_button_type_from_string(const char * str,MetaTheme * theme)6364 meta_button_type_from_string (const char *str, MetaTheme *theme)
6365 {
6366   if (META_THEME_ALLOWS(theme, META_THEME_SHADE_STICK_ABOVE_BUTTONS))
6367     {
6368       if (strcmp ("shade", str) == 0)
6369         return META_BUTTON_TYPE_SHADE;
6370       else if (strcmp ("above", str) == 0)
6371         return META_BUTTON_TYPE_ABOVE;
6372       else if (strcmp ("stick", str) == 0)
6373         return META_BUTTON_TYPE_STICK;
6374       else if (strcmp ("unshade", str) == 0)
6375         return META_BUTTON_TYPE_UNSHADE;
6376       else if (strcmp ("unabove", str) == 0)
6377         return META_BUTTON_TYPE_UNABOVE;
6378       else if (strcmp ("unstick", str) == 0)
6379         return META_BUTTON_TYPE_UNSTICK;
6380      }
6381 
6382   if (strcmp ("close", str) == 0)
6383     return META_BUTTON_TYPE_CLOSE;
6384   else if (strcmp ("maximize", str) == 0)
6385     return META_BUTTON_TYPE_MAXIMIZE;
6386   else if (strcmp ("minimize", str) == 0)
6387     return META_BUTTON_TYPE_MINIMIZE;
6388   else if (strcmp ("menu", str) == 0)
6389     return META_BUTTON_TYPE_MENU;
6390   else if (strcmp ("appmenu", str) == 0)
6391     return META_BUTTON_TYPE_APPMENU;
6392   else if (strcmp ("left_left_background", str) == 0)
6393     return META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND;
6394   else if (strcmp ("left_middle_background", str) == 0)
6395     return META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND;
6396   else if (strcmp ("left_right_background", str) == 0)
6397     return META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND;
6398   else if (strcmp ("left_single_background", str) == 0)
6399     return META_BUTTON_TYPE_LEFT_SINGLE_BACKGROUND;
6400   else if (strcmp ("right_left_background", str) == 0)
6401     return META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND;
6402   else if (strcmp ("right_middle_background", str) == 0)
6403     return META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND;
6404   else if (strcmp ("right_right_background", str) == 0)
6405     return META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND;
6406   else if (strcmp ("right_single_background", str) == 0)
6407     return META_BUTTON_TYPE_RIGHT_SINGLE_BACKGROUND;
6408   else
6409     return META_BUTTON_TYPE_LAST;
6410 }
6411 
6412 const char*
meta_button_type_to_string(MetaButtonType type)6413 meta_button_type_to_string (MetaButtonType type)
6414 {
6415   switch (type)
6416     {
6417     case META_BUTTON_TYPE_CLOSE:
6418       return "close";
6419     case META_BUTTON_TYPE_MAXIMIZE:
6420       return "maximize";
6421     case META_BUTTON_TYPE_MINIMIZE:
6422       return "minimize";
6423     case META_BUTTON_TYPE_SHADE:
6424      return "shade";
6425     case META_BUTTON_TYPE_ABOVE:
6426       return "above";
6427     case META_BUTTON_TYPE_STICK:
6428       return "stick";
6429     case META_BUTTON_TYPE_UNSHADE:
6430       return "unshade";
6431     case META_BUTTON_TYPE_UNABOVE:
6432       return "unabove";
6433     case META_BUTTON_TYPE_UNSTICK:
6434       return "unstick";
6435     case META_BUTTON_TYPE_MENU:
6436       return "menu";
6437     case META_BUTTON_TYPE_APPMENU:
6438       return "appmenu";
6439     case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND:
6440       return "left_left_background";
6441     case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND:
6442       return "left_middle_background";
6443     case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND:
6444       return "left_right_background";
6445     case META_BUTTON_TYPE_LEFT_SINGLE_BACKGROUND:
6446       return "left_single_background";
6447     case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND:
6448       return "right_left_background";
6449     case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND:
6450       return "right_middle_background";
6451     case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND:
6452       return "right_right_background";
6453     case META_BUTTON_TYPE_RIGHT_SINGLE_BACKGROUND:
6454       return "right_single_background";
6455     case META_BUTTON_TYPE_LAST:
6456       break;
6457     }
6458 
6459   return "<unknown>";
6460 }
6461 
6462 MetaFramePiece
meta_frame_piece_from_string(const char * str)6463 meta_frame_piece_from_string (const char *str)
6464 {
6465   if (strcmp ("entire_background", str) == 0)
6466     return META_FRAME_PIECE_ENTIRE_BACKGROUND;
6467   else if (strcmp ("titlebar", str) == 0)
6468     return META_FRAME_PIECE_TITLEBAR;
6469   else if (strcmp ("titlebar_middle", str) == 0)
6470     return META_FRAME_PIECE_TITLEBAR_MIDDLE;
6471   else if (strcmp ("left_titlebar_edge", str) == 0)
6472     return META_FRAME_PIECE_LEFT_TITLEBAR_EDGE;
6473   else if (strcmp ("right_titlebar_edge", str) == 0)
6474     return META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE;
6475   else if (strcmp ("top_titlebar_edge", str) == 0)
6476     return META_FRAME_PIECE_TOP_TITLEBAR_EDGE;
6477   else if (strcmp ("bottom_titlebar_edge", str) == 0)
6478     return META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE;
6479   else if (strcmp ("title", str) == 0)
6480     return META_FRAME_PIECE_TITLE;
6481   else if (strcmp ("left_edge", str) == 0)
6482     return META_FRAME_PIECE_LEFT_EDGE;
6483   else if (strcmp ("right_edge", str) == 0)
6484     return META_FRAME_PIECE_RIGHT_EDGE;
6485   else if (strcmp ("bottom_edge", str) == 0)
6486     return META_FRAME_PIECE_BOTTOM_EDGE;
6487   else if (strcmp ("overlay", str) == 0)
6488     return META_FRAME_PIECE_OVERLAY;
6489   else
6490     return META_FRAME_PIECE_LAST;
6491 }
6492 
6493 const char*
meta_frame_piece_to_string(MetaFramePiece piece)6494 meta_frame_piece_to_string (MetaFramePiece piece)
6495 {
6496   switch (piece)
6497     {
6498     case META_FRAME_PIECE_ENTIRE_BACKGROUND:
6499       return "entire_background";
6500     case META_FRAME_PIECE_TITLEBAR:
6501       return "titlebar";
6502     case META_FRAME_PIECE_TITLEBAR_MIDDLE:
6503       return "titlebar_middle";
6504     case META_FRAME_PIECE_LEFT_TITLEBAR_EDGE:
6505       return "left_titlebar_edge";
6506     case META_FRAME_PIECE_RIGHT_TITLEBAR_EDGE:
6507       return "right_titlebar_edge";
6508     case META_FRAME_PIECE_TOP_TITLEBAR_EDGE:
6509       return "top_titlebar_edge";
6510     case META_FRAME_PIECE_BOTTOM_TITLEBAR_EDGE:
6511       return "bottom_titlebar_edge";
6512     case META_FRAME_PIECE_TITLE:
6513       return "title";
6514     case META_FRAME_PIECE_LEFT_EDGE:
6515       return "left_edge";
6516     case META_FRAME_PIECE_RIGHT_EDGE:
6517       return "right_edge";
6518     case META_FRAME_PIECE_BOTTOM_EDGE:
6519       return "bottom_edge";
6520     case META_FRAME_PIECE_OVERLAY:
6521       return "overlay";
6522     case META_FRAME_PIECE_LAST:
6523       break;
6524     }
6525 
6526   return "<unknown>";
6527 }
6528 
6529 MetaFrameState
meta_frame_state_from_string(const char * str)6530 meta_frame_state_from_string (const char *str)
6531 {
6532   if (strcmp ("normal", str) == 0)
6533     return META_FRAME_STATE_NORMAL;
6534   else if (strcmp ("maximized", str) == 0)
6535     return META_FRAME_STATE_MAXIMIZED;
6536   else if (strcmp ("tiled_left", str) == 0)
6537     return META_FRAME_STATE_TILED_LEFT;
6538   else if (strcmp ("tiled_right", str) == 0)
6539     return META_FRAME_STATE_TILED_RIGHT;
6540   else if (strcmp ("shaded", str) == 0)
6541     return META_FRAME_STATE_SHADED;
6542   else if (strcmp ("maximized_and_shaded", str) == 0)
6543     return META_FRAME_STATE_MAXIMIZED_AND_SHADED;
6544   else if (strcmp ("tiled_left_and_shaded", str) == 0)
6545     return META_FRAME_STATE_TILED_LEFT_AND_SHADED;
6546   else if (strcmp ("tiled_right_and_shaded", str) == 0)
6547     return META_FRAME_STATE_TILED_RIGHT_AND_SHADED;
6548   else
6549     return META_FRAME_STATE_LAST;
6550 }
6551 
6552 const char*
meta_frame_state_to_string(MetaFrameState state)6553 meta_frame_state_to_string (MetaFrameState state)
6554 {
6555   switch (state)
6556     {
6557     case META_FRAME_STATE_NORMAL:
6558       return "normal";
6559     case META_FRAME_STATE_MAXIMIZED:
6560       return "maximized";
6561     case META_FRAME_STATE_TILED_LEFT:
6562       return "tiled_left";
6563     case META_FRAME_STATE_TILED_RIGHT:
6564       return "tiled_right";
6565     case META_FRAME_STATE_SHADED:
6566       return "shaded";
6567     case META_FRAME_STATE_MAXIMIZED_AND_SHADED:
6568       return "maximized_and_shaded";
6569     case META_FRAME_STATE_TILED_LEFT_AND_SHADED:
6570       return "tiled_left_and_shaded";
6571     case META_FRAME_STATE_TILED_RIGHT_AND_SHADED:
6572       return "tiled_right_and_shaded";
6573     case META_FRAME_STATE_LAST:
6574       break;
6575     }
6576 
6577   return "<unknown>";
6578 }
6579 
6580 MetaFrameResize
meta_frame_resize_from_string(const char * str)6581 meta_frame_resize_from_string (const char *str)
6582 {
6583   if (strcmp ("none", str) == 0)
6584     return META_FRAME_RESIZE_NONE;
6585   else if (strcmp ("vertical", str) == 0)
6586     return META_FRAME_RESIZE_VERTICAL;
6587   else if (strcmp ("horizontal", str) == 0)
6588     return META_FRAME_RESIZE_HORIZONTAL;
6589   else if (strcmp ("both", str) == 0)
6590     return META_FRAME_RESIZE_BOTH;
6591   else
6592     return META_FRAME_RESIZE_LAST;
6593 }
6594 
6595 const char*
meta_frame_resize_to_string(MetaFrameResize resize)6596 meta_frame_resize_to_string (MetaFrameResize resize)
6597 {
6598   switch (resize)
6599     {
6600     case META_FRAME_RESIZE_NONE:
6601       return "none";
6602     case META_FRAME_RESIZE_VERTICAL:
6603       return "vertical";
6604     case META_FRAME_RESIZE_HORIZONTAL:
6605       return "horizontal";
6606     case META_FRAME_RESIZE_BOTH:
6607       return "both";
6608     case META_FRAME_RESIZE_LAST:
6609       break;
6610     }
6611 
6612   return "<unknown>";
6613 }
6614 
6615 MetaFrameFocus
meta_frame_focus_from_string(const char * str)6616 meta_frame_focus_from_string (const char *str)
6617 {
6618   if (strcmp ("no", str) == 0)
6619     return META_FRAME_FOCUS_NO;
6620   else if (strcmp ("yes", str) == 0)
6621     return META_FRAME_FOCUS_YES;
6622   else
6623     return META_FRAME_FOCUS_LAST;
6624 }
6625 
6626 const char*
meta_frame_focus_to_string(MetaFrameFocus focus)6627 meta_frame_focus_to_string (MetaFrameFocus focus)
6628 {
6629   switch (focus)
6630     {
6631     case META_FRAME_FOCUS_NO:
6632       return "no";
6633     case META_FRAME_FOCUS_YES:
6634       return "yes";
6635     case META_FRAME_FOCUS_LAST:
6636       break;
6637     }
6638 
6639   return "<unknown>";
6640 }
6641 
6642 MetaFrameType
meta_frame_type_from_string(const char * str)6643 meta_frame_type_from_string (const char *str)
6644 {
6645   if (strcmp ("normal", str) == 0)
6646     return META_FRAME_TYPE_NORMAL;
6647   else if (strcmp ("dialog", str) == 0)
6648     return META_FRAME_TYPE_DIALOG;
6649   else if (strcmp ("modal_dialog", str) == 0)
6650     return META_FRAME_TYPE_MODAL_DIALOG;
6651   else if (strcmp ("utility", str) == 0)
6652     return META_FRAME_TYPE_UTILITY;
6653   else if (strcmp ("menu", str) == 0)
6654     return META_FRAME_TYPE_MENU;
6655   else if (strcmp ("border", str) == 0)
6656     return META_FRAME_TYPE_BORDER;
6657   else if (strcmp ("attached", str) == 0)
6658     return META_FRAME_TYPE_ATTACHED;
6659 #if 0
6660   else if (strcmp ("toolbar", str) == 0)
6661     return META_FRAME_TYPE_TOOLBAR;
6662 #endif
6663   else
6664     return META_FRAME_TYPE_LAST;
6665 }
6666 
6667 const char*
meta_frame_type_to_string(MetaFrameType type)6668 meta_frame_type_to_string (MetaFrameType type)
6669 {
6670   switch (type)
6671     {
6672     case META_FRAME_TYPE_NORMAL:
6673       return "normal";
6674     case META_FRAME_TYPE_DIALOG:
6675       return "dialog";
6676     case META_FRAME_TYPE_MODAL_DIALOG:
6677       return "modal_dialog";
6678     case META_FRAME_TYPE_UTILITY:
6679       return "utility";
6680     case META_FRAME_TYPE_MENU:
6681       return "menu";
6682     case META_FRAME_TYPE_BORDER:
6683       return "border";
6684     case META_FRAME_TYPE_ATTACHED:
6685       return "attached";
6686 #if 0
6687     case META_FRAME_TYPE_TOOLBAR:
6688       return "toolbar";
6689 #endif
6690     case  META_FRAME_TYPE_LAST:
6691       break;
6692     }
6693 
6694   return "<unknown>";
6695 }
6696 
6697 MetaGradientType
meta_gradient_type_from_string(const char * str)6698 meta_gradient_type_from_string (const char *str)
6699 {
6700   if (strcmp ("vertical", str) == 0)
6701     return META_GRADIENT_VERTICAL;
6702   else if (strcmp ("horizontal", str) == 0)
6703     return META_GRADIENT_HORIZONTAL;
6704   else if (strcmp ("diagonal", str) == 0)
6705     return META_GRADIENT_DIAGONAL;
6706   else
6707     return META_GRADIENT_LAST;
6708 }
6709 
6710 const char*
meta_gradient_type_to_string(MetaGradientType type)6711 meta_gradient_type_to_string (MetaGradientType type)
6712 {
6713   switch (type)
6714     {
6715     case META_GRADIENT_VERTICAL:
6716       return "vertical";
6717     case META_GRADIENT_HORIZONTAL:
6718       return "horizontal";
6719     case META_GRADIENT_DIAGONAL:
6720       return "diagonal";
6721     case META_GRADIENT_LAST:
6722       break;
6723     }
6724 
6725   return "<unknown>";
6726 }
6727 
6728 GtkStateFlags
meta_gtk_state_from_string(const char * str)6729 meta_gtk_state_from_string (const char *str)
6730 {
6731   if (g_ascii_strcasecmp ("normal", str) == 0)
6732     return GTK_STATE_FLAG_NORMAL;
6733   else if (g_ascii_strcasecmp ("prelight", str) == 0)
6734     return GTK_STATE_FLAG_PRELIGHT;
6735   else if (g_ascii_strcasecmp ("active", str) == 0)
6736     return GTK_STATE_FLAG_ACTIVE;
6737   else if (g_ascii_strcasecmp ("selected", str) == 0)
6738     return GTK_STATE_FLAG_SELECTED;
6739   else if (g_ascii_strcasecmp ("insensitive", str) == 0)
6740     return GTK_STATE_FLAG_INSENSITIVE;
6741   else if (g_ascii_strcasecmp ("inconsistent", str) == 0)
6742     return GTK_STATE_FLAG_INCONSISTENT;
6743   else if (g_ascii_strcasecmp ("focused", str) == 0)
6744     return GTK_STATE_FLAG_FOCUSED;
6745   else if (g_ascii_strcasecmp ("backdrop", str) == 0)
6746     return GTK_STATE_FLAG_BACKDROP;
6747   else
6748     return -1; /* hack */
6749 }
6750 
6751 GtkShadowType
meta_gtk_shadow_from_string(const char * str)6752 meta_gtk_shadow_from_string (const char *str)
6753 {
6754   if (strcmp ("none", str) == 0)
6755     return GTK_SHADOW_NONE;
6756   else if (strcmp ("in", str) == 0)
6757     return GTK_SHADOW_IN;
6758   else if (strcmp ("out", str) == 0)
6759     return GTK_SHADOW_OUT;
6760   else if (strcmp ("etched_in", str) == 0)
6761     return GTK_SHADOW_ETCHED_IN;
6762   else if (strcmp ("etched_out", str) == 0)
6763     return GTK_SHADOW_ETCHED_OUT;
6764   else
6765     return -1;
6766 }
6767 
6768 const char*
meta_gtk_shadow_to_string(GtkShadowType shadow)6769 meta_gtk_shadow_to_string (GtkShadowType shadow)
6770 {
6771   switch (shadow)
6772     {
6773     case GTK_SHADOW_NONE:
6774       return "none";
6775     case GTK_SHADOW_IN:
6776       return "in";
6777     case GTK_SHADOW_OUT:
6778       return "out";
6779     case GTK_SHADOW_ETCHED_IN:
6780       return "etched_in";
6781     case GTK_SHADOW_ETCHED_OUT:
6782       return "etched_out";
6783     }
6784 
6785   return "<unknown>";
6786 }
6787 
6788 GtkArrowType
meta_gtk_arrow_from_string(const char * str)6789 meta_gtk_arrow_from_string (const char *str)
6790 {
6791   if (strcmp ("up", str) == 0)
6792     return GTK_ARROW_UP;
6793   else if (strcmp ("down", str) == 0)
6794     return GTK_ARROW_DOWN;
6795   else if (strcmp ("left", str) == 0)
6796     return GTK_ARROW_LEFT;
6797   else if (strcmp ("right", str) == 0)
6798     return GTK_ARROW_RIGHT;
6799   else if (strcmp ("none", str) == 0)
6800     return GTK_ARROW_NONE;
6801   else
6802     return -1;
6803 }
6804 
6805 const char*
meta_gtk_arrow_to_string(GtkArrowType arrow)6806 meta_gtk_arrow_to_string (GtkArrowType arrow)
6807 {
6808   switch (arrow)
6809     {
6810     case GTK_ARROW_UP:
6811       return "up";
6812     case GTK_ARROW_DOWN:
6813       return "down";
6814     case GTK_ARROW_LEFT:
6815       return "left";
6816     case GTK_ARROW_RIGHT:
6817       return "right";
6818     case GTK_ARROW_NONE:
6819       return "none";
6820     }
6821 
6822   return "<unknown>";
6823 }
6824 
6825 /**
6826  * Returns a fill_type from a string.  The inverse of
6827  * meta_image_fill_type_to_string().
6828  *
6829  * \param str  a string representing a fill_type
6830  * \result  the fill_type, or -1 if it represents no fill_type.
6831  */
6832 MetaImageFillType
meta_image_fill_type_from_string(const char * str)6833 meta_image_fill_type_from_string (const char *str)
6834 {
6835   if (strcmp ("tile", str) == 0)
6836     return META_IMAGE_FILL_TILE;
6837   else if (strcmp ("scale", str) == 0)
6838     return META_IMAGE_FILL_SCALE;
6839   else
6840     return -1;
6841 }
6842 
6843 /**
6844  * Returns a string representation of a fill_type.  The inverse of
6845  * meta_image_fill_type_from_string().
6846  *
6847  * \param fill_type  the fill type
6848  * \result  a string representing that type
6849  */
6850 const char*
meta_image_fill_type_to_string(MetaImageFillType fill_type)6851 meta_image_fill_type_to_string (MetaImageFillType fill_type)
6852 {
6853   switch (fill_type)
6854     {
6855     case META_IMAGE_FILL_TILE:
6856       return "tile";
6857     case META_IMAGE_FILL_SCALE:
6858       return "scale";
6859     }
6860 
6861   return "<unknown>";
6862 }
6863 
6864 /**
6865  * Takes a colour "a", scales the lightness and saturation by a certain amount,
6866  * and sets "b" to the resulting colour.
6867  * gtkstyle.c cut-and-pastage.
6868  *
6869  * \param a  the starting colour
6870  * \param b  [out] the resulting colour
6871  * \param k  amount to scale lightness and saturation by
6872  */
6873 static void
gtk_style_shade(GdkRGBA * a,GdkRGBA * b,gdouble k)6874 gtk_style_shade (GdkRGBA *a,
6875                  GdkRGBA *b,
6876                  gdouble  k)
6877 {
6878   gdouble red;
6879   gdouble green;
6880   gdouble blue;
6881 
6882   red = a->red;
6883   green = a->green;
6884   blue = a->blue;
6885 
6886   rgb_to_hls (&red, &green, &blue);
6887 
6888   green *= k;
6889   if (green > 1.0)
6890     green = 1.0;
6891   else if (green < 0.0)
6892     green = 0.0;
6893 
6894   blue *= k;
6895   if (blue > 1.0)
6896     blue = 1.0;
6897   else if (blue < 0.0)
6898     blue = 0.0;
6899 
6900   hls_to_rgb (&red, &green, &blue);
6901 
6902   b->red = red;
6903   b->green = green;
6904   b->blue = blue;
6905 }
6906 
6907 /**
6908  * Converts a red/green/blue triplet to a hue/lightness/saturation triplet.
6909  *
6910  * \param r  on input, red; on output, hue
6911  * \param g  on input, green; on output, lightness
6912  * \param b  on input, blue; on output, saturation
6913  */
6914 static void
rgb_to_hls(gdouble * r,gdouble * g,gdouble * b)6915 rgb_to_hls (gdouble *r,
6916             gdouble *g,
6917             gdouble *b)
6918 {
6919   gdouble min;
6920   gdouble max;
6921   gdouble red;
6922   gdouble green;
6923   gdouble blue;
6924   gdouble h, l, s;
6925   gdouble delta;
6926 
6927   red = *r;
6928   green = *g;
6929   blue = *b;
6930 
6931   if (red > green)
6932     {
6933       if (red > blue)
6934         max = red;
6935       else
6936         max = blue;
6937 
6938       if (green < blue)
6939         min = green;
6940       else
6941         min = blue;
6942     }
6943   else
6944     {
6945       if (green > blue)
6946         max = green;
6947       else
6948         max = blue;
6949 
6950       if (red < blue)
6951         min = red;
6952       else
6953         min = blue;
6954     }
6955 
6956   l = (max + min) / 2;
6957   s = 0;
6958   h = 0;
6959 
6960   if (max != min)
6961     {
6962       if (l <= 0.5)
6963         s = (max - min) / (max + min);
6964       else
6965         s = (max - min) / (2 - max - min);
6966 
6967       delta = max -min;
6968       if (red == max)
6969         h = (green - blue) / delta;
6970       else if (green == max)
6971         h = 2 + (blue - red) / delta;
6972       else if (blue == max)
6973         h = 4 + (red - green) / delta;
6974 
6975       h *= 60;
6976       if (h < 0.0)
6977         h += 360;
6978     }
6979 
6980   *r = h;
6981   *g = l;
6982   *b = s;
6983 }
6984 
6985 /**
6986  * Converts a hue/lightness/saturation triplet to a red/green/blue triplet.
6987  *
6988  * \param h  on input, hue; on output, red
6989  * \param l  on input, lightness; on output, green
6990  * \param s  on input, saturation; on output, blue
6991  */
6992 static void
hls_to_rgb(gdouble * h,gdouble * l,gdouble * s)6993 hls_to_rgb (gdouble *h,
6994             gdouble *l,
6995             gdouble *s)
6996 {
6997   gdouble hue;
6998   gdouble lightness;
6999   gdouble saturation;
7000   gdouble m1, m2;
7001   gdouble r, g, b;
7002 
7003   lightness = *l;
7004   saturation = *s;
7005 
7006   if (lightness <= 0.5)
7007     m2 = lightness * (1 + saturation);
7008   else
7009     m2 = lightness + saturation - lightness * saturation;
7010   m1 = 2 * lightness - m2;
7011 
7012   if (saturation == 0)
7013     {
7014       *h = lightness;
7015       *l = lightness;
7016       *s = lightness;
7017     }
7018   else
7019     {
7020       hue = *h + 120;
7021       while (hue > 360)
7022         hue -= 360;
7023       while (hue < 0)
7024         hue += 360;
7025 
7026       if (hue < 60)
7027         r = m1 + (m2 - m1) * hue / 60;
7028       else if (hue < 180)
7029         r = m2;
7030       else if (hue < 240)
7031         r = m1 + (m2 - m1) * (240 - hue) / 60;
7032       else
7033         r = m1;
7034 
7035       hue = *h;
7036       while (hue > 360)
7037         hue -= 360;
7038       while (hue < 0)
7039         hue += 360;
7040 
7041       if (hue < 60)
7042         g = m1 + (m2 - m1) * hue / 60;
7043       else if (hue < 180)
7044         g = m2;
7045       else if (hue < 240)
7046         g = m1 + (m2 - m1) * (240 - hue) / 60;
7047       else
7048         g = m1;
7049 
7050       hue = *h - 120;
7051       while (hue > 360)
7052         hue -= 360;
7053       while (hue < 0)
7054         hue += 360;
7055 
7056       if (hue < 60)
7057         b = m1 + (m2 - m1) * hue / 60;
7058       else if (hue < 180)
7059         b = m2;
7060       else if (hue < 240)
7061         b = m1 + (m2 - m1) * (240 - hue) / 60;
7062       else
7063         b = m1;
7064 
7065       *h = r;
7066       *l = g;
7067       *s = b;
7068     }
7069 }
7070 
7071 #if 0
7072 /* These are some functions I'm saving to use in optimizing
7073  * MetaDrawOpList, namely to pre-composite pixbufs on client side
7074  * prior to rendering to the server
7075  */
7076 static void
7077 draw_bg_solid_composite (const MetaTextureSpec *bg,
7078                          const MetaTextureSpec *fg,
7079                          double                 alpha,
7080                          GtkWidget             *widget,
7081                          GdkDrawable           *drawable,
7082                          const GdkRectangle    *clip,
7083                          MetaTextureDrawMode    mode,
7084                          double                 xalign,
7085                          double                 yalign,
7086                          int                    x,
7087                          int                    y,
7088                          int                    width,
7089                          int                    height)
7090 {
7091   GdkColor bg_color;
7092 
7093   g_assert (bg->type == META_TEXTURE_SOLID);
7094   g_assert (fg->type != META_TEXTURE_COMPOSITE);
7095   g_assert (fg->type != META_TEXTURE_SHAPE_LIST);
7096 
7097   meta_color_spec_render (bg->data.solid.color_spec,
7098                           widget,
7099                           &bg_color);
7100 
7101   switch (fg->type)
7102     {
7103     case META_TEXTURE_SOLID:
7104       {
7105         GdkColor fg_color;
7106 
7107         meta_color_spec_render (fg->data.solid.color_spec,
7108                                 widget,
7109                                 &fg_color);
7110 
7111         color_composite (&bg_color, &fg_color,
7112                          alpha, &fg_color);
7113 
7114         draw_color_rectangle (widget, drawable, &fg_color, clip,
7115                               x, y, width, height);
7116       }
7117       break;
7118 
7119     case META_TEXTURE_GRADIENT:
7120       /* FIXME I think we could just composite all the colors in
7121        * the gradient prior to generating the gradient?
7122        */
7123       /* FALL THRU */
7124     case META_TEXTURE_IMAGE:
7125       {
7126         GdkPixbuf *pixbuf;
7127         GdkPixbuf *composited;
7128 
7129         pixbuf = meta_texture_spec_render (fg, widget, mode, 255,
7130                                            width, height);
7131 
7132         if (pixbuf == NULL)
7133           return;
7134 
7135         composited = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
7136                                      gdk_pixbuf_get_has_alpha (pixbuf), 8,
7137                                      gdk_pixbuf_get_width (pixbuf),
7138                                      gdk_pixbuf_get_height (pixbuf));
7139 
7140         if (composited == NULL)
7141           {
7142             g_object_unref (G_OBJECT (pixbuf));
7143             return;
7144           }
7145 
7146         gdk_pixbuf_composite_color (pixbuf,
7147                                     composited,
7148                                     0, 0,
7149                                     gdk_pixbuf_get_width (pixbuf),
7150                                     gdk_pixbuf_get_height (pixbuf),
7151                                     0.0, 0.0, /* offsets */
7152                                     1.0, 1.0, /* scale */
7153                                     GDK_INTERP_BILINEAR,
7154                                     255 * alpha,
7155                                     0, 0,     /* check offsets */
7156                                     0,        /* check size */
7157                                     GDK_COLOR_RGB (bg_color),
7158                                     GDK_COLOR_RGB (bg_color));
7159 
7160         /* Need to draw background since pixbuf is not
7161          * necessarily covering the whole thing
7162          */
7163         draw_color_rectangle (widget, drawable, &bg_color, clip,
7164                               x, y, width, height);
7165 
7166         render_pixbuf_aligned (drawable, clip, composited,
7167                                xalign, yalign,
7168                                x, y, width, height);
7169 
7170         g_object_unref (G_OBJECT (pixbuf));
7171         g_object_unref (G_OBJECT (composited));
7172       }
7173       break;
7174 
7175     case META_TEXTURE_BLANK:
7176     case META_TEXTURE_COMPOSITE:
7177     case META_TEXTURE_SHAPE_LIST:
7178       g_assert_not_reached ();
7179       break;
7180     }
7181 }
7182 
7183 static void
7184 draw_bg_gradient_composite (const MetaTextureSpec *bg,
7185                             const MetaTextureSpec *fg,
7186                             double                 alpha,
7187                             GtkWidget             *widget,
7188                             GdkDrawable           *drawable,
7189                             const GdkRectangle    *clip,
7190                             MetaTextureDrawMode    mode,
7191                             double                 xalign,
7192                             double                 yalign,
7193                             int                    x,
7194                             int                    y,
7195                             int                    width,
7196                             int                    height)
7197 {
7198   g_assert (bg->type == META_TEXTURE_GRADIENT);
7199   g_assert (fg->type != META_TEXTURE_COMPOSITE);
7200   g_assert (fg->type != META_TEXTURE_SHAPE_LIST);
7201 
7202   switch (fg->type)
7203     {
7204     case META_TEXTURE_SOLID:
7205     case META_TEXTURE_GRADIENT:
7206     case META_TEXTURE_IMAGE:
7207       {
7208         GdkPixbuf *bg_pixbuf;
7209         GdkPixbuf *fg_pixbuf;
7210         GdkPixbuf *composited;
7211         int fg_width, fg_height;
7212 
7213         bg_pixbuf = meta_texture_spec_render (bg, widget, mode, 255,
7214                                               width, height);
7215 
7216         if (bg_pixbuf == NULL)
7217           return;
7218 
7219         fg_pixbuf = meta_texture_spec_render (fg, widget, mode, 255,
7220                                               width, height);
7221 
7222         if (fg_pixbuf == NULL)
7223           {
7224             g_object_unref (G_OBJECT (bg_pixbuf));
7225             return;
7226           }
7227 
7228         /* gradients always fill the entire target area */
7229         g_assert (gdk_pixbuf_get_width (bg_pixbuf) == width);
7230         g_assert (gdk_pixbuf_get_height (bg_pixbuf) == height);
7231 
7232         composited = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
7233                                      gdk_pixbuf_get_has_alpha (bg_pixbuf), 8,
7234                                      gdk_pixbuf_get_width (bg_pixbuf),
7235                                      gdk_pixbuf_get_height (bg_pixbuf));
7236 
7237         if (composited == NULL)
7238           {
7239             g_object_unref (G_OBJECT (bg_pixbuf));
7240             g_object_unref (G_OBJECT (fg_pixbuf));
7241             return;
7242           }
7243 
7244         fg_width = gdk_pixbuf_get_width (fg_pixbuf);
7245         fg_height = gdk_pixbuf_get_height (fg_pixbuf);
7246 
7247         /* If we wanted to be all cool we could deal with the
7248          * offsets and try to composite only in the clip rectangle,
7249          * but I just don't care enough to figure it out.
7250          */
7251 
7252         gdk_pixbuf_composite (fg_pixbuf,
7253                               composited,
7254                               x + (width - fg_width) * xalign,
7255                               y + (height - fg_height) * yalign,
7256                               gdk_pixbuf_get_width (fg_pixbuf),
7257                               gdk_pixbuf_get_height (fg_pixbuf),
7258                               0.0, 0.0, /* offsets */
7259                               1.0, 1.0, /* scale */
7260                               GDK_INTERP_BILINEAR,
7261                               255 * alpha);
7262 
7263         gdk_cairo_set_source_pixbuf (cr, composited, x, y);
7264         cairo_paint (cr);
7265 
7266         g_object_unref (G_OBJECT (bg_pixbuf));
7267         g_object_unref (G_OBJECT (fg_pixbuf));
7268         g_object_unref (G_OBJECT (composited));
7269       }
7270       break;
7271 
7272     case META_TEXTURE_BLANK:
7273     case META_TEXTURE_SHAPE_LIST:
7274     case META_TEXTURE_COMPOSITE:
7275       g_assert_not_reached ();
7276       break;
7277     }
7278 }
7279 #endif
7280 
7281 /**
7282  * Returns the earliest version of the theme format which required support
7283  * for a particular button.  (For example, "shade" first appeared in v2, and
7284  * "close" in v1.)
7285  *
7286  * \param type  the button type
7287  * \return  the number of the theme format
7288  */
7289 guint
meta_theme_earliest_version_with_button(MetaButtonType type)7290 meta_theme_earliest_version_with_button (MetaButtonType type)
7291 {
7292   switch (type)
7293     {
7294     case META_BUTTON_TYPE_CLOSE:
7295     case META_BUTTON_TYPE_MAXIMIZE:
7296     case META_BUTTON_TYPE_MINIMIZE:
7297     case META_BUTTON_TYPE_MENU:
7298     case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND:
7299     case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND:
7300     case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND:
7301     case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND:
7302     case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND:
7303     case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND:
7304       return 1000;
7305 
7306     case META_BUTTON_TYPE_SHADE:
7307     case META_BUTTON_TYPE_ABOVE:
7308     case META_BUTTON_TYPE_STICK:
7309     case META_BUTTON_TYPE_UNSHADE:
7310     case META_BUTTON_TYPE_UNABOVE:
7311     case META_BUTTON_TYPE_UNSTICK:
7312       return 2000;
7313 
7314     case META_BUTTON_TYPE_LEFT_SINGLE_BACKGROUND:
7315     case META_BUTTON_TYPE_RIGHT_SINGLE_BACKGROUND:
7316       return 3003;
7317 
7318     case META_BUTTON_TYPE_APPMENU:
7319       return 3005;
7320 
7321     default:
7322       meta_warning("Unknown button %d\n", type);
7323       return 1000;
7324     }
7325 }
7326