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