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