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