1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 preview.c for ObConf, the configuration tool for Openbox
4 Copyright (c) 2007 Javeed Shaikh
5 Copyright (c) 2007 Dana Jansens
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.
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.
17 See the COPYING file for a copy of the GNU General Public License.
26 #include <obrender/theme.h>
28 #include <cairo-xlib.h>
30 #define PADDING 2 /* openbox does it :/ */
32 static void theme_pixmap_paint(RrAppearance *a, gint w, gint h)
34 Pixmap out = RrPaintPixmap(a, w, h);
35 if (out) XFreePixmap(RrDisplay(a->inst), out);
38 static guint32 rr_color_pixel(const RrColor *c)
40 return (guint32)((RrColorRed(c) << 24) + (RrColorGreen(c) << 16) +
41 + (RrColorBlue(c) << 8) + 0xff);
44 /* XXX: Make this more general */
45 static GdkPixbuf* preview_menu(RrTheme *theme)
48 RrAppearance *title_text;
51 RrAppearance *background;
54 RrAppearance *disabled;
55 RrAppearance *selected;
56 RrAppearance *bullet; /* for submenu */
58 cairo_surface_t *surface;
62 GdkPixbuf *pixbuf, *tmp_pixbuf;
64 /* width and height of the whole menu */
72 screen = gdk_screen_get_default();
73 xdisplay = gdk_x11_get_default_xdisplay();
74 xvisual = gdk_x11_visual_get_xvisual(gdk_screen_get_system_visual(screen));
76 /* set up appearances */
77 title = theme->a_menu_title;
79 title_text = theme->a_menu_text_title;
80 title_text->surface.parent = title;
81 title_text->texture[0].data.text.string = "Menu";
83 normal = theme->a_menu_text_normal;
84 normal->texture[0].data.text.string = "Normal";
86 disabled = theme->a_menu_text_disabled;
87 disabled->texture[0].data.text.string = "Disabled";
89 selected = theme->a_menu_text_selected;
90 selected->texture[0].data.text.string = "Selected";
92 bullet = theme->a_menu_bullet_normal;
94 /* determine window size */
95 RrMinSize(normal, &width, &th);
96 width += th + PADDING; /* make space for the bullet */
98 width += 2*theme->mbwidth + 2*PADDING;
100 /* get minimum title size */
101 RrMinSize(title, &tw, &title_h);
103 /* size of background behind each text line */
104 bw = width - 2*theme->mbwidth;
105 //title_h += 2*PADDING;
106 title_h = theme->menu_title_height;
108 RrMinSize(normal, &unused, &th);
111 height = title_h + 3*bh + 3*theme->mbwidth;
114 pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, width, height);
115 gdk_pixbuf_fill(pixbuf, rr_color_pixel(theme->menu_border_color));
116 tmp_pixbuf = gdk_pixbuf_copy(pixbuf);
118 /* menu appears after inside the border */
119 x = y = theme->mbwidth;
121 /* fill in menu appearance, used as the parent to every menu item's bg */
122 menu = theme->a_menu;
123 th = height - 2 * theme->mbwidth;
124 theme_pixmap_paint(menu, bw, th);
126 /* draw title, it appears at the top of the menu background */
127 title->surface.parent = theme->a_menu;
128 title->surface.parentx = 0;
129 title->surface.parenty = 0;
130 theme_pixmap_paint(title, bw, title_h);
132 /* draw title text */
133 title_text->surface.parentx = 0;
134 title_text->surface.parenty = 0;
136 theme_pixmap_paint(title_text, bw, title_h);
138 surface = cairo_xlib_surface_create(xdisplay, title_text->pixmap,
141 tmp_pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, bw, title_h);
142 cairo_surface_destroy(surface);
143 gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, bw, title_h, pixbuf, x, y);
145 y += title_h + theme->mbwidth;
147 /* fill in background appearance, used as the parent to text items */
148 background = theme->a_menu_normal;
149 background->surface.parent = menu;
150 background->surface.parentx = x - theme->mbwidth;
151 background->surface.parenty = y - theme->mbwidth;
153 /* draw background for normal entry */
154 theme_pixmap_paint(background, bw, bh);
155 surface = cairo_xlib_surface_create(xdisplay, background->pixmap,
158 tmp_pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, bw, bh);
159 cairo_surface_destroy(surface);
160 gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, bw, bh, pixbuf, x, y);
162 /* draw normal entry */
163 normal->surface.parent = background;
164 normal->surface.parentx = PADDING;
165 normal->surface.parenty = PADDING;
166 RrMinSize(normal, &tw, &th);
167 theme_pixmap_paint(normal, tw, th);
168 surface = cairo_xlib_surface_create(xdisplay, normal->pixmap,
171 tmp_pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, tw, th);
172 cairo_surface_destroy(surface);
173 gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, tw, th, pixbuf,
174 x + PADDING, y + PADDING);
177 RrMinSize(normal, &tw, &th);
178 bullet->surface.parent = background;
179 bullet->surface.parentx = bw - th;
180 bullet->surface.parenty = PADDING;
181 theme_pixmap_paint(bullet, th, th);
182 surface = cairo_xlib_surface_create(xdisplay, bullet->pixmap,
185 tmp_pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, th, th);
186 cairo_surface_destroy(surface);
187 gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, th, th, pixbuf,
188 width - theme->mbwidth - th, y + PADDING);
192 /* draw background for disabled entry */
193 background->surface.parent = menu;
194 background->surface.parentx = x - theme->mbwidth;
195 background->surface.parenty = y - theme->mbwidth;
196 theme_pixmap_paint(background, bw, bh);
197 surface = cairo_xlib_surface_create(xdisplay, background->pixmap,
200 tmp_pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, bw, bh);
201 cairo_surface_destroy(surface);
202 gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, bw, bh, pixbuf, x, y);
204 /* draw disabled entry */
205 RrMinSize(disabled, &tw, &th);
206 disabled->surface.parent = background;
207 disabled->surface.parentx = PADDING;
208 disabled->surface.parenty = PADDING;
209 theme_pixmap_paint(disabled, tw, th);
210 surface = cairo_xlib_surface_create(xdisplay, disabled->pixmap,
213 tmp_pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, tw, th);
214 cairo_surface_destroy(surface);
215 gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, tw, th, pixbuf,
216 x + PADDING, y + PADDING);
220 /* draw background for selected entry */
221 background = theme->a_menu_selected;
222 background->surface.parent = menu;
223 background->surface.parentx = x - theme->mbwidth;
224 background->surface.parenty = y - theme->mbwidth;
226 theme_pixmap_paint(background, bw, bh);
227 surface = cairo_xlib_surface_create(xdisplay, background->pixmap,
230 tmp_pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, bw, bh);
231 cairo_surface_destroy(surface);
232 gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, bw, bh, pixbuf, x, y);
234 /* draw selected entry */
235 RrMinSize(selected, &tw, &th);
236 selected->surface.parent = background;
237 selected->surface.parentx = PADDING;
238 selected->surface.parenty = PADDING;
239 theme_pixmap_paint(selected, tw, th);
240 surface = cairo_xlib_surface_create(xdisplay, selected->pixmap,
243 tmp_pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, tw, th);
244 cairo_surface_destroy(surface);
245 gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, tw, th, pixbuf,
246 x + PADDING, y + PADDING);
247 g_object_unref(tmp_pixbuf);
252 static GdkPixbuf* preview_window(RrTheme *theme, const gchar *titlelayout,
253 gboolean focus, gint width, gint height)
256 RrAppearance *handle;
259 cairo_surface_t *surface;
263 GdkPixbuf *pixbuf = NULL, *tmp_pixbuf = NULL;
266 gint w, label_w, h, x, y;
270 screen = gdk_screen_get_default();
271 xdisplay = gdk_x11_get_default_xdisplay();
272 xvisual = gdk_x11_visual_get_xvisual(gdk_screen_get_system_visual(screen));
274 title = focus ? theme->a_focused_title : theme->a_unfocused_title;
277 pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height);
278 gdk_pixbuf_fill(pixbuf,
279 rr_color_pixel(focus ?
280 theme->frame_focused_border_color :
281 theme->frame_unfocused_border_color));
282 tmp_pixbuf = gdk_pixbuf_copy(pixbuf);
285 w = width - 2*theme->fbwidth;
286 h = theme->title_height;
287 theme_pixmap_paint(title, w, h);
289 x = y = theme->fbwidth;
290 surface = cairo_xlib_surface_create(xdisplay, title->pixmap,
293 tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
295 cairo_surface_destroy(surface);
296 gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y);
298 /* calculate label width */
299 label_w = width - (theme->paddingx + theme->fbwidth + 1) * 2;
301 for (layout = titlelayout; *layout; layout++) {
304 label_w -= theme->button_size + 2 + theme->paddingx + 1;
311 label_w -= theme->button_size + theme->paddingx + 1;
318 x = theme->paddingx + theme->fbwidth + 1;
319 y += theme->paddingy;
320 for (layout = titlelayout; *layout; layout++) {
322 if (*layout == 'N') {
324 /* set default icon */
325 a->texture[0].type = RR_TEXTURE_RGBA;
326 a->texture[0].data.rgba.width = 48;
327 a->texture[0].data.rgba.height = 48;
328 a->texture[0].data.rgba.alpha = 0xff;
329 a->texture[0].data.rgba.data = theme->def_win_icon;
331 a->surface.parent = title;
332 a->surface.parentx = x - theme->fbwidth;
333 a->surface.parenty = theme->paddingy;
335 w = h = theme->button_size + 2;
337 theme_pixmap_paint(a, w, h);
338 surface = cairo_xlib_surface_create(xdisplay, a->pixmap,
341 tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
343 cairo_surface_destroy(surface);
344 gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y);
346 x += theme->button_size + 2 + theme->paddingx + 1;
347 } else if (*layout == 'L') { /* label */
348 a = focus ? theme->a_focused_label : theme->a_unfocused_label;
349 a->texture[0].data.text.string = focus ? "Active" : "Inactive";
351 a->surface.parent = title;
352 a->surface.parentx = x - theme->fbwidth;
353 a->surface.parenty = theme->paddingy;
355 h = theme->label_height;
357 theme_pixmap_paint(a, w, h);
358 surface = cairo_xlib_surface_create(xdisplay, a->pixmap,
361 tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
363 cairo_surface_destroy(surface);
364 gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y);
366 x += w + theme->paddingx + 1;
372 theme->btn_desk->a_focused_unpressed :
373 theme->btn_desk->a_unfocused_unpressed;
377 theme->btn_shade->a_focused_unpressed :
378 theme->btn_shade->a_unfocused_unpressed;
382 theme->btn_iconify->a_focused_unpressed :
383 theme->btn_iconify->a_unfocused_unpressed;
387 theme->btn_max->a_focused_unpressed :
388 theme->btn_max->a_unfocused_unpressed;
392 theme->btn_close->a_focused_unpressed :
393 theme->btn_close->a_unfocused_unpressed;
399 a->surface.parent = title;
400 a->surface.parentx = x - theme->fbwidth;
401 a->surface.parenty = theme->paddingy + 1;
403 w = theme->button_size;
404 h = theme->button_size;
406 theme_pixmap_paint(a, w, h);
407 surface = cairo_xlib_surface_create(xdisplay, a->pixmap,
410 /* use y + 1 because these buttons should be centered wrt the label
412 tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
414 cairo_surface_destroy(surface);
415 gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y + 1);
417 x += theme->button_size + theme->paddingx + 1;
421 if (theme->handle_height) {
423 handle = focus ? theme->a_focused_handle : theme->a_unfocused_handle;
424 x = 2*theme->fbwidth + theme->grip_width;
425 y = height - theme->fbwidth - theme->handle_height;
426 w = width - 4*theme->fbwidth - 2*theme->grip_width;
427 h = theme->handle_height;
429 theme_pixmap_paint(handle, w, h);
430 surface = cairo_xlib_surface_create(xdisplay, handle->pixmap,
433 tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
435 cairo_surface_destroy(surface);
436 gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y);
438 /* openbox handles this drawing stuff differently (it fills the bottom
439 * of the window with the handle), so it avoids this bug where
440 * parentrelative grips are not fully filled. i'm avoiding it slightly
443 theme_pixmap_paint(handle, width, h);
446 a = focus ? theme->a_focused_grip : theme->a_unfocused_grip;
447 a->surface.parent = handle;
450 /* same y and h as handle */
451 w = theme->grip_width;
453 theme_pixmap_paint(a, w, h);
454 surface = cairo_xlib_surface_create(xdisplay, a->pixmap,
456 tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
458 gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y);
461 x = width - theme->fbwidth - theme->grip_width;
462 tmp_pixbuf = gdk_pixbuf_get_from_surface(surface,
464 cairo_surface_destroy(surface);
465 gdk_pixbuf_copy_area(tmp_pixbuf, 0, 0, w, h, pixbuf, x, y);
468 /* title separator colour */
470 y = theme->fbwidth + theme->title_height;
471 w = width - 2*theme->fbwidth;
474 scratch = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, h);
475 gdk_pixbuf_fill(scratch, rr_color_pixel(focus ?
476 theme->title_separator_focused_color :
477 theme->title_separator_unfocused_color));
479 gdk_pixbuf_copy_area(scratch, 0, 0, w, h, pixbuf, x, y);
481 /* retarded way of adding client colour */
483 y = theme->title_height + 2*theme->fbwidth;
484 w = width - 2*theme->fbwidth;
485 h = height - theme->title_height - 3*theme->fbwidth -
486 (theme->handle_height ? (theme->fbwidth + theme->handle_height) : 0);
488 scratch = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, h);
489 gdk_pixbuf_fill(scratch, rr_color_pixel(focus ?
490 theme->cb_focused_color :
491 theme->cb_unfocused_color));
493 gdk_pixbuf_copy_area(scratch, 0, 0, w, h, pixbuf, x, y);
495 /* clear (no alpha!) the area where the client resides */
496 gdk_pixbuf_fill(scratch, 0xffffffff);
497 gdk_pixbuf_copy_area(scratch, 0, 0,
498 w - 2*theme->cbwidthx,
499 h - 2*theme->cbwidthy,
502 y + theme->cbwidthy);
503 g_object_unref(scratch);
508 static gint theme_label_width(RrTheme *theme, gboolean active)
514 label = theme->a_focused_label;
515 label->texture[0].data.text.string = "Active";
517 label = theme->a_unfocused_label;
518 label->texture[0].data.text.string = "Inactive";
521 return RrMinWidth(label);
524 static gint theme_window_min_width(RrTheme *theme, const gchar *titlelayout)
526 gint numbuttons = strlen(titlelayout);
527 gint w = 2 * theme->fbwidth + (numbuttons + 3) * (theme->paddingx + 1);
529 if (g_strrstr(titlelayout, "L")) {
531 w += MAX(theme_label_width(theme, TRUE),
532 theme_label_width(theme, FALSE));
535 w += theme->button_size * numbuttons;
540 GdkPixbuf *preview_theme(const gchar *name, const gchar *titlelayout,
541 RrFont *active_window_font,
542 RrFont *inactive_window_font,
543 RrFont *menu_title_font,
544 RrFont *menu_item_font,
545 RrFont *osd_active_font,
546 RrFont *osd_inactive_font)
558 RrTheme *theme = RrThemeNew(rrinst, name, FALSE,
559 active_window_font, inactive_window_font,
560 menu_title_font, menu_item_font,
561 osd_active_font, osd_inactive_font);
565 menu = preview_menu(theme);
567 window_w = theme_window_min_width(theme, titlelayout);
569 menu_w = gdk_pixbuf_get_width(menu);
570 h = gdk_pixbuf_get_height(menu);
572 w = MAX(window_w, menu_w) + 20;
574 /* we don't want windows disappearing on us */
575 if (!window_w) window_w = menu_w;
577 preview = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,
578 w, h + 2*(theme->title_height +5) + 1);
579 gdk_pixbuf_fill(preview, 0); /* clear */
581 window = preview_window(theme, titlelayout, FALSE, window_w, h);
582 gdk_pixbuf_copy_area(window, 0, 0, window_w, h, preview, 20, 0);
583 g_object_unref(window);
585 window = preview_window(theme, titlelayout, TRUE, window_w, h);
586 gdk_pixbuf_copy_area(window, 0, 0, window_w, h,
587 preview, 10, theme->title_height + 5);
588 g_object_unref(window);
590 gdk_pixbuf_copy_area(menu, 0, 0, menu_w, h,
591 preview, 0, 2 * (theme->title_height + 5));
592 g_object_unref(menu);