]> icculus.org git repositories - dana/obconf.git/blob - src/preview.c
c00c3c08ec4727be1a8f879cd428ac6cd24d836b
[dana/obconf.git] / src / preview.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    preview.c for ObConf, the configuration tool for Openbox
4    Copyright (c) 2007        Javeed Shaikh
5    Copyright (c) 2007        Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "theme.h"
21 #include "main.h"
22 #include "tree.h"
23
24 #include <string.h>
25
26 #include <obrender/theme.h>
27 #include <gdk/gdkx.h>
28 #include <cairo-xlib.h>
29
30 #define PADDING 2 /* openbox does it :/ */
31
32 static void theme_pixmap_paint(RrAppearance *a, gint w, gint h)
33 {
34     Pixmap out = RrPaintPixmap(a, w, h);
35     if (out) XFreePixmap(RrDisplay(a->inst), out);
36 }
37
38 static guint32 rr_color_pixel(const RrColor *c)
39 {
40     return (guint32)((RrColorRed(c) << 24) + (RrColorGreen(c) << 16) +
41                      + (RrColorBlue(c) << 8) + 0xff);
42 }
43
44 /* XXX: Make this more general */
45 static GdkPixbuf* preview_menu(RrTheme *theme)
46 {
47     RrAppearance *title;
48     RrAppearance *title_text;
49
50     RrAppearance *menu;
51     RrAppearance *background;
52
53     RrAppearance *normal;
54     RrAppearance *disabled;
55     RrAppearance *selected;
56     RrAppearance *bullet; /* for submenu */
57
58     cairo_surface_t *surface;
59     GdkScreen *screen;
60     Display *xdisplay;
61     Visual *xvisual;
62     GdkPixbuf *pixbuf, *tmp_pixbuf;
63
64     /* width and height of the whole menu */
65     gint width, height;
66     gint x, y;
67     gint title_h;
68     gint tw, th;
69     gint bw, bh;
70     gint unused;
71
72     screen = gdk_screen_get_default();
73     xdisplay = gdk_x11_get_default_xdisplay();
74     xvisual = gdk_x11_visual_get_xvisual(gdk_screen_get_system_visual(screen));
75
76     /* set up appearances */
77     title = theme->a_menu_title;
78
79     title_text = theme->a_menu_text_title;
80     title_text->surface.parent = title;
81     title_text->texture[0].data.text.string = "Menu";
82
83     normal = theme->a_menu_text_normal;
84     normal->texture[0].data.text.string = "Normal";
85
86     disabled = theme->a_menu_text_disabled;
87     disabled->texture[0].data.text.string = "Disabled";
88
89     selected = theme->a_menu_text_selected;
90     selected->texture[0].data.text.string = "Selected";
91
92     bullet = theme->a_menu_bullet_normal;
93
94     /* determine window size */
95     RrMinSize(normal, &width, &th);
96     width += th + PADDING; /* make space for the bullet */
97
98     width += 2*theme->mbwidth + 2*PADDING;
99
100     /* get minimum title size */
101     RrMinSize(title, &tw, &title_h);
102
103     /* size of background behind each text line */
104     bw = width - 2*theme->mbwidth;
105     //title_h += 2*PADDING;
106     title_h = theme->menu_title_height;
107
108     RrMinSize(normal, &unused, &th);
109     bh = th + 2*PADDING;
110
111     height = title_h + 3*bh + 3*theme->mbwidth;
112
113     /* set border */
114     pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, width, height);
115     gdk_pixbuf_fill(pixbuf, rr_color_pixel(theme->menu_border_color));
116     tmp_pixbuf = gdk_pixbuf_copy(pixbuf);
117
118     /* menu appears after inside the border */
119     x = y = theme->mbwidth;
120
121     /* fill in menu appearance, used as the parent to every menu item's bg */
122     menu = theme->a_menu;
123     th = height - 2 * theme->mbwidth;
124     theme_pixmap_paint(menu, bw, th);
125
126     /* draw title, it appears at the top of the menu background */
127     title->surface.parent = theme->a_menu;
128     title->surface.parentx = 0;
129     title->surface.parenty = 0;
130     theme_pixmap_paint(title, bw, title_h);
131
132     /* draw title text */
133     title_text->surface.parentx = 0;
134     title_text->surface.parenty = 0;
135
136     theme_pixmap_paint(title_text, bw, title_h);
137
138     surface = cairo_xlib_surface_create(xdisplay, title_text->pixmap,
139                                         xvisual,
140                                         bw, title_h);
141     tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
142                                              0, 0,
143                                              bw, title_h);
144     cairo_surface_destroy(surface);
145     gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, bw, title_h, pixbuf, x, y);
146
147     y += title_h + theme->mbwidth;
148
149     /* fill in background appearance, used as the parent to text items */
150     background = theme->a_menu_normal;
151     background->surface.parent = menu;
152     background->surface.parentx = x - theme->mbwidth;
153     background->surface.parenty = y - theme->mbwidth;
154
155     /* draw background for normal entry */
156     theme_pixmap_paint(background, bw, bh);
157     surface = cairo_xlib_surface_create(xdisplay, background->pixmap,
158                                         xvisual,
159                                         bw, bh);
160     tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
161                                              0, 0,
162                                              bw, bh);
163     cairo_surface_destroy(surface);
164     gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, bw, bh, pixbuf, x, y);
165
166     /* draw normal entry */
167     normal->surface.parent = background;
168     normal->surface.parentx = PADDING;
169     normal->surface.parenty = PADDING;
170     RrMinSize(normal, &tw, &th);
171     theme_pixmap_paint(normal, tw, th);
172     surface = cairo_xlib_surface_create(xdisplay, normal->pixmap,
173                                         xvisual,
174                                         tw, th);
175     tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
176                                              0, 0,
177                                              tw, th);
178     cairo_surface_destroy(surface);
179     gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, tw, th, pixbuf,
180                          x + PADDING, y + PADDING);
181
182     /* draw bullet */
183     RrMinSize(normal, &tw, &th);
184     bullet->surface.parent = background;
185     bullet->surface.parentx = bw - th;
186     bullet->surface.parenty = PADDING;
187     theme_pixmap_paint(bullet, th, th);
188     surface = cairo_xlib_surface_create(xdisplay, bullet->pixmap,
189                                         xvisual,
190                                         th, th);
191     tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
192                                              0, 0,
193                                              th, th);
194     cairo_surface_destroy(surface);
195     gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, th, th, pixbuf,
196                          width - theme->mbwidth - th, y + PADDING);
197
198     y += th + 2*PADDING;
199
200     /* draw background for disabled entry */
201     background->surface.parent = menu;
202     background->surface.parentx = x - theme->mbwidth;
203     background->surface.parenty = y - theme->mbwidth;
204     theme_pixmap_paint(background, bw, bh);
205     surface = cairo_xlib_surface_create(xdisplay, background->pixmap,
206                                         xvisual,
207                                         bw, bh);
208     tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
209                                              0, 0,
210                                              bw, bh);
211     cairo_surface_destroy(surface);
212     gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, bw, bh, pixbuf, x, y);
213
214     /* draw disabled entry */
215     RrMinSize(disabled, &tw, &th);
216     disabled->surface.parent = background;
217     disabled->surface.parentx = PADDING;
218     disabled->surface.parenty = PADDING;
219     theme_pixmap_paint(disabled, tw, th);
220     surface = cairo_xlib_surface_create(xdisplay, disabled->pixmap,
221                                         xvisual,
222                                         tw, th);
223     tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
224                                              0, 0,
225                                              tw, th);
226     cairo_surface_destroy(surface);
227     gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, tw, th, pixbuf,
228                          x + PADDING, y + PADDING);
229
230     y += th + 2*PADDING;
231
232     /* draw background for selected entry */
233     background = theme->a_menu_selected;
234     background->surface.parent = menu;
235     background->surface.parentx = x - theme->mbwidth;
236     background->surface.parenty = y - theme->mbwidth;
237
238     theme_pixmap_paint(background, bw, bh);
239     surface = cairo_xlib_surface_create(xdisplay, background->pixmap,
240                                         xvisual,
241                                         bw, bh);
242     tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
243                                              0, 0,
244                                              bw, bh);
245     cairo_surface_destroy(surface);
246     gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, bw, bh, pixbuf, x, y);
247
248     /* draw selected entry */
249     RrMinSize(selected, &tw, &th);
250     selected->surface.parent = background;
251     selected->surface.parentx = PADDING;
252     selected->surface.parenty = PADDING;
253     theme_pixmap_paint(selected, tw, th);
254     surface = cairo_xlib_surface_create(xdisplay, selected->pixmap,
255                                         xvisual,
256                                         tw, th);
257     tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
258                                              0, 0,
259                                              tw, th);
260     cairo_surface_destroy(surface);
261     gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, tw, th, pixbuf,
262                          x + PADDING, y + PADDING);
263     g_object_unref(tmp_pixbuf);
264
265     return pixbuf;
266 }
267
268 static GdkPixbuf* preview_window(RrTheme *theme, const gchar *titlelayout,
269                                  gboolean focus, gint width, gint height)
270 {
271     RrAppearance *title;
272     RrAppearance *handle;
273     RrAppearance *a;
274
275     cairo_surface_t *surface;
276     GdkScreen *screen;
277     Display *xdisplay;
278     Visual *xvisual;
279     GdkPixbuf *pixbuf = NULL, *tmp_pixbuf = NULL;
280     GdkPixbuf *scratch;
281
282     gint w, label_w, h, x, y;
283
284     const gchar *layout;
285
286     screen = gdk_screen_get_default();
287     xdisplay = gdk_x11_get_default_xdisplay();
288     xvisual = gdk_x11_visual_get_xvisual(gdk_screen_get_system_visual(screen));
289
290     title = focus ? theme->a_focused_title : theme->a_unfocused_title;
291
292     /* set border */
293     pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height);
294     gdk_pixbuf_fill(pixbuf,
295                     rr_color_pixel(focus ?
296                                    theme->frame_focused_border_color :
297                                    theme->frame_unfocused_border_color));
298     tmp_pixbuf = gdk_pixbuf_copy(pixbuf);
299
300     /* title */
301     w = width - 2*theme->fbwidth;
302     h = theme->title_height;
303     theme_pixmap_paint(title, w, h);
304
305     x = y = theme->fbwidth;
306     surface = cairo_xlib_surface_create(xdisplay, title->pixmap,
307                                         xvisual,
308                                         w, h);
309     tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
310                                              0, 0, w, h);
311     cairo_surface_destroy(surface);
312     gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y);
313
314     /* calculate label width */
315     label_w = width - (theme->paddingx + theme->fbwidth + 1) * 2;
316
317     for (layout = titlelayout; *layout; layout++) {
318         switch (*layout) {
319         case 'N':
320             label_w -= theme->button_size + 2 + theme->paddingx + 1;
321             break;
322         case 'D':
323         case 'S':
324         case 'I':
325         case 'M':
326         case 'C':
327             label_w -= theme->button_size + theme->paddingx + 1;
328             break;
329         default:
330             break;
331         }
332     }
333
334     x = theme->paddingx + theme->fbwidth + 1;
335     y += theme->paddingy;
336     for (layout = titlelayout; *layout; layout++) {
337         /* icon */
338         if (*layout == 'N') {
339             a = theme->a_icon;
340             /* set default icon */
341             a->texture[0].type = RR_TEXTURE_RGBA;
342             a->texture[0].data.rgba.width = 48;
343             a->texture[0].data.rgba.height = 48;
344             a->texture[0].data.rgba.alpha = 0xff;
345             a->texture[0].data.rgba.data = theme->def_win_icon;
346
347             a->surface.parent = title;
348             a->surface.parentx = x - theme->fbwidth;
349             a->surface.parenty = theme->paddingy;
350
351             w = h = theme->button_size + 2;
352
353             theme_pixmap_paint(a, w, h);
354             surface = cairo_xlib_surface_create(xdisplay, a->pixmap,
355                                                 xvisual,
356                                                 w, h);
357             tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
358                                                      0, 0, w, h);
359             cairo_surface_destroy(surface);
360             gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y);
361
362             x += theme->button_size + 2 + theme->paddingx + 1;
363         } else if (*layout == 'L') { /* label */
364             a = focus ? theme->a_focused_label : theme->a_unfocused_label;
365             a->texture[0].data.text.string = focus ? "Active" : "Inactive";
366
367             a->surface.parent = title;
368             a->surface.parentx = x - theme->fbwidth;
369             a->surface.parenty = theme->paddingy;
370             w = label_w;
371             h = theme->label_height;
372
373             theme_pixmap_paint(a, w, h);
374             surface = cairo_xlib_surface_create(xdisplay, a->pixmap,
375                                                 xvisual,
376                                                 w, h);
377             tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
378                                                      0, 0, w, h);
379             cairo_surface_destroy(surface);
380             gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y);
381
382             x += w + theme->paddingx + 1;
383         } else {
384             /* buttons */
385             switch (*layout) {
386             case 'D':
387                 a = focus ?
388                     theme->btn_desk->a_focused_unpressed :
389                     theme->btn_desk->a_unfocused_unpressed;
390                 break;
391             case 'S':
392                 a = focus ?
393                     theme->btn_shade->a_focused_unpressed :
394                     theme->btn_shade->a_unfocused_unpressed;
395                 break;
396             case 'I':
397                 a = focus ?
398                     theme->btn_iconify->a_focused_unpressed :
399                     theme->btn_iconify->a_unfocused_unpressed;
400                 break;
401             case 'M':
402                 a = focus ?
403                     theme->btn_max->a_focused_unpressed :
404                     theme->btn_max->a_unfocused_unpressed;
405                 break;
406             case 'C':
407                 a = focus ?
408                     theme->btn_close->a_focused_unpressed :
409                     theme->btn_close->a_unfocused_unpressed;
410                 break;
411             default:
412                 continue;
413             }
414
415             a->surface.parent = title;
416             a->surface.parentx = x - theme->fbwidth;
417             a->surface.parenty = theme->paddingy + 1;
418
419             w = theme->button_size;
420             h = theme->button_size;
421
422             theme_pixmap_paint(a, w, h);
423             surface = cairo_xlib_surface_create(xdisplay, a->pixmap,
424                                                 xvisual,
425                                                 w, h);
426             /* use y + 1 because these buttons should be centered wrt the label
427              */
428             tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
429                                                      0, 0, w, h);
430             cairo_surface_destroy(surface);
431             gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y + 1);
432
433             x += theme->button_size + theme->paddingx + 1;
434         }
435     }
436
437     if (theme->handle_height) {
438         /* handle */
439         handle = focus ? theme->a_focused_handle : theme->a_unfocused_handle;
440         x = 2*theme->fbwidth + theme->grip_width;
441         y = height - theme->fbwidth - theme->handle_height;
442         w = width - 4*theme->fbwidth - 2*theme->grip_width;
443         h = theme->handle_height;
444
445         theme_pixmap_paint(handle, w, h);
446         surface = cairo_xlib_surface_create(xdisplay, handle->pixmap,
447                                             xvisual,
448                                             w, h);
449         tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
450                                                  0, 0, w, h);
451         cairo_surface_destroy(surface);
452         gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y);
453
454         /* openbox handles this drawing stuff differently (it fills the bottom
455          * of the window with the handle), so it avoids this bug where
456          * parentrelative grips are not fully filled. i'm avoiding it slightly
457          * differently. */
458
459         theme_pixmap_paint(handle, width, h);
460
461         /* grips */
462         a = focus ? theme->a_focused_grip : theme->a_unfocused_grip;
463         a->surface.parent = handle;
464
465         x = theme->fbwidth;
466         /* same y and h as handle */
467         w = theme->grip_width;
468
469         theme_pixmap_paint(a, w, h);
470         surface = cairo_xlib_surface_create(xdisplay, a->pixmap,
471                                             xvisual, w, h);
472         tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
473                                                  0, 0, w, h);
474         gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y);
475
476         /* right grip */
477         x = width - theme->fbwidth - theme->grip_width;
478         tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
479                                                  0, 0, w, h);
480         cairo_surface_destroy(surface);
481         gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y);
482     }
483
484     /* title separator colour */
485     x = theme->fbwidth;
486     y = theme->fbwidth + theme->title_height;
487     w = width - 2*theme->fbwidth;
488     h = theme->fbwidth;
489
490     scratch = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, h);
491     gdk_pixbuf_fill(scratch, rr_color_pixel(focus ?
492                                             theme->title_separator_focused_color :
493                                             theme->title_separator_unfocused_color));
494
495     gdk_pixbuf_copy_area(scratch, 0, 0, w, h, pixbuf, x, y);
496
497     /* retarded way of adding client colour */
498     x = theme->fbwidth;
499     y = theme->title_height + 2*theme->fbwidth;
500     w = width - 2*theme->fbwidth;
501     h = height - theme->title_height - 3*theme->fbwidth -
502         (theme->handle_height ? (theme->fbwidth + theme->handle_height) : 0);
503
504     scratch = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, h);
505     gdk_pixbuf_fill(scratch, rr_color_pixel(focus ?
506                                             theme->cb_focused_color :
507                                             theme->cb_unfocused_color));
508
509     gdk_pixbuf_copy_area(scratch, 0, 0, w, h, pixbuf, x, y);
510
511     /* clear (no alpha!) the area where the client resides */
512     gdk_pixbuf_fill(scratch, 0xffffffff);
513     gdk_pixbuf_copy_area(scratch, 0, 0,
514                          w - 2*theme->cbwidthx,
515                          h - 2*theme->cbwidthy,
516                          pixbuf,
517                          x + theme->cbwidthx,
518                          y + theme->cbwidthy);
519     g_object_unref(scratch);
520
521     return pixbuf;
522 }
523
524 static gint theme_label_width(RrTheme *theme, gboolean active)
525 {
526     gint w, h;
527     RrAppearance *label;
528
529     if (active) {
530         label = theme->a_focused_label;
531         label->texture[0].data.text.string = "Active";
532     } else {
533         label = theme->a_unfocused_label;
534         label->texture[0].data.text.string = "Inactive";
535     }
536
537     return RrMinWidth(label);
538 }
539
540 static gint theme_window_min_width(RrTheme *theme, const gchar *titlelayout)
541 {
542     gint numbuttons = strlen(titlelayout);
543     gint w =  2 * theme->fbwidth + (numbuttons + 3) * (theme->paddingx + 1);
544
545     if (g_strrstr(titlelayout, "L")) {
546         numbuttons--;
547         w += MAX(theme_label_width(theme, TRUE),
548                  theme_label_width(theme, FALSE));
549     }
550
551     w += theme->button_size * numbuttons;
552
553     return w;
554 }
555
556 GdkPixbuf *preview_theme(const gchar *name, const gchar *titlelayout,
557                          RrFont *active_window_font,
558                          RrFont *inactive_window_font,
559                          RrFont *menu_title_font,
560                          RrFont *menu_item_font,
561                          RrFont *osd_active_font,
562                          RrFont *osd_inactive_font)
563 {
564
565     GdkPixbuf *preview;
566     GdkPixbuf *menu;
567     GdkPixbuf *window;
568
569     gint window_w;
570     gint menu_w;
571
572     gint w, h;
573
574     RrTheme *theme = RrThemeNew(rrinst, name, FALSE,
575                                 active_window_font, inactive_window_font,
576                                 menu_title_font, menu_item_font,
577                                 osd_active_font, osd_inactive_font);
578     if (!theme)
579         return NULL;
580
581     menu = preview_menu(theme);
582   
583     window_w = theme_window_min_width(theme, titlelayout);
584
585     menu_w = gdk_pixbuf_get_width(menu);
586     h = gdk_pixbuf_get_height(menu);
587
588     w = MAX(window_w, menu_w) + 20;
589   
590     /* we don't want windows disappearing on us */
591     if (!window_w) window_w = menu_w;
592
593     preview = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,
594                              w, h + 2*(theme->title_height +5) + 1);
595     gdk_pixbuf_fill(preview, 0); /* clear */
596
597     window = preview_window(theme, titlelayout, FALSE, window_w, h);
598     gdk_pixbuf_copy_area(window, 0, 0, window_w, h, preview, 20, 0);
599     g_object_unref(window);
600
601     window = preview_window(theme, titlelayout, TRUE, window_w, h);
602     gdk_pixbuf_copy_area(window, 0, 0, window_w, h,
603                          preview, 10, theme->title_height + 5);
604     g_object_unref(window);
605
606     gdk_pixbuf_copy_area(menu, 0, 0, menu_w, h,
607                          preview, 0, 2 * (theme->title_height + 5));
608     g_object_unref(menu);
609
610     RrThemeFree(theme);
611
612     return preview;
613 }