add a config option hideDisabled in the theme section that hides disabled buttons...
[mikachu/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
421             frame_adjust_shape(self);
422         }
423
424         if (!STRUT_EQUAL(self->size, oldsize)) {
425             guint32 vals[4];
426             vals[0] = self->size.left;
427             vals[1] = self->size.right;
428             vals[2] = self->size.top;
429             vals[3] = self->size.bottom;
430             PROP_SETA32(self->client->window, kde_net_wm_frame_strut,
431                         cardinal, vals, 4);
432         }
433
434         /* if this occurs while we are focus cycling, the indicator needs to
435            match the changes */
436         if (focus_cycle_target == self->client)
437             focus_cycle_draw_indicator();
438     }
439 }
440
441 void frame_adjust_state(ObFrame *self)
442 {
443     framerender_frame(self);
444 }
445
446 void frame_adjust_focus(ObFrame *self, gboolean hilite)
447 {
448     self->focused = hilite;
449     framerender_frame(self);
450 }
451
452 void frame_adjust_title(ObFrame *self)
453 {
454     framerender_frame(self);
455 }
456
457 void frame_adjust_icon(ObFrame *self)
458 {
459     framerender_frame(self);
460 }
461
462 void frame_grab_client(ObFrame *self, ObClient *client)
463 {
464     self->client = client;
465
466     /* reparent the client to the frame */
467     XReparentWindow(ob_display, client->window, self->plate, 0, 0);
468     /*
469       When reparenting the client window, it is usually not mapped yet, since
470       this occurs from a MapRequest. However, in the case where Openbox is
471       starting up, the window is already mapped, so we'll see unmap events for
472       it. There are 2 unmap events generated that we see, one with the 'event'
473       member set the root window, and one set to the client, but both get
474       handled and need to be ignored.
475     */
476     if (ob_state() == OB_STATE_STARTING)
477         client->ignore_unmaps += 2;
478
479     /* select the event mask on the client's parent (to receive config/map
480        req's) the ButtonPress is to catch clicks on the client border */
481     XSelectInput(ob_display, self->plate, PLATE_EVENTMASK);
482
483     /* map the client so it maps when the frame does */
484     XMapWindow(ob_display, client->window);
485
486     frame_adjust_area(self, TRUE, TRUE, FALSE);
487
488     /* set all the windows for the frame in the window_map */
489     g_hash_table_insert(window_map, &self->window, client);
490     g_hash_table_insert(window_map, &self->plate, client);
491     g_hash_table_insert(window_map, &self->title, client);
492     g_hash_table_insert(window_map, &self->label, client);
493     g_hash_table_insert(window_map, &self->max, client);
494     g_hash_table_insert(window_map, &self->close, client);
495     g_hash_table_insert(window_map, &self->desk, client);
496     g_hash_table_insert(window_map, &self->shade, client);
497     g_hash_table_insert(window_map, &self->icon, client);
498     g_hash_table_insert(window_map, &self->iconify, client);
499     g_hash_table_insert(window_map, &self->handle, client);
500     g_hash_table_insert(window_map, &self->lgrip, client);
501     g_hash_table_insert(window_map, &self->rgrip, client);
502     g_hash_table_insert(window_map, &self->tlresize, client);
503     g_hash_table_insert(window_map, &self->trresize, client);
504 }
505
506 void frame_release_client(ObFrame *self, ObClient *client)
507 {
508     XEvent ev;
509     gboolean reparent = TRUE;
510
511     g_assert(self->client == client);
512
513     /* check if the app has already reparented its window away */
514     while (XCheckTypedWindowEvent(ob_display, client->window,
515                                   ReparentNotify, &ev))
516     {
517         /* This check makes sure we don't catch our own reparent action to
518            our frame window. This doesn't count as the app reparenting itself
519            away of course.
520
521            Reparent events that are generated by us are just discarded here.
522            They are of no consequence to us anyhow.
523         */
524         if (ev.xreparent.parent != self->plate) {
525             reparent = FALSE;
526             XPutBackEvent(ob_display, &ev);
527             break;
528         }
529     }
530
531     if (reparent) {
532         /* according to the ICCCM - if the client doesn't reparent itself,
533            then we will reparent the window to root for them */
534         XReparentWindow(ob_display, client->window,
535                         RootWindow(ob_display, ob_screen),
536                         client->area.x,
537                         client->area.y);
538     }
539
540     /* remove all the windows for the frame from the window_map */
541     g_hash_table_remove(window_map, &self->window);
542     g_hash_table_remove(window_map, &self->plate);
543     g_hash_table_remove(window_map, &self->title);
544     g_hash_table_remove(window_map, &self->label);
545     g_hash_table_remove(window_map, &self->max);
546     g_hash_table_remove(window_map, &self->close);
547     g_hash_table_remove(window_map, &self->desk);
548     g_hash_table_remove(window_map, &self->shade);
549     g_hash_table_remove(window_map, &self->icon);
550     g_hash_table_remove(window_map, &self->iconify);
551     g_hash_table_remove(window_map, &self->handle);
552     g_hash_table_remove(window_map, &self->lgrip);
553     g_hash_table_remove(window_map, &self->rgrip);
554     g_hash_table_remove(window_map, &self->tlresize);
555     g_hash_table_remove(window_map, &self->trresize);
556
557     ob_main_loop_timeout_remove_data(ob_main_loop, flash_timeout, self);
558
559     frame_free(self);
560 }
561
562 static void layout_title(ObFrame *self)
563 {
564     gchar *lc;
565     gint x;
566     gboolean n, d, i, l, m, c, s;
567
568     n = d = i = l = m = c = s = FALSE;
569
570     /* figure out whats being shown, and the width of the label */
571     self->label_width = self->width - (ob_rr_theme->padding + 1) * 2;
572     for (lc = config_title_layout; *lc != '\0'; ++lc) {
573         switch (*lc) {
574         case 'N':
575             if (n) { *lc = ' '; break; } /* rm duplicates */
576             n = TRUE;
577             self->label_width -= (ob_rr_theme->button_size + 2 +
578                                   ob_rr_theme->padding + 1);
579             break;
580         case 'D':
581             if (d) { *lc = ' '; break; }
582             if (!(self->decorations & OB_FRAME_DECOR_ALLDESKTOPS) && config_theme_hidedisabled)
583                 break;
584             d = TRUE;
585             self->label_width -= (ob_rr_theme->button_size +
586                                   ob_rr_theme->padding + 1);
587             break;
588         case 'S':
589             if (s) { *lc = ' '; break; }
590             if (!(self->decorations & OB_FRAME_DECOR_SHADE) && config_theme_hidedisabled)
591                 break;
592             s = TRUE;
593             self->label_width -= (ob_rr_theme->button_size +
594                                   ob_rr_theme->padding + 1);
595             break;
596         case 'I':
597             if (i) { *lc = ' '; break; }
598             if (!(self->decorations & OB_FRAME_DECOR_ICONIFY) && config_theme_hidedisabled)
599                 break;
600             i = TRUE;
601             self->label_width -= (ob_rr_theme->button_size +
602                                   ob_rr_theme->padding + 1);
603             break;
604         case 'L':
605             if (l) { *lc = ' '; break; }
606             l = TRUE;
607             break;
608         case 'M':
609             if (m) { *lc = ' '; break; }
610             if (!(self->decorations & OB_FRAME_DECOR_MAXIMIZE) && config_theme_hidedisabled)
611                 break;
612             m = TRUE;
613             self->label_width -= (ob_rr_theme->button_size +
614                                   ob_rr_theme->padding + 1);
615             break;
616         case 'C':
617             if (c) { *lc = ' '; break; }
618             if (!(self->decorations & OB_FRAME_DECOR_CLOSE) && config_theme_hidedisabled)
619                 break;
620             c = TRUE;
621             self->label_width -= (ob_rr_theme->button_size +
622                                   ob_rr_theme->padding + 1);
623             break;
624         }
625     }
626     if (self->label_width < 1) self->label_width = 1;
627
628     XResizeWindow(ob_display, self->label, self->label_width,
629                   ob_rr_theme->label_height);
630   
631     if (!n) XUnmapWindow(ob_display, self->icon);
632     if (!d) XUnmapWindow(ob_display, self->desk);
633     if (!s) XUnmapWindow(ob_display, self->shade);
634     if (!i) XUnmapWindow(ob_display, self->iconify);
635     if (!l) XUnmapWindow(ob_display, self->label);
636     if (!m) XUnmapWindow(ob_display, self->max);
637     if (!c) XUnmapWindow(ob_display, self->close);
638
639     x = ob_rr_theme->padding + 1;
640     for (lc = config_title_layout; *lc != '\0'; ++lc) {
641         switch (*lc) {
642         case 'N':
643             if (!n) break;
644             self->icon_x = x;
645             XMapWindow(ob_display, self->icon);
646             XMoveWindow(ob_display, self->icon, x, ob_rr_theme->padding);
647             x += ob_rr_theme->button_size + 2 + ob_rr_theme->padding + 1;
648             break;
649         case 'D':
650             if (!d) break;
651             self->desk_x = x;
652             XMapWindow(ob_display, self->desk);
653             XMoveWindow(ob_display, self->desk, x, ob_rr_theme->padding + 1);
654             x += ob_rr_theme->button_size + ob_rr_theme->padding + 1;
655             break;
656         case 'S':
657             if (!s) break;
658             self->shade_x = x;
659             XMapWindow(ob_display, self->shade);
660             XMoveWindow(ob_display, self->shade, x, ob_rr_theme->padding + 1);
661             x += ob_rr_theme->button_size + ob_rr_theme->padding + 1;
662             break;
663         case 'I':
664             if (!i) break;
665             self->iconify_x = x;
666             XMapWindow(ob_display, self->iconify);
667             XMoveWindow(ob_display,self->iconify, x, ob_rr_theme->padding + 1);
668             x += ob_rr_theme->button_size + ob_rr_theme->padding + 1;
669             break;
670         case 'L':
671             if (!l) break;
672             self->label_x = x;
673             XMapWindow(ob_display, self->label);
674             XMoveWindow(ob_display, self->label, x, ob_rr_theme->padding);
675             x += self->label_width + ob_rr_theme->padding + 1;
676             break;
677         case 'M':
678             if (!m) break;
679             self->max_x = x;
680             XMapWindow(ob_display, self->max);
681             XMoveWindow(ob_display, self->max, x, ob_rr_theme->padding + 1);
682             x += ob_rr_theme->button_size + ob_rr_theme->padding + 1;
683             break;
684         case 'C':
685             if (!c) break;
686             self->close_x = x;
687             XMapWindow(ob_display, self->close);
688             XMoveWindow(ob_display, self->close, x, ob_rr_theme->padding + 1);
689             x += ob_rr_theme->button_size + ob_rr_theme->padding + 1;
690             break;
691         }
692     }
693 }
694
695 ObFrameContext frame_context_from_string(const gchar *name)
696 {
697     if (!g_ascii_strcasecmp("Desktop", name))
698         return OB_FRAME_CONTEXT_DESKTOP;
699     else if (!g_ascii_strcasecmp("Client", name))
700         return OB_FRAME_CONTEXT_CLIENT;
701     else if (!g_ascii_strcasecmp("Titlebar", name))
702         return OB_FRAME_CONTEXT_TITLEBAR;
703     else if (!g_ascii_strcasecmp("Handle", name))
704         return OB_FRAME_CONTEXT_HANDLE;
705     else if (!g_ascii_strcasecmp("Frame", name))
706         return OB_FRAME_CONTEXT_FRAME;
707     else if (!g_ascii_strcasecmp("TLCorner", name))
708         return OB_FRAME_CONTEXT_TLCORNER;
709     else if (!g_ascii_strcasecmp("TRCorner", name))
710         return OB_FRAME_CONTEXT_TRCORNER;
711     else if (!g_ascii_strcasecmp("BLCorner", name))
712         return OB_FRAME_CONTEXT_BLCORNER;
713     else if (!g_ascii_strcasecmp("BRCorner", name))
714         return OB_FRAME_CONTEXT_BRCORNER;
715     else if (!g_ascii_strcasecmp("Maximize", name))
716         return OB_FRAME_CONTEXT_MAXIMIZE;
717     else if (!g_ascii_strcasecmp("AllDesktops", name))
718         return OB_FRAME_CONTEXT_ALLDESKTOPS;
719     else if (!g_ascii_strcasecmp("Shade", name))
720         return OB_FRAME_CONTEXT_SHADE;
721     else if (!g_ascii_strcasecmp("Iconify", name))
722         return OB_FRAME_CONTEXT_ICONIFY;
723     else if (!g_ascii_strcasecmp("Icon", name))
724         return OB_FRAME_CONTEXT_ICON;
725     else if (!g_ascii_strcasecmp("Close", name))
726         return OB_FRAME_CONTEXT_CLOSE;
727     else if (!g_ascii_strcasecmp("MoveResize", name))
728         return OB_FRAME_CONTEXT_MOVE_RESIZE;
729     return OB_FRAME_CONTEXT_NONE;
730 }
731
732 ObFrameContext frame_context(ObClient *client, Window win)
733 {
734     ObFrame *self;
735
736     if (moveresize_in_progress)
737         return OB_FRAME_CONTEXT_MOVE_RESIZE;
738
739     if (win == RootWindow(ob_display, ob_screen))
740         return OB_FRAME_CONTEXT_DESKTOP;
741     if (client == NULL) return OB_FRAME_CONTEXT_NONE;
742     if (win == client->window) {
743         /* conceptually, this is the desktop, as far as users are
744            concerned */
745         if (client->type == OB_CLIENT_TYPE_DESKTOP)
746             return OB_FRAME_CONTEXT_DESKTOP;
747         return OB_FRAME_CONTEXT_CLIENT;
748     }
749
750     self = client->frame;
751     if (win == self->plate) {
752         /* conceptually, this is the desktop, as far as users are
753            concerned */
754         if (client->type == OB_CLIENT_TYPE_DESKTOP)
755             return OB_FRAME_CONTEXT_DESKTOP;
756         return OB_FRAME_CONTEXT_CLIENT;
757     }
758
759     if (win == self->window)   return OB_FRAME_CONTEXT_FRAME;
760     if (win == self->title)    return OB_FRAME_CONTEXT_TITLEBAR;
761     if (win == self->label)    return OB_FRAME_CONTEXT_TITLEBAR;
762     if (win == self->handle)   return OB_FRAME_CONTEXT_HANDLE;
763     if (win == self->lgrip)    return OB_FRAME_CONTEXT_BLCORNER;
764     if (win == self->rgrip)    return OB_FRAME_CONTEXT_BRCORNER;
765     if (win == self->tlresize) return OB_FRAME_CONTEXT_TLCORNER;
766     if (win == self->trresize) return OB_FRAME_CONTEXT_TRCORNER;
767     if (win == self->max)      return OB_FRAME_CONTEXT_MAXIMIZE;
768     if (win == self->iconify)  return OB_FRAME_CONTEXT_ICONIFY;
769     if (win == self->close)    return OB_FRAME_CONTEXT_CLOSE;
770     if (win == self->icon)     return OB_FRAME_CONTEXT_ICON;
771     if (win == self->desk)     return OB_FRAME_CONTEXT_ALLDESKTOPS;
772     if (win == self->shade)    return OB_FRAME_CONTEXT_SHADE;
773
774     return OB_FRAME_CONTEXT_NONE;
775 }
776
777 void frame_client_gravity(ObFrame *self, gint *x, gint *y)
778 {
779     /* horizontal */
780     switch (self->client->gravity) {
781     default:
782     case NorthWestGravity:
783     case SouthWestGravity:
784     case WestGravity:
785         break;
786
787     case NorthGravity:
788     case SouthGravity:
789     case CenterGravity:
790         *x -= (self->size.left + self->size.right) / 2;
791         break;
792
793     case NorthEastGravity:
794     case SouthEastGravity:
795     case EastGravity:
796         *x -= self->size.left + self->size.right;
797         break;
798
799     case ForgetGravity:
800     case StaticGravity:
801         *x -= self->size.left;
802         break;
803     }
804
805     /* vertical */
806     switch (self->client->gravity) {
807     default:
808     case NorthWestGravity:
809     case NorthEastGravity:
810     case NorthGravity:
811         break;
812
813     case CenterGravity:
814     case EastGravity:
815     case WestGravity:
816         *y -= (self->size.top + self->size.bottom) / 2;
817         break;
818
819     case SouthWestGravity:
820     case SouthEastGravity:
821     case SouthGravity:
822         *y -= self->size.top + self->size.bottom;
823         break;
824
825     case ForgetGravity:
826     case StaticGravity:
827         *y -= self->size.top;
828         break;
829     }
830 }
831
832 void frame_frame_gravity(ObFrame *self, gint *x, gint *y)
833 {
834     /* horizontal */
835     switch (self->client->gravity) {
836     default:
837     case NorthWestGravity:
838     case WestGravity:
839     case SouthWestGravity:
840         break;
841     case NorthGravity:
842     case CenterGravity:
843     case SouthGravity:
844         *x += (self->size.left + self->size.right) / 2;
845         break;
846     case NorthEastGravity:
847     case EastGravity:
848     case SouthEastGravity:
849         *x += self->size.left + self->size.right;
850         break;
851     case StaticGravity:
852     case ForgetGravity:
853         *x += self->size.left;
854         break;
855     }
856
857     /* vertical */
858     switch (self->client->gravity) {
859     default:
860     case NorthWestGravity:
861     case NorthGravity:
862     case NorthEastGravity:
863         break;
864     case WestGravity:
865     case CenterGravity:
866     case EastGravity:
867         *y += (self->size.top + self->size.bottom) / 2;
868         break;
869     case SouthWestGravity:
870     case SouthGravity:
871     case SouthEastGravity:
872         *y += self->size.top + self->size.bottom;
873         break;
874     case StaticGravity:
875     case ForgetGravity:
876         *y += self->size.top;
877         break;
878     }
879 }
880
881 static void flash_done(gpointer data)
882 {
883     ObFrame *self = data;
884
885     if (self->focused != self->flash_on)
886         frame_adjust_focus(self, self->focused);
887 }
888
889 static gboolean flash_timeout(gpointer data)
890 {
891     ObFrame *self = data;
892     GTimeVal now;
893
894     g_get_current_time(&now);
895     if (now.tv_sec > self->flash_end.tv_sec ||
896         (now.tv_sec == self->flash_end.tv_sec &&
897          now.tv_usec >= self->flash_end.tv_usec))
898         self->flashing = FALSE;
899
900     if (!self->flashing)
901         return FALSE; /* we are done */
902
903     self->flash_on = !self->flash_on;
904     if (!self->focused) {
905         frame_adjust_focus(self, self->flash_on);
906         self->focused = FALSE;
907     }
908
909     return TRUE; /* go again */
910 }
911
912 void frame_flash_start(ObFrame *self)
913 {
914     self->flash_on = self->focused;
915
916     if (!self->flashing)
917         ob_main_loop_timeout_add(ob_main_loop,
918                                  G_USEC_PER_SEC * 0.6,
919                                  flash_timeout,
920                                  self,
921                                  flash_done);
922     g_get_current_time(&self->flash_end);
923     g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
924     
925     self->flashing = TRUE;
926 }
927
928 void frame_flash_stop(ObFrame *self)
929 {
930     self->flashing = FALSE;
931 }