add theme previews! yay syscrash!
[dana/obconf.git] / src / preview.c
1 #include "theme.h"
2 #include "main.h"
3 #include "tree.h"
4
5 #include <string.h>
6
7 #include <openbox/theme.h>
8
9 #define PADDING 2 /* openbox does it :/ */
10
11 static void theme_pixmap_paint(RrAppearance *a, gint w, gint h)
12 {
13     Pixmap out = RrPaintPixmap(a, w, h);
14     if (out) XFreePixmap(RrDisplay(a->inst), out);
15 }
16
17 static guint32 rr_color_pixel(const RrColor *c)
18 {
19     return (guint32)((RrColorRed(c) << 24) | (RrColorGreen(c) << 16)
20                      | (RrColorBlue(c) << 8) | 255);
21 }
22
23 /* XXX: Make this more general */
24 static GdkPixbuf* preview_menu(RrTheme *theme)
25 {
26     RrAppearance *title;
27     RrAppearance *title_text;
28
29     RrAppearance *menu;
30     RrAppearance *background;
31
32     RrAppearance *normal;
33     RrAppearance *disabled;
34     RrAppearance *selected;
35     RrAppearance *bullet; /* for submenu */
36
37     GdkPixmap *pixmap;
38     GdkPixbuf *pixbuf;
39
40     /* width and height of the whole menu */
41     gint width, height;
42     gint x, y;
43     gint title_h;
44     gint tw, th;
45     gint bw, bh;
46     gint unused;
47
48     /* set up appearances */
49     title = theme->a_menu_title;
50
51     title_text = theme->a_menu_text_title;
52     title_text->surface.parent = title;
53     title_text->texture[0].data.text.string = "menu";
54
55     normal = theme->a_menu_text_normal;
56     normal->texture[0].data.text.string = "normal";
57
58     disabled = theme->a_menu_text_disabled;
59     disabled->texture[0].data.text.string = "disabled";
60
61     selected = theme->a_menu_text_selected;
62     selected->texture[0].data.text.string = "selected";
63
64     bullet = theme->a_menu_bullet_normal;
65
66     /* determine window size */
67     RrMinSize(normal, &width, &th);
68     width += th + PADDING; /* make space for the bullet */
69     //height = th;
70
71     width += 2*theme->mbwidth + 2*PADDING;
72
73     /* get minimum title size */
74     RrMinSize(title, &tw, &title_h);
75
76     /* size of background behind each text line */
77     bw = width - 2*theme->mbwidth;
78     //title_h += 2*PADDING;
79     title_h = theme->menu_title_height;
80
81     RrMinSize(normal, &unused, &th);
82     bh = th + 2*PADDING;
83
84     height = title_h + 3*bh + 3*theme->mbwidth;
85
86     //height += 3*th + 3*theme->mbwidth + 5*PADDING;
87
88     /* set border */
89     pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, width, height);
90     gdk_pixbuf_fill(pixbuf, rr_color_pixel(theme->menu_border_color));
91
92     /* draw title */
93     x = y = theme->mbwidth;
94     theme_pixmap_paint(title, bw, title_h);
95
96     /* draw title text */
97     title_text->surface.parentx = 0;
98     title_text->surface.parenty = 0;
99
100     theme_pixmap_paint(title_text, bw, title_h);
101
102     pixmap = gdk_pixmap_foreign_new(title_text->pixmap);
103     pixbuf = gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_colormap_get_system(), 0, 0, x, y, bw, title_h);
104
105     /* menu appears after title */
106     y += theme->mbwidth + title_h;
107
108     /* fill in menu appearance, used as the parent to every menu item's bg */
109     menu = theme->a_menu;
110     th = height - 3*theme->mbwidth - title_h;
111     theme_pixmap_paint(menu, bw, th);
112
113     pixmap = gdk_pixmap_foreign_new(menu->pixmap);
114     pixbuf = gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_colormap_get_system(), 0, 0, x, y, bw, th);
115
116     /* fill in background appearance, used as the parent to text items */
117     background = theme->a_menu_normal;
118     background->surface.parent = menu;
119     background->surface.parentx = 0;
120     background->surface.parenty = 0;
121
122     /* draw background for normal entry */
123     theme_pixmap_paint(background, bw, bh);
124     pixmap = gdk_pixmap_foreign_new(background->pixmap);
125     pixbuf = gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_colormap_get_system(), 0, 0, x, y, bw, bh);
126
127     /* draw normal entry */
128     normal->surface.parent = background;
129     normal->surface.parentx = PADDING;
130     normal->surface.parenty = PADDING;
131     x += PADDING;
132     y += PADDING;
133     RrMinSize(normal, &tw, &th);
134     theme_pixmap_paint(normal, tw, th);
135     pixmap = gdk_pixmap_foreign_new(normal->pixmap);
136     pixbuf = gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_colormap_get_system(), 0, 0, x, y, tw, th);
137
138     /* draw bullet */
139     RrMinSize(normal, &tw, &th);
140     bullet->surface.parent = background;
141     bullet->surface.parentx = bw - th;
142     bullet->surface.parenty = PADDING;
143     theme_pixmap_paint(bullet, th, th);
144     pixmap = gdk_pixmap_foreign_new(bullet->pixmap);
145     pixbuf = gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_colormap_get_system(), 0, 0, width - theme->mbwidth - th, y, th, th);
146
147     y += th + 2*PADDING;
148
149     /* draw background for disabled entry */
150     background->surface.parenty = bh;
151     theme_pixmap_paint(background, bw, bh);
152     pixmap = gdk_pixmap_foreign_new(background->pixmap);
153     pixbuf = gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_colormap_get_system(), 0, 0, x - PADDING, y - PADDING, bw, bh);
154
155     /* draw disabled entry */
156     RrMinSize(disabled, &tw, &th);
157     disabled->surface.parent = background;
158     disabled->surface.parentx = PADDING;
159     disabled->surface.parenty = PADDING;
160     theme_pixmap_paint(disabled, tw, th);
161     pixmap = gdk_pixmap_foreign_new(disabled->pixmap);
162     pixbuf = gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_colormap_get_system(), 0, 0, x, y, tw, th);
163
164     y += th + 2*PADDING;
165
166     /* draw background for selected entry */
167     background = theme->a_menu_selected;
168     background->surface.parent = menu;
169     background->surface.parentx = 2*bh;
170
171     theme_pixmap_paint(background, bw, bh);
172     pixmap = gdk_pixmap_foreign_new(background->pixmap);
173     pixbuf = gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_colormap_get_system(), 0, 0, x - PADDING, y - PADDING, bw, bh);
174
175     /* draw selected entry */
176     RrMinSize(selected, &tw, &th);
177     selected->surface.parent = background;
178     selected->surface.parentx = PADDING;
179     selected->surface.parenty = PADDING;
180     theme_pixmap_paint(selected, tw, th);
181     pixmap = gdk_pixmap_foreign_new(selected->pixmap);
182     pixbuf = gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_colormap_get_system(), 0, 0, x, y, tw, th);
183
184     return pixbuf;
185 }
186
187 static GdkPixbuf* preview_window(RrTheme *theme, const gchar *titlelayout, gboolean focus, gint width, gint height)
188 {
189     RrAppearance *title;
190     RrAppearance *handle;
191     RrAppearance *a;
192
193     GdkPixmap *pixmap;
194     GdkPixbuf *pixbuf = NULL;
195     GdkPixbuf *scratch;
196
197     gint w, label_w, h, x, y;
198
199     const gchar *layout;
200
201     title = focus ? theme->a_focused_title : theme->a_unfocused_title;
202
203     /* set border */
204     pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, width, height);
205     gdk_pixbuf_fill(pixbuf, rr_color_pixel(theme->menu_border_color));
206
207     /* title */
208     w = width - 2*theme->fbwidth;
209     h = theme->title_height;
210     theme_pixmap_paint(title, w, h);
211
212     x = y = theme->fbwidth;;
213     pixmap = gdk_pixmap_foreign_new(title->pixmap);
214     pixbuf = gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_colormap_get_system(), 0, 0, x, y, w, h);
215
216     /* calculate label width */
217     label_w = width - (theme->paddingx + theme->fbwidth + 1) * 2;
218
219     for (layout = titlelayout; *layout; layout++) {
220         switch (*layout) {
221         case 'N':
222             label_w -= theme->button_size + 2 + theme->paddingx + 1;
223             break;
224         case 'D':
225         case 'S':
226         case 'I':
227         case 'M':
228         case 'C':
229             label_w -= theme->button_size + theme->paddingx + 1;
230             break;
231         default:
232             break;
233         }
234     }
235
236     x = theme->paddingx + theme->fbwidth + 1;
237     y += theme->paddingy + 1;
238     for (layout = titlelayout; *layout; layout++) {
239         /* icon */
240         if (*layout == 'N') {
241             a = theme->a_icon;
242             /* set default icon */
243             a->texture[0].type = RR_TEXTURE_RGBA;
244             a->texture[0].data.rgba.width = 48;
245             a->texture[0].data.rgba.height = 48;
246             a->texture[0].data.rgba.data = theme->def_win_icon;
247
248             a->surface.parent = title;
249             a->surface.parentx = x;
250             a->surface.parenty = theme->paddingy;
251
252             w = h = theme->button_size + 2;
253
254             theme_pixmap_paint(a, w, h);
255             pixmap = gdk_pixmap_foreign_new(a->pixmap);
256             pixbuf = gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_colormap_get_system(), 0, 0, x, y - 1, w, h);
257
258             x += theme->button_size + 2 + theme->paddingx + 1;
259         } else if (*layout == 'L') { /* label */
260             a = focus ? theme->a_focused_label : theme->a_unfocused_label;
261             a->texture[0].data.text.string = focus ? "active" : "inactive";
262
263             a->surface.parent = title;
264             a->surface.parentx = x;
265             a->surface.parenty = theme->paddingy;
266             w = label_w;
267             h = theme->label_height;
268
269             theme_pixmap_paint(a, w, h);
270             pixmap = gdk_pixmap_foreign_new(a->pixmap);
271             pixbuf = gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_colormap_get_system(), 0, 0, x, y - 1, w, h);
272
273             x += w + theme->paddingx + 1;
274         } else {
275             /* buttons */
276             switch (*layout) {
277             case 'D':
278                 a = focus ? theme->a_focused_unpressed_desk : theme->a_unfocused_unpressed_desk;
279                 break;
280             case 'S':
281                 a = focus ? theme->a_focused_unpressed_shade : theme->a_unfocused_unpressed_shade;
282                 break;
283             case 'I':
284                 a = focus ? theme->a_focused_unpressed_iconify : theme->a_unfocused_unpressed_iconify;
285                 break;
286             case 'M':
287                 a = focus ? theme->a_focused_unpressed_max : theme->a_unfocused_unpressed_max;
288                 break;
289             case 'C':
290                 a = focus ? theme->a_focused_unpressed_close : theme->a_unfocused_unpressed_close;
291                 break;
292             default:
293                 continue;
294             }
295
296             a->surface.parent = title;
297             a->surface.parentx = x;
298             a->surface.parenty = theme->paddingy + 1;
299
300             w = theme->button_size;
301             h = theme->button_size;
302
303             theme_pixmap_paint(a, w, h);
304             pixmap = gdk_pixmap_foreign_new(a->pixmap);
305             pixbuf = gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_colormap_get_system(), 0, 0, x, y, w, h);
306
307             x += theme->button_size + theme->paddingx + 1;
308         }
309     }
310
311     if (theme->handle_height) {
312         /* handle */
313         handle = focus ? theme->a_focused_handle : theme->a_unfocused_handle;
314         x = 2*theme->fbwidth + theme->grip_width;
315         y = height - theme->fbwidth - theme->handle_height;
316         w = width - 4*theme->fbwidth - 2*theme->grip_width;
317         h = theme->handle_height;
318
319         theme_pixmap_paint(handle, w, h);
320         pixmap = gdk_pixmap_foreign_new(handle->pixmap);
321         pixbuf = gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_colormap_get_system(), 0, 0, x, y, w, h);
322
323         /* openbox handles this drawing stuff differently (it fills the bottom
324          * of the window with the handle), so it avoids this bug where
325          * parentrelative grips are not fully filled. i'm avoiding it slightly
326          * differently. */
327
328         theme_pixmap_paint(handle, width, h);
329
330         /* grips */
331         a = focus ? theme->a_focused_grip : theme->a_unfocused_grip;
332         a->surface.parent = handle;
333
334         x = theme->fbwidth;
335         /* same y and h as handle */
336         w = theme->grip_width;
337
338         theme_pixmap_paint(a, w, h);
339         pixmap = gdk_pixmap_foreign_new(a->pixmap);
340         pixbuf = gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_colormap_get_system(), 0, 0, x, y, w, h);
341
342         /* right grip */
343         x = width - theme->fbwidth - theme->grip_width;
344         pixbuf = gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_colormap_get_system(), 0, 0, x, y, w, h);
345     }
346
347     /* retarded way of adding client colour */
348     x = theme->fbwidth;
349     y = theme->title_height + 2*theme->fbwidth;
350     w = width - 2*theme->fbwidth;
351     h = height - theme->title_height - 3*theme->fbwidth - (theme->handle_height ? (theme->fbwidth + theme->handle_height) : 0);
352
353     scratch = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, w, h);
354     gdk_pixbuf_fill(scratch, rr_color_pixel((focus ? theme->cb_focused_color : theme->cb_unfocused_color)));
355
356     gdk_pixbuf_copy_area(scratch, 0, 0, w, h, pixbuf, x, y);
357
358     return pixbuf;
359 }
360
361 static gint theme_label_width(RrTheme *theme, gboolean active)
362 {
363     gint w, h;
364     RrAppearance *label;
365
366     if (active) {
367         label = theme->a_focused_label;
368         label->texture[0].data.text.string = "active";
369     } else {
370         label = theme->a_unfocused_label;
371         label->texture[0].data.text.string = "inactive";
372     }
373
374     return RrMinWidth(label);
375 }
376
377 static gint theme_window_min_width(RrTheme *theme, const gchar *titlelayout)
378 {
379     gint numbuttons = strlen(titlelayout);
380     gint w =  2 * (theme->fbwidth + theme->paddingx + 1);
381
382     if (g_strrstr(titlelayout, "L")) {
383         numbuttons--;
384         w += MAX(theme_label_width(theme, TRUE), theme_label_width(theme, FALSE)) + theme->paddingx + 1;
385     }
386
387     w += (theme->button_size + theme->paddingx + 1) * numbuttons;
388
389     return w;
390 }
391
392 GdkPixbuf *preview_theme(const gchar *name, const gchar *titlelayout,
393                          RrFont *active_window_font,
394                          RrFont *inactive_window_font,
395                          RrFont *menu_title_font,
396                          RrFont *menu_item_font,
397                          RrFont *osd_font)
398 {
399
400     GdkPixbuf *preview;
401     GdkPixbuf *menu;
402     GdkPixbuf *window;
403
404     gint window_w;
405     gint menu_w;
406
407     gint w, h;
408
409     RrTheme *theme = RrThemeNew(rrinst, name,
410                                 active_window_font, inactive_window_font,
411                                 menu_title_font, menu_item_font, osd_font);
412
413     menu = preview_menu(theme);
414   
415     window_w = theme_window_min_width(theme, titlelayout);
416
417     menu_w = gdk_pixbuf_get_width(menu);
418     h = gdk_pixbuf_get_height(menu);
419
420     w = MAX(window_w, menu_w) + 20;
421   
422     preview = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, h + 2*(theme->title_height +5) + 1);
423     gdk_pixbuf_fill(preview, 0); /* clear */
424
425     window = preview_window(theme, titlelayout, FALSE, window_w, h);
426     gdk_pixbuf_copy_area(window, 0, 0, window_w, h, preview, 20, 0);
427
428     window = preview_window(theme, titlelayout, TRUE, window_w, h);
429     gdk_pixbuf_copy_area(window, 0, 0, window_w, h, preview, 10, theme->title_height + 5);
430
431     gdk_pixbuf_copy_area(menu, 0, 0, menu_w, h, preview, 0, 2 * (theme->title_height + 5));
432
433     RrThemeFree(theme);
434
435     return preview;
436 }