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