]> icculus.org git repositories - dana/obconf.git/blob - src/preview.c
Fix indenting
[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, 0, 0, bw, title_h);
142     cairo_surface_destroy(surface);
143     gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, bw, title_h, pixbuf, x, y);
144
145     y += title_h + theme->mbwidth;
146
147     /* fill in background appearance, used as the parent to text items */
148     background = theme->a_menu_normal;
149     background->surface.parent = menu;
150     background->surface.parentx = x - theme->mbwidth;
151     background->surface.parenty = y - theme->mbwidth;
152
153     /* draw background for normal entry */
154     theme_pixmap_paint(background, bw, bh);
155     surface = cairo_xlib_surface_create(xdisplay, background->pixmap,
156                                         xvisual,
157                                         bw, bh);
158     tmp_pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, bw, bh);
159     cairo_surface_destroy(surface);
160     gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, bw, bh, pixbuf, x, y);
161
162     /* draw normal entry */
163     normal->surface.parent = background;
164     normal->surface.parentx = PADDING;
165     normal->surface.parenty = PADDING;
166     RrMinSize(normal, &tw, &th);
167     theme_pixmap_paint(normal, tw, th);
168     surface = cairo_xlib_surface_create(xdisplay, normal->pixmap,
169                                         xvisual,
170                                         tw, th);
171     tmp_pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, tw, th);
172     cairo_surface_destroy(surface);
173     gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, tw, th, pixbuf,
174                          x + PADDING, y + PADDING);
175
176     /* draw bullet */
177     RrMinSize(normal, &tw, &th);
178     bullet->surface.parent = background;
179     bullet->surface.parentx = bw - th;
180     bullet->surface.parenty = PADDING;
181     theme_pixmap_paint(bullet, th, th);
182     surface = cairo_xlib_surface_create(xdisplay, bullet->pixmap,
183                                         xvisual,
184                                         th, th);
185     tmp_pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, th, th);
186     cairo_surface_destroy(surface);
187     gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, th, th, pixbuf,
188                          width - theme->mbwidth - th, y + PADDING);
189
190     y += th + 2*PADDING;
191
192     /* draw background for disabled entry */
193     background->surface.parent = menu;
194     background->surface.parentx = x - theme->mbwidth;
195     background->surface.parenty = y - theme->mbwidth;
196     theme_pixmap_paint(background, bw, bh);
197     surface = cairo_xlib_surface_create(xdisplay, background->pixmap,
198                                         xvisual,
199                                         bw, bh);
200     tmp_pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, bw, bh);
201     cairo_surface_destroy(surface);
202     gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, bw, bh, pixbuf, x, y);
203
204     /* draw disabled entry */
205     RrMinSize(disabled, &tw, &th);
206     disabled->surface.parent = background;
207     disabled->surface.parentx = PADDING;
208     disabled->surface.parenty = PADDING;
209     theme_pixmap_paint(disabled, tw, th);
210     surface = cairo_xlib_surface_create(xdisplay, disabled->pixmap,
211                                         xvisual,
212                                         tw, th);
213     tmp_pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, tw, th);
214     cairo_surface_destroy(surface);
215     gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, tw, th, pixbuf,
216                          x + PADDING, y + PADDING);
217
218     y += th + 2*PADDING;
219
220     /* draw background for selected entry */
221     background = theme->a_menu_selected;
222     background->surface.parent = menu;
223     background->surface.parentx = x - theme->mbwidth;
224     background->surface.parenty = y - theme->mbwidth;
225
226     theme_pixmap_paint(background, bw, bh);
227     surface = cairo_xlib_surface_create(xdisplay, background->pixmap,
228                                         xvisual,
229                                         bw, bh);
230     tmp_pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, bw, bh);
231     cairo_surface_destroy(surface);
232     gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, bw, bh, pixbuf, x, y);
233
234     /* draw selected entry */
235     RrMinSize(selected, &tw, &th);
236     selected->surface.parent = background;
237     selected->surface.parentx = PADDING;
238     selected->surface.parenty = PADDING;
239     theme_pixmap_paint(selected, tw, th);
240     surface = cairo_xlib_surface_create(xdisplay, selected->pixmap,
241                                         xvisual,
242                                         tw, th);
243     tmp_pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, tw, th);
244     cairo_surface_destroy(surface);
245     gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, tw, th, pixbuf,
246                          x + PADDING, y + PADDING);
247     g_object_unref(tmp_pixbuf);
248
249     return pixbuf;
250 }
251
252 static GdkPixbuf* preview_window(RrTheme *theme, const gchar *titlelayout,
253                                  gboolean focus, gint width, gint height)
254 {
255     RrAppearance *title;
256     RrAppearance *handle;
257     RrAppearance *a;
258
259     cairo_surface_t *surface;
260     GdkScreen *screen;
261     Display *xdisplay;
262     Visual *xvisual;
263     GdkPixbuf *pixbuf = NULL, *tmp_pixbuf = NULL;
264     GdkPixbuf *scratch;
265
266     gint w, label_w, h, x, y;
267
268     const gchar *layout;
269
270     screen = gdk_screen_get_default();
271     xdisplay = gdk_x11_get_default_xdisplay();
272     xvisual = gdk_x11_visual_get_xvisual(gdk_screen_get_system_visual(screen));
273
274     title = focus ? theme->a_focused_title : theme->a_unfocused_title;
275
276     /* set border */
277     pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height);
278     gdk_pixbuf_fill(pixbuf,
279                     rr_color_pixel(focus ?
280                                    theme->frame_focused_border_color :
281                                    theme->frame_unfocused_border_color));
282     tmp_pixbuf = gdk_pixbuf_copy(pixbuf);
283
284     /* title */
285     w = width - 2*theme->fbwidth;
286     h = theme->title_height;
287     theme_pixmap_paint(title, w, h);
288
289     x = y = theme->fbwidth;
290     surface = cairo_xlib_surface_create(xdisplay, title->pixmap,
291                                         xvisual,
292                                         w, h);
293     tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
294                                              0, 0, w, h);
295     cairo_surface_destroy(surface);
296     gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y);
297
298     /* calculate label width */
299     label_w = width - (theme->paddingx + theme->fbwidth + 1) * 2;
300
301     for (layout = titlelayout; *layout; layout++) {
302         switch (*layout) {
303         case 'N':
304             label_w -= theme->button_size + 2 + theme->paddingx + 1;
305             break;
306         case 'D':
307         case 'S':
308         case 'I':
309         case 'M':
310         case 'C':
311             label_w -= theme->button_size + theme->paddingx + 1;
312             break;
313         default:
314             break;
315         }
316     }
317
318     x = theme->paddingx + theme->fbwidth + 1;
319     y += theme->paddingy;
320     for (layout = titlelayout; *layout; layout++) {
321         /* icon */
322         if (*layout == 'N') {
323             a = theme->a_icon;
324             /* set default icon */
325             a->texture[0].type = RR_TEXTURE_RGBA;
326             a->texture[0].data.rgba.width = 48;
327             a->texture[0].data.rgba.height = 48;
328             a->texture[0].data.rgba.alpha = 0xff;
329             a->texture[0].data.rgba.data = theme->def_win_icon;
330
331             a->surface.parent = title;
332             a->surface.parentx = x - theme->fbwidth;
333             a->surface.parenty = theme->paddingy;
334
335             w = h = theme->button_size + 2;
336
337             theme_pixmap_paint(a, w, h);
338             surface = cairo_xlib_surface_create(xdisplay, a->pixmap,
339                                                 xvisual,
340                                                 w, h);
341             tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
342                                                      0, 0, w, h);
343             cairo_surface_destroy(surface);
344             gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y);
345
346             x += theme->button_size + 2 + theme->paddingx + 1;
347         } else if (*layout == 'L') { /* label */
348             a = focus ? theme->a_focused_label : theme->a_unfocused_label;
349             a->texture[0].data.text.string = focus ? "Active" : "Inactive";
350
351             a->surface.parent = title;
352             a->surface.parentx = x - theme->fbwidth;
353             a->surface.parenty = theme->paddingy;
354             w = label_w;
355             h = theme->label_height;
356
357             theme_pixmap_paint(a, w, h);
358             surface = cairo_xlib_surface_create(xdisplay, a->pixmap,
359                                                 xvisual,
360                                                 w, h);
361             tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
362                                                      0, 0, w, h);
363             cairo_surface_destroy(surface);
364             gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y);
365
366             x += w + theme->paddingx + 1;
367         } else {
368             /* buttons */
369             switch (*layout) {
370             case 'D':
371                 a = focus ?
372                     theme->btn_desk->a_focused_unpressed :
373                     theme->btn_desk->a_unfocused_unpressed;
374                 break;
375             case 'S':
376                 a = focus ?
377                     theme->btn_shade->a_focused_unpressed :
378                     theme->btn_shade->a_unfocused_unpressed;
379                 break;
380             case 'I':
381                 a = focus ?
382                     theme->btn_iconify->a_focused_unpressed :
383                     theme->btn_iconify->a_unfocused_unpressed;
384                 break;
385             case 'M':
386                 a = focus ?
387                     theme->btn_max->a_focused_unpressed :
388                     theme->btn_max->a_unfocused_unpressed;
389                 break;
390             case 'C':
391                 a = focus ?
392                     theme->btn_close->a_focused_unpressed :
393                     theme->btn_close->a_unfocused_unpressed;
394                 break;
395             default:
396                 continue;
397             }
398
399             a->surface.parent = title;
400             a->surface.parentx = x - theme->fbwidth;
401             a->surface.parenty = theme->paddingy + 1;
402
403             w = theme->button_size;
404             h = theme->button_size;
405
406             theme_pixmap_paint(a, w, h);
407             surface = cairo_xlib_surface_create(xdisplay, a->pixmap,
408                                                 xvisual,
409                                                 w, h);
410             /* use y + 1 because these buttons should be centered wrt the label
411              */
412             tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
413                                                      0, 0, w, h);
414             cairo_surface_destroy(surface);
415             gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y + 1);
416
417             x += theme->button_size + theme->paddingx + 1;
418         }
419     }
420
421     if (theme->handle_height) {
422         /* handle */
423         handle = focus ? theme->a_focused_handle : theme->a_unfocused_handle;
424         x = 2*theme->fbwidth + theme->grip_width;
425         y = height - theme->fbwidth - theme->handle_height;
426         w = width - 4*theme->fbwidth - 2*theme->grip_width;
427         h = theme->handle_height;
428
429         theme_pixmap_paint(handle, w, h);
430         surface = cairo_xlib_surface_create(xdisplay, handle->pixmap,
431                                             xvisual,
432                                             w, h);
433         tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
434                                                  0, 0, w, h);
435         cairo_surface_destroy(surface);
436         gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y);
437
438         /* openbox handles this drawing stuff differently (it fills the bottom
439          * of the window with the handle), so it avoids this bug where
440          * parentrelative grips are not fully filled. i'm avoiding it slightly
441          * differently. */
442
443         theme_pixmap_paint(handle, width, h);
444
445         /* grips */
446         a = focus ? theme->a_focused_grip : theme->a_unfocused_grip;
447         a->surface.parent = handle;
448
449         x = theme->fbwidth;
450         /* same y and h as handle */
451         w = theme->grip_width;
452
453         theme_pixmap_paint(a, w, h);
454         surface = cairo_xlib_surface_create(xdisplay, a->pixmap,
455                                             xvisual, w, h);
456         tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
457                                                  0, 0, w, h);
458         gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y);
459
460         /* right grip */
461         x = width - theme->fbwidth - theme->grip_width;
462         tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
463                                                  0, 0, w, h);
464         cairo_surface_destroy(surface);
465         gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y);
466     }
467
468     /* title separator colour */
469     x = theme->fbwidth;
470     y = theme->fbwidth + theme->title_height;
471     w = width - 2*theme->fbwidth;
472     h = theme->fbwidth;
473
474     scratch = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, h);
475     gdk_pixbuf_fill(scratch, rr_color_pixel(focus ?
476                                             theme->title_separator_focused_color :
477                                             theme->title_separator_unfocused_color));
478
479     gdk_pixbuf_copy_area(scratch, 0, 0, w, h, pixbuf, x, y);
480
481     /* retarded way of adding client colour */
482     x = theme->fbwidth;
483     y = theme->title_height + 2*theme->fbwidth;
484     w = width - 2*theme->fbwidth;
485     h = height - theme->title_height - 3*theme->fbwidth -
486         (theme->handle_height ? (theme->fbwidth + theme->handle_height) : 0);
487
488     scratch = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, h);
489     gdk_pixbuf_fill(scratch, rr_color_pixel(focus ?
490                                             theme->cb_focused_color :
491                                             theme->cb_unfocused_color));
492
493     gdk_pixbuf_copy_area(scratch, 0, 0, w, h, pixbuf, x, y);
494
495     /* clear (no alpha!) the area where the client resides */
496     gdk_pixbuf_fill(scratch, 0xffffffff);
497     gdk_pixbuf_copy_area(scratch, 0, 0,
498                          w - 2*theme->cbwidthx,
499                          h - 2*theme->cbwidthy,
500                          pixbuf,
501                          x + theme->cbwidthx,
502                          y + theme->cbwidthy);
503     g_object_unref(scratch);
504
505     return pixbuf;
506 }
507
508 static gint theme_label_width(RrTheme *theme, gboolean active)
509 {
510     gint w, h;
511     RrAppearance *label;
512
513     if (active) {
514         label = theme->a_focused_label;
515         label->texture[0].data.text.string = "Active";
516     } else {
517         label = theme->a_unfocused_label;
518         label->texture[0].data.text.string = "Inactive";
519     }
520
521     return RrMinWidth(label);
522 }
523
524 static gint theme_window_min_width(RrTheme *theme, const gchar *titlelayout)
525 {
526     gint numbuttons = strlen(titlelayout);
527     gint w =  2 * theme->fbwidth + (numbuttons + 3) * (theme->paddingx + 1);
528
529     if (g_strrstr(titlelayout, "L")) {
530         numbuttons--;
531         w += MAX(theme_label_width(theme, TRUE),
532                  theme_label_width(theme, FALSE));
533     }
534
535     w += theme->button_size * numbuttons;
536
537     return w;
538 }
539
540 GdkPixbuf *preview_theme(const gchar *name, const gchar *titlelayout,
541                          RrFont *active_window_font,
542                          RrFont *inactive_window_font,
543                          RrFont *menu_title_font,
544                          RrFont *menu_item_font,
545                          RrFont *osd_active_font,
546                          RrFont *osd_inactive_font)
547 {
548
549     GdkPixbuf *preview;
550     GdkPixbuf *menu;
551     GdkPixbuf *window;
552
553     gint window_w;
554     gint menu_w;
555
556     gint w, h;
557
558     RrTheme *theme = RrThemeNew(rrinst, name, FALSE,
559                                 active_window_font, inactive_window_font,
560                                 menu_title_font, menu_item_font,
561                                 osd_active_font, osd_inactive_font);
562     if (!theme)
563         return NULL;
564
565     menu = preview_menu(theme);
566   
567     window_w = theme_window_min_width(theme, titlelayout);
568
569     menu_w = gdk_pixbuf_get_width(menu);
570     h = gdk_pixbuf_get_height(menu);
571
572     w = MAX(window_w, menu_w) + 20;
573   
574     /* we don't want windows disappearing on us */
575     if (!window_w) window_w = menu_w;
576
577     preview = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,
578                              w, h + 2*(theme->title_height +5) + 1);
579     gdk_pixbuf_fill(preview, 0); /* clear */
580
581     window = preview_window(theme, titlelayout, FALSE, window_w, h);
582     gdk_pixbuf_copy_area(window, 0, 0, window_w, h, preview, 20, 0);
583     g_object_unref(window);
584
585     window = preview_window(theme, titlelayout, TRUE, window_w, h);
586     gdk_pixbuf_copy_area(window, 0, 0, window_w, h,
587                          preview, 10, theme->title_height + 5);
588     g_object_unref(window);
589
590     gdk_pixbuf_copy_area(menu, 0, 0, menu_w, h,
591                          preview, 0, 2 * (theme->title_height + 5));
592     g_object_unref(menu);
593
594     RrThemeFree(theme);
595
596     return preview;
597 }