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