oops, i left in the really long animate time heh
[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) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "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 "screen.h"
31 #include "render/theme.h"
32
33 #define PLATE_EVENTMASK (SubstructureRedirectMask | FocusChangeMask)
34 #define FRAME_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
35                          ButtonPressMask | ButtonReleaseMask)
36 #define ELEMENT_EVENTMASK (ButtonPressMask | ButtonReleaseMask | \
37                            ButtonMotionMask | \
38                            EnterWindowMask | LeaveWindowMask)
39 /* The inner window does not need enter/leave events.
40    If it does get them, then it needs its own context for enter events
41    because sloppy focus will focus the window when you enter the inner window
42    from the frame. */
43 #define INNER_EVENTMASK (ButtonPressMask)
44
45 #define FRAME_ANIMATE_ICONIFY_TIME 150000 /* .15 seconds */
46 #define FRAME_ANIMATE_ICONIFY_STEP_TIME (G_USEC_PER_SEC / 30) /* 30 Hz */
47
48 #define FRAME_HANDLE_Y(f) (f->innersize.top + f->client->area.height + \
49                            f->cbwidth_y)
50
51 static void layout_title(ObFrame *self);
52 static void flash_done(gpointer data);
53 static gboolean flash_timeout(gpointer data);
54
55 static void set_theme_statics(ObFrame *self);
56 static void free_theme_statics(ObFrame *self);
57 static gboolean frame_animate_iconify(gpointer self);
58
59 static Window createWindow(Window parent, Visual *visual,
60                            gulong mask, XSetWindowAttributes *attrib)
61 {
62     return XCreateWindow(ob_display, parent, 0, 0, 1, 1, 0,
63                          (visual ? 32 : RrDepth(ob_rr_inst)), InputOutput,
64                          (visual ? visual : RrVisual(ob_rr_inst)),
65                          mask, attrib);
66                        
67 }
68
69 static Visual *check_32bit_client(ObClient *c)
70 {
71     XWindowAttributes wattrib;
72     Status ret;
73
74     ret = XGetWindowAttributes(ob_display, c->window, &wattrib);
75     g_assert(ret != BadDrawable);
76     g_assert(ret != BadWindow);
77
78     if (wattrib.depth == 32)
79         return wattrib.visual;
80     return NULL;
81 }
82
83 ObFrame *frame_new(ObClient *client)
84 {
85     XSetWindowAttributes attrib;
86     gulong mask;
87     ObFrame *self;
88     Visual *visual;
89
90     self = g_new0(ObFrame, 1);
91
92     visual = check_32bit_client(client);
93
94     /* create the non-visible decor windows */
95
96     mask = CWEventMask;
97     if (visual) {
98         /* client has a 32-bit visual */
99         mask |= CWColormap | CWBackPixel | CWBorderPixel;
100         /* create a colormap with the visual */
101         self->colormap = attrib.colormap =
102             XCreateColormap(ob_display,
103                             RootWindow(ob_display, ob_screen),
104                             visual, AllocNone);
105         attrib.background_pixel = BlackPixel(ob_display, 0);
106         attrib.border_pixel = BlackPixel(ob_display, 0);
107     }
108     attrib.event_mask = FRAME_EVENTMASK;
109     self->window = createWindow(RootWindow(ob_display, ob_screen), visual,
110                                 mask, &attrib);
111
112     attrib.event_mask = INNER_EVENTMASK;
113     self->inner = createWindow(self->window, visual, mask, &attrib);
114
115     mask &= ~CWEventMask;
116     self->plate = createWindow(self->inner, visual, mask, &attrib);
117
118     /* create the visible decor windows */
119
120     mask = CWEventMask;
121     if (visual) {
122         /* client has a 32-bit visual */
123         mask |= CWColormap | CWBackPixel | CWBorderPixel;
124         attrib.colormap = RrColormap(ob_rr_inst);
125     }
126     attrib.event_mask = ELEMENT_EVENTMASK;
127     self->title = createWindow(self->window, NULL, mask, &attrib);
128
129     mask |= CWCursor;
130     attrib.cursor = ob_cursor(OB_CURSOR_NORTHWEST);
131     self->tltresize = createWindow(self->title, NULL, mask, &attrib);
132     self->tllresize = createWindow(self->title, NULL, mask, &attrib);
133     attrib.cursor = ob_cursor(OB_CURSOR_NORTHEAST);
134     self->trtresize = createWindow(self->title, NULL, mask, &attrib);
135     self->trrresize = createWindow(self->title, NULL, mask, &attrib);
136
137     mask &= ~CWCursor;
138     self->label = createWindow(self->title, NULL, mask, &attrib);
139     self->max = createWindow(self->title, NULL, mask, &attrib);
140     self->close = createWindow(self->title, NULL, mask, &attrib);
141     self->desk = createWindow(self->title, NULL, mask, &attrib);
142     self->shade = createWindow(self->title, NULL, mask, &attrib);
143     self->icon = createWindow(self->title, NULL, mask, &attrib);
144     self->iconify = createWindow(self->title, NULL, mask, &attrib);
145     self->handle = createWindow(self->window, NULL, mask, &attrib);
146
147     mask |= CWCursor;
148     attrib.cursor = ob_cursor(OB_CURSOR_SOUTHWEST);
149     self->lgrip = createWindow(self->handle, NULL, mask, &attrib);
150     attrib.cursor = ob_cursor(OB_CURSOR_SOUTHEAST);
151     self->rgrip = createWindow(self->handle, NULL, mask, &attrib); 
152
153     self->focused = FALSE;
154
155     /* the other stuff is shown based on decor settings */
156     XMapWindow(ob_display, self->plate);
157     XMapWindow(ob_display, self->inner);
158     XMapWindow(ob_display, self->lgrip);
159     XMapWindow(ob_display, self->rgrip);
160     XMapWindow(ob_display, self->label);
161
162     self->max_press = self->close_press = self->desk_press = 
163         self->iconify_press = self->shade_press = FALSE;
164     self->max_hover = self->close_hover = self->desk_hover = 
165         self->iconify_hover = self->shade_hover = FALSE;
166
167     set_theme_statics(self);
168
169     return (ObFrame*)self;
170 }
171
172 static void set_theme_statics(ObFrame *self)
173 {
174     /* set colors/appearance/sizes for stuff that doesn't change */
175     XSetWindowBorder(ob_display, self->window,
176                      RrColorPixel(ob_rr_theme->frame_b_color));
177     XSetWindowBorder(ob_display, self->inner,
178                      RrColorPixel(ob_rr_theme->frame_b_color));
179     XSetWindowBorder(ob_display, self->title,
180                      RrColorPixel(ob_rr_theme->frame_b_color));
181     XSetWindowBorder(ob_display, self->handle,
182                      RrColorPixel(ob_rr_theme->frame_b_color));
183     XSetWindowBorder(ob_display, self->rgrip,
184                      RrColorPixel(ob_rr_theme->frame_b_color));
185     XSetWindowBorder(ob_display, self->lgrip,
186                      RrColorPixel(ob_rr_theme->frame_b_color));
187
188     XResizeWindow(ob_display, self->max,
189                   ob_rr_theme->button_size, ob_rr_theme->button_size);
190     XResizeWindow(ob_display, self->iconify,
191                   ob_rr_theme->button_size, ob_rr_theme->button_size);
192     XResizeWindow(ob_display, self->icon,
193                   ob_rr_theme->button_size + 2, ob_rr_theme->button_size + 2);
194     XResizeWindow(ob_display, self->close,
195                   ob_rr_theme->button_size, ob_rr_theme->button_size);
196     XResizeWindow(ob_display, self->desk,
197                   ob_rr_theme->button_size, ob_rr_theme->button_size);
198     XResizeWindow(ob_display, self->shade,
199                   ob_rr_theme->button_size, ob_rr_theme->button_size);
200     if (ob_rr_theme->handle_height > 0) {
201         XResizeWindow(ob_display, self->lgrip,
202                       ob_rr_theme->grip_width, ob_rr_theme->handle_height);
203         XResizeWindow(ob_display, self->rgrip,
204                       ob_rr_theme->grip_width, ob_rr_theme->handle_height);
205     }
206     XResizeWindow(ob_display, self->tltresize,
207                   ob_rr_theme->grip_width, ob_rr_theme->paddingy + 1);
208     XResizeWindow(ob_display, self->trtresize,
209                   ob_rr_theme->grip_width, ob_rr_theme->paddingy + 1);
210     XResizeWindow(ob_display, self->tllresize,
211                   ob_rr_theme->paddingx + 1, ob_rr_theme->title_height);
212     XResizeWindow(ob_display, self->trrresize,
213                   ob_rr_theme->paddingx + 1, ob_rr_theme->title_height);
214
215     /* set up the dynamic appearances */
216     self->a_unfocused_title = RrAppearanceCopy(ob_rr_theme->a_unfocused_title);
217     self->a_focused_title = RrAppearanceCopy(ob_rr_theme->a_focused_title);
218     self->a_unfocused_label = RrAppearanceCopy(ob_rr_theme->a_unfocused_label);
219     self->a_focused_label = RrAppearanceCopy(ob_rr_theme->a_focused_label);
220     self->a_unfocused_handle =
221         RrAppearanceCopy(ob_rr_theme->a_unfocused_handle);
222     self->a_focused_handle = RrAppearanceCopy(ob_rr_theme->a_focused_handle);
223     self->a_icon = RrAppearanceCopy(ob_rr_theme->a_icon);
224 }
225
226 static void free_theme_statics(ObFrame *self)
227 {
228     RrAppearanceFree(self->a_unfocused_title); 
229     RrAppearanceFree(self->a_focused_title);
230     RrAppearanceFree(self->a_unfocused_label);
231     RrAppearanceFree(self->a_focused_label);
232     RrAppearanceFree(self->a_unfocused_handle);
233     RrAppearanceFree(self->a_focused_handle);
234     RrAppearanceFree(self->a_icon);
235 }
236
237 static void frame_free(ObFrame *self)
238 {
239     free_theme_statics(self);
240
241     XDestroyWindow(ob_display, self->window);
242     if (self->colormap)
243         XFreeColormap(ob_display, self->colormap);
244
245     g_free(self);
246 }
247
248 void frame_show(ObFrame *self)
249 {
250     if (!self->visible) {
251         self->visible = TRUE;
252         XMapWindow(ob_display, self->client->window);
253         XMapWindow(ob_display, self->window);
254     }
255 }
256
257 void frame_hide(ObFrame *self)
258 {
259     if (self->visible) {
260         self->visible = FALSE;
261         self->client->ignore_unmaps += 1;
262         /* we unmap the client itself so that we can get MapRequest
263            events, and because the ICCCM tells us to! */
264         XUnmapWindow(ob_display, self->window);
265         XUnmapWindow(ob_display, self->client->window);
266     }
267 }
268
269 void frame_adjust_theme(ObFrame *self)
270 {
271     free_theme_statics(self);
272     set_theme_statics(self);
273 }
274
275 void frame_adjust_shape(ObFrame *self)
276 {
277 #ifdef SHAPE
278     gint num;
279     XRectangle xrect[2];
280
281     if (!self->client->shaped) {
282         /* clear the shape on the frame window */
283         XShapeCombineMask(ob_display, self->window, ShapeBounding,
284                           self->innersize.left,
285                           self->innersize.top,
286                           None, ShapeSet);
287     } else {
288         /* make the frame's shape match the clients */
289         XShapeCombineShape(ob_display, self->window, ShapeBounding,
290                            self->innersize.left,
291                            self->innersize.top,
292                            self->client->window,
293                            ShapeBounding, ShapeSet);
294
295         num = 0;
296         if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
297             xrect[0].x = -ob_rr_theme->fbwidth;
298             xrect[0].y = -ob_rr_theme->fbwidth;
299             xrect[0].width = self->width + self->rbwidth * 2;
300             xrect[0].height = ob_rr_theme->title_height +
301                 self->bwidth * 2;
302             ++num;
303         }
304
305         if (self->decorations & OB_FRAME_DECOR_HANDLE) {
306             xrect[1].x = -ob_rr_theme->fbwidth;
307             xrect[1].y = FRAME_HANDLE_Y(self);
308             xrect[1].width = self->width + self->rbwidth * 2;
309             xrect[1].height = ob_rr_theme->handle_height +
310                 self->bwidth * 2;
311             ++num;
312         }
313
314         XShapeCombineRectangles(ob_display, self->window,
315                                 ShapeBounding, 0, 0, xrect, num,
316                                 ShapeUnion, Unsorted);
317     }
318 #endif
319 }
320
321 void frame_adjust_area(ObFrame *self, gboolean moved,
322                        gboolean resized, gboolean fake)
323 {
324     Strut oldsize;
325
326     oldsize = self->size;
327
328     if (resized) {
329         self->decorations = self->client->decorations;
330         self->max_horz = self->client->max_horz;
331
332         if (self->decorations & OB_FRAME_DECOR_BORDER) {
333             self->bwidth = ob_rr_theme->fbwidth;
334             self->cbwidth_x = ob_rr_theme->cbwidthx;
335             self->cbwidth_y = ob_rr_theme->cbwidthy;
336         } else {
337             self->bwidth = self->cbwidth_x = self->cbwidth_y = 0;
338         }
339         self->rbwidth = self->bwidth;
340
341         if (self->max_horz)
342             self->bwidth = self->cbwidth_x = 0;
343
344         STRUT_SET(self->innersize,
345                   self->cbwidth_x,
346                   self->cbwidth_y,
347                   self->cbwidth_x,
348                   self->cbwidth_y);
349         self->width = self->client->area.width + self->cbwidth_x * 2 -
350             (self->max_horz ? self->rbwidth * 2 : 0);
351         self->width = MAX(self->width, 1); /* no lower than 1 */
352
353         /* set border widths */
354         if (!fake) {
355             XSetWindowBorderWidth(ob_display, self->window, self->bwidth);
356             XSetWindowBorderWidth(ob_display, self->inner, self->bwidth);
357             XSetWindowBorderWidth(ob_display, self->title,  self->rbwidth);
358             XSetWindowBorderWidth(ob_display, self->handle, self->rbwidth);
359             XSetWindowBorderWidth(ob_display, self->lgrip,  self->rbwidth);
360             XSetWindowBorderWidth(ob_display, self->rgrip,  self->rbwidth);
361         }
362
363         if (self->decorations & OB_FRAME_DECOR_TITLEBAR)
364             self->innersize.top += ob_rr_theme->title_height + self->rbwidth +
365                 (self->rbwidth - self->bwidth);
366         if (self->decorations & OB_FRAME_DECOR_HANDLE &&
367             ob_rr_theme->handle_height > 0)
368             self->innersize.bottom += ob_rr_theme->handle_height +
369                 self->rbwidth + (self->rbwidth - self->bwidth);
370   
371         /* they all default off, they're turned on in layout_title */
372         self->icon_x = -1;
373         self->desk_x = -1;
374         self->shade_x = -1;
375         self->iconify_x = -1;
376         self->label_x = -1;
377         self->max_x = -1;
378         self->close_x = -1;
379
380         /* position/size and map/unmap all the windows */
381
382         if (!fake) {
383             if (self->decorations & OB_FRAME_DECOR_TITLEBAR) {
384                 XMoveResizeWindow(ob_display, self->title,
385                                   -self->bwidth, -self->bwidth,
386                                   self->width, ob_rr_theme->title_height);
387                 XMapWindow(ob_display, self->title);
388
389                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
390                     XMoveWindow(ob_display, self->tltresize, 0, 0);
391                     XMoveWindow(ob_display, self->tllresize, 0, 0);
392                     XMoveWindow(ob_display, self->trtresize,
393                                 self->width - ob_rr_theme->grip_width, 0);
394                     XMoveWindow(ob_display, self->trrresize,
395                                 self->width - ob_rr_theme->paddingx - 1, 0);
396                     XMapWindow(ob_display, self->tltresize);
397                     XMapWindow(ob_display, self->tllresize);
398                     XMapWindow(ob_display, self->trtresize);
399                     XMapWindow(ob_display, self->trrresize);
400                 } else {
401                     XUnmapWindow(ob_display, self->tltresize);
402                     XUnmapWindow(ob_display, self->tllresize);
403                     XUnmapWindow(ob_display, self->trtresize);
404                     XUnmapWindow(ob_display, self->trrresize);
405                 }
406             } else
407                 XUnmapWindow(ob_display, self->title);
408         }
409
410         if (self->decorations & OB_FRAME_DECOR_TITLEBAR)
411             /* layout the title bar elements */
412             layout_title(self);
413
414         if (!fake) {
415             if (self->decorations & OB_FRAME_DECOR_HANDLE &&
416                 ob_rr_theme->handle_height > 0)
417             {
418                 XMoveResizeWindow(ob_display, self->handle,
419                                   -self->bwidth, FRAME_HANDLE_Y(self),
420                                   self->width, ob_rr_theme->handle_height);
421                 XMapWindow(ob_display, self->handle);
422
423                 if (self->decorations & OB_FRAME_DECOR_GRIPS) {
424                     XMoveWindow(ob_display, self->lgrip,
425                                 -self->rbwidth, -self->rbwidth);
426                     XMoveWindow(ob_display, self->rgrip,
427                                 -self->rbwidth + self->width -
428                                 ob_rr_theme->grip_width, -self->rbwidth);
429                     XMapWindow(ob_display, self->lgrip);
430                     XMapWindow(ob_display, self->rgrip);
431                 } else {
432                     XUnmapWindow(ob_display, self->lgrip);
433                     XUnmapWindow(ob_display, self->rgrip);
434                 }
435             } else
436                 XUnmapWindow(ob_display, self->handle);
437
438             /* move and resize the inner border window which contains the plate
439              */
440             XMoveResizeWindow(ob_display, self->inner,
441                               self->innersize.left - self->cbwidth_x -
442                               self->bwidth,
443                               self->innersize.top - self->cbwidth_y -
444                               self->bwidth,
445                               self->client->area.width +
446                               self->cbwidth_x * 2,
447                               self->client->area.height +
448                               self->cbwidth_y * 2);
449
450             /* move the plate */
451             XMoveWindow(ob_display, self->plate,
452                         self->cbwidth_x, self->cbwidth_y);
453
454             /* when the client has StaticGravity, it likes to move around. */
455             XMoveWindow(ob_display, self->client->window, 0, 0);
456         }
457
458         STRUT_SET(self->size,
459                   self->innersize.left + self->bwidth,
460                   self->innersize.top + self->bwidth,
461                   self->innersize.right + self->bwidth,
462                   self->innersize.bottom + self->bwidth);
463     }
464
465     /* shading can change without being moved or resized */
466     RECT_SET_SIZE(self->area,
467                   self->client->area.width +
468                   self->size.left + self->size.right,
469                   (self->client->shaded ?
470                    ob_rr_theme->title_height + self->rbwidth * 2:
471                    self->client->area.height +
472                    self->size.top + self->size.bottom));
473
474     if (moved || resized) {
475         /* find the new coordinates, done after setting the frame.size, for
476            frame_client_gravity. */
477         self->area.x = self->client->area.x;
478         self->area.y = self->client->area.y;
479         frame_client_gravity(self, &self->area.x, &self->area.y,
480                              self->client->area.width,
481                              self->client->area.height);
482     }
483
484     if (!fake) {
485         if (!frame_iconify_animating(self))
486             /* move and resize the top level frame.
487                shading can change without being moved or resized.
488                
489                but don't do this during an iconify animation. it will be
490                reflected afterwards.
491             */
492             XMoveResizeWindow(ob_display, self->window,
493                               self->area.x, self->area.y,
494                               self->area.width - self->bwidth * 2,
495                               self->area.height - self->bwidth * 2);
496
497         if (resized) {
498             framerender_frame(self);
499             frame_adjust_shape(self);
500         }
501
502         if (!STRUT_EQUAL(self->size, oldsize)) {
503             gulong vals[4];
504             vals[0] = self->size.left;
505             vals[1] = self->size.right;
506             vals[2] = self->size.top;
507             vals[3] = self->size.bottom;
508             PROP_SETA32(self->client->window, net_frame_extents,
509                         cardinal, vals, 4);
510         }
511
512         /* if this occurs while we are focus cycling, the indicator needs to
513            match the changes */
514         if (focus_cycle_target == self->client)
515             focus_cycle_draw_indicator();
516     }
517     if (resized && (self->decorations & OB_FRAME_DECOR_TITLEBAR))
518         XResizeWindow(ob_display, self->label, self->label_width,
519                       ob_rr_theme->label_height);
520 }
521
522 void frame_adjust_client_area(ObFrame *self)
523 {
524     /* resize the plate */
525     XResizeWindow(ob_display, self->plate,
526                   self->client->area.width, self->client->area.height);
527 }
528
529 void frame_adjust_state(ObFrame *self)
530 {
531     framerender_frame(self);
532 }
533
534 void frame_adjust_focus(ObFrame *self, gboolean hilite)
535 {
536     self->focused = hilite;
537     framerender_frame(self);
538     XFlush(ob_display);
539 }
540
541 void frame_adjust_title(ObFrame *self)
542 {
543     framerender_frame(self);
544 }
545
546 void frame_adjust_icon(ObFrame *self)
547 {
548     framerender_frame(self);
549 }
550
551 void frame_grab_client(ObFrame *self, ObClient *client)
552 {
553     self->client = client;
554
555     /* reparent the client to the frame */
556     XReparentWindow(ob_display, client->window, self->plate, 0, 0);
557     /*
558       When reparenting the client window, it is usually not mapped yet, since
559       this occurs from a MapRequest. However, in the case where Openbox is
560       starting up, the window is already mapped, so we'll see unmap events for
561       it. There are 2 unmap events generated that we see, one with the 'event'
562       member set the root window, and one set to the client, but both get
563       handled and need to be ignored.
564     */
565     if (ob_state() == OB_STATE_STARTING)
566         client->ignore_unmaps += 2;
567
568     /* select the event mask on the client's parent (to receive config/map
569        req's) the ButtonPress is to catch clicks on the client border */
570     XSelectInput(ob_display, self->plate, PLATE_EVENTMASK);
571
572     frame_adjust_area(self, TRUE, TRUE, FALSE);
573
574     /* map the client so it maps when the frame does */
575     XMapWindow(ob_display, client->window);
576
577     /* set all the windows for the frame in the window_map */
578     g_hash_table_insert(window_map, &self->window, client);
579     g_hash_table_insert(window_map, &self->plate, client);
580     g_hash_table_insert(window_map, &self->inner, client);
581     g_hash_table_insert(window_map, &self->title, client);
582     g_hash_table_insert(window_map, &self->label, client);
583     g_hash_table_insert(window_map, &self->max, client);
584     g_hash_table_insert(window_map, &self->close, client);
585     g_hash_table_insert(window_map, &self->desk, client);
586     g_hash_table_insert(window_map, &self->shade, client);
587     g_hash_table_insert(window_map, &self->icon, client);
588     g_hash_table_insert(window_map, &self->iconify, client);
589     g_hash_table_insert(window_map, &self->handle, client);
590     g_hash_table_insert(window_map, &self->lgrip, client);
591     g_hash_table_insert(window_map, &self->rgrip, client);
592     g_hash_table_insert(window_map, &self->tltresize, client);
593     g_hash_table_insert(window_map, &self->tllresize, client);
594     g_hash_table_insert(window_map, &self->trtresize, client);
595     g_hash_table_insert(window_map, &self->trrresize, client);
596 }
597
598 void frame_release_client(ObFrame *self, ObClient *client)
599 {
600     XEvent ev;
601     gboolean reparent = TRUE;
602
603     g_assert(self->client == client);
604
605     /* if there was any animation going on, kill it */
606     ob_main_loop_timeout_remove_data(ob_main_loop, frame_animate_iconify,
607                                      self, FALSE);
608
609     /* check if the app has already reparented its window away */
610     while (XCheckTypedWindowEvent(ob_display, client->window,
611                                   ReparentNotify, &ev))
612     {
613         /* This check makes sure we don't catch our own reparent action to
614            our frame window. This doesn't count as the app reparenting itself
615            away of course.
616
617            Reparent events that are generated by us are just discarded here.
618            They are of no consequence to us anyhow.
619         */
620         if (ev.xreparent.parent != self->plate) {
621             reparent = FALSE;
622             XPutBackEvent(ob_display, &ev);
623             break;
624         }
625     }
626
627     if (reparent) {
628         /* according to the ICCCM - if the client doesn't reparent itself,
629            then we will reparent the window to root for them */
630         XReparentWindow(ob_display, client->window,
631                         RootWindow(ob_display, ob_screen),
632                         client->area.x,
633                         client->area.y);
634     }
635
636     /* remove all the windows for the frame from the window_map */
637     g_hash_table_remove(window_map, &self->window);
638     g_hash_table_remove(window_map, &self->plate);
639     g_hash_table_remove(window_map, &self->inner);
640     g_hash_table_remove(window_map, &self->title);
641     g_hash_table_remove(window_map, &self->label);
642     g_hash_table_remove(window_map, &self->max);
643     g_hash_table_remove(window_map, &self->close);
644     g_hash_table_remove(window_map, &self->desk);
645     g_hash_table_remove(window_map, &self->shade);
646     g_hash_table_remove(window_map, &self->icon);
647     g_hash_table_remove(window_map, &self->iconify);
648     g_hash_table_remove(window_map, &self->handle);
649     g_hash_table_remove(window_map, &self->lgrip);
650     g_hash_table_remove(window_map, &self->rgrip);
651     g_hash_table_remove(window_map, &self->tltresize);
652     g_hash_table_remove(window_map, &self->tllresize);
653     g_hash_table_remove(window_map, &self->trtresize);
654     g_hash_table_remove(window_map, &self->trrresize);
655
656     ob_main_loop_timeout_remove_data(ob_main_loop, flash_timeout, self, TRUE);
657
658     frame_free(self);
659 }
660
661 static void layout_title(ObFrame *self)
662 {
663     gchar *lc;
664     gint x;
665     gboolean n, d, i, l, m, c, s;
666
667     n = d = i = l = m = c = s = FALSE;
668
669     /* figure out whats being shown, and the width of the label */
670     self->label_width = self->width - (ob_rr_theme->paddingx + 1) * 2;
671     for (lc = config_title_layout; *lc != '\0'; ++lc) {
672         switch (*lc) {
673         case 'N':
674             if (n) { *lc = ' '; break; } /* rm duplicates */
675             n = TRUE;
676             self->label_width -= (ob_rr_theme->button_size + 2 +
677                                   ob_rr_theme->paddingx + 1);
678             break;
679         case 'D':
680             if (d) { *lc = ' '; break; }
681             if (!(self->decorations & OB_FRAME_DECOR_ALLDESKTOPS)
682                 && config_theme_hidedisabled)
683                 break;
684             d = TRUE;
685             self->label_width -= (ob_rr_theme->button_size +
686                                   ob_rr_theme->paddingx + 1);
687             break;
688         case 'S':
689             if (s) { *lc = ' '; break; }
690             if (!(self->decorations & OB_FRAME_DECOR_SHADE)
691                 && config_theme_hidedisabled)
692                 break;
693             s = TRUE;
694             self->label_width -= (ob_rr_theme->button_size +
695                                   ob_rr_theme->paddingx + 1);
696             break;
697         case 'I':
698             if (i) { *lc = ' '; break; }
699             if (!(self->decorations & OB_FRAME_DECOR_ICONIFY)
700                 && config_theme_hidedisabled)
701                 break;
702             i = TRUE;
703             self->label_width -= (ob_rr_theme->button_size +
704                                   ob_rr_theme->paddingx + 1);
705             break;
706         case 'L':
707             if (l) { *lc = ' '; break; }
708             l = TRUE;
709             break;
710         case 'M':
711             if (m) { *lc = ' '; break; }
712             if (!(self->decorations & OB_FRAME_DECOR_MAXIMIZE)
713                 && config_theme_hidedisabled)
714                 break;
715             m = TRUE;
716             self->label_width -= (ob_rr_theme->button_size +
717                                   ob_rr_theme->paddingx + 1);
718             break;
719         case 'C':
720             if (c) { *lc = ' '; break; }
721             if (!(self->decorations & OB_FRAME_DECOR_CLOSE)
722                 && config_theme_hidedisabled)
723                 break;
724             c = TRUE;
725             self->label_width -= (ob_rr_theme->button_size +
726                                   ob_rr_theme->paddingx + 1);
727             break;
728         }
729     }
730     if (self->label_width < 1) self->label_width = 1;
731
732     if (!n) XUnmapWindow(ob_display, self->icon);
733     if (!d) XUnmapWindow(ob_display, self->desk);
734     if (!s) XUnmapWindow(ob_display, self->shade);
735     if (!i) XUnmapWindow(ob_display, self->iconify);
736     if (!l) XUnmapWindow(ob_display, self->label);
737     if (!m) XUnmapWindow(ob_display, self->max);
738     if (!c) XUnmapWindow(ob_display, self->close);
739
740     x = ob_rr_theme->paddingx + 1;
741     for (lc = config_title_layout; *lc != '\0'; ++lc) {
742         switch (*lc) {
743         case 'N':
744             if (!n) break;
745             self->icon_x = x;
746             XMapWindow(ob_display, self->icon);
747             XMoveWindow(ob_display, self->icon, x, ob_rr_theme->paddingy);
748             x += ob_rr_theme->button_size + 2 + ob_rr_theme->paddingx + 1;
749             break;
750         case 'D':
751             if (!d) break;
752             self->desk_x = x;
753             XMapWindow(ob_display, self->desk);
754             XMoveWindow(ob_display, self->desk, x, ob_rr_theme->paddingy + 1);
755             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
756             break;
757         case 'S':
758             if (!s) break;
759             self->shade_x = x;
760             XMapWindow(ob_display, self->shade);
761             XMoveWindow(ob_display, self->shade, x, ob_rr_theme->paddingy + 1);
762             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
763             break;
764         case 'I':
765             if (!i) break;
766             self->iconify_x = x;
767             XMapWindow(ob_display, self->iconify);
768             XMoveWindow(ob_display,self->iconify, x, ob_rr_theme->paddingy + 1);
769             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
770             break;
771         case 'L':
772             if (!l) break;
773             self->label_x = x;
774             XMapWindow(ob_display, self->label);
775             XMoveWindow(ob_display, self->label, x, ob_rr_theme->paddingy);
776             x += self->label_width + ob_rr_theme->paddingx + 1;
777             break;
778         case 'M':
779             if (!m) break;
780             self->max_x = x;
781             XMapWindow(ob_display, self->max);
782             XMoveWindow(ob_display, self->max, x, ob_rr_theme->paddingy + 1);
783             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
784             break;
785         case 'C':
786             if (!c) break;
787             self->close_x = x;
788             XMapWindow(ob_display, self->close);
789             XMoveWindow(ob_display, self->close, x, ob_rr_theme->paddingy + 1);
790             x += ob_rr_theme->button_size + ob_rr_theme->paddingx + 1;
791             break;
792         }
793     }
794 }
795
796 ObFrameContext frame_context_from_string(const gchar *name)
797 {
798     if (!g_ascii_strcasecmp("Desktop", name))
799         return OB_FRAME_CONTEXT_DESKTOP;
800     else if (!g_ascii_strcasecmp("Client", name))
801         return OB_FRAME_CONTEXT_CLIENT;
802     else if (!g_ascii_strcasecmp("Titlebar", name))
803         return OB_FRAME_CONTEXT_TITLEBAR;
804     else if (!g_ascii_strcasecmp("Handle", name))
805         return OB_FRAME_CONTEXT_HANDLE;
806     else if (!g_ascii_strcasecmp("Frame", name))
807         return OB_FRAME_CONTEXT_FRAME;
808     else if (!g_ascii_strcasecmp("TLCorner", name))
809         return OB_FRAME_CONTEXT_TLCORNER;
810     else if (!g_ascii_strcasecmp("TRCorner", name))
811         return OB_FRAME_CONTEXT_TRCORNER;
812     else if (!g_ascii_strcasecmp("BLCorner", name))
813         return OB_FRAME_CONTEXT_BLCORNER;
814     else if (!g_ascii_strcasecmp("BRCorner", name))
815         return OB_FRAME_CONTEXT_BRCORNER;
816     else if (!g_ascii_strcasecmp("Maximize", name))
817         return OB_FRAME_CONTEXT_MAXIMIZE;
818     else if (!g_ascii_strcasecmp("AllDesktops", name))
819         return OB_FRAME_CONTEXT_ALLDESKTOPS;
820     else if (!g_ascii_strcasecmp("Shade", name))
821         return OB_FRAME_CONTEXT_SHADE;
822     else if (!g_ascii_strcasecmp("Iconify", name))
823         return OB_FRAME_CONTEXT_ICONIFY;
824     else if (!g_ascii_strcasecmp("Icon", name))
825         return OB_FRAME_CONTEXT_ICON;
826     else if (!g_ascii_strcasecmp("Close", name))
827         return OB_FRAME_CONTEXT_CLOSE;
828     else if (!g_ascii_strcasecmp("MoveResize", name))
829         return OB_FRAME_CONTEXT_MOVE_RESIZE;
830     return OB_FRAME_CONTEXT_NONE;
831 }
832
833 ObFrameContext frame_context(ObClient *client, Window win)
834 {
835     ObFrame *self;
836
837     if (moveresize_in_progress)
838         return OB_FRAME_CONTEXT_MOVE_RESIZE;
839
840     if (win == RootWindow(ob_display, ob_screen))
841         return OB_FRAME_CONTEXT_DESKTOP;
842     if (client == NULL) return OB_FRAME_CONTEXT_NONE;
843     if (win == client->window) {
844         /* conceptually, this is the desktop, as far as users are
845            concerned */
846         if (client->type == OB_CLIENT_TYPE_DESKTOP)
847             return OB_FRAME_CONTEXT_DESKTOP;
848         return OB_FRAME_CONTEXT_CLIENT;
849     }
850
851     self = client->frame;
852     if (win == self->inner || win == self->plate) {
853         /* conceptually, this is the desktop, as far as users are
854            concerned */
855         if (client->type == OB_CLIENT_TYPE_DESKTOP)
856             return OB_FRAME_CONTEXT_DESKTOP;
857         return OB_FRAME_CONTEXT_CLIENT;
858     }
859
860     if (win == self->window)    return OB_FRAME_CONTEXT_FRAME;
861     if (win == self->title)     return OB_FRAME_CONTEXT_TITLEBAR;
862     if (win == self->label)     return OB_FRAME_CONTEXT_TITLEBAR;
863     if (win == self->handle)    return OB_FRAME_CONTEXT_HANDLE;
864     if (win == self->lgrip)     return OB_FRAME_CONTEXT_BLCORNER;
865     if (win == self->rgrip)     return OB_FRAME_CONTEXT_BRCORNER;
866     if (win == self->tltresize) return OB_FRAME_CONTEXT_TLCORNER;
867     if (win == self->tllresize) return OB_FRAME_CONTEXT_TLCORNER;
868     if (win == self->trtresize) return OB_FRAME_CONTEXT_TRCORNER;
869     if (win == self->trrresize) return OB_FRAME_CONTEXT_TRCORNER;
870     if (win == self->max)       return OB_FRAME_CONTEXT_MAXIMIZE;
871     if (win == self->iconify)   return OB_FRAME_CONTEXT_ICONIFY;
872     if (win == self->close)     return OB_FRAME_CONTEXT_CLOSE;
873     if (win == self->icon)      return OB_FRAME_CONTEXT_ICON;
874     if (win == self->desk)      return OB_FRAME_CONTEXT_ALLDESKTOPS;
875     if (win == self->shade)     return OB_FRAME_CONTEXT_SHADE;
876
877     return OB_FRAME_CONTEXT_NONE;
878 }
879
880 void frame_client_gravity(ObFrame *self, gint *x, gint *y, gint w, gint h)
881 {
882     /* horizontal */
883     switch (self->client->gravity) {
884     default:
885     case NorthWestGravity:
886     case SouthWestGravity:
887     case WestGravity:
888         break;
889
890     case NorthGravity:
891     case SouthGravity:
892     case CenterGravity:
893         *x -= (self->size.left + w) / 2;
894         break;
895
896     case NorthEastGravity:
897     case SouthEastGravity:
898     case EastGravity:
899         *x -= (self->size.left + self->size.right + w) - 1;
900         break;
901
902     case ForgetGravity:
903     case StaticGravity:
904         *x -= self->size.left;
905         break;
906     }
907
908     /* vertical */
909     switch (self->client->gravity) {
910     default:
911     case NorthWestGravity:
912     case NorthEastGravity:
913     case NorthGravity:
914         break;
915
916     case CenterGravity:
917     case EastGravity:
918     case WestGravity:
919         *y -= (self->size.top + h) / 2;
920         break;
921
922     case SouthWestGravity:
923     case SouthEastGravity:
924     case SouthGravity:
925         *y -= (self->size.top + self->size.bottom + h) - 1;
926         break;
927
928     case ForgetGravity:
929     case StaticGravity:
930         *y -= self->size.top;
931         break;
932     }
933 }
934
935 void frame_frame_gravity(ObFrame *self, gint *x, gint *y, gint w, gint h)
936 {
937     /* horizontal */
938     switch (self->client->gravity) {
939     default:
940     case NorthWestGravity:
941     case WestGravity:
942     case SouthWestGravity:
943         break;
944     case NorthGravity:
945     case CenterGravity:
946     case SouthGravity:
947         *x += (self->size.left + w) / 2;
948         break;
949     case NorthEastGravity:
950     case EastGravity:
951     case SouthEastGravity:
952         *x += (self->size.left + self->size.right + w) - 1;
953         break;
954     case StaticGravity:
955     case ForgetGravity:
956         *x += self->size.left;
957         break;
958     }
959
960     /* vertical */
961     switch (self->client->gravity) {
962     default:
963     case NorthWestGravity:
964     case NorthGravity:
965     case NorthEastGravity:
966         break;
967     case WestGravity:
968     case CenterGravity:
969     case EastGravity:
970         *y += (self->size.top + h) / 2;
971         break;
972     case SouthWestGravity:
973     case SouthGravity:
974     case SouthEastGravity:
975         *y += (self->size.top + self->size.bottom + h) - 1;
976         break;
977     case StaticGravity:
978     case ForgetGravity:
979         *y += self->size.top;
980         break;
981     }
982 }
983
984 static void flash_done(gpointer data)
985 {
986     ObFrame *self = data;
987
988     if (self->focused != self->flash_on)
989         frame_adjust_focus(self, self->focused);
990 }
991
992 static gboolean flash_timeout(gpointer data)
993 {
994     ObFrame *self = data;
995     GTimeVal now;
996
997     g_get_current_time(&now);
998     if (now.tv_sec > self->flash_end.tv_sec ||
999         (now.tv_sec == self->flash_end.tv_sec &&
1000          now.tv_usec >= self->flash_end.tv_usec))
1001         self->flashing = FALSE;
1002
1003     if (!self->flashing)
1004         return FALSE; /* we are done */
1005
1006     self->flash_on = !self->flash_on;
1007     if (!self->focused) {
1008         frame_adjust_focus(self, self->flash_on);
1009         self->focused = FALSE;
1010     }
1011
1012     return TRUE; /* go again */
1013 }
1014
1015 void frame_flash_start(ObFrame *self)
1016 {
1017     self->flash_on = self->focused;
1018
1019     if (!self->flashing)
1020         ob_main_loop_timeout_add(ob_main_loop,
1021                                  G_USEC_PER_SEC * 0.6,
1022                                  flash_timeout,
1023                                  self,
1024                                  g_direct_equal,
1025                                  flash_done);
1026     g_get_current_time(&self->flash_end);
1027     g_time_val_add(&self->flash_end, G_USEC_PER_SEC * 5);
1028     
1029     self->flashing = TRUE;
1030 }
1031
1032 void frame_flash_stop(ObFrame *self)
1033 {
1034     self->flashing = FALSE;
1035 }
1036
1037 static gulong frame_animate_iconify_time_left(ObFrame *self,
1038                                               const GTimeVal *now)
1039 {
1040     glong sec, usec;
1041     sec = self->iconify_animation_end.tv_sec - now->tv_sec;
1042     usec = self->iconify_animation_end.tv_usec - now->tv_usec;
1043     if (usec < 0) {
1044         usec += G_USEC_PER_SEC;
1045         sec--;
1046     }
1047     /* no negative values */
1048     return MAX(sec * G_USEC_PER_SEC + usec, 0);
1049 }
1050
1051 static gboolean frame_animate_iconify(gpointer p)
1052 {
1053     ObFrame *self = p;
1054     gint x, y, w, h;
1055     gint iconx, icony, iconw;
1056     GTimeVal now;
1057     gulong time;
1058     gboolean iconifying;
1059
1060     if (self->client->icon_geometry.width == 0) {
1061         /* there is no icon geometry set so just go straight down */
1062         Rect *a = screen_physical_area();
1063         iconx = self->area.x + self->area.width / 2 + 32;
1064         icony = a->y + a->width;
1065         iconw = 64;
1066     } else {
1067         iconx = self->client->icon_geometry.x;
1068         icony = self->client->icon_geometry.y;
1069         iconw = self->client->icon_geometry.width;
1070     }
1071
1072     iconifying = self->iconify_animation_going > 0;
1073
1074     /* how far do we have left to go ? */
1075     g_get_current_time(&now);
1076     time = frame_animate_iconify_time_left(self, &now);
1077     
1078     if (time == 0 || iconifying) {
1079         /* start where the frame is supposed to be */
1080         x = self->area.x;
1081         y = self->area.y;
1082         w = self->area.width - self->bwidth * 2;
1083         h = self->area.height - self->bwidth * 2;
1084     } else {
1085         /* start at the icon */
1086         x = iconx;
1087         y = icony;
1088         w = iconw;
1089         h = self->innersize.top; /* just the titlebar */
1090     }
1091
1092     if (time > 0) {
1093         glong dx, dy, dw;
1094         glong elapsed;
1095
1096         dx = self->area.x - iconx;
1097         dy = self->area.y - icony;
1098         dw = self->area.width - self->bwidth * 2 - iconw;
1099          /* if restoring, we move in the opposite direction */
1100         if (!iconifying) { dx = -dx; dy = -dy; dw = -dw; }
1101
1102         elapsed = FRAME_ANIMATE_ICONIFY_TIME - time;
1103         x = x - (dx * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1104         y = y - (dy * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1105         w = w - (dw * elapsed) / FRAME_ANIMATE_ICONIFY_TIME;
1106         h = self->innersize.top; /* just the titlebar */
1107     }
1108
1109     if (time == 0)
1110         frame_end_iconify_animation(self);
1111     else {
1112         XMoveResizeWindow(ob_display, self->window, x, y, w, h);
1113         XFlush(ob_display);
1114     }
1115
1116     return time > 0; /* repeat until we're out of time */
1117 }
1118
1119 void frame_end_iconify_animation(ObFrame *self)
1120 {
1121     /* see if there is an animation going */
1122     if (self->iconify_animation_going == 0) return;
1123
1124     /* call the callback when it's done */
1125     if (self->iconify_animation_cb)
1126         self->iconify_animation_cb(self->iconify_animation_data);
1127     /* we're not animating any more ! */
1128     self->iconify_animation_going = 0;
1129
1130     /* move after the callback for the animation ending */
1131     XMoveResizeWindow(ob_display, self->window,
1132                       self->area.x, self->area.y,
1133                       self->area.width - self->bwidth * 2,
1134                       self->area.height - self->bwidth * 2);
1135     XFlush(ob_display);
1136 }
1137
1138 void frame_begin_iconify_animation(ObFrame *self, gboolean iconifying,
1139                                    ObFrameIconifyAnimateFunc callback,
1140                                    gpointer data)
1141 {
1142     gulong time;
1143     gboolean new_anim = FALSE;
1144     gboolean set_end = TRUE;
1145     GTimeVal now;
1146
1147     /* if there is no titlebar, just don't animate for now
1148        XXX it would be nice tho.. */
1149     if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR)) {
1150         if (callback) callback(data);
1151         return;
1152     }
1153
1154     /* get the current time */
1155     g_get_current_time(&now);
1156
1157     /* get how long until the end */
1158     time = FRAME_ANIMATE_ICONIFY_TIME;
1159     if (self->iconify_animation_going) {
1160         if (!!iconifying != (self->iconify_animation_going > 0)) {
1161             /* animation was already going on in the opposite direction */
1162             time = time - frame_animate_iconify_time_left(self, &now);
1163         } else
1164             /* animation was already going in the same direction */
1165             set_end = FALSE;
1166     } else
1167         new_anim = TRUE;
1168     self->iconify_animation_going = iconifying ? 1 : -1;
1169
1170     self->iconify_animation_cb = callback;
1171     self->iconify_animation_data = data;
1172
1173     /* set the ending time */
1174     if (set_end) {
1175         self->iconify_animation_end.tv_sec = now.tv_sec;
1176         self->iconify_animation_end.tv_usec = now.tv_usec;
1177         g_time_val_add(&self->iconify_animation_end, time);
1178     }
1179
1180     if (new_anim) {
1181         ob_main_loop_timeout_remove_data(ob_main_loop, frame_animate_iconify,
1182                                          self, FALSE);
1183         ob_main_loop_timeout_add(ob_main_loop,
1184                                  FRAME_ANIMATE_ICONIFY_STEP_TIME,
1185                                  frame_animate_iconify, self,
1186                                  g_direct_equal, NULL);
1187
1188         /* do the first step */
1189         frame_animate_iconify(self);
1190
1191         if (!self->visible)
1192             frame_show(self);
1193     }
1194 }
1195
1196 gboolean frame_visible(ObFrame *self)
1197 {
1198     /* if it is animating back from iconic state then it is considered
1199        visible. but if it is iconifying then it is not visible. */
1200     return self->visible && self->iconify_animation_going <= 0;
1201 }