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