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