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