resize label after background pixmap has updated to avoid display problems
[dana/openbox.git] / openbox / frame.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    frame.c for the Openbox window manager
4    Copyright (c) 2004        Mikael Magnusson
5    Copyright (c) 2003        Ben 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 "frame.h"
21 #include "client.h"
22 #include "openbox.h"
23 #include "extensions.h"
24 #include "prop.h"
25 #include "config.h"
26 #include "framerender.h"
27 #include "mainloop.h"
28 #include "focus.h"
29 #include "moveresize.h"
30 #include "render/theme.h"
31
32 #define PLATE_EVENTMASK (SubstructureRedirectMask | ButtonPressMask)
33 #define FRAME_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
34                          ButtonPressMask | ButtonReleaseMask | \
35                          VisibilityChangeMask)
36 #define ELEMENT_EVENTMASK (ButtonPressMask | ButtonReleaseMask | \
37                            ButtonMotionMask | ExposureMask | \
38                            EnterWindowMask | LeaveWindowMask)
39
40 #define FRAME_HANDLE_Y(f) (f->innersize.top + f->client->area.height + \
41                            f->cbwidth_y)
42
43 static void layout_title(ObFrame *self);
44 static void flash_done(gpointer data);
45 static gboolean flash_timeout(gpointer data);
46
47 static void set_theme_statics(ObFrame *self);
48 static void free_theme_statics(ObFrame *self);
49
50 static Window createWindow(Window parent, gulong mask,
51                            XSetWindowAttributes *attrib)
52 {
53     return XCreateWindow(ob_display, parent, 0, 0, 1, 1, 0,
54                          RrDepth(ob_rr_inst), InputOutput,
55                          RrVisual(ob_rr_inst), mask, attrib);
56                        
57 }
58
59 ObFrame *frame_new()
60 {
61     XSetWindowAttributes attrib;
62     gulong mask;
63     ObFrame *self;
64
65     self = g_new0(ObFrame, 1);
66
67     self->obscured = TRUE;
68
69     /* create all of the decor windows */
70     mask = CWEventMask;
71     attrib.event_mask = FRAME_EVENTMASK;
72     self->window = createWindow(RootWindow(ob_display, ob_screen),
73                                 mask, &attrib);
74
75     mask = 0;
76     self->plate = createWindow(self->window, mask, &attrib);
77
78     mask = CWEventMask;
79     attrib.event_mask = ELEMENT_EVENTMASK;
80     self->title = createWindow(self->window, mask, &attrib);
81
82     mask |= CWCursor;
83     attrib.cursor = ob_cursor(OB_CURSOR_NORTHWEST);
84     self->tlresize = createWindow(self->title, mask, &attrib);
85     attrib.cursor = ob_cursor(OB_CURSOR_NORTHEAST);
86     self->trresize = createWindow(self->title, mask, &attrib);
87
88     mask &= ~CWCursor;
89     self->label = createWindow(self->title, mask, &attrib);
90     self->max = createWindow(self->title, mask, &attrib);
91     self->close = createWindow(self->title, mask, &attrib);
92     self->desk = createWindow(self->title, mask, &attrib);
93     self->shade = createWindow(self->title, mask, &attrib);
94     self->icon = createWindow(self->title, mask, &attrib);
95     self->iconify = createWindow(self->title, mask, &attrib);
96     self->handle = createWindow(self->window, mask, &attrib);
97
98     mask |= CWCursor;
99     attrib.cursor = ob_cursor(OB_CURSOR_SOUTHWEST);
100     self->lgrip = createWindow(self->handle, mask, &attrib);
101     attrib.cursor = ob_cursor(OB_CURSOR_SOUTHEAST);
102     self->rgrip = createWindow(self->handle, mask, &attrib); 
103
104     self->focused = FALSE;
105
106     /* the other stuff is shown based on decor settings */
107     XMapWindow(ob_display, self->plate);
108     XMapWindow(ob_display, self->lgrip);
109     XMapWindow(ob_display, self->rgrip);
110     XMapWindow(ob_display, self->label);
111
112     self->max_press = self->close_press = self->desk_press = 
113         self->iconify_press = self->shade_press = FALSE;
114     self->max_hover = self->close_hover = self->desk_hover = 
115         self->iconify_hover = self->shade_hover = FALSE;
116
117     set_theme_statics(self);
118
119     return (ObFrame*)self;
120 }
121
122 static void set_theme_statics(ObFrame *self)
123 {
124     /* set colors/appearance/sizes for stuff that doesn't change */
125     XSetWindowBorder(ob_display, self->window,
126                      RrColorPixel(ob_rr_theme->b_color));
127     XSetWindowBorder(ob_display, self->title,
128                      RrColorPixel(ob_rr_theme->b_color));
129     XSetWindowBorder(ob_display, self->handle,
130                      RrColorPixel(ob_rr_theme->b_color));
131     XSetWindowBorder(ob_display, self->rgrip,
132                      RrColorPixel(ob_rr_theme->b_color));
133     XSetWindowBorder(ob_display, self->lgrip,
134                      RrColorPixel(ob_rr_theme->b_color));
135
136     XResizeWindow(ob_display, self->max,
137                   ob_rr_theme->button_size, ob_rr_theme->button_size);
138     XResizeWindow(ob_display, self->iconify,
139                   ob_rr_theme->button_size, ob_rr_theme->button_size);
140     XResizeWindow(ob_display, self->icon,
141                   ob_rr_theme->button_size + 2, ob_rr_theme->button_size + 2);
142     XResizeWindow(ob_display, self->close,
143                   ob_rr_theme->button_size, ob_rr_theme->button_size);
144     XResizeWindow(ob_display, self->desk,
145                   ob_rr_theme->button_size, ob_rr_theme->button_size);
146     XResizeWindow(ob_display, self->shade,
147                   ob_rr_theme->button_size, ob_rr_theme->button_size);
148     XResizeWindow(ob_display, self->lgrip,
149                   ob_rr_theme->grip_width, ob_rr_theme->handle_height);
150     XResizeWindow(ob_display, self->rgrip,
151                   ob_rr_theme->grip_width, ob_rr_theme->handle_height);
152     XResizeWindow(ob_display, self->tlresize,
153                   ob_rr_theme->grip_width, ob_rr_theme->handle_height);
154     XResizeWindow(ob_display, self->trresize,
155                   ob_rr_theme->grip_width, ob_rr_theme->handle_height);
156
157     /* set up the dynamic appearances */
158     self->a_unfocused_title = RrAppearanceCopy(ob_rr_theme->a_unfocused_title);
159     self->a_focused_title = RrAppearanceCopy(ob_rr_theme->a_focused_title);
160     self->a_unfocused_label = RrAppearanceCopy(ob_rr_theme->a_unfocused_label);
161     self->a_focused_label = RrAppearanceCopy(ob_rr_theme->a_focused_label);
162     self->a_unfocused_handle =
163         RrAppearanceCopy(ob_rr_theme->a_unfocused_handle);
164     self->a_focused_handle = RrAppearanceCopy(ob_rr_theme->a_focused_handle);
165     self->a_icon = RrAppearanceCopy(ob_rr_theme->a_icon);
166 }
167
168 static void free_theme_statics(ObFrame *self)
169 {
170     RrAppearanceFree(self->a_unfocused_title); 
171     RrAppearanceFree(self->a_focused_title);
172     RrAppearanceFree(self->a_unfocused_label);
173     RrAppearanceFree(self->a_focused_label);
174     RrAppearanceFree(self->a_unfocused_handle);
175     RrAppearanceFree(self->a_focused_handle);
176     RrAppearanceFree(self->a_icon);
177 }
178
179 static void frame_free(ObFrame *self)
180 {
181     free_theme_statics(self);
182
183     XDestroyWindow(ob_display, self->window);
184
185     g_free(self);
186 }
187
188 void frame_show(ObFrame *self)
189 {
190     if (!self->visible) {
191         self->visible = TRUE;
192         XMapWindow(ob_display, self->client->window);
193         XMapWindow(ob_display, self->window);
194     }
195 }
196
197 void frame_hide(ObFrame *self)
198 {
199     if (self->visible) {
200         self->visible = FALSE;
201         self->client->ignore_unmaps += 2;
202         /* we unmap the client itself so that we can get MapRequest
203            events, and because the ICCCM tells us to! */
204         XUnmapWindow(ob_display, self->window);
205         XUnmapWindow(ob_display, self->client->window);
206     }
207 }
208
209 void frame_adjust_theme(ObFrame *self)
210 {
211     free_theme_statics(self);
212     set_theme_statics(self);
213 }
214
215 void frame_adjust_shape(ObFrame *self)
216 {
217 #ifdef SHAPE
218     gint num;
219     XRectangle xrect[2];
220
221     if (!self->client->shaped) {
222         /* clear the shape on the frame window */
223         XShapeCombineMask(ob_display, self->window, ShapeBounding,
224                           self->innersize.left,
225                           self->innersize.top,
226                           None, ShapeSet);
227     } else {
228         /* make the frame's shape match the clients */
229         XShapeCombineShape(ob_display, self->window, ShapeBounding,
230                            self->innersize.left,
231                            self->innersize.top,
232                            self->client->window,
233                            ShapeBounding, ShapeSet);
234
235         num = 0;
236         if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
237             xrect[0].x = -ob_rr_theme->bwidth;
238             xrect[0].y = -ob_rr_theme->bwidth;
239             xrect[0].width = self->width + self->rbwidth * 2;
240             xrect[0].height = ob_rr_theme->title_height +
241                 self->bwidth * 2;
242             ++num;
243         }
244
245         if (self->decorations & OB_FRAME_DECOR_HANDLE) {
246             xrect[1].x = -ob_rr_theme->bwidth;
247             xrect[1].y = FRAME_HANDLE_Y(self);
248             xrect[1].width = self->width + self->rbwidth * 2;
249             xrect[1].height = ob_rr_theme->handle_height +
250                 self->bwidth * 2;
251             ++num;
252         }
253
254         XShapeCombineRectangles(ob_display, self->window,
255                                 ShapeBounding, 0, 0, xrect, num,
256                                 ShapeUnion, Unsorted);
257     }
258 #endif
259 }
260
261 void frame_adjust_area(ObFrame *self, gboolean moved,
262                        gboolean resized, gboolean fake)
263 {
264     Strut oldsize;
265
266     oldsize = self->size;
267
268     if (resized) {
269         self->decorations = self->client->decorations;
270         self->max_horz = self->client->max_horz;
271
272         if (self->decorations & OB_FRAME_DECOR_BORDER) {
273             self->bwidth = ob_rr_theme->bwidth;
274             self->cbwidth_x = self->cbwidth_y = ob_rr_theme->cbwidth;
275         } else {
276             self->bwidth = self->cbwidth_x = self->cbwidth_y = 0;
277         }
278         self->rbwidth = self->bwidth;
279
280         if (self->max_horz)
281             self->bwidth = self->cbwidth_x = 0;
282
283         STRUT_SET(self->innersize,
284                   self->cbwidth_x,
285                   self->cbwidth_y,
286                   self->cbwidth_x,
287                   self->cbwidth_y);
288         self->width = self->client->area.width + self->cbwidth_x * 2 -
289             (self->max_horz ? self->rbwidth * 2 : 0);
290         self->width = MAX(self->width, 1); /* no lower than 1 */
291
292         /* set border widths */
293         if (!fake) {
294             XSetWindowBorderWidth(ob_display, self->window, self->bwidth);
295             XSetWindowBorderWidth(ob_display, self->title,  self->rbwidth);
296             XSetWindowBorderWidth(ob_display, self->handle, self->rbwidth);
297             XSetWindowBorderWidth(ob_display, self->lgrip,  self->rbwidth);
298             XSetWindowBorderWidth(ob_display, self->rgrip,  self->rbwidth);
299         }
300
301         if (self->decorations & OB_FRAME_DECOR_TITLEBAR)
302             self->innersize.top += ob_rr_theme->title_height + self->rbwidth +
303                 (self->rbwidth - self->bwidth);
304         if (self->decorations & OB_FRAME_DECOR_HANDLE &&
305             ob_rr_theme->show_handle)
306             self->innersize.bottom += ob_rr_theme->handle_height +
307                 self->rbwidth + (self->rbwidth - self->bwidth);
308   
309         /* they all default off, they're turned on in layout_title */
310         self->icon_x = -1;
311         self->desk_x = -1;
312         self->shade_x = -1;
313         self->iconify_x = -1;
314         self->label_x = -1;
315         self->max_x = -1;
316         self->close_x = -1;
317
318         /* position/size and map/unmap all the windows */
319
320         if (!fake) {
321             if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
322                 XMoveResizeWindow(ob_display, self->title,
323                                   -self->bwidth, -self->bwidth,
324                                   self->width, ob_rr_theme->title_height);
325                 XMapWindow(ob_display, self->title);
326
327                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
328                     XMoveWindow(ob_display, self->tlresize, 0, 0);
329                     XMoveWindow(ob_display, self->trresize,
330                                 self->width - ob_rr_theme->grip_width, 0);
331                     XMapWindow(ob_display, self->tlresize);
332                     XMapWindow(ob_display, self->trresize);
333                 } else {
334                     XUnmapWindow(ob_display, self->tlresize);
335                     XUnmapWindow(ob_display, self->trresize);
336                 }
337             } else
338                 XUnmapWindow(ob_display, self->title);
339         }
340
341         if (self->decorations & OB_FRAME_DECOR_TITLEBAR)
342             /* layout the title bar elements */
343             layout_title(self);
344
345         if (!fake) {
346             if (self->decorations & OB_FRAME_DECOR_HANDLE &&
347                 ob_rr_theme->show_handle)
348             {
349                 XMoveResizeWindow(ob_display, self->handle,
350                                   -self->bwidth, FRAME_HANDLE_Y(self),
351                                   self->width, ob_rr_theme->handle_height);
352                 XMapWindow(ob_display, self->handle);
353
354                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
355                     XMoveWindow(ob_display, self->lgrip,
356                                 -self->rbwidth, -self->rbwidth);
357                     XMoveWindow(ob_display, self->rgrip,
358                                 -self->rbwidth + self->width -
359                                 ob_rr_theme->grip_width, -self->rbwidth);
360                     XMapWindow(ob_display, self->lgrip);
361                     XMapWindow(ob_display, self->rgrip);
362                 } else {
363                     XUnmapWindow(ob_display, self->lgrip);
364                     XUnmapWindow(ob_display, self->rgrip);
365                 }
366
367                 /* XXX make a subwindow with these dimentions?
368                    ob_rr_theme->grip_width + self->bwidth, 0,
369                    self->width - (ob_rr_theme->grip_width + self->bwidth) * 2,
370                    ob_rr_theme->handle_height);
371                 */
372             } else
373                 XUnmapWindow(ob_display, self->handle);
374
375             /* move and resize the plate */
376             XMoveResizeWindow(ob_display, self->plate,
377                               self->innersize.left - self->cbwidth_x,
378                               self->innersize.top - self->cbwidth_y,
379                               self->client->area.width + self->cbwidth_x * 2,
380                               self->client->area.height + self->cbwidth_y * 2);
381             /* when the client has StaticGravity, it likes to move around. */
382             XMoveWindow(ob_display, self->client->window,
383                         self->cbwidth_x, self->cbwidth_y);
384         }
385
386         STRUT_SET(self->size,
387                   self->innersize.left + self->bwidth,
388                   self->innersize.top + self->bwidth,
389                   self->innersize.right + self->bwidth,
390                   self->innersize.bottom + self->bwidth);
391     }
392
393     /* shading can change without being moved or resized */
394     RECT_SET_SIZE(self->area,
395                   self->client->area.width +
396                   self->size.left + self->size.right,
397                   (self->client->shaded ?
398                    ob_rr_theme->title_height + self->rbwidth * 2:
399                    self->client->area.height +
400                    self->size.top + self->size.bottom));
401
402     if (moved) {
403         /* find the new coordinates, done after setting the frame.size, for
404            frame_client_gravity. */
405         self->area.x = self->client->area.x;
406         self->area.y = self->client->area.y;
407         frame_client_gravity(self, &self->area.x, &self->area.y);
408     }
409
410     if (!fake) {
411         /* move and resize the top level frame.
412            shading can change without being moved or resized */
413         XMoveResizeWindow(ob_display, self->window,
414                           self->area.x, self->area.y,
415                           self->area.width - self->bwidth * 2,
416                           self->area.height - self->bwidth * 2);
417
418         if (resized) {
419             framerender_frame(self);
420             frame_adjust_shape(self);
421         }
422
423         if (!STRUT_EQUAL(self->size, oldsize)) {
424             gulong vals[4];
425             vals[0] = self->size.left;
426             vals[1] = self->size.right;
427             vals[2] = self->size.top;
428             vals[3] = self->size.bottom;
429             PROP_SETA32(self->client->window, kde_net_wm_frame_strut,
430                         cardinal, vals, 4);
431         }
432
433         /* if this occurs while we are focus cycling, the indicator needs to
434            match the changes */
435         if (focus_cycle_target == self->client)
436             focus_cycle_draw_indicator();
437     }
438     if (resized && (self->decorations & OB_FRAME_DECOR_TITLEBAR))
439         XResizeWindow(ob_display, self->label, self->label_width,
440                       ob_rr_theme->label_height);
441 }
442
443 void frame_adjust_state(ObFrame *self)
444 {
445     framerender_frame(self);
446 }
447
448 void frame_adjust_focus(ObFrame *self, gboolean hilite)
449 {
450     self->focused = hilite;
451     framerender_frame(self);
452 }
453
454 void frame_adjust_title(ObFrame *self)
455 {
456     framerender_frame(self);
457 }
458
459 void frame_adjust_icon(ObFrame *self)
460 {
461     framerender_frame(self);
462 }
463
464 void frame_grab_client(ObFrame *self, ObClient *client)
465 {
466     self->client = client;
467
468     /* reparent the client to the frame */
469     XReparentWindow(ob_display, client->window, self->plate, 0, 0);
470     /*
471       When reparenting the client window, it is usually not mapped yet, since
472       this occurs from a MapRequest. However, in the case where Openbox is
473       starting up, the window is already mapped, so we'll see unmap events for
474       it. There are 2 unmap events generated that we see, one with the 'event'
475       member set the root window, and one set to the client, but both get
476       handled and need to be ignored.
477     */
478     if (ob_state() == OB_STATE_STARTING)
479         client->ignore_unmaps += 2;
480
481     /* select the event mask on the client's parent (to receive config/map
482        req's) the ButtonPress is to catch clicks on the client border */
483     XSelectInput(ob_display, self->plate, PLATE_EVENTMASK);
484
485     /* map the client so it maps when the frame does */
486     XMapWindow(ob_display, client->window);
487
488     frame_adjust_area(self, TRUE, TRUE, FALSE);
489
490     /* set all the windows for the frame in the window_map */
491     g_hash_table_insert(window_map, &self->window, client);
492     g_hash_table_insert(window_map, &self->plate, client);
493     g_hash_table_insert(window_map, &self->title, client);
494     g_hash_table_insert(window_map, &self->label, client);
495     g_hash_table_insert(window_map, &self->max, client);
496     g_hash_table_insert(window_map, &self->close, client);
497     g_hash_table_insert(window_map, &self->desk, client);
498     g_hash_table_insert(window_map, &self->shade, client);
499     g_hash_table_insert(window_map, &self->icon, client);
500     g_hash_table_insert(window_map, &self->iconify, client);
501     g_hash_table_insert(window_map, &self->handle, client);
502     g_hash_table_insert(window_map, &self->lgrip, client);
503     g_hash_table_insert(window_map, &self->rgrip, client);
504     g_hash_table_insert(window_map, &self->tlresize, client);
505     g_hash_table_insert(window_map, &self->trresize, client);
506 }
507
508 void frame_release_client(ObFrame *self, ObClient *client)
509 {
510     XEvent ev;
511     gboolean reparent = TRUE;
512
513     g_assert(self->client == client);
514
515     /* check if the app has already reparented its window away */
516     while (XCheckTypedWindowEvent(ob_display, client->window,
517                                   ReparentNotify, &ev))
518     {
519         /* This check makes sure we don't catch our own reparent action to
520            our frame window. This doesn't count as the app reparenting itself
521            away of course.
522
523            Reparent events that are generated by us are just discarded here.
524            They are of no consequence to us anyhow.
525         */
526         if (ev.xreparent.parent != self->plate) {
527             reparent = FALSE;
528             XPutBackEvent(ob_display, &ev);
529             break;
530         }
531     }
532
533     if (reparent) {
534         /* according to the ICCCM - if the client doesn't reparent itself,
535            then we will reparent the window to root for them */
536         XReparentWindow(ob_display, client->window,
537                         RootWindow(ob_display, ob_screen),
538                         client->area.x,
539                         client->area.y);
540     }
541
542     /* remove all the windows for the frame from the window_map */
543     g_hash_table_remove(window_map, &self->window);
544     g_hash_table_remove(window_map, &self->plate);
545     g_hash_table_remove(window_map, &self->title);
546     g_hash_table_remove(window_map, &self->label);
547     g_hash_table_remove(window_map, &self->max);
548     g_hash_table_remove(window_map, &self->close);
549     g_hash_table_remove(window_map, &self->desk);
550     g_hash_table_remove(window_map, &self->shade);
551     g_hash_table_remove(window_map, &self->icon);
552     g_hash_table_remove(window_map, &self->iconify);
553     g_hash_table_remove(window_map, &self->handle);
554     g_hash_table_remove(window_map, &self->lgrip);
555     g_hash_table_remove(window_map, &self->rgrip);
556     g_hash_table_remove(window_map, &self->tlresize);
557     g_hash_table_remove(window_map, &self->trresize);
558
559     ob_main_loop_timeout_remove_data(ob_main_loop, flash_timeout, self);
560
561     frame_free(self);
562 }
563
564 static void layout_title(ObFrame *self)
565 {
566     gchar *lc;
567     gint x;
568     gboolean n, d, i, l, m, c, s;
569
570     n = d = i = l = m = c = s = FALSE;
571
572     /* figure out whats being shown, and the width of the label */
573     self->label_width = self->width - (ob_rr_theme->padding + 1) * 2;
574     for (lc = config_title_layout; *lc != '\0'; ++lc) {
575         switch (*lc) {
576         case 'N':
577             if (n) { *lc = ' '; break; } /* rm duplicates */
578             n = TRUE;
579             self->label_width -= (ob_rr_theme->button_size + 2 +
580                                   ob_rr_theme->padding + 1);
581             break;
582         case 'D':
583             if (d) { *lc = ' '; break; }
584             if (!(self->decorations & OB_FRAME_DECOR_ALLDESKTOPS) && config_theme_hidedisabled)
585                 break;
586             d = TRUE;
587             self->label_width -= (ob_rr_theme->button_size +
588                                   ob_rr_theme->padding + 1);
589             break;
590         case 'S':
591             if (s) { *lc = ' '; break; }
592             if (!(self->decorations & OB_FRAME_DECOR_SHADE) && config_theme_hidedisabled)
593                 break;
594             s = TRUE;
595             self->label_width -= (ob_rr_theme->button_size +
596                                   ob_rr_theme->padding + 1);
597             break;
598         case 'I':
599             if (i) { *lc = ' '; break; }
600             if (!(self->decorations & OB_FRAME_DECOR_ICONIFY) && config_theme_hidedisabled)
601                 break;
602             i = TRUE;
603             self->label_width -= (ob_rr_theme->button_size +
604                                   ob_rr_theme->padding + 1);
605             break;
606         case 'L':
607             if (l) { *lc = ' '; break; }
608             l = TRUE;
609             break;
610         case 'M':
611             if (m) { *lc = ' '; break; }
612             if (!(self->decorations & OB_FRAME_DECOR_MAXIMIZE) && config_theme_hidedisabled)
613                 break;
614             m = TRUE;
615             self->label_width -= (ob_rr_theme->button_size +
616                                   ob_rr_theme->padding + 1);
617             break;
618         case 'C':
619             if (c) { *lc = ' '; break; }
620             if (!(self->decorations & OB_FRAME_DECOR_CLOSE) && config_theme_hidedisabled)
621                 break;
622             c = TRUE;
623             self->label_width -= (ob_rr_theme->button_size +
624                                   ob_rr_theme->padding + 1);
625             break;
626         }
627     }
628     if (self->label_width < 1) self->label_width = 1;
629
630     if (!n) XUnmapWindow(ob_display, self->icon);
631     if (!d) XUnmapWindow(ob_display, self->desk);
632     if (!s) XUnmapWindow(ob_display, self->shade);
633     if (!i) XUnmapWindow(ob_display, self->iconify);
634     if (!l) XUnmapWindow(ob_display, self->label);
635     if (!m) XUnmapWindow(ob_display, self->max);
636     if (!c) XUnmapWindow(ob_display, self->close);
637
638     x = ob_rr_theme->padding + 1;
639     for (lc = config_title_layout; *lc != '\0'; ++lc) {
640         switch (*lc) {
641         case 'N':
642             if (!n) break;
643             self->icon_x = x;
644             XMapWindow(ob_display, self->icon);
645             XMoveWindow(ob_display, self->icon, x, ob_rr_theme->padding);
646             x += ob_rr_theme->button_size + 2 + ob_rr_theme->padding + 1;
647             break;
648         case 'D':
649             if (!d) break;
650             self->desk_x = x;
651             XMapWindow(ob_display, self->desk);
652             XMoveWindow(ob_display, self->desk, x, ob_rr_theme->padding + 1);
653             x += ob_rr_theme->button_size + ob_rr_theme->padding + 1;
654             break;
655         case 'S':
656             if (!s) break;
657             self->shade_x = x;
658             XMapWindow(ob_display, self->shade);
659             XMoveWindow(ob_display, self->shade, x, ob_rr_theme->padding + 1);
660             x += ob_rr_theme->button_size + ob_rr_theme->padding + 1;
661             break;
662         case 'I':
663             if (!i) break;
664             self->iconify_x = x;
665             XMapWindow(ob_display, self->iconify);
666             XMoveWindow(ob_display,self->iconify, x, ob_rr_theme->padding + 1);
667             x += ob_rr_theme->button_size + ob_rr_theme->padding + 1;
668             break;
669         case 'L':
670             if (!l) break;
671             self->label_x = x;
672             XMapWindow(ob_display, self->label);
673             XMoveWindow(ob_display, self->label, x, ob_rr_theme->padding);
674             x += self->label_width + ob_rr_theme->padding + 1;
675             break;
676         case 'M':
677             if (!m) break;
678             self->max_x = x;
679             XMapWindow(ob_display, self->max);
680             XMoveWindow(ob_display, self->max, x, ob_rr_theme->padding + 1);
681             x += ob_rr_theme->button_size + ob_rr_theme->padding + 1;
682             break;
683         case 'C':
684             if (!c) break;
685             self->close_x = x;
686             XMapWindow(ob_display, self->close);
687             XMoveWindow(ob_display, self->close, x, ob_rr_theme->padding + 1);
688             x += ob_rr_theme->button_size + ob_rr_theme->padding + 1;
689             break;
690         }
691     }
692 }
693
694 ObFrameContext frame_context_from_string(const gchar *name)
695 {
696     if (!g_ascii_strcasecmp("Desktop", name))
697         return OB_FRAME_CONTEXT_DESKTOP;
698     else if (!g_ascii_strcasecmp("Client", name))
699         return OB_FRAME_CONTEXT_CLIENT;
700     else if (!g_ascii_strcasecmp("Titlebar", name))
701         return OB_FRAME_CONTEXT_TITLEBAR;
702     else if (!g_ascii_strcasecmp("Handle", name))
703         return OB_FRAME_CONTEXT_HANDLE;
704     else if (!g_ascii_strcasecmp("Frame", name))
705         return OB_FRAME_CONTEXT_FRAME;
706     else if (!g_ascii_strcasecmp("TLCorner", name))
707         return OB_FRAME_CONTEXT_TLCORNER;
708     else if (!g_ascii_strcasecmp("TRCorner", name))
709         return OB_FRAME_CONTEXT_TRCORNER;
710     else if (!g_ascii_strcasecmp("BLCorner", name))
711         return OB_FRAME_CONTEXT_BLCORNER;
712     else if (!g_ascii_strcasecmp("BRCorner", name))
713         return OB_FRAME_CONTEXT_BRCORNER;
714     else if (!g_ascii_strcasecmp("Maximize", name))
715         return OB_FRAME_CONTEXT_MAXIMIZE;
716     else if (!g_ascii_strcasecmp("AllDesktops", name))
717         return OB_FRAME_CONTEXT_ALLDESKTOPS;
718     else if (!g_ascii_strcasecmp("Shade", name))
719         return OB_FRAME_CONTEXT_SHADE;
720     else if (!g_ascii_strcasecmp("Iconify", name))
721         return OB_FRAME_CONTEXT_ICONIFY;
722     else if (!g_ascii_strcasecmp("Icon", name))
723         return OB_FRAME_CONTEXT_ICON;
724     else if (!g_ascii_strcasecmp("Close", name))
725         return OB_FRAME_CONTEXT_CLOSE;
726     else if (!g_ascii_strcasecmp("MoveResize", name))
727         return OB_FRAME_CONTEXT_MOVE_RESIZE;
728     return OB_FRAME_CONTEXT_NONE;
729 }
730
731 ObFrameContext frame_context(ObClient *client, Window win)
732 {
733     ObFrame *self;
734
735     if (moveresize_in_progress)
736         return OB_FRAME_CONTEXT_MOVE_RESIZE;
737
738     if (win == RootWindow(ob_display, ob_screen))
739         return OB_FRAME_CONTEXT_DESKTOP;
740     if (client == NULL) return OB_FRAME_CONTEXT_NONE;
741     if (win == client->window) {
742         /* conceptually, this is the desktop, as far as users are
743            concerned */
744         if (client->type == OB_CLIENT_TYPE_DESKTOP)
745             return OB_FRAME_CONTEXT_DESKTOP;
746         return OB_FRAME_CONTEXT_CLIENT;
747     }
748
749     self = client->frame;
750     if (win == self->plate) {
751         /* conceptually, this is the desktop, as far as users are
752            concerned */
753         if (client->type == OB_CLIENT_TYPE_DESKTOP)
754             return OB_FRAME_CONTEXT_DESKTOP;
755         return OB_FRAME_CONTEXT_CLIENT;
756     }
757
758     if (win == self->window)   return OB_FRAME_CONTEXT_FRAME;
759     if (win == self->title)    return OB_FRAME_CONTEXT_TITLEBAR;
760     if (win == self->label)    return OB_FRAME_CONTEXT_TITLEBAR;
761     if (win == self->handle)   return OB_FRAME_CONTEXT_HANDLE;
762     if (win == self->lgrip)    return OB_FRAME_CONTEXT_BLCORNER;
763     if (win == self->rgrip)    return OB_FRAME_CONTEXT_BRCORNER;
764     if (win == self->tlresize) return OB_FRAME_CONTEXT_TLCORNER;
765     if (win == self->trresize) return OB_FRAME_CONTEXT_TRCORNER;
766     if (win == self->max)      return OB_FRAME_CONTEXT_MAXIMIZE;
767     if (win == self->iconify)  return OB_FRAME_CONTEXT_ICONIFY;
768     if (win == self->close)    return OB_FRAME_CONTEXT_CLOSE;
769     if (win == self->icon)     return OB_FRAME_CONTEXT_ICON;
770     if (win == self->desk)     return OB_FRAME_CONTEXT_ALLDESKTOPS;
771     if (win == self->shade)    return OB_FRAME_CONTEXT_SHADE;
772
773     return OB_FRAME_CONTEXT_NONE;
774 }
775
776 void frame_client_gravity(ObFrame *self, gint *x, gint *y)
777 {
778     /* horizontal */
779     switch (self->client->gravity) {
780     default:
781     case NorthWestGravity:
782     case SouthWestGravity:
783     case WestGravity:
784         break;
785
786     case NorthGravity:
787     case SouthGravity:
788     case CenterGravity:
789         *x -= (self->size.left + self->size.right) / 2;
790         break;
791
792     case NorthEastGravity:
793     case SouthEastGravity:
794     case EastGravity:
795         *x -= self->size.left + self->size.right;
796         break;
797
798     case ForgetGravity:
799     case StaticGravity:
800         *x -= self->size.left;
801         break;
802     }
803
804     /* vertical */
805     switch (self->client->gravity) {
806     default:
807     case NorthWestGravity:
808     case NorthEastGravity:
809     case NorthGravity:
810         break;
811
812     case CenterGravity:
813     case EastGravity:
814     case WestGravity:
815         *y -= (self->size.top + self->size.bottom) / 2;
816         break;
817
818     case SouthWestGravity:
819     case SouthEastGravity:
820     case SouthGravity:
821         *y -= self->size.top + self->size.bottom;
822         break;
823
824     case ForgetGravity:
825     case StaticGravity:
826         *y -= self->size.top;
827         break;
828     }
829 }
830
831 void frame_frame_gravity(ObFrame *self, gint *x, gint *y)
832 {
833     /* horizontal */
834     switch (self->client->gravity) {
835     default:
836     case NorthWestGravity:
837     case WestGravity:
838     case SouthWestGravity:
839         break;
840     case NorthGravity:
841     case CenterGravity:
842     case SouthGravity:
843         *x += (self->size.left + self->size.right) / 2;
844         break;
845     case NorthEastGravity:
846     case EastGravity:
847     case SouthEastGravity:
848         *x += self->size.left + self->size.right;
849         break;
850     case StaticGravity:
851     case ForgetGravity:
852         *x += self->size.left;
853         break;
854     }
855
856     /* vertical */
857     switch (self->client->gravity) {
858     default:
859     case NorthWestGravity:
860     case NorthGravity:
861     case NorthEastGravity:
862         break;
863     case WestGravity:
864     case CenterGravity:
865     case EastGravity:
866         *y += (self->size.top + self->size.bottom) / 2;
867         break;
868     case SouthWestGravity:
869     case SouthGravity:
870     case SouthEastGravity:
871         *y += self->size.top + self->size.bottom;
872         break;
873     case StaticGravity:
874     case ForgetGravity:
875         *y += self->size.top;
876         break;
877     }
878 }
879
880 static void flash_done(gpointer data)
881 {
882     ObFrame *self = data;
883
884     if (self->focused != self->flash_on)
885         frame_adjust_focus(self, self->focused);
886 }
887
888 static gboolean flash_timeout(gpointer data)
889 {
890     ObFrame *self = data;
891     GTimeVal now;
892
893     g_get_current_time(&now);
894     if (now.tv_sec > self->flash_end.tv_sec ||
895         (now.tv_sec == self->flash_end.tv_sec &&
896          now.tv_usec >= self->flash_end.tv_usec))
897         self->flashing = FALSE;
898
899     if (!self->flashing)
900         return FALSE; /* we are done */
901
902     self->flash_on = !self->flash_on;
903     if (!self->focused) {
904         frame_adjust_focus(self, self->flash_on);
905         self->focused = FALSE;
906     }
907
908     return TRUE; /* go again */
909 }
910
911 void frame_flash_start(ObFrame *self)
912 {
913     self->flash_on = self->focused;
914
915     if (!self->flashing)
916         ob_main_loop_timeout_add(ob_main_loop,
917                                  G_USEC_PER_SEC * 0.6,
918                                  flash_timeout,
919                                  self,
920                                  flash_done);
921     g_get_current_time(&self->flash_end);
922     g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
923     
924     self->flashing = TRUE;
925 }
926
927 void frame_flash_stop(ObFrame *self)
928 {
929     self->flashing = FALSE;
930 }