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