]> icculus.org git repositories - dana/openbox.git/blob - src/Window.cc
snapping/resistance is optional for window and edge snapping
[dana/openbox.git] / src / Window.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2 // Window.cc for Blackbox - an X11 Window manager
3 // Copyright (c) 2001 - 2002 Sean 'Shaleh' Perry <shaleh at debian.org>
4 // Copyright (c) 1997 - 2000, 2002 Brad Hughes <bhughes at trolltech.com>
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining a
7 // copy of this software and associated documentation files (the "Software"),
8 // to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 // and/or sell copies of the Software, and to permit persons to whom the
11 // Software is furnished to do so, subject to the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 // DEALINGS IN THE SOFTWARE.
23
24 #ifdef    HAVE_CONFIG_H
25 #  include "../config.h"
26 #endif // HAVE_CONFIG_H
27
28 extern "C" {
29 #include <X11/Xatom.h>
30 #include <X11/keysym.h>
31
32 #ifdef HAVE_STRING_H
33 #  include <string.h>
34 #endif // HAVE_STRING_H
35
36 #ifdef    DEBUG
37 #  ifdef    HAVE_STDIO_H
38 #    include <stdio.h>
39 #  endif // HAVE_STDIO_H
40 #endif // DEBUG
41
42 #ifdef HAVE_STDLIB_H
43    #include <stdlib.h>
44 #endif // HAVE_STDLIB_H
45 }
46
47 #include "i18n.hh"
48 #include "blackbox.hh"
49 #include "Clientmenu.hh"
50 #include "Font.hh"
51 #include "GCCache.hh"
52 #include "Iconmenu.hh"
53 #include "Image.hh"
54 #include "Screen.hh"
55 #include "Toolbar.hh"
56 #include "Util.hh"
57 #include "Window.hh"
58 #include "Windowmenu.hh"
59 #include "Workspace.hh"
60 #include "Slit.hh"
61
62 using std::string;
63 using std::abs;
64
65 // change this to change what modifier keys openbox uses for mouse bindings
66 // for example: Mod1Mask | ControlMask
67 //          or: ControlMask| ShiftMask
68 const unsigned int ModMask = Mod1Mask;
69
70 /*
71  * Initializes the class with default values/the window's set initial values.
72  */
73 BlackboxWindow::BlackboxWindow(Blackbox *b, Window w, BScreen *s) {
74   // fprintf(stderr, "BlackboxWindow size: %d bytes\n",
75   // sizeof(BlackboxWindow));
76
77 #ifdef    DEBUG
78   fprintf(stderr, "BlackboxWindow::BlackboxWindow(): creating 0x%lx\n", w);
79 #endif // DEBUG
80
81   /*
82     set timer to zero... it is initialized properly later, so we check
83     if timer is zero in the destructor, and assume that the window is not
84     fully constructed if timer is zero...
85   */
86   timer = 0;
87   blackbox = b;
88   client.window = w;
89   screen = s;
90   xatom = blackbox->getXAtom();
91
92   if (! validateClient()) {
93     delete this;
94     return;
95   }
96
97   // fetch client size and placement
98   XWindowAttributes wattrib;
99   if (! XGetWindowAttributes(blackbox->getXDisplay(),
100                              client.window, &wattrib) ||
101       ! wattrib.screen || wattrib.override_redirect) {
102 #ifdef    DEBUG
103     fprintf(stderr,
104             "BlackboxWindow::BlackboxWindow(): XGetWindowAttributes failed\n");
105 #endif // DEBUG
106
107     delete this;
108     return;
109   }
110
111   // set the eventmask early in the game so that we make sure we get
112   // all the events we are interested in
113   XSetWindowAttributes attrib_set;
114   attrib_set.event_mask = PropertyChangeMask | FocusChangeMask |
115                           StructureNotifyMask;
116   attrib_set.do_not_propagate_mask = ButtonPressMask | ButtonReleaseMask |
117                                      ButtonMotionMask;
118   XChangeWindowAttributes(blackbox->getXDisplay(), client.window,
119                           CWEventMask|CWDontPropagate, &attrib_set);
120
121   flags.moving = flags.resizing = flags.shaded = flags.visible =
122     flags.iconic = flags.focused = flags.stuck = flags.modal =
123     flags.send_focus_message = flags.shaped = flags.skip_taskbar =
124     flags.skip_pager = flags.fullscreen = False;
125   flags.maximized = 0;
126
127   blackbox_attrib.workspace = window_number = BSENTINEL;
128
129   blackbox_attrib.flags = blackbox_attrib.attrib = blackbox_attrib.stack
130     = blackbox_attrib.decoration = 0l;
131   blackbox_attrib.premax_x = blackbox_attrib.premax_y = 0;
132   blackbox_attrib.premax_w = blackbox_attrib.premax_h = 0;
133
134   frame.border_w = 1;
135   frame.window = frame.plate = frame.title = frame.handle = None;
136   frame.close_button = frame.iconify_button = frame.maximize_button = None;
137   frame.right_grip = frame.left_grip = None;
138
139   frame.ulabel_pixel = frame.flabel_pixel = frame.utitle_pixel =
140   frame.ftitle_pixel = frame.uhandle_pixel = frame.fhandle_pixel =
141     frame.ubutton_pixel = frame.fbutton_pixel = frame.pbutton_pixel =
142     frame.uborder_pixel = frame.fborder_pixel = frame.ugrip_pixel =
143     frame.fgrip_pixel = 0;
144   frame.utitle = frame.ftitle = frame.uhandle = frame.fhandle = None;
145   frame.ulabel = frame.flabel = frame.ubutton = frame.fbutton = None;
146   frame.pbutton = frame.ugrip = frame.fgrip = None;
147
148   decorations = Decor_Titlebar | Decor_Border | Decor_Handle |
149                 Decor_Iconify | Decor_Maximize;
150   functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize;
151
152   client.normal_hint_flags = 0;
153   client.window_group = None;
154   client.transient_for = 0;
155
156   current_state = NormalState;
157
158   /*
159     get the initial size and location of client window (relative to the
160     _root window_). This position is the reference point used with the
161     window's gravity to find the window's initial position.
162   */
163   client.rect.setRect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
164   client.old_bw = wattrib.border_width;
165
166   lastButtonPressTime = 0;
167
168   timer = new BTimer(blackbox, this);
169   timer->setTimeout(blackbox->getAutoRaiseDelay());
170
171   windowmenu = new Windowmenu(this);
172
173   // get size, aspect, minimum/maximum size and other hints set by the
174   // client
175
176   if (! getBlackboxHints()) {
177     getMWMHints();
178     getNetWMHints();
179   }
180
181   getWMProtocols();
182   getWMHints();
183   getWMNormalHints();
184
185   frame.window = createToplevelWindow();
186
187   blackbox->saveWindowSearch(frame.window, this);
188   
189   frame.plate = createChildWindow(frame.window);
190   blackbox->saveWindowSearch(frame.plate, this);
191
192   // determine if this is a transient window
193   getTransientInfo();
194
195   // determine the window's type, so we can decide its decorations and
196   // functionality, or if we should not manage it at all
197   getWindowType();
198
199   // adjust the window decorations/behavior based on the window type
200
201   switch (window_type) {
202   case Type_Desktop:
203   case Type_Dock:
204   case Type_Menu:
205   case Type_Toolbar:
206   case Type_Utility:
207   case Type_Splash:
208     // none of these windows are decorated or manipulated by the window manager
209     decorations = 0;
210     functions = 0;
211     blackbox_attrib.workspace = 0;  // we do need to belong to a workspace
212     flags.stuck = True;             // we show up on all workspaces
213     break;
214
215   case Type_Dialog:
216     // dialogs cannot be maximized, and don't display a handle
217     decorations &= ~(Decor_Maximize | Decor_Handle);
218     functions &= ~Func_Maximize;
219     break;
220
221   case Type_Normal:
222     // normal windows retain all of the possible decorations and functionality
223     break;
224   }
225   
226   setAllowedActions();
227
228   // further adjeust the window's decorations/behavior based on window sizes
229   if ((client.normal_hint_flags & PMinSize) &&
230       (client.normal_hint_flags & PMaxSize) &&
231       client.max_width <= client.min_width &&
232       client.max_height <= client.min_height) {
233     decorations &= ~(Decor_Maximize | Decor_Handle);
234     functions &= ~(Func_Resize | Func_Maximize);
235   }
236   
237   if (decorations & Decor_Titlebar)
238     createTitlebar();
239
240   if (decorations & Decor_Handle)
241     createHandle();
242
243   // apply the size and gravity hint to the frame
244
245   upsize();
246
247   bool place_window = True;
248   if (blackbox->isStartup() || isTransient() ||
249       client.normal_hint_flags & (PPosition|USPosition)) {
250     applyGravity(frame.rect);
251
252     if (blackbox->isStartup() || client.rect.intersects(screen->getRect()))
253       place_window = False;
254   }
255
256   // add the window's strut. note this is done *after* placing the window.
257   screen->addStrut(&client.strut);
258   updateStrut();
259   
260 #ifdef    SHAPE
261   if (blackbox->hasShapeExtensions() && flags.shaped)
262     configureShape();
263 #endif // SHAPE
264   
265   // get the window's title before adding it to the workspace
266   getWMName();
267   getWMIconName();
268
269   if (blackbox_attrib.workspace >= screen->getWorkspaceCount())
270     screen->getCurrentWorkspace()->addWindow(this, place_window);
271   else
272     screen->getWorkspace(blackbox_attrib.workspace)->
273       addWindow(this, place_window);
274
275   /*
276     the server needs to be grabbed here to prevent client's from sending
277     events while we are in the process of configuring their window.
278     We hold the grab until after we are done moving the window around.
279   */
280
281   XGrabServer(blackbox->getXDisplay());
282
283   associateClientWindow();
284
285   blackbox->saveWindowSearch(client.window, this);
286
287   if (! place_window) {
288     // don't need to call configure if we are letting the workspace
289     // place the window
290     configure(frame.rect.x(), frame.rect.y(),
291               frame.rect.width(), frame.rect.height());
292
293   }
294
295   positionWindows();
296
297   XUngrabServer(blackbox->getXDisplay());
298
299   // now that we know where to put the window and what it should look like
300   // we apply the decorations
301   decorate();
302
303   grabButtons();
304
305   XMapSubwindows(blackbox->getXDisplay(), frame.window);
306
307   // this ensures the title, buttons, and other decor are properly displayed
308   redrawWindowFrame();
309
310   // preserve the window's initial state on first map, and its current state
311   // across a restart
312   unsigned long initial_state = current_state;
313   if (! getState())
314     current_state = initial_state;
315
316   // get sticky state from our parent window if we've got one
317   if (isTransient() && client.transient_for != (BlackboxWindow *) ~0ul &&
318       client.transient_for->isStuck() != flags.stuck)
319     flags.stuck = True;
320
321   if (flags.shaded) {
322     flags.shaded = False;
323     initial_state = current_state;
324     shade();
325
326     /*
327       At this point in the life of a window, current_state should only be set
328       to IconicState if the window was an *icon*, not if it was shaded.
329     */
330     if (initial_state != IconicState)
331       current_state = NormalState;
332   }
333
334   if (flags.stuck) {
335     flags.stuck = False;
336     stick();
337   }
338
339   if (flags.maximized && (functions & Func_Maximize))
340     remaximize();
341 }
342
343
344 BlackboxWindow::~BlackboxWindow(void) {
345 #ifdef    DEBUG
346   fprintf(stderr, "BlackboxWindow::~BlackboxWindow: destroying 0x%lx\n",
347           client.window);
348 #endif // DEBUG
349
350   if (! timer) // window not managed...
351     return;
352
353   screen->removeStrut(&client.strut);
354   screen->updateAvailableArea();
355
356   // We don't need to worry about resizing because resizing always grabs the X
357   // server. This should only ever happen if using opaque moving.
358   if (flags.moving)
359     endMove();
360
361   delete timer;
362
363   delete windowmenu;
364
365   if (client.window_group) {
366     BWindowGroup *group = blackbox->searchGroup(client.window_group);
367     if (group) group->removeWindow(this);
368   }
369
370   // remove ourselves from our transient_for
371   if (isTransient()) {
372     if (client.transient_for != (BlackboxWindow *) ~0ul) {
373       client.transient_for->client.transientList.remove(this);
374     }
375     client.transient_for = (BlackboxWindow*) 0;
376   }
377
378   if (client.transientList.size() > 0) {
379     // reset transient_for for all transients
380     BlackboxWindowList::iterator it, end = client.transientList.end();
381     for (it = client.transientList.begin(); it != end; ++it) {
382       (*it)->client.transient_for = (BlackboxWindow*) 0;
383     }
384   }
385
386   if (frame.title)
387     destroyTitlebar();
388
389   if (frame.handle)
390     destroyHandle();
391
392   if (frame.plate) {
393     blackbox->removeWindowSearch(frame.plate);
394     XDestroyWindow(blackbox->getXDisplay(), frame.plate);
395   }
396
397   if (frame.window) {
398     blackbox->removeWindowSearch(frame.window);
399     XDestroyWindow(blackbox->getXDisplay(), frame.window);
400   }
401
402   blackbox->removeWindowSearch(client.window);
403 }
404
405
406 /*
407  * Creates a new top level window, with a given location, size, and border
408  * width.
409  * Returns: the newly created window
410  */
411 Window BlackboxWindow::createToplevelWindow(void) {
412   XSetWindowAttributes attrib_create;
413   unsigned long create_mask = CWBackPixmap | CWBorderPixel | CWColormap |
414                               CWOverrideRedirect | CWEventMask;
415
416   attrib_create.background_pixmap = None;
417   attrib_create.colormap = screen->getColormap();
418   attrib_create.override_redirect = True;
419   attrib_create.event_mask = ButtonPressMask | ButtonReleaseMask |
420                              ButtonMotionMask | EnterWindowMask;
421
422   return XCreateWindow(blackbox->getXDisplay(), screen->getRootWindow(),
423                        0, 0, 1, 1, frame.border_w, screen->getDepth(),
424                        InputOutput, screen->getVisual(), create_mask,
425                        &attrib_create);
426 }
427
428
429 /*
430  * Creates a child window, and optionally associates a given cursor with
431  * the new window.
432  */
433 Window BlackboxWindow::createChildWindow(Window parent, Cursor cursor) {
434   XSetWindowAttributes attrib_create;
435   unsigned long create_mask = CWBackPixmap | CWBorderPixel |
436                               CWEventMask;
437
438   attrib_create.background_pixmap = None;
439   attrib_create.event_mask = ButtonPressMask | ButtonReleaseMask |
440                              ButtonMotionMask | ExposureMask;
441
442   if (cursor) {
443     create_mask |= CWCursor;
444     attrib_create.cursor = cursor;
445   }
446
447   return XCreateWindow(blackbox->getXDisplay(), parent, 0, 0, 1, 1, 0,
448                        screen->getDepth(), InputOutput, screen->getVisual(),
449                        create_mask, &attrib_create);
450 }
451
452
453 void BlackboxWindow::associateClientWindow(void) {
454   XSetWindowBorderWidth(blackbox->getXDisplay(), client.window, 0);
455
456   XChangeSaveSet(blackbox->getXDisplay(), client.window, SetModeInsert);
457
458   XSelectInput(blackbox->getXDisplay(), frame.plate, SubstructureRedirectMask);
459
460   /*
461     note we used to grab around this call to XReparentWindow however the
462     server is now grabbed before this method is called
463   */
464   unsigned long event_mask = PropertyChangeMask | FocusChangeMask |
465                              StructureNotifyMask;
466   XSelectInput(blackbox->getXDisplay(), client.window,
467                event_mask & ~StructureNotifyMask);
468   XReparentWindow(blackbox->getXDisplay(), client.window, frame.plate, 0, 0);
469   XSelectInput(blackbox->getXDisplay(), client.window, event_mask);
470
471   XRaiseWindow(blackbox->getXDisplay(), frame.plate);
472   XMapSubwindows(blackbox->getXDisplay(), frame.plate);
473
474 #ifdef    SHAPE
475   if (blackbox->hasShapeExtensions()) {
476     XShapeSelectInput(blackbox->getXDisplay(), client.window,
477                       ShapeNotifyMask);
478
479     Bool shaped = False;
480     int foo;
481     unsigned int ufoo;
482
483     XShapeQueryExtents(blackbox->getXDisplay(), client.window, &shaped,
484                        &foo, &foo, &ufoo, &ufoo, &foo, &foo, &foo,
485                        &ufoo, &ufoo);
486     flags.shaped = shaped;
487   }
488 #endif // SHAPE
489 }
490
491
492 void BlackboxWindow::decorate(void) {
493   BTexture* texture;
494
495   texture = &(screen->getWindowStyle()->b_focus);
496   frame.fbutton = texture->render(frame.button_w, frame.button_w,
497                                   frame.fbutton);
498   if (! frame.fbutton)
499     frame.fbutton_pixel = texture->color().pixel();
500
501   texture = &(screen->getWindowStyle()->b_unfocus);
502   frame.ubutton = texture->render(frame.button_w, frame.button_w,
503                                   frame.ubutton);
504   if (! frame.ubutton)
505     frame.ubutton_pixel = texture->color().pixel();
506
507   texture = &(screen->getWindowStyle()->b_pressed);
508   frame.pbutton = texture->render(frame.button_w, frame.button_w,
509                                   frame.pbutton);
510   if (! frame.pbutton)
511     frame.pbutton_pixel = texture->color().pixel();
512
513   if (decorations & Decor_Titlebar) {
514     texture = &(screen->getWindowStyle()->t_focus);
515     frame.ftitle = texture->render(frame.inside_w, frame.title_h,
516                                    frame.ftitle);
517     if (! frame.ftitle)
518       frame.ftitle_pixel = texture->color().pixel();
519
520     texture = &(screen->getWindowStyle()->t_unfocus);
521     frame.utitle = texture->render(frame.inside_w, frame.title_h,
522                                    frame.utitle);
523     if (! frame.utitle)
524       frame.utitle_pixel = texture->color().pixel();
525
526     XSetWindowBorder(blackbox->getXDisplay(), frame.title,
527                      screen->getBorderColor()->pixel());
528
529     decorateLabel();
530   }
531
532   if (decorations & Decor_Border) {
533     frame.fborder_pixel = screen->getWindowStyle()->f_focus.pixel();
534     frame.uborder_pixel = screen->getWindowStyle()->f_unfocus.pixel();
535     blackbox_attrib.flags |= AttribDecoration;
536     blackbox_attrib.decoration = DecorNormal;
537   } else {
538     blackbox_attrib.flags |= AttribDecoration;
539     blackbox_attrib.decoration = DecorNone;
540   }
541
542   if (decorations & Decor_Handle) {
543     texture = &(screen->getWindowStyle()->h_focus);
544     frame.fhandle = texture->render(frame.inside_w, frame.handle_h,
545                                     frame.fhandle);
546     if (! frame.fhandle)
547       frame.fhandle_pixel = texture->color().pixel();
548
549     texture = &(screen->getWindowStyle()->h_unfocus);
550     frame.uhandle = texture->render(frame.inside_w, frame.handle_h,
551                                     frame.uhandle);
552     if (! frame.uhandle)
553       frame.uhandle_pixel = texture->color().pixel();
554
555     texture = &(screen->getWindowStyle()->g_focus);
556     frame.fgrip = texture->render(frame.grip_w, frame.handle_h, frame.fgrip);
557     if (! frame.fgrip)
558       frame.fgrip_pixel = texture->color().pixel();
559
560     texture = &(screen->getWindowStyle()->g_unfocus);
561     frame.ugrip = texture->render(frame.grip_w, frame.handle_h, frame.ugrip);
562     if (! frame.ugrip)
563       frame.ugrip_pixel = texture->color().pixel();
564
565     XSetWindowBorder(blackbox->getXDisplay(), frame.handle,
566                      screen->getBorderColor()->pixel());
567     XSetWindowBorder(blackbox->getXDisplay(), frame.left_grip,
568                      screen->getBorderColor()->pixel());
569     XSetWindowBorder(blackbox->getXDisplay(), frame.right_grip,
570                      screen->getBorderColor()->pixel());
571   }
572
573   XSetWindowBorder(blackbox->getXDisplay(), frame.window,
574                    screen->getBorderColor()->pixel());
575 }
576
577
578 void BlackboxWindow::decorateLabel(void) {
579   BTexture *texture;
580
581   texture = &(screen->getWindowStyle()->l_focus);
582   frame.flabel = texture->render(frame.label_w, frame.label_h, frame.flabel);
583   if (! frame.flabel)
584     frame.flabel_pixel = texture->color().pixel();
585
586   texture = &(screen->getWindowStyle()->l_unfocus);
587   frame.ulabel = texture->render(frame.label_w, frame.label_h, frame.ulabel);
588   if (! frame.ulabel)
589     frame.ulabel_pixel = texture->color().pixel();
590 }
591
592
593 void BlackboxWindow::createHandle(void) {
594   frame.handle = createChildWindow(frame.window);
595   blackbox->saveWindowSearch(frame.handle, this);
596
597   frame.left_grip =
598     createChildWindow(frame.handle, blackbox->getLowerLeftAngleCursor());
599   blackbox->saveWindowSearch(frame.left_grip, this);
600
601   frame.right_grip =
602     createChildWindow(frame.handle, blackbox->getLowerRightAngleCursor());
603   blackbox->saveWindowSearch(frame.right_grip, this);
604 }
605
606
607 void BlackboxWindow::destroyHandle(void) {
608   if (frame.fhandle)
609     screen->getImageControl()->removeImage(frame.fhandle);
610
611   if (frame.uhandle)
612     screen->getImageControl()->removeImage(frame.uhandle);
613
614   if (frame.fgrip)
615     screen->getImageControl()->removeImage(frame.fgrip);
616
617   if (frame.ugrip)
618     screen->getImageControl()->removeImage(frame.ugrip);
619
620   blackbox->removeWindowSearch(frame.left_grip);
621   blackbox->removeWindowSearch(frame.right_grip);
622
623   XDestroyWindow(blackbox->getXDisplay(), frame.left_grip);
624   XDestroyWindow(blackbox->getXDisplay(), frame.right_grip);
625   frame.left_grip = frame.right_grip = None;
626
627   blackbox->removeWindowSearch(frame.handle);
628   XDestroyWindow(blackbox->getXDisplay(), frame.handle);
629   frame.handle = None;
630 }
631
632
633 void BlackboxWindow::createTitlebar(void) {
634   frame.title = createChildWindow(frame.window);
635   frame.label = createChildWindow(frame.title);
636   blackbox->saveWindowSearch(frame.title, this);
637   blackbox->saveWindowSearch(frame.label, this);
638
639   if (decorations & Decor_Iconify) createIconifyButton();
640   if (decorations & Decor_Maximize) createMaximizeButton();
641   if (decorations & Decor_Close) createCloseButton();
642 }
643
644
645 void BlackboxWindow::destroyTitlebar(void) {
646   if (frame.close_button)
647     destroyCloseButton();
648
649   if (frame.iconify_button)
650     destroyIconifyButton();
651
652   if (frame.maximize_button)
653     destroyMaximizeButton();
654
655   if (frame.ftitle)
656     screen->getImageControl()->removeImage(frame.ftitle);
657
658   if (frame.utitle)
659     screen->getImageControl()->removeImage(frame.utitle);
660
661   if (frame.flabel)
662     screen->getImageControl()->removeImage(frame.flabel);
663
664   if( frame.ulabel)
665     screen->getImageControl()->removeImage(frame.ulabel);
666
667   if (frame.fbutton)
668     screen->getImageControl()->removeImage(frame.fbutton);
669
670   if (frame.ubutton)
671     screen->getImageControl()->removeImage(frame.ubutton);
672
673   if (frame.pbutton)
674     screen->getImageControl()->removeImage(frame.pbutton);
675
676   blackbox->removeWindowSearch(frame.title);
677   blackbox->removeWindowSearch(frame.label);
678
679   XDestroyWindow(blackbox->getXDisplay(), frame.label);
680   XDestroyWindow(blackbox->getXDisplay(), frame.title);
681   frame.title = frame.label = None;
682 }
683
684
685 void BlackboxWindow::createCloseButton(void) {
686   if (frame.title != None) {
687     frame.close_button = createChildWindow(frame.title);
688     blackbox->saveWindowSearch(frame.close_button, this);
689   }
690 }
691
692
693 void BlackboxWindow::destroyCloseButton(void) {
694   blackbox->removeWindowSearch(frame.close_button);
695   XDestroyWindow(blackbox->getXDisplay(), frame.close_button);
696   frame.close_button = None;
697 }
698
699
700 void BlackboxWindow::createIconifyButton(void) {
701   if (frame.title != None) {
702     frame.iconify_button = createChildWindow(frame.title);
703     blackbox->saveWindowSearch(frame.iconify_button, this);
704   }
705 }
706
707
708 void BlackboxWindow::destroyIconifyButton(void) {
709   blackbox->removeWindowSearch(frame.iconify_button);
710   XDestroyWindow(blackbox->getXDisplay(), frame.iconify_button);
711   frame.iconify_button = None;
712 }
713
714
715 void BlackboxWindow::createMaximizeButton(void) {
716   if (frame.title != None) {
717     frame.maximize_button = createChildWindow(frame.title);
718     blackbox->saveWindowSearch(frame.maximize_button, this);
719   }
720 }
721
722
723 void BlackboxWindow::destroyMaximizeButton(void) {
724   blackbox->removeWindowSearch(frame.maximize_button);
725   XDestroyWindow(blackbox->getXDisplay(), frame.maximize_button);
726   frame.maximize_button = None;
727 }
728
729
730 void BlackboxWindow::positionButtons(bool redecorate_label) {
731   string layout = blackbox->getTitlebarLayout();
732   string parsed;
733
734   bool hasclose, hasiconify, hasmaximize, haslabel;
735   hasclose = hasiconify = hasmaximize = haslabel = false;
736
737   string::const_iterator it, end;
738   for (it = layout.begin(), end = layout.end(); it != end; ++it) {
739     switch(*it) {
740     case 'C':
741       if (! hasclose && (decorations & Decor_Close)) {
742         hasclose = true;
743         parsed += *it;
744       }
745       break;
746     case 'I':
747       if (! hasiconify && (decorations & Decor_Iconify)) {
748         hasiconify = true;
749         parsed += *it;
750       }
751       break;
752     case 'M':
753       if (! hasmaximize && (decorations & Decor_Maximize)) {
754         hasmaximize = true;
755         parsed += *it;
756       }
757       break;
758     case 'L':
759       if (! haslabel) {
760         haslabel = true;
761         parsed += *it;
762       }
763     }
764   }
765   if (! hasclose && frame.close_button)
766     destroyCloseButton();
767   if (! hasiconify && frame.iconify_button)
768     destroyIconifyButton();
769   if (! hasmaximize && frame.maximize_button)
770     destroyMaximizeButton();
771   if (! haslabel)
772     parsed += 'L';      // require that the label be in the layout
773
774   const unsigned int bsep = frame.bevel_w + 1;  // separation between elements
775   const unsigned int by = frame.bevel_w + 1;
776   const unsigned int ty = frame.bevel_w;
777
778   frame.label_w = frame.inside_w - bsep * 2 -
779     (frame.button_w + bsep) * (parsed.size() - 1);
780
781   unsigned int x = bsep;
782   for (it = parsed.begin(), end = parsed.end(); it != end; ++it) {
783     switch(*it) {
784     case 'C':
785       if (! frame.close_button) createCloseButton();
786       XMoveResizeWindow(blackbox->getXDisplay(), frame.close_button, x, by,
787                         frame.button_w, frame.button_w);
788       x += frame.button_w + bsep;
789       break;
790     case 'I':
791       if (! frame.iconify_button) createIconifyButton();
792       XMoveResizeWindow(blackbox->getXDisplay(), frame.iconify_button, x, by,
793                         frame.button_w, frame.button_w);
794       x += frame.button_w + bsep;
795       break;
796     case 'M':
797       if (! frame.maximize_button) createMaximizeButton();
798       XMoveResizeWindow(blackbox->getXDisplay(), frame.maximize_button, x, by,
799                         frame.button_w, frame.button_w);
800       x += frame.button_w + bsep;
801       break;
802     case 'L':
803       XMoveResizeWindow(blackbox->getXDisplay(), frame.label, x, ty,
804                         frame.label_w, frame.label_h);
805       x += frame.label_w + bsep;
806       break;
807     }
808   }
809
810   if (redecorate_label) decorateLabel();
811   redrawLabel();
812   redrawAllButtons();
813 }
814
815
816 void BlackboxWindow::reconfigure(void) {
817   restoreGravity(client.rect);
818   upsize();
819   applyGravity(frame.rect);
820   positionWindows();
821   decorate();
822   redrawWindowFrame();
823
824   ungrabButtons();
825   grabButtons();
826
827   if (windowmenu) {
828     windowmenu->move(windowmenu->getX(), frame.rect.y() + frame.title_h);
829     windowmenu->reconfigure();
830   }
831 }
832
833
834 void BlackboxWindow::grabButtons(void) {
835   if (! screen->isSloppyFocus() || screen->doClickRaise())
836     // grab button 1 for changing focus/raising
837     blackbox->grabButton(Button1, 0, frame.plate, True, ButtonPressMask,
838                          GrabModeSync, GrabModeSync, frame.plate, None,
839                          screen->allowScrollLock());
840   
841   if (functions & Func_Move)
842     blackbox->grabButton(Button1, ModMask, frame.window, True,
843                          ButtonReleaseMask | ButtonMotionMask, GrabModeAsync,
844                          GrabModeAsync, frame.window, None,
845                          screen->allowScrollLock());
846   if (functions & Func_Resize)
847     blackbox->grabButton(Button3, ModMask, frame.window, True,
848                          ButtonReleaseMask | ButtonMotionMask, GrabModeAsync,
849                          GrabModeAsync, frame.window, None,
850                          screen->allowScrollLock());
851   // alt+middle lowers the window
852   blackbox->grabButton(Button2, ModMask, frame.window, True,
853                        ButtonReleaseMask, GrabModeAsync, GrabModeAsync,
854                        frame.window, None,
855                        screen->allowScrollLock());
856 }
857
858
859 void BlackboxWindow::ungrabButtons(void) {
860   blackbox->ungrabButton(Button1, 0, frame.plate);
861   blackbox->ungrabButton(Button1, ModMask, frame.window);
862   blackbox->ungrabButton(Button2, ModMask, frame.window);
863   blackbox->ungrabButton(Button3, ModMask, frame.window);
864 }
865
866
867 void BlackboxWindow::positionWindows(void) {
868   XMoveResizeWindow(blackbox->getXDisplay(), frame.window,
869                     frame.rect.x(), frame.rect.y(), frame.inside_w,
870                     (flags.shaded) ? frame.title_h : frame.inside_h);
871   XSetWindowBorderWidth(blackbox->getXDisplay(), frame.window,
872                         frame.border_w);
873   XSetWindowBorderWidth(blackbox->getXDisplay(), frame.plate,
874                         frame.mwm_border_w);
875   XMoveResizeWindow(blackbox->getXDisplay(), frame.plate,
876                     frame.margin.left - frame.mwm_border_w - frame.border_w,
877                     frame.margin.top - frame.mwm_border_w - frame.border_w,
878                     client.rect.width(), client.rect.height());
879   XMoveResizeWindow(blackbox->getXDisplay(), client.window,
880                     0, 0, client.rect.width(), client.rect.height());
881   // ensure client.rect contains the real location
882   client.rect.setPos(frame.rect.left() + frame.margin.left,
883                      frame.rect.top() + frame.margin.top);
884
885   if (decorations & Decor_Titlebar) {
886     if (frame.title == None) createTitlebar();
887
888     XSetWindowBorderWidth(blackbox->getXDisplay(), frame.title,
889                           frame.border_w);
890     XMoveResizeWindow(blackbox->getXDisplay(), frame.title, -frame.border_w,
891                       -frame.border_w, frame.inside_w, frame.title_h);
892
893     positionButtons();
894     XMapSubwindows(blackbox->getXDisplay(), frame.title);
895     XMapWindow(blackbox->getXDisplay(), frame.title);
896   } else if (frame.title) {
897     destroyTitlebar();
898   }
899   if (decorations & Decor_Handle) {
900     if (frame.handle == None) createHandle();
901     XSetWindowBorderWidth(blackbox->getXDisplay(), frame.handle,
902                           frame.border_w);
903     XSetWindowBorderWidth(blackbox->getXDisplay(), frame.left_grip,
904                           frame.border_w);
905     XSetWindowBorderWidth(blackbox->getXDisplay(), frame.right_grip,
906                           frame.border_w);
907
908     // use client.rect here so the value is correct even if shaded
909     XMoveResizeWindow(blackbox->getXDisplay(), frame.handle,
910                       -frame.border_w,
911                       client.rect.height() + frame.margin.top +
912                       frame.mwm_border_w - frame.border_w,
913                       frame.inside_w, frame.handle_h);
914     XMoveResizeWindow(blackbox->getXDisplay(), frame.left_grip,
915                       -frame.border_w, -frame.border_w,
916                       frame.grip_w, frame.handle_h);
917     XMoveResizeWindow(blackbox->getXDisplay(), frame.right_grip,
918                       frame.inside_w - frame.grip_w - frame.border_w,
919                       -frame.border_w, frame.grip_w, frame.handle_h);
920
921     XMapSubwindows(blackbox->getXDisplay(), frame.handle);
922     XMapWindow(blackbox->getXDisplay(), frame.handle);
923   } else if (frame.handle) {
924     destroyHandle();
925   }
926   XSync(blackbox->getXDisplay(), False);
927 }
928
929
930 void BlackboxWindow::updateStrut(void) {
931   unsigned long num = 4;
932   unsigned long *data;
933   if (! xatom->getValue(client.window, XAtom::net_wm_strut, XAtom::cardinal,
934                         num, &data))
935     return;
936  
937   if (num == 4) {
938     client.strut.left = data[0];
939     client.strut.right = data[1];
940     client.strut.top = data[2];
941     client.strut.bottom = data[3];
942
943     screen->updateAvailableArea();
944   }
945
946   delete [] data;
947 }
948
949
950 void BlackboxWindow::getWindowType(void) {
951   unsigned long val;
952   if (xatom->getValue(client.window, XAtom::net_wm_window_type, XAtom::atom,
953                       val)) {
954     if (val == xatom->getAtom(XAtom::net_wm_window_type_desktop))
955       window_type = Type_Desktop;
956     else if (val == xatom->getAtom(XAtom::net_wm_window_type_dock))
957       window_type = Type_Dock;
958     else if (val == xatom->getAtom(XAtom::net_wm_window_type_toolbar))
959       window_type = Type_Toolbar;
960     else if (val == xatom->getAtom(XAtom::net_wm_window_type_menu))
961       window_type = Type_Menu;
962     else if (val == xatom->getAtom(XAtom::net_wm_window_type_utility))
963       window_type = Type_Utility;
964     else if (val == xatom->getAtom(XAtom::net_wm_window_type_splash))
965       window_type = Type_Splash;
966     else if (val == xatom->getAtom(XAtom::net_wm_window_type_dialog))
967       window_type = Type_Dialog;
968     else //if (val[0] == xatom->getAtom(XAtom::net_wm_window_type_normal))
969       window_type = Type_Normal;
970     return;
971   }
972
973   /*
974    * the window type hint was not set, which means we either classify ourself
975    * as a normal window or a dialog, depending on if we are a transient.
976    */
977   if (isTransient())
978     window_type = Type_Dialog;
979
980   window_type = Type_Normal;
981 }
982
983
984 void BlackboxWindow::getWMName(void) {
985   if (xatom->getValue(client.window, XAtom::net_wm_name,
986                       XAtom::utf8, client.title) &&
987       !client.title.empty()) {
988     xatom->eraseValue(client.window, XAtom::net_wm_visible_name);
989     return;
990   }
991   //fall through to using WM_NAME
992   if (xatom->getValue(client.window, XAtom::wm_name, XAtom::ansi, client.title)
993       && !client.title.empty()) {
994     xatom->eraseValue(client.window, XAtom::net_wm_visible_name);
995     return;
996   }
997   // fall back to an internal default
998   client.title = i18n(WindowSet, WindowUnnamed, "Unnamed");
999   xatom->setValue(client.window, XAtom::net_wm_visible_name, XAtom::utf8,
1000                   client.title);
1001 }
1002
1003
1004 void BlackboxWindow::getWMIconName(void) {
1005   if (xatom->getValue(client.window, XAtom::net_wm_icon_name,
1006                       XAtom::utf8, client.icon_title) && 
1007       !client.icon_title.empty()) {
1008     xatom->eraseValue(client.window, XAtom::net_wm_visible_icon_name);
1009     return;
1010   }
1011   //fall through to using WM_ICON_NAME
1012   if (xatom->getValue(client.window, XAtom::wm_icon_name, XAtom::ansi,
1013                       client.icon_title) && 
1014       !client.icon_title.empty()) {
1015     xatom->eraseValue(client.window, XAtom::net_wm_visible_icon_name);
1016     return;
1017   }
1018   // fall back to using the main name
1019   client.icon_title = client.title;
1020   xatom->setValue(client.window, XAtom::net_wm_visible_icon_name, XAtom::utf8,
1021                   client.icon_title);
1022 }
1023
1024
1025 /*
1026  * Retrieve which WM Protocols are supported by the client window.
1027  * If the WM_DELETE_WINDOW protocol is supported, add the close button to the
1028  * window's decorations and allow the close behavior.
1029  * If the WM_TAKE_FOCUS protocol is supported, save a value that indicates
1030  * this.
1031  */
1032 void BlackboxWindow::getWMProtocols(void) {
1033   Atom *proto;
1034   int num_return = 0;
1035
1036   if (XGetWMProtocols(blackbox->getXDisplay(), client.window,
1037                       &proto, &num_return)) {
1038     for (int i = 0; i < num_return; ++i) {
1039       if (proto[i] == xatom->getAtom(XAtom::wm_delete_window)) {
1040         decorations |= Decor_Close;
1041         functions |= Func_Close;
1042       } else if (proto[i] == xatom->getAtom(XAtom::wm_take_focus))
1043         flags.send_focus_message = True;
1044       else if (proto[i] == xatom->getAtom(XAtom::blackbox_structure_messages))
1045         screen->addNetizen(new Netizen(screen, client.window));
1046     }
1047
1048     XFree(proto);
1049   }
1050 }
1051
1052
1053 /*
1054  * Gets the value of the WM_HINTS property.
1055  * If the property is not set, then use a set of default values.
1056  */
1057 void BlackboxWindow::getWMHints(void) {
1058   focus_mode = F_Passive;
1059
1060   // remove from current window group
1061   if (client.window_group) {
1062     BWindowGroup *group = blackbox->searchGroup(client.window_group);
1063     if (group) group->removeWindow(this);
1064   }
1065   client.window_group = None;
1066
1067   XWMHints *wmhint = XGetWMHints(blackbox->getXDisplay(), client.window);
1068   if (! wmhint) {
1069     return;
1070   }
1071
1072   if (wmhint->flags & InputHint) {
1073     if (wmhint->input == True) {
1074       if (flags.send_focus_message)
1075         focus_mode = F_LocallyActive;
1076     } else {
1077       if (flags.send_focus_message)
1078         focus_mode = F_GloballyActive;
1079       else
1080         focus_mode = F_NoInput;
1081     }
1082   }
1083
1084   if (wmhint->flags & StateHint)
1085     current_state = wmhint->initial_state;
1086
1087   if (wmhint->flags & WindowGroupHint) {
1088     client.window_group = wmhint->window_group;
1089
1090     // add window to the appropriate group
1091     BWindowGroup *group = blackbox->searchGroup(client.window_group);
1092     if (! group) { // no group found, create it!
1093       new BWindowGroup(blackbox, client.window_group);
1094       group = blackbox->searchGroup(client.window_group);
1095     }
1096     if (group)
1097       group->addWindow(this);
1098   }
1099
1100   XFree(wmhint);
1101 }
1102
1103
1104 /*
1105  * Gets the value of the WM_NORMAL_HINTS property.
1106  * If the property is not set, then use a set of default values.
1107  */
1108 void BlackboxWindow::getWMNormalHints(void) {
1109   long icccm_mask;
1110   XSizeHints sizehint;
1111
1112   client.min_width = client.min_height =
1113     client.width_inc = client.height_inc = 1;
1114   client.base_width = client.base_height = 0;
1115   client.win_gravity = NorthWestGravity;
1116 #if 0
1117   client.min_aspect_x = client.min_aspect_y =
1118     client.max_aspect_x = client.max_aspect_y = 1;
1119 #endif
1120
1121   /*
1122     use the full screen, not the strut modified size. otherwise when the
1123     availableArea changes max_width/height will be incorrect and lead to odd
1124     rendering bugs.
1125   */
1126   const Rect& screen_area = screen->getRect();
1127   client.max_width = screen_area.width();
1128   client.max_height = screen_area.height();
1129
1130   if (! XGetWMNormalHints(blackbox->getXDisplay(), client.window,
1131                           &sizehint, &icccm_mask))
1132     return;
1133
1134   client.normal_hint_flags = sizehint.flags;
1135
1136   if (sizehint.flags & PMinSize) {
1137     if (sizehint.min_width >= 0)
1138       client.min_width = sizehint.min_width;
1139     if (sizehint.min_height >= 0)
1140       client.min_height = sizehint.min_height;
1141   }
1142
1143   if (sizehint.flags & PMaxSize) {
1144     if (sizehint.max_width > static_cast<signed>(client.min_width))
1145       client.max_width = sizehint.max_width;
1146     else
1147       client.max_width = client.min_width;
1148
1149     if (sizehint.max_height > static_cast<signed>(client.min_height))
1150       client.max_height = sizehint.max_height;
1151     else
1152       client.max_height = client.min_height;
1153   }
1154
1155   if (sizehint.flags & PResizeInc) {
1156     client.width_inc = sizehint.width_inc;
1157     client.height_inc = sizehint.height_inc;
1158   }
1159
1160 #if 0 // we do not support this at the moment
1161   if (sizehint.flags & PAspect) {
1162     client.min_aspect_x = sizehint.min_aspect.x;
1163     client.min_aspect_y = sizehint.min_aspect.y;
1164     client.max_aspect_x = sizehint.max_aspect.x;
1165     client.max_aspect_y = sizehint.max_aspect.y;
1166   }
1167 #endif
1168
1169   if (sizehint.flags & PBaseSize) {
1170     client.base_width = sizehint.base_width;
1171     client.base_height = sizehint.base_height;
1172   }
1173
1174   if (sizehint.flags & PWinGravity)
1175     client.win_gravity = sizehint.win_gravity;
1176 }
1177
1178
1179 /*
1180  * Gets the NETWM hints for the class' contained window.
1181  */
1182 void BlackboxWindow::getNetWMHints(void) {
1183   unsigned long workspace;
1184
1185   if (xatom->getValue(client.window, XAtom::net_wm_desktop, XAtom::cardinal,
1186                       workspace)) {
1187     if (workspace == 0xffffffff)
1188       flags.stuck = True;
1189     else
1190       blackbox_attrib.workspace = workspace;
1191   }
1192
1193   unsigned long *state;
1194   unsigned long num = (unsigned) -1;
1195   if (xatom->getValue(client.window, XAtom::net_wm_state, XAtom::atom,
1196                       num, &state)) {
1197     bool vert = False,
1198          horz = False;
1199     for (unsigned long i = 0; i < num; ++i) {
1200       if (state[i] == xatom->getAtom(XAtom::net_wm_state_modal))
1201         flags.modal = True;
1202       else if (state[i] == xatom->getAtom(XAtom::net_wm_state_shaded))
1203         flags.shaded = True;
1204       else if (state[i] == xatom->getAtom(XAtom::net_wm_state_skip_taskbar))
1205         flags.skip_taskbar = True;
1206       else if (state[i] == xatom->getAtom(XAtom::net_wm_state_skip_pager))
1207         flags.skip_pager = True;
1208       else if (state[i] == xatom->getAtom(XAtom::net_wm_state_fullscreen))
1209         flags.fullscreen = True;
1210       else if (state[i] == xatom->getAtom(XAtom::net_wm_state_hidden))
1211         setState(IconicState);
1212       else if (state[i] == xatom->getAtom(XAtom::net_wm_state_maximized_vert))
1213         vert = True;
1214       else if (state[i] == xatom->getAtom(XAtom::net_wm_state_maximized_horz))
1215         horz = True;
1216     }
1217     if (vert && horz)
1218       flags.maximized = 1;
1219     else if (vert)
1220       flags.maximized = 2;
1221     else if (horz)
1222       flags.maximized = 3;
1223
1224     delete [] state;
1225   }
1226 }
1227
1228
1229 /*
1230  * Gets the MWM hints for the class' contained window.
1231  * This is used while initializing the window to its first state, and not
1232  * thereafter.
1233  * Returns: true if the MWM hints are successfully retreived and applied;
1234  * false if they are not.
1235  */
1236 void BlackboxWindow::getMWMHints(void) {
1237   unsigned long num;
1238   MwmHints *mwm_hint;
1239
1240   num = PropMwmHintsElements;
1241   if (! xatom->getValue(client.window, XAtom::motif_wm_hints,
1242                         XAtom::motif_wm_hints, num,
1243                         (unsigned long **)&mwm_hint))
1244     return;
1245   if (num < PropMwmHintsElements) {
1246     delete [] mwm_hint;
1247     return;
1248   }
1249
1250   if (mwm_hint->flags & MwmHintsDecorations) {
1251     if (mwm_hint->decorations & MwmDecorAll) {
1252       decorations = Decor_Titlebar | Decor_Handle | Decor_Border |
1253                     Decor_Iconify | Decor_Maximize | Decor_Close;
1254     } else {
1255       decorations = 0;
1256
1257       if (mwm_hint->decorations & MwmDecorBorder)
1258         decorations |= Decor_Border;
1259       if (mwm_hint->decorations & MwmDecorHandle)
1260         decorations |= Decor_Handle;
1261       if (mwm_hint->decorations & MwmDecorTitle)
1262         decorations |= Decor_Titlebar;
1263       if (mwm_hint->decorations & MwmDecorIconify)
1264         decorations |= Decor_Iconify;
1265       if (mwm_hint->decorations & MwmDecorMaximize)
1266         decorations |= Decor_Maximize;
1267     }
1268   }
1269
1270   if (mwm_hint->flags & MwmHintsFunctions) {
1271     if (mwm_hint->functions & MwmFuncAll) {
1272       functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize |
1273                   Func_Close;
1274     } else {
1275       functions = 0;
1276
1277       if (mwm_hint->functions & MwmFuncResize)
1278         functions |= Func_Resize;
1279       if (mwm_hint->functions & MwmFuncMove)
1280         functions |= Func_Move;
1281       if (mwm_hint->functions & MwmFuncIconify)
1282         functions |= Func_Iconify;
1283       if (mwm_hint->functions & MwmFuncMaximize)
1284         functions |= Func_Maximize;
1285       if (mwm_hint->functions & MwmFuncClose)
1286         functions |= Func_Close;
1287     }
1288   }
1289   delete [] mwm_hint;
1290 }
1291
1292
1293 /*
1294  * Gets the blackbox hints from the class' contained window.
1295  * This is used while initializing the window to its first state, and not
1296  * thereafter.
1297  * Returns: true if the hints are successfully retreived and applied; false if
1298  * they are not.
1299  */
1300 bool BlackboxWindow::getBlackboxHints(void) {
1301   unsigned long num;
1302   BlackboxHints *blackbox_hint;
1303
1304   num = PropBlackboxHintsElements;
1305   if (! xatom->getValue(client.window, XAtom::blackbox_hints,
1306                         XAtom::blackbox_hints, num,
1307                         (unsigned long **)&blackbox_hint))
1308     return False;
1309   if (num < PropBlackboxHintsElements) {
1310     delete [] blackbox_hint;
1311     return False;
1312   }
1313
1314   if (blackbox_hint->flags & AttribShaded)
1315     flags.shaded = (blackbox_hint->attrib & AttribShaded);
1316
1317   if ((blackbox_hint->flags & AttribMaxHoriz) &&
1318       (blackbox_hint->flags & AttribMaxVert))
1319     flags.maximized = (blackbox_hint->attrib &
1320                        (AttribMaxHoriz | AttribMaxVert)) ? 1 : 0;
1321   else if (blackbox_hint->flags & AttribMaxVert)
1322     flags.maximized = (blackbox_hint->attrib & AttribMaxVert) ? 2 : 0;
1323   else if (blackbox_hint->flags & AttribMaxHoriz)
1324     flags.maximized = (blackbox_hint->attrib & AttribMaxHoriz) ? 3 : 0;
1325
1326   if (blackbox_hint->flags & AttribOmnipresent)
1327     flags.stuck = (blackbox_hint->attrib & AttribOmnipresent);
1328
1329   if (blackbox_hint->flags & AttribWorkspace)
1330     blackbox_attrib.workspace = blackbox_hint->workspace;
1331
1332   // if (blackbox_hint->flags & AttribStack)
1333   //   don't yet have always on top/bottom for blackbox yet... working
1334   //   on that
1335
1336   if (blackbox_hint->flags & AttribDecoration) {
1337     switch (blackbox_hint->decoration) {
1338     case DecorNone:
1339       decorations = 0;
1340       break;
1341
1342     case DecorTiny:
1343       decorations |= Decor_Titlebar | Decor_Iconify;
1344       decorations &= ~(Decor_Border | Decor_Handle | Decor_Maximize);
1345       functions &= ~(Func_Resize | Func_Maximize);
1346
1347       break;
1348
1349     case DecorTool:
1350       decorations |= Decor_Titlebar;
1351       decorations &= ~(Decor_Iconify | Decor_Border | Decor_Handle);
1352       functions &= ~(Func_Resize | Func_Maximize | Func_Iconify);
1353
1354       break;
1355
1356     case DecorNormal:
1357     default:
1358       decorations |= Decor_Titlebar | Decor_Border | Decor_Handle |
1359                      Decor_Iconify | Decor_Maximize;
1360       break;
1361     }
1362
1363     reconfigure();
1364   }
1365   
1366   delete [] blackbox_hint;
1367
1368   return True;
1369 }
1370
1371
1372 void BlackboxWindow::getTransientInfo(void) {
1373   if (client.transient_for &&
1374       client.transient_for != (BlackboxWindow *) ~0ul) {
1375     // reset transient_for in preparation of looking for a new owner
1376     client.transient_for->client.transientList.remove(this);
1377   }
1378
1379   // we have no transient_for until we find a new one
1380   client.transient_for = 0;
1381
1382   Window trans_for;
1383   if (! XGetTransientForHint(blackbox->getXDisplay(), client.window,
1384                              &trans_for)) {
1385     // transient_for hint not set
1386     return;
1387   }
1388
1389   if (trans_for == client.window) {
1390     // wierd client... treat this window as a normal window
1391     return;
1392   }
1393
1394   if (trans_for == None || trans_for == screen->getRootWindow()) {
1395     // this is an undocumented interpretation of the ICCCM. a transient
1396     // associated with None/Root/itself is assumed to be a modal root
1397     // transient.  we don't support the concept of a global transient,
1398     // so we just associate this transient with nothing, and perhaps
1399     // we will add support later for global modality.
1400     client.transient_for = (BlackboxWindow *) ~0ul;
1401     flags.modal = True;
1402     return;
1403   }
1404
1405   client.transient_for = blackbox->searchWindow(trans_for);
1406   if (! client.transient_for &&
1407       client.window_group && trans_for == client.window_group) {
1408     // no direct transient_for, perhaps this is a group transient?
1409     BWindowGroup *group = blackbox->searchGroup(client.window_group);
1410     if (group) client.transient_for = group->find(screen);
1411   }
1412
1413   if (! client.transient_for || client.transient_for == this) {
1414     // no transient_for found, or we have a wierd client that wants to be
1415     // a transient for itself, so we treat this window as a normal window
1416     client.transient_for = (BlackboxWindow*) 0;
1417     return;
1418   }
1419
1420   // register ourselves with our new transient_for
1421   client.transient_for->client.transientList.push_back(this);
1422   flags.stuck = client.transient_for->flags.stuck;
1423 }
1424
1425
1426 BlackboxWindow *BlackboxWindow::getTransientFor(void) const {
1427   if (client.transient_for &&
1428       client.transient_for != (BlackboxWindow*) ~0ul)
1429     return client.transient_for;
1430   return 0;
1431 }
1432
1433
1434 /*
1435  * This function is responsible for updating both the client and the frame
1436  * rectangles.
1437  * According to the ICCCM a client message is not sent for a resize, only a
1438  * move.
1439  */
1440 void BlackboxWindow::configure(int dx, int dy,
1441                                unsigned int dw, unsigned int dh) {
1442   bool send_event = ((frame.rect.x() != dx || frame.rect.y() != dy) &&
1443                      ! flags.moving);
1444
1445   if (dw != frame.rect.width() || dh != frame.rect.height()) {
1446     frame.rect.setRect(dx, dy, dw, dh);
1447     frame.inside_w = frame.rect.width() - (frame.border_w * 2);
1448     frame.inside_h = frame.rect.height() - (frame.border_w * 2);
1449
1450     if (frame.rect.right() <= 0 || frame.rect.bottom() <= 0)
1451       frame.rect.setPos(0, 0);
1452
1453     client.rect.setCoords(frame.rect.left() + frame.margin.left,
1454                           frame.rect.top() + frame.margin.top,
1455                           frame.rect.right() - frame.margin.right,
1456                           frame.rect.bottom() - frame.margin.bottom);
1457
1458 #ifdef    SHAPE
1459     if (blackbox->hasShapeExtensions() && flags.shaped) {
1460       configureShape();
1461     }
1462 #endif // SHAPE
1463
1464     positionWindows();
1465     decorate();
1466     redrawWindowFrame();
1467   } else {
1468     frame.rect.setPos(dx, dy);
1469
1470     XMoveWindow(blackbox->getXDisplay(), frame.window,
1471                 frame.rect.x(), frame.rect.y());
1472     /*
1473       we may have been called just after an opaque window move, so even though
1474       the old coords match the new ones no ConfigureNotify has been sent yet.
1475       There are likely other times when this will be relevant as well.
1476     */
1477     if (! flags.moving) send_event = True;
1478   }
1479
1480   if (send_event) {
1481     // if moving, the update and event will occur when the move finishes
1482     client.rect.setPos(frame.rect.left() + frame.margin.left,
1483                        frame.rect.top() + frame.margin.top);
1484
1485     XEvent event;
1486     event.type = ConfigureNotify;
1487
1488     event.xconfigure.display = blackbox->getXDisplay();
1489     event.xconfigure.event = client.window;
1490     event.xconfigure.window = client.window;
1491     event.xconfigure.x = client.rect.x();
1492     event.xconfigure.y = client.rect.y();
1493     event.xconfigure.width = client.rect.width();
1494     event.xconfigure.height = client.rect.height();
1495     event.xconfigure.border_width = client.old_bw;
1496     event.xconfigure.above = frame.window;
1497     event.xconfigure.override_redirect = False;
1498
1499     XSendEvent(blackbox->getXDisplay(), client.window, False,
1500                StructureNotifyMask, &event);
1501     screen->updateNetizenConfigNotify(&event);
1502     XFlush(blackbox->getXDisplay());
1503   }
1504 }
1505
1506
1507 #ifdef SHAPE
1508 void BlackboxWindow::configureShape(void) {
1509   XShapeCombineShape(blackbox->getXDisplay(), frame.window, ShapeBounding,
1510                      frame.margin.left - frame.border_w,
1511                      frame.margin.top - frame.border_w,
1512                      client.window, ShapeBounding, ShapeSet);
1513
1514   int num = 0;
1515   XRectangle xrect[2];
1516
1517   if (decorations & Decor_Titlebar) {
1518     xrect[0].x = xrect[0].y = -frame.border_w;
1519     xrect[0].width = frame.rect.width();
1520     xrect[0].height = frame.title_h + (frame.border_w * 2);
1521     ++num;
1522   }
1523
1524   if (decorations & Decor_Handle) {
1525     xrect[1].x = -frame.border_w;
1526     xrect[1].y = frame.rect.height() - frame.margin.bottom +
1527                  frame.mwm_border_w - frame.border_w;
1528     xrect[1].width = frame.rect.width();
1529     xrect[1].height = frame.handle_h + (frame.border_w * 2);
1530     ++num;
1531   }
1532
1533   XShapeCombineRectangles(blackbox->getXDisplay(), frame.window,
1534                           ShapeBounding, 0, 0, xrect, num,
1535                           ShapeUnion, Unsorted);
1536 }
1537 #endif // SHAPE
1538
1539
1540 bool BlackboxWindow::setInputFocus(void) {
1541   if (flags.focused) return True;
1542
1543   assert(flags.stuck ||  // window must be on the current workspace or sticky
1544          blackbox_attrib.workspace == screen->getCurrentWorkspaceID());
1545
1546   /*
1547      We only do this check for normal windows and dialogs because other windows
1548      do this on purpose, such as kde's kicker, and we don't want to go moving
1549      it.
1550   */
1551   if (window_type == Type_Normal || window_type == Type_Dialog)
1552     if (! frame.rect.intersects(screen->getRect())) {
1553       // client is outside the screen, move it to the center
1554       configure((screen->getWidth() - frame.rect.width()) / 2,
1555                 (screen->getHeight() - frame.rect.height()) / 2,
1556                 frame.rect.width(), frame.rect.height());
1557     }
1558
1559   if (client.transientList.size() > 0) {
1560     // transfer focus to any modal transients
1561     BlackboxWindowList::iterator it, end = client.transientList.end();
1562     for (it = client.transientList.begin(); it != end; ++it) {
1563       if ((*it)->flags.modal) return (*it)->setInputFocus();
1564     }
1565   }
1566
1567   bool ret = True;
1568   if (focus_mode == F_LocallyActive || focus_mode == F_Passive) {
1569     XSetInputFocus(blackbox->getXDisplay(), client.window,
1570                    RevertToPointerRoot, CurrentTime);
1571   } else {
1572     /* we could set the focus to none, since the window doesn't accept focus,
1573      * but we shouldn't set focus to nothing since this would surely make
1574      * someone angry
1575      */
1576     ret = False;
1577   }
1578
1579   if (flags.send_focus_message) {
1580     XEvent ce;
1581     ce.xclient.type = ClientMessage;
1582     ce.xclient.message_type = xatom->getAtom(XAtom::wm_protocols);
1583     ce.xclient.display = blackbox->getXDisplay();
1584     ce.xclient.window = client.window;
1585     ce.xclient.format = 32;
1586     ce.xclient.data.l[0] = xatom->getAtom(XAtom::wm_take_focus);
1587     ce.xclient.data.l[1] = blackbox->getLastTime();
1588     ce.xclient.data.l[2] = 0l;
1589     ce.xclient.data.l[3] = 0l;
1590     ce.xclient.data.l[4] = 0l;
1591     XSendEvent(blackbox->getXDisplay(), client.window, False,
1592                NoEventMask, &ce);
1593     XFlush(blackbox->getXDisplay());
1594   }
1595
1596   return ret;
1597 }
1598
1599
1600 void BlackboxWindow::iconify(void) {
1601   if (flags.iconic) return;
1602
1603   // We don't need to worry about resizing because resizing always grabs the X
1604   // server. This should only ever happen if using opaque moving.
1605   if (flags.moving)
1606     endMove();
1607     
1608   if (windowmenu) windowmenu->hide();
1609
1610   /*
1611    * we don't want this XUnmapWindow call to generate an UnmapNotify event, so
1612    * we need to clear the event mask on client.window for a split second.
1613    * HOWEVER, since X11 is asynchronous, the window could be destroyed in that
1614    * split second, leaving us with a ghost window... so, we need to do this
1615    * while the X server is grabbed
1616    */
1617   unsigned long event_mask = PropertyChangeMask | FocusChangeMask |
1618                              StructureNotifyMask;
1619   XGrabServer(blackbox->getXDisplay());
1620   XSelectInput(blackbox->getXDisplay(), client.window,
1621                event_mask & ~StructureNotifyMask);
1622   XUnmapWindow(blackbox->getXDisplay(), client.window);
1623   XSelectInput(blackbox->getXDisplay(), client.window, event_mask);
1624   XUngrabServer(blackbox->getXDisplay());
1625
1626   XUnmapWindow(blackbox->getXDisplay(), frame.window);
1627   flags.visible = False;
1628   flags.iconic = True;
1629
1630   setState(IconicState);
1631
1632   screen->getWorkspace(blackbox_attrib.workspace)->removeWindow(this);
1633   if (flags.stuck) {
1634     for (unsigned int i = 0; i < screen->getNumberOfWorkspaces(); ++i)
1635       if (i != blackbox_attrib.workspace)
1636         screen->getWorkspace(i)->removeWindow(this, True);
1637   }
1638
1639   if (isTransient()) {
1640     if (client.transient_for != (BlackboxWindow *) ~0ul &&
1641         ! client.transient_for->flags.iconic) {
1642       // iconify our transient_for
1643       client.transient_for->iconify();
1644     }
1645   }
1646
1647   screen->addIcon(this);
1648
1649   if (client.transientList.size() > 0) {
1650     // iconify all transients
1651     BlackboxWindowList::iterator it, end = client.transientList.end();
1652     for (it = client.transientList.begin(); it != end; ++it) {
1653       if (! (*it)->flags.iconic) (*it)->iconify();
1654     }
1655   }
1656   screen->updateStackingList();
1657 }
1658
1659
1660 void BlackboxWindow::show(void) {
1661   flags.visible = True;
1662   flags.iconic = False;
1663
1664   current_state = (flags.shaded) ? IconicState : NormalState;
1665   setState(current_state);
1666
1667   XMapWindow(blackbox->getXDisplay(), client.window);
1668   XMapSubwindows(blackbox->getXDisplay(), frame.window);
1669   XMapWindow(blackbox->getXDisplay(), frame.window);
1670
1671 #if 0
1672   int real_x, real_y;
1673   Window child;
1674   XTranslateCoordinates(blackbox->getXDisplay(), client.window,
1675                         screen->getRootWindow(),
1676                         0, 0, &real_x, &real_y, &child);
1677   fprintf(stderr, "%s -- assumed: (%d, %d), real: (%d, %d)\n", getTitle(),
1678           client.rect.left(), client.rect.top(), real_x, real_y);
1679   assert(client.rect.left() == real_x && client.rect.top() == real_y);
1680 #endif
1681 }
1682
1683
1684 void BlackboxWindow::deiconify(bool reassoc, bool raise) {
1685   if (flags.iconic || reassoc)
1686     screen->reassociateWindow(this, BSENTINEL, False);
1687   else if (blackbox_attrib.workspace != screen->getCurrentWorkspaceID())
1688     return;
1689
1690   show();
1691
1692   // reassociate and deiconify all transients
1693   if (reassoc && client.transientList.size() > 0) {
1694     BlackboxWindowList::iterator it, end = client.transientList.end();
1695     for (it = client.transientList.begin(); it != end; ++it) {
1696       (*it)->deiconify(True, False);
1697     }
1698   }
1699
1700   if (raise)
1701     screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
1702 }
1703
1704
1705 void BlackboxWindow::close(void) {
1706   XEvent ce;
1707   ce.xclient.type = ClientMessage;
1708   ce.xclient.message_type =  xatom->getAtom(XAtom::wm_protocols);
1709   ce.xclient.display = blackbox->getXDisplay();
1710   ce.xclient.window = client.window;
1711   ce.xclient.format = 32;
1712   ce.xclient.data.l[0] = xatom->getAtom(XAtom::wm_delete_window);
1713   ce.xclient.data.l[1] = CurrentTime;
1714   ce.xclient.data.l[2] = 0l;
1715   ce.xclient.data.l[3] = 0l;
1716   ce.xclient.data.l[4] = 0l;
1717   XSendEvent(blackbox->getXDisplay(), client.window, False, NoEventMask, &ce);
1718   XFlush(blackbox->getXDisplay());
1719 }
1720
1721
1722 void BlackboxWindow::withdraw(void) {
1723   // We don't need to worry about resizing because resizing always grabs the X
1724   // server. This should only ever happen if using opaque moving.
1725   if (flags.moving)
1726     endMove();
1727     
1728   flags.visible = False;
1729   flags.iconic = False;
1730
1731   setState(current_state);
1732
1733   XUnmapWindow(blackbox->getXDisplay(), frame.window);
1734
1735   XGrabServer(blackbox->getXDisplay());
1736
1737   unsigned long event_mask = PropertyChangeMask | FocusChangeMask |
1738                              StructureNotifyMask;
1739   XSelectInput(blackbox->getXDisplay(), client.window,
1740                event_mask & ~StructureNotifyMask);
1741   XUnmapWindow(blackbox->getXDisplay(), client.window);
1742   XSelectInput(blackbox->getXDisplay(), client.window, event_mask);
1743
1744   XUngrabServer(blackbox->getXDisplay());
1745
1746   if (windowmenu) windowmenu->hide();
1747 }
1748
1749
1750 void BlackboxWindow::maximize(unsigned int button) {
1751   // We don't need to worry about resizing because resizing always grabs the X
1752   // server. This should only ever happen if using opaque moving.
1753   if (flags.moving)
1754     endMove();
1755
1756   // handle case where menu is open then the max button is used instead
1757   if (windowmenu && windowmenu->isVisible()) windowmenu->hide();
1758
1759   if (flags.maximized) {
1760     flags.maximized = 0;
1761
1762     blackbox_attrib.flags &= ! (AttribMaxHoriz | AttribMaxVert);
1763     blackbox_attrib.attrib &= ! (AttribMaxHoriz | AttribMaxVert);
1764
1765     /*
1766       when a resize finishes, maximize(0) is called to clear any maximization
1767       flags currently set.  Otherwise it still thinks it is maximized.
1768       so we do not need to call configure() because resizing will handle it
1769     */
1770     if (! flags.resizing)
1771       configure(blackbox_attrib.premax_x, blackbox_attrib.premax_y,
1772                 blackbox_attrib.premax_w, blackbox_attrib.premax_h);
1773
1774     blackbox_attrib.premax_x = blackbox_attrib.premax_y = 0;
1775     blackbox_attrib.premax_w = blackbox_attrib.premax_h = 0;
1776
1777     redrawAllButtons(); // in case it is not called in configure()
1778     setState(current_state);
1779     return;
1780   }
1781
1782   blackbox_attrib.premax_x = frame.rect.x();
1783   blackbox_attrib.premax_y = frame.rect.y();
1784   blackbox_attrib.premax_w = frame.rect.width();
1785   // use client.rect so that clients can be restored even if shaded
1786   blackbox_attrib.premax_h =
1787     client.rect.height() + frame.margin.top + frame.margin.bottom;
1788
1789 #ifdef    XINERAMA
1790   if (screen->isXineramaActive() && blackbox->doXineramaMaximizing()) {
1791     // find the area to use
1792     RectList availableAreas = screen->allAvailableAreas();
1793     RectList::iterator it, end = availableAreas.end();
1794
1795     for (it = availableAreas.begin(); it != end; ++it)
1796       if (it->intersects(frame.rect)) break;
1797     if (it == end) // the window isn't inside an area
1798       it = availableAreas.begin(); // so just default to the first one
1799
1800     frame.changing = *it;
1801   } else
1802 #endif // XINERAMA
1803   frame.changing = screen->availableArea();
1804
1805   switch(button) {
1806   case 1:
1807     blackbox_attrib.flags |= AttribMaxHoriz | AttribMaxVert;
1808     blackbox_attrib.attrib |= AttribMaxHoriz | AttribMaxVert;
1809     break;
1810
1811   case 2:
1812     blackbox_attrib.flags |= AttribMaxVert;
1813     blackbox_attrib.attrib |= AttribMaxVert;
1814
1815     frame.changing.setX(frame.rect.x());
1816     frame.changing.setWidth(frame.rect.width());
1817     break;
1818
1819   case 3:
1820     blackbox_attrib.flags |= AttribMaxHoriz;
1821     blackbox_attrib.attrib |= AttribMaxHoriz;
1822
1823     frame.changing.setY(frame.rect.y());
1824     frame.changing.setHeight(frame.rect.height());
1825     break;
1826   }
1827
1828   constrain(TopLeft);
1829
1830   if (flags.shaded) {
1831     blackbox_attrib.flags ^= AttribShaded;
1832     blackbox_attrib.attrib ^= AttribShaded;
1833     flags.shaded = False;
1834   }
1835
1836   flags.maximized = button;
1837
1838   configure(frame.changing.x(), frame.changing.y(),
1839             frame.changing.width(), frame.changing.height());
1840   if (flags.focused)
1841     screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
1842   redrawAllButtons(); // in case it is not called in configure()
1843   setState(current_state);
1844 }
1845
1846
1847 // re-maximizes the window to take into account availableArea changes
1848 void BlackboxWindow::remaximize(void) {
1849   if (flags.shaded) {
1850     // we only update the window's attributes otherwise we lose the shade bit
1851     switch(flags.maximized) {
1852     case 1:
1853       blackbox_attrib.flags |= AttribMaxHoriz | AttribMaxVert;
1854       blackbox_attrib.attrib |= AttribMaxHoriz | AttribMaxVert;
1855       break;
1856
1857     case 2:
1858       blackbox_attrib.flags |= AttribMaxVert;
1859       blackbox_attrib.attrib |= AttribMaxVert;
1860       break;
1861
1862     case 3:
1863       blackbox_attrib.flags |= AttribMaxHoriz;
1864       blackbox_attrib.attrib |= AttribMaxHoriz;
1865       break;
1866     }
1867     return;
1868   }
1869
1870   // save the original dimensions because maximize will wipe them out
1871   int premax_x = blackbox_attrib.premax_x,
1872     premax_y = blackbox_attrib.premax_y,
1873     premax_w = blackbox_attrib.premax_w,
1874     premax_h = blackbox_attrib.premax_h;
1875
1876   unsigned int button = flags.maximized;
1877   flags.maximized = 0; // trick maximize() into working
1878   maximize(button);
1879
1880   // restore saved values
1881   blackbox_attrib.premax_x = premax_x;
1882   blackbox_attrib.premax_y = premax_y;
1883   blackbox_attrib.premax_w = premax_w;
1884   blackbox_attrib.premax_h = premax_h;
1885 }
1886
1887
1888 void BlackboxWindow::setWorkspace(unsigned int n) {
1889   blackbox_attrib.flags |= AttribWorkspace;
1890   blackbox_attrib.workspace = n;
1891   if (n == BSENTINEL) { // iconified window
1892     /*
1893        we set the workspace to 'all workspaces' so that taskbars will show the
1894        window. otherwise, it made uniconifying a window imposible without the
1895        blackbox workspace menu
1896     */
1897     n = 0xffffffff;
1898   }
1899   xatom->setValue(client.window, XAtom::net_wm_desktop, XAtom::cardinal, n);
1900 }
1901
1902
1903 void BlackboxWindow::shade(void) {
1904   if (flags.shaded) {
1905     XResizeWindow(blackbox->getXDisplay(), frame.window,
1906                   frame.inside_w, frame.inside_h);
1907     flags.shaded = False;
1908     blackbox_attrib.flags ^= AttribShaded;
1909     blackbox_attrib.attrib ^= AttribShaded;
1910
1911     setState(NormalState);
1912
1913     // set the frame rect to the normal size
1914     frame.rect.setHeight(client.rect.height() + frame.margin.top +
1915                          frame.margin.bottom);
1916   } else {
1917     if (! (decorations & Decor_Titlebar))
1918       return; // can't shade it without a titlebar!
1919
1920     XResizeWindow(blackbox->getXDisplay(), frame.window,
1921                   frame.inside_w, frame.title_h);
1922     flags.shaded = True;
1923     blackbox_attrib.flags |= AttribShaded;
1924     blackbox_attrib.attrib |= AttribShaded;
1925
1926     setState(IconicState);
1927
1928     // set the frame rect to the shaded size
1929     frame.rect.setHeight(frame.title_h + (frame.border_w * 2));
1930   }
1931 }
1932
1933
1934 /*
1935  * (Un)Sticks a window and its relatives.
1936  */
1937 void BlackboxWindow::stick(void) {
1938   if (flags.stuck) {
1939     blackbox_attrib.flags ^= AttribOmnipresent;
1940     blackbox_attrib.attrib ^= AttribOmnipresent;
1941
1942     flags.stuck = False;
1943     
1944     for (unsigned int i = 0; i < screen->getNumberOfWorkspaces(); ++i)
1945       if (i != blackbox_attrib.workspace)
1946         screen->getWorkspace(i)->removeWindow(this, True);
1947
1948     if (! flags.iconic)
1949       screen->reassociateWindow(this, BSENTINEL, True);
1950     // temporary fix since sticky windows suck. set the hint to what we
1951     // actually hold in our data.
1952     xatom->setValue(client.window, XAtom::net_wm_desktop, XAtom::cardinal,
1953                     blackbox_attrib.workspace);
1954
1955     setState(current_state);
1956   } else {
1957     flags.stuck = True;
1958
1959     blackbox_attrib.flags |= AttribOmnipresent;
1960     blackbox_attrib.attrib |= AttribOmnipresent;
1961
1962     // temporary fix since sticky windows suck. set the hint to a different
1963     // value than that contained in the class' data.
1964     xatom->setValue(client.window, XAtom::net_wm_desktop, XAtom::cardinal,
1965                     0xffffffff);
1966     
1967     for (unsigned int i = 0; i < screen->getNumberOfWorkspaces(); ++i)
1968       if (i != blackbox_attrib.workspace)
1969         screen->getWorkspace(i)->addWindow(this, False, True);
1970
1971     setState(current_state);
1972   }
1973   // go up the chain
1974   if (isTransient() && client.transient_for != (BlackboxWindow *) ~0ul &&
1975       client.transient_for->isStuck() != flags.stuck)
1976     client.transient_for->stick();
1977   // go down the chain
1978   BlackboxWindowList::iterator it;
1979   const BlackboxWindowList::iterator end = client.transientList.end();
1980   for (it = client.transientList.begin(); it != end; ++it)
1981     if ((*it)->isStuck() != flags.stuck)
1982       (*it)->stick();
1983 }
1984
1985
1986 void BlackboxWindow::redrawWindowFrame(void) const {
1987   if (decorations & Decor_Titlebar) {
1988     if (flags.focused) {
1989       if (frame.ftitle)
1990         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
1991                                    frame.title, frame.ftitle);
1992       else
1993         XSetWindowBackground(blackbox->getXDisplay(),
1994                              frame.title, frame.ftitle_pixel);
1995     } else {
1996       if (frame.utitle)
1997         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
1998                                    frame.title, frame.utitle);
1999       else
2000         XSetWindowBackground(blackbox->getXDisplay(),
2001                              frame.title, frame.utitle_pixel);
2002     }
2003     XClearWindow(blackbox->getXDisplay(), frame.title);
2004
2005     redrawLabel();
2006     redrawAllButtons();
2007   }
2008
2009   if (decorations & Decor_Handle) {
2010     if (flags.focused) {
2011       if (frame.fhandle)
2012         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2013                                    frame.handle, frame.fhandle);
2014       else
2015         XSetWindowBackground(blackbox->getXDisplay(),
2016                              frame.handle, frame.fhandle_pixel);
2017
2018       if (frame.fgrip) {
2019         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2020                                    frame.left_grip, frame.fgrip);
2021         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2022                                    frame.right_grip, frame.fgrip);
2023       } else {
2024         XSetWindowBackground(blackbox->getXDisplay(),
2025                              frame.left_grip, frame.fgrip_pixel);
2026         XSetWindowBackground(blackbox->getXDisplay(),
2027                              frame.right_grip, frame.fgrip_pixel);
2028       }
2029     } else {
2030       if (frame.uhandle)
2031         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2032                                    frame.handle, frame.uhandle);
2033       else
2034         XSetWindowBackground(blackbox->getXDisplay(),
2035                              frame.handle, frame.uhandle_pixel);
2036
2037       if (frame.ugrip) {
2038         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2039                                    frame.left_grip, frame.ugrip);
2040         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2041                                    frame.right_grip, frame.ugrip);
2042       } else {
2043         XSetWindowBackground(blackbox->getXDisplay(),
2044                              frame.left_grip, frame.ugrip_pixel);
2045         XSetWindowBackground(blackbox->getXDisplay(),
2046                              frame.right_grip, frame.ugrip_pixel);
2047       }
2048     }
2049     XClearWindow(blackbox->getXDisplay(), frame.handle);
2050     XClearWindow(blackbox->getXDisplay(), frame.left_grip);
2051     XClearWindow(blackbox->getXDisplay(), frame.right_grip);
2052   }
2053
2054   if (decorations & Decor_Border) {
2055     if (flags.focused)
2056       XSetWindowBorder(blackbox->getXDisplay(),
2057                        frame.plate, frame.fborder_pixel);
2058     else
2059       XSetWindowBorder(blackbox->getXDisplay(),
2060                        frame.plate, frame.uborder_pixel);
2061   }
2062 }
2063
2064
2065 void BlackboxWindow::setFocusFlag(bool focus) {
2066   // only focus a window if it is visible
2067   if (focus && !flags.visible)
2068     return;
2069
2070   flags.focused = focus;
2071
2072   redrawWindowFrame();
2073
2074   if (screen->isSloppyFocus() && screen->doAutoRaise()) {
2075     if (isFocused()) timer->start();
2076     else timer->stop();
2077   }
2078
2079   if (flags.focused)
2080     blackbox->setFocusedWindow(this);
2081  
2082   if (! flags.iconic) {
2083     // iconic windows arent in a workspace menu!
2084     if (flags.stuck)
2085       screen->getCurrentWorkspace()->setFocused(this, isFocused());
2086     else
2087       screen->getWorkspace(blackbox_attrib.workspace)->
2088         setFocused(this, flags.focused);
2089   }
2090 }
2091
2092
2093 void BlackboxWindow::installColormap(bool install) {
2094   int i = 0, ncmap = 0;
2095   Colormap *cmaps = XListInstalledColormaps(blackbox->getXDisplay(),
2096                                             client.window, &ncmap);
2097   if (cmaps) {
2098     XWindowAttributes wattrib;
2099     if (XGetWindowAttributes(blackbox->getXDisplay(),
2100                              client.window, &wattrib)) {
2101       if (install) {
2102         // install the window's colormap
2103         for (i = 0; i < ncmap; i++) {
2104           if (*(cmaps + i) == wattrib.colormap)
2105             // this window is using an installed color map... do not install
2106             install = False;
2107         }
2108         // otherwise, install the window's colormap
2109         if (install)
2110           XInstallColormap(blackbox->getXDisplay(), wattrib.colormap);
2111       } else {
2112         // uninstall the window's colormap
2113         for (i = 0; i < ncmap; i++) {
2114           if (*(cmaps + i) == wattrib.colormap)
2115             // we found the colormap to uninstall
2116             XUninstallColormap(blackbox->getXDisplay(), wattrib.colormap);
2117         }
2118       }
2119     }
2120
2121     XFree(cmaps);
2122   }
2123 }
2124
2125
2126 void BlackboxWindow::setAllowedActions(void) {
2127   Atom actions[7];
2128   int num = 0;
2129   
2130   actions[num++] = xatom->getAtom(XAtom::net_wm_action_shade);
2131   actions[num++] = xatom->getAtom(XAtom::net_wm_action_change_desktop);
2132   actions[num++] = xatom->getAtom(XAtom::net_wm_action_close);
2133
2134   if (functions & Func_Move)
2135     actions[num++] = xatom->getAtom(XAtom::net_wm_action_move);
2136   if (functions & Func_Resize)
2137     actions[num++] = xatom->getAtom(XAtom::net_wm_action_resize);
2138   if (functions & Func_Maximize) {
2139     actions[num++] = xatom->getAtom(XAtom::net_wm_action_maximize_horz);
2140     actions[num++] = xatom->getAtom(XAtom::net_wm_action_maximize_vert);
2141   }
2142
2143   xatom->setValue(client.window, XAtom::net_wm_allowed_actions, XAtom::atom,
2144                   actions, num);
2145 }
2146
2147
2148 void BlackboxWindow::setState(unsigned long new_state) {
2149   current_state = new_state;
2150
2151   unsigned long state[2];
2152   state[0] = current_state;
2153   state[1] = None;
2154   xatom->setValue(client.window, XAtom::wm_state, XAtom::wm_state, state, 2);
2155  
2156   xatom->setValue(client.window, XAtom::blackbox_attributes,
2157                   XAtom::blackbox_attributes, (unsigned long *)&blackbox_attrib,
2158                   PropBlackboxAttributesElements);
2159
2160   Atom netstate[8];
2161   int num = 0;
2162   if (flags.modal)
2163     netstate[num++] = xatom->getAtom(XAtom::net_wm_state_modal);
2164   if (flags.shaded)
2165     netstate[num++] = xatom->getAtom(XAtom::net_wm_state_shaded);
2166   if (flags.iconic)
2167     netstate[num++] = xatom->getAtom(XAtom::net_wm_state_hidden);
2168   if (flags.skip_taskbar)
2169     netstate[num++] = xatom->getAtom(XAtom::net_wm_state_skip_taskbar);
2170   if (flags.skip_pager)
2171     netstate[num++] = xatom->getAtom(XAtom::net_wm_state_skip_pager);
2172   if (flags.fullscreen)
2173     netstate[num++] = xatom->getAtom(XAtom::net_wm_state_fullscreen);
2174   if (flags.maximized == 1 || flags.maximized == 2)
2175     netstate[num++] = xatom->getAtom(XAtom::net_wm_state_maximized_vert);
2176   if (flags.maximized == 1 || flags.maximized == 3)
2177     netstate[num++] = xatom->getAtom(XAtom::net_wm_state_maximized_horz);
2178   xatom->setValue(client.window, XAtom::net_wm_state, XAtom::atom,
2179                   netstate, num);
2180 }
2181
2182
2183 bool BlackboxWindow::getState(void) {
2184   bool ret = xatom->getValue(client.window, XAtom::wm_state, XAtom::wm_state,
2185                              current_state);
2186   if (! ret) current_state = 0;
2187   return ret;
2188 }
2189
2190
2191 void BlackboxWindow::restoreAttributes(void) {
2192   unsigned long num = PropBlackboxAttributesElements;
2193   BlackboxAttributes *net;
2194   if (! xatom->getValue(client.window, XAtom::blackbox_attributes,
2195                         XAtom::blackbox_attributes, num,
2196                         (unsigned long **)&net))
2197     return;
2198   if (num < PropBlackboxAttributesElements) {
2199     delete [] net;
2200     return;
2201   }
2202
2203   if (net->flags & AttribShaded && net->attrib & AttribShaded) {
2204     flags.shaded = False;
2205     unsigned long orig_state = current_state;
2206     shade();
2207
2208     /*
2209       At this point in the life of a window, current_state should only be set
2210       to IconicState if the window was an *icon*, not if it was shaded.
2211     */
2212     if (orig_state != IconicState)
2213       current_state = WithdrawnState;
2214  }
2215
2216   if (net->workspace != screen->getCurrentWorkspaceID() &&
2217       net->workspace < screen->getWorkspaceCount())
2218     screen->reassociateWindow(this, net->workspace, True);
2219
2220   if ((blackbox_attrib.workspace != screen->getCurrentWorkspaceID()) &&
2221       (blackbox_attrib.workspace < screen->getWorkspaceCount())) {
2222     // set to WithdrawnState so it will be mapped on the new workspace
2223     if (current_state == NormalState) current_state = WithdrawnState;
2224   } else if (current_state == WithdrawnState) {
2225     // the window is on this workspace and is Withdrawn, so it is waiting to
2226     // be mapped
2227     current_state = NormalState;
2228   }
2229
2230   if (net->flags & AttribOmnipresent && net->attrib & AttribOmnipresent &&
2231       ! flags.stuck) {
2232     stick();
2233
2234     // if the window was on another workspace, it was going to be hidden. this
2235     // specifies that the window should be mapped since it is sticky.
2236     if (current_state == WithdrawnState) current_state = NormalState;
2237   }
2238
2239   if (net->flags & AttribMaxHoriz || net->flags & AttribMaxVert) {
2240     int x = net->premax_x, y = net->premax_y;
2241     unsigned int w = net->premax_w, h = net->premax_h;
2242     flags.maximized = 0;
2243
2244     unsigned int m = 0;
2245     if ((net->flags & AttribMaxHoriz) &&
2246         (net->flags & AttribMaxVert))
2247       m = (net->attrib & (AttribMaxHoriz | AttribMaxVert)) ? 1 : 0;
2248     else if (net->flags & AttribMaxVert)
2249       m = (net->attrib & AttribMaxVert) ? 2 : 0;
2250     else if (net->flags & AttribMaxHoriz)
2251       m = (net->attrib & AttribMaxHoriz) ? 3 : 0;
2252
2253     if (m) maximize(m);
2254
2255     blackbox_attrib.premax_x = x;
2256     blackbox_attrib.premax_y = y;
2257     blackbox_attrib.premax_w = w;
2258     blackbox_attrib.premax_h = h;
2259   }
2260
2261   if (net->flags & AttribDecoration) {
2262     switch (net->decoration) {
2263     case DecorNone:
2264       decorations = 0;
2265
2266       break;
2267
2268     default:
2269     case DecorNormal:
2270       decorations |= Decor_Titlebar | Decor_Handle | Decor_Border |
2271         Decor_Iconify | Decor_Maximize;
2272
2273       break;
2274
2275     case DecorTiny:
2276       decorations |= Decor_Titlebar | Decor_Iconify;
2277       decorations &= ~(Decor_Border | Decor_Handle | Decor_Maximize);
2278
2279       break;
2280
2281     case DecorTool:
2282       decorations |= Decor_Titlebar;
2283       decorations &= ~(Decor_Iconify | Decor_Border | Decor_Handle);
2284
2285       break;
2286     }
2287
2288     // sanity check the new decor
2289     if (! (functions & Func_Resize) || isTransient())
2290       decorations &= ~(Decor_Maximize | Decor_Handle);
2291     if (! (functions & Func_Maximize))
2292       decorations &= ~Decor_Maximize;
2293
2294     if (decorations & Decor_Titlebar) {
2295       if (functions & Func_Close)   // close button is controlled by function
2296         decorations |= Decor_Close; // not decor type
2297     } else { 
2298       if (flags.shaded) // we can not be shaded if we lack a titlebar
2299         shade();
2300     }
2301
2302     if (flags.visible && frame.window) {
2303       XMapSubwindows(blackbox->getXDisplay(), frame.window);
2304       XMapWindow(blackbox->getXDisplay(), frame.window);
2305     }
2306
2307     reconfigure();
2308     setState(current_state);
2309   }
2310
2311   // with the state set it will then be the map event's job to read the
2312   // window's state and behave accordingly
2313
2314   delete [] net;
2315 }
2316
2317
2318 /*
2319  * Positions the Rect r according the the client window position and
2320  * window gravity.
2321  */
2322 void BlackboxWindow::applyGravity(Rect &r) {
2323   // apply horizontal window gravity
2324   switch (client.win_gravity) {
2325   default:
2326   case NorthWestGravity:
2327   case SouthWestGravity:
2328   case WestGravity:
2329     r.setX(client.rect.x());
2330     break;
2331
2332   case NorthGravity:
2333   case SouthGravity:
2334   case CenterGravity:
2335     r.setX(client.rect.x() - (frame.margin.left + frame.margin.right) / 2);
2336     break;
2337
2338   case NorthEastGravity:
2339   case SouthEastGravity:
2340   case EastGravity:
2341     r.setX(client.rect.x() - frame.margin.left - frame.margin.right + 2);
2342     break;
2343
2344   case ForgetGravity:
2345   case StaticGravity:
2346     r.setX(client.rect.x() - frame.margin.left);
2347     break;
2348   }
2349
2350   // apply vertical window gravity
2351   switch (client.win_gravity) {
2352   default:
2353   case NorthWestGravity:
2354   case NorthEastGravity:
2355   case NorthGravity:
2356     r.setY(client.rect.y());
2357     break;
2358
2359   case CenterGravity:
2360   case EastGravity:
2361   case WestGravity:
2362     r.setY(client.rect.y() - (frame.margin.top + frame.margin.bottom) / 2);
2363     break;
2364
2365   case SouthWestGravity:
2366   case SouthEastGravity:
2367   case SouthGravity:
2368     r.setY(client.rect.y() - frame.margin.top - frame.margin.bottom + 2);
2369     break;
2370
2371   case ForgetGravity:
2372   case StaticGravity:
2373     r.setY(client.rect.y() - frame.margin.top);
2374     break;
2375   }
2376 }
2377
2378
2379 /*
2380  * The reverse of the applyGravity function.
2381  *
2382  * Positions the Rect r according to the frame window position and
2383  * window gravity.
2384  */
2385 void BlackboxWindow::restoreGravity(Rect &r) {
2386   // restore horizontal window gravity
2387   switch (client.win_gravity) {
2388   default:
2389   case NorthWestGravity:
2390   case SouthWestGravity:
2391   case WestGravity:
2392     r.setX(frame.rect.x());
2393     break;
2394
2395   case NorthGravity:
2396   case SouthGravity:
2397   case CenterGravity:
2398     r.setX(frame.rect.x() + (frame.margin.left + frame.margin.right) / 2);
2399     break;
2400
2401   case NorthEastGravity:
2402   case SouthEastGravity:
2403   case EastGravity:
2404     r.setX(frame.rect.x() + frame.margin.left + frame.margin.right - 2);
2405     break;
2406
2407   case ForgetGravity:
2408   case StaticGravity:
2409     r.setX(frame.rect.x() + frame.margin.left);
2410     break;
2411   }
2412
2413   // restore vertical window gravity
2414   switch (client.win_gravity) {
2415   default:
2416   case NorthWestGravity:
2417   case NorthEastGravity:
2418   case NorthGravity:
2419     r.setY(frame.rect.y());
2420     break;
2421
2422   case CenterGravity:
2423   case EastGravity:
2424   case WestGravity:
2425     r.setY(frame.rect.y() + (frame.margin.top + frame.margin.bottom) / 2);
2426     break;
2427
2428   case SouthWestGravity:
2429   case SouthEastGravity:
2430   case SouthGravity:
2431     r.setY(frame.rect.y() + frame.margin.top + frame.margin.bottom - 2);
2432     break;
2433
2434   case ForgetGravity:
2435   case StaticGravity:
2436     r.setY(frame.rect.y() + frame.margin.top);
2437     break;
2438   }
2439 }
2440
2441
2442 void BlackboxWindow::redrawLabel(void) const {
2443   if (flags.focused) {
2444     if (frame.flabel)
2445       XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2446                                  frame.label, frame.flabel);
2447     else
2448       XSetWindowBackground(blackbox->getXDisplay(),
2449                            frame.label, frame.flabel_pixel);
2450   } else {
2451     if (frame.ulabel)
2452       XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2453                                  frame.label, frame.ulabel);
2454     else
2455       XSetWindowBackground(blackbox->getXDisplay(),
2456                            frame.label, frame.ulabel_pixel);
2457   }
2458   XClearWindow(blackbox->getXDisplay(), frame.label);
2459
2460   WindowStyle *style = screen->getWindowStyle();
2461
2462   int pos = frame.bevel_w * 2;
2463   style->doJustify(client.title.c_str(), pos, frame.label_w, frame.bevel_w * 4);
2464   style->font->drawString(frame.label, pos, 1,
2465                           (flags.focused ? style->l_text_focus :
2466                            style->l_text_unfocus),
2467                           client.title);
2468 }
2469
2470
2471 void BlackboxWindow::redrawAllButtons(void) const {
2472   if (frame.iconify_button) redrawIconifyButton(False);
2473   if (frame.maximize_button) redrawMaximizeButton(flags.maximized);
2474   if (frame.close_button) redrawCloseButton(False);
2475 }
2476
2477
2478 void BlackboxWindow::redrawIconifyButton(bool pressed) const {
2479   if (! pressed) {
2480     if (flags.focused) {
2481       if (frame.fbutton)
2482         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2483                                    frame.iconify_button, frame.fbutton);
2484       else
2485         XSetWindowBackground(blackbox->getXDisplay(),
2486                              frame.iconify_button, frame.fbutton_pixel);
2487     } else {
2488       if (frame.ubutton)
2489         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2490                                    frame.iconify_button, frame.ubutton);
2491       else
2492         XSetWindowBackground(blackbox->getXDisplay(), frame.iconify_button,
2493                              frame.ubutton_pixel);
2494     }
2495   } else {
2496     if (frame.pbutton)
2497       XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2498                                  frame.iconify_button, frame.pbutton);
2499     else
2500       XSetWindowBackground(blackbox->getXDisplay(),
2501                            frame.iconify_button, frame.pbutton_pixel);
2502   }
2503   XClearWindow(blackbox->getXDisplay(), frame.iconify_button);
2504
2505   BPen pen((flags.focused) ? screen->getWindowStyle()->b_pic_focus :
2506            screen->getWindowStyle()->b_pic_unfocus);
2507   XDrawRectangle(blackbox->getXDisplay(), frame.iconify_button, pen.gc(),
2508                  2, (frame.button_w - 5), (frame.button_w - 5), 2);
2509 }
2510
2511
2512 void BlackboxWindow::redrawMaximizeButton(bool pressed) const {
2513   if (! pressed) {
2514     if (flags.focused) {
2515       if (frame.fbutton)
2516         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2517                                    frame.maximize_button, frame.fbutton);
2518       else
2519         XSetWindowBackground(blackbox->getXDisplay(), frame.maximize_button,
2520                              frame.fbutton_pixel);
2521     } else {
2522       if (frame.ubutton)
2523         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2524                                    frame.maximize_button, frame.ubutton);
2525       else
2526         XSetWindowBackground(blackbox->getXDisplay(), frame.maximize_button,
2527                              frame.ubutton_pixel);
2528     }
2529   } else {
2530     if (frame.pbutton)
2531       XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2532                                  frame.maximize_button, frame.pbutton);
2533     else
2534       XSetWindowBackground(blackbox->getXDisplay(), frame.maximize_button,
2535                            frame.pbutton_pixel);
2536   }
2537   XClearWindow(blackbox->getXDisplay(), frame.maximize_button);
2538
2539   BPen pen((flags.focused) ? screen->getWindowStyle()->b_pic_focus :
2540            screen->getWindowStyle()->b_pic_unfocus);
2541   XDrawRectangle(blackbox->getXDisplay(), frame.maximize_button, pen.gc(),
2542                  2, 2, (frame.button_w - 5), (frame.button_w - 5));
2543   XDrawLine(blackbox->getXDisplay(), frame.maximize_button, pen.gc(),
2544             2, 3, (frame.button_w - 3), 3);
2545 }
2546
2547
2548 void BlackboxWindow::redrawCloseButton(bool pressed) const {
2549   if (! pressed) {
2550     if (flags.focused) {
2551       if (frame.fbutton)
2552         XSetWindowBackgroundPixmap(blackbox->getXDisplay(), frame.close_button,
2553                                    frame.fbutton);
2554       else
2555         XSetWindowBackground(blackbox->getXDisplay(), frame.close_button,
2556                              frame.fbutton_pixel);
2557     } else {
2558       if (frame.ubutton)
2559         XSetWindowBackgroundPixmap(blackbox->getXDisplay(), frame.close_button,
2560                                    frame.ubutton);
2561       else
2562         XSetWindowBackground(blackbox->getXDisplay(), frame.close_button,
2563                              frame.ubutton_pixel);
2564     }
2565   } else {
2566     if (frame.pbutton)
2567       XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2568                                  frame.close_button, frame.pbutton);
2569     else
2570       XSetWindowBackground(blackbox->getXDisplay(),
2571                            frame.close_button, frame.pbutton_pixel);
2572   }
2573   XClearWindow(blackbox->getXDisplay(), frame.close_button);
2574
2575   BPen pen((flags.focused) ? screen->getWindowStyle()->b_pic_focus :
2576            screen->getWindowStyle()->b_pic_unfocus);
2577   XDrawLine(blackbox->getXDisplay(), frame.close_button, pen.gc(),
2578             2, 2, (frame.button_w - 3), (frame.button_w - 3));
2579   XDrawLine(blackbox->getXDisplay(), frame.close_button, pen.gc(),
2580             2, (frame.button_w - 3), (frame.button_w - 3), 2);
2581 }
2582
2583
2584 void BlackboxWindow::mapRequestEvent(const XMapRequestEvent *re) {
2585   if (re->window != client.window)
2586     return;
2587
2588 #ifdef    DEBUG
2589   fprintf(stderr, "BlackboxWindow::mapRequestEvent() for 0x%lx\n",
2590           client.window);
2591 #endif // DEBUG
2592
2593   switch (current_state) {
2594   case IconicState:
2595     iconify();
2596     break;
2597
2598   case WithdrawnState:
2599     withdraw();
2600     break;
2601
2602   case NormalState:
2603   case InactiveState:
2604   case ZoomState:
2605   default:
2606     show();
2607     screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
2608     if (isNormal()) {
2609       if (! blackbox->isStartup()) {
2610         XSync(blackbox->getXDisplay(), False); // make sure the frame is mapped
2611         if (screen->doFocusNew()|| (isTransient() && getTransientFor() &&
2612                                     getTransientFor()->isFocused())) {
2613           setInputFocus();
2614         }
2615         if (screen->getPlacementPolicy() == BScreen::ClickMousePlacement) {
2616           int x, y, rx, ry;
2617           Window c, r;
2618           unsigned int m;
2619           XQueryPointer(blackbox->getXDisplay(), screen->getRootWindow(),
2620                         &r, &c, &rx, &ry, &x, &y, &m);
2621           beginMove(rx, ry);
2622         }
2623       }
2624     }
2625     break;
2626   }
2627 }
2628
2629
2630 void BlackboxWindow::unmapNotifyEvent(const XUnmapEvent *ue) {
2631   if (ue->window != client.window)
2632     return;
2633
2634 #ifdef    DEBUG
2635   fprintf(stderr, "BlackboxWindow::unmapNotifyEvent() for 0x%lx\n",
2636           client.window);
2637 #endif // DEBUG
2638
2639   screen->unmanageWindow(this, False);
2640 }
2641
2642
2643 void BlackboxWindow::destroyNotifyEvent(const XDestroyWindowEvent *de) {
2644   if (de->window != client.window)
2645     return;
2646
2647 #ifdef    DEBUG
2648   fprintf(stderr, "BlackboxWindow::destroyNotifyEvent() for 0x%lx\n",
2649           client.window);
2650 #endif // DEBUG
2651
2652   screen->unmanageWindow(this, False);
2653 }
2654
2655
2656 void BlackboxWindow::reparentNotifyEvent(const XReparentEvent *re) {
2657   if (re->window != client.window || re->parent == frame.plate)
2658     return;
2659
2660 #ifdef    DEBUG
2661   fprintf(stderr, "BlackboxWindow::reparentNotifyEvent(): reparent 0x%lx to "
2662           "0x%lx.\n", client.window, re->parent);
2663 #endif // DEBUG
2664
2665   XEvent ev;
2666   ev.xreparent = *re;
2667   XPutBackEvent(blackbox->getXDisplay(), &ev);
2668   screen->unmanageWindow(this, True);
2669 }
2670
2671
2672 void BlackboxWindow::propertyNotifyEvent(const XPropertyEvent *pe) {
2673   if (pe->state == PropertyDelete)
2674     return;
2675
2676 #ifdef    DEBUG
2677   fprintf(stderr, "BlackboxWindow::propertyNotifyEvent(): for 0x%lx\n",
2678           client.window);
2679 #endif
2680
2681   switch(pe->atom) {
2682   case XA_WM_CLASS:
2683   case XA_WM_CLIENT_MACHINE:
2684   case XA_WM_COMMAND:
2685     break;
2686
2687   case XA_WM_TRANSIENT_FOR: {
2688     // determine if this is a transient window
2689     getTransientInfo();
2690
2691     // adjust the window decorations based on transience
2692     if (isTransient()) {
2693       decorations &= ~(Decor_Maximize | Decor_Handle);
2694       functions &= ~Func_Maximize;
2695       setAllowedActions();
2696     }
2697
2698     reconfigure();
2699   }
2700     break;
2701
2702   case XA_WM_HINTS:
2703     getWMHints();
2704     break;
2705
2706   case XA_WM_ICON_NAME:
2707     getWMIconName();
2708     if (flags.iconic) screen->propagateWindowName(this);
2709     break;
2710
2711   case XAtom::net_wm_name:
2712   case XA_WM_NAME:
2713     getWMName();
2714
2715     if (decorations & Decor_Titlebar)
2716       redrawLabel();
2717
2718     screen->propagateWindowName(this);
2719     break;
2720
2721   case XA_WM_NORMAL_HINTS: {
2722     getWMNormalHints();
2723
2724     if ((client.normal_hint_flags & PMinSize) &&
2725         (client.normal_hint_flags & PMaxSize)) {
2726       // the window now can/can't resize itself, so the buttons need to be
2727       // regrabbed.
2728       ungrabButtons();
2729       if (client.max_width <= client.min_width &&
2730           client.max_height <= client.min_height) {
2731         decorations &= ~(Decor_Maximize | Decor_Handle);
2732         functions &= ~(Func_Resize | Func_Maximize);
2733       } else {
2734         if (! isTransient()) {
2735           decorations |= Decor_Maximize | Decor_Handle;
2736           functions |= Func_Maximize;
2737         }
2738         functions |= Func_Resize;
2739       }
2740       grabButtons();
2741       setAllowedActions();
2742     }
2743
2744     Rect old_rect = frame.rect;
2745
2746     upsize();
2747
2748     if (old_rect != frame.rect)
2749       reconfigure();
2750
2751     break;
2752   }
2753
2754   default:
2755     if (pe->atom == xatom->getAtom(XAtom::wm_protocols)) {
2756       getWMProtocols();
2757
2758       if ((decorations & Decor_Close) && (! frame.close_button)) {
2759         createCloseButton();
2760         if (decorations & Decor_Titlebar) {
2761           positionButtons(True);
2762           XMapSubwindows(blackbox->getXDisplay(), frame.title);
2763         }
2764         if (windowmenu) windowmenu->reconfigure();
2765       }
2766     } else if (pe->atom == xatom->getAtom(XAtom::net_wm_strut)) {
2767       updateStrut();
2768     }
2769
2770     break;
2771   }
2772 }
2773
2774
2775 void BlackboxWindow::exposeEvent(const XExposeEvent *ee) {
2776 #ifdef DEBUG
2777   fprintf(stderr, "BlackboxWindow::exposeEvent() for 0x%lx\n", client.window);
2778 #endif
2779
2780   if (frame.label == ee->window && (decorations & Decor_Titlebar))
2781     redrawLabel();
2782   else if (frame.close_button == ee->window)
2783     redrawCloseButton(False);
2784   else if (frame.maximize_button == ee->window)
2785     redrawMaximizeButton(flags.maximized);
2786   else if (frame.iconify_button == ee->window)
2787     redrawIconifyButton(False);
2788 }
2789
2790
2791 void BlackboxWindow::configureRequestEvent(const XConfigureRequestEvent *cr) {
2792   if (cr->window != client.window || flags.iconic)
2793     return;
2794
2795   if (cr->value_mask & CWBorderWidth)
2796     client.old_bw = cr->border_width;
2797
2798   if (cr->value_mask & (CWX | CWY | CWWidth | CWHeight)) {
2799     Rect req = frame.rect;
2800
2801     if (cr->value_mask & (CWX | CWY)) {
2802       if (cr->value_mask & CWX)
2803         client.rect.setX(cr->x);
2804       if (cr->value_mask & CWY)
2805         client.rect.setY(cr->y);
2806
2807       applyGravity(req);
2808     }
2809
2810     if (cr->value_mask & CWWidth)
2811       req.setWidth(cr->width + frame.margin.left + frame.margin.right);
2812
2813     if (cr->value_mask & CWHeight)
2814       req.setHeight(cr->height + frame.margin.top + frame.margin.bottom);
2815
2816     configure(req.x(), req.y(), req.width(), req.height());
2817   }
2818
2819   if (cr->value_mask & CWStackMode && !isDesktop()) {
2820     switch (cr->detail) {
2821     case Below:
2822     case BottomIf:
2823       screen->getWorkspace(blackbox_attrib.workspace)->lowerWindow(this);
2824       break;
2825
2826     case Above:
2827     case TopIf:
2828     default:
2829       screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
2830       break;
2831     }
2832   }
2833 }
2834
2835
2836 void BlackboxWindow::buttonPressEvent(const XButtonEvent *be) {
2837 #ifdef DEBUG
2838   fprintf(stderr, "BlackboxWindow::buttonPressEvent() for 0x%lx\n",
2839           client.window);
2840 #endif
2841
2842   if (frame.maximize_button == be->window && be->button <= 3) {
2843     redrawMaximizeButton(True);
2844   } else if (be->button == 1 || (be->button == 3 && be->state == ModMask)) {
2845     if (! flags.focused)
2846       setInputFocus();
2847
2848     if (frame.iconify_button == be->window) {
2849       redrawIconifyButton(True);
2850     } else if (frame.close_button == be->window) {
2851       redrawCloseButton(True);
2852     } else if (frame.plate == be->window) {
2853       if (windowmenu && windowmenu->isVisible()) windowmenu->hide();
2854
2855       screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
2856
2857       XAllowEvents(blackbox->getXDisplay(), ReplayPointer, be->time);
2858     } else {
2859       if (frame.title == be->window || frame.label == be->window) {
2860         if (((be->time - lastButtonPressTime) <=
2861              blackbox->getDoubleClickInterval()) ||
2862             (be->state == ControlMask)) {
2863           lastButtonPressTime = 0;
2864           shade();
2865         } else {
2866           lastButtonPressTime = be->time;
2867         }
2868       }
2869
2870       if (windowmenu && windowmenu->isVisible()) windowmenu->hide();
2871
2872       screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
2873     }
2874   } else if (be->button == 2 && (be->window != frame.iconify_button) &&
2875              (be->window != frame.close_button)) {
2876     screen->getWorkspace(blackbox_attrib.workspace)->lowerWindow(this);
2877   } else if (windowmenu && be->button == 3 &&
2878              (frame.title == be->window || frame.label == be->window ||
2879               frame.handle == be->window || frame.window == be->window)) {
2880     if (windowmenu->isVisible()) {
2881       windowmenu->hide();
2882     } else {
2883       int mx = be->x_root - windowmenu->getWidth() / 2,
2884           my = be->y_root - windowmenu->getHeight() / 2;
2885
2886       // snap the window menu into a corner/side if necessary
2887       int left_edge, right_edge, top_edge, bottom_edge;
2888
2889       /*
2890          the " + (frame.border_w * 2) - 1" bits are to get the proper width
2891          and height of the menu, as the sizes returned by it do not include
2892          the borders.
2893        */
2894       left_edge = frame.rect.x();
2895       right_edge = frame.rect.right() -
2896         (windowmenu->getWidth() + (frame.border_w * 2) - 1);
2897       top_edge = client.rect.top() - (frame.border_w + frame.mwm_border_w);
2898       bottom_edge = client.rect.bottom() -
2899         (windowmenu->getHeight() + (frame.border_w * 2) - 1) +
2900         (frame.border_w + frame.mwm_border_w);
2901
2902       if (mx < left_edge)
2903         mx = left_edge;
2904       if (mx > right_edge)
2905         mx = right_edge;
2906       if (my < top_edge)
2907         my = top_edge;
2908       if (my > bottom_edge)
2909         my = bottom_edge;
2910
2911       windowmenu->move(mx, my);
2912       windowmenu->show();
2913       XRaiseWindow(blackbox->getXDisplay(), windowmenu->getWindowID());
2914       XRaiseWindow(blackbox->getXDisplay(),
2915                    windowmenu->getSendToMenu()->getWindowID());
2916     }
2917   // mouse wheel up
2918   } else if (be->button == 4) {
2919     if ((be->window == frame.label ||
2920          be->window == frame.title ||
2921          be->window == frame.maximize_button ||
2922          be->window == frame.iconify_button ||
2923          be->window == frame.close_button) &&
2924         ! flags.shaded)
2925       shade();
2926   // mouse wheel down
2927   } else if (be->button == 5) {
2928     if ((be->window == frame.label ||
2929          be->window == frame.title ||
2930          be->window == frame.maximize_button ||
2931          be->window == frame.iconify_button ||
2932          be->window == frame.close_button) &&
2933         flags.shaded)
2934       shade();
2935   }
2936 }
2937
2938
2939 void BlackboxWindow::buttonReleaseEvent(const XButtonEvent *re) {
2940 #ifdef DEBUG
2941   fprintf(stderr, "BlackboxWindow::buttonReleaseEvent() for 0x%lx\n",
2942           client.window);
2943 #endif
2944
2945   if (re->window == frame.maximize_button &&
2946       re->button >= 1 && re->button <= 3) {
2947     if ((re->x >= 0 && re->x <= static_cast<signed>(frame.button_w)) &&
2948         (re->y >= 0 && re->y <= static_cast<signed>(frame.button_w))) {
2949       maximize(re->button);
2950     } else {
2951       redrawMaximizeButton(flags.maximized);
2952     }
2953   } else if (re->window == frame.iconify_button && re->button == 1) {
2954     if ((re->x >= 0 && re->x <= static_cast<signed>(frame.button_w)) &&
2955         (re->y >= 0 && re->y <= static_cast<signed>(frame.button_w))) {
2956       iconify();
2957     } else {
2958       redrawIconifyButton(False);
2959     }
2960   } else if (re->window == frame.close_button & re->button == 1) {
2961     if ((re->x >= 0 && re->x <= static_cast<signed>(frame.button_w)) &&
2962         (re->y >= 0 && re->y <= static_cast<signed>(frame.button_w)))
2963       close();
2964     redrawCloseButton(False);
2965   } else if (flags.moving) {
2966     endMove();
2967   } else if (flags.resizing) {
2968     endResize();
2969   } else if (re->window == frame.window) {
2970     if (re->button == 2 && re->state == ModMask)
2971       XUngrabPointer(blackbox->getXDisplay(), CurrentTime);
2972   }
2973 }
2974
2975
2976
2977 void BlackboxWindow::beginMove(int x_root, int y_root) {
2978   assert(! (flags.resizing || flags.moving));
2979
2980   /*
2981     Only one window can be moved/resized at a time. If another window is already
2982     being moved or resized, then stop it before whating to work with this one.
2983   */
2984   BlackboxWindow *changing = blackbox->getChangingWindow();
2985   if (changing && changing != this) {
2986     if (changing->flags.moving)
2987       changing->endMove();
2988     else // if (changing->flags.resizing)
2989       changing->endResize();
2990   }
2991   
2992   XGrabPointer(blackbox->getXDisplay(), frame.window, False,
2993                PointerMotionMask | ButtonReleaseMask,
2994                GrabModeAsync, GrabModeAsync,
2995                None, blackbox->getMoveCursor(), CurrentTime);
2996
2997   if (windowmenu && windowmenu->isVisible())
2998     windowmenu->hide();
2999
3000   flags.moving = True;
3001   blackbox->setChangingWindow(this);
3002
3003   if (! screen->doOpaqueMove()) {
3004     XGrabServer(blackbox->getXDisplay());
3005
3006     frame.changing = frame.rect;
3007     screen->showPosition(frame.changing.x(), frame.changing.y());
3008
3009     XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
3010                    screen->getOpGC(),
3011                    frame.changing.x(),
3012                    frame.changing.y(),
3013                    frame.changing.width() - 1,
3014                    frame.changing.height() - 1);
3015   }
3016
3017   frame.grab_x = x_root - frame.rect.x() - frame.border_w;
3018   frame.grab_y = y_root - frame.rect.y() - frame.border_w;
3019 }
3020
3021
3022 void BlackboxWindow::doMove(int x_root, int y_root) {
3023   assert(flags.moving);
3024   assert(blackbox->getChangingWindow() == this);
3025
3026   int dx = x_root - frame.grab_x, dy = y_root - frame.grab_y;
3027   dx -= frame.border_w;
3028   dy -= frame.border_w;
3029
3030   if (screen->doWorkspaceWarping())
3031     if (doWorkspaceWarping(x_root, y_root, dx, dy))
3032       return;
3033
3034   doWindowSnapping(dx, dy);
3035
3036   if (screen->doOpaqueMove()) {
3037     configure(dx, dy, frame.rect.width(), frame.rect.height());
3038   } else {
3039     XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
3040                    screen->getOpGC(),
3041                    frame.changing.x(),
3042                    frame.changing.y(),
3043                    frame.changing.width() - 1,
3044                    frame.changing.height() - 1);
3045
3046     frame.changing.setPos(dx, dy);
3047
3048     XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
3049                    screen->getOpGC(),
3050                    frame.changing.x(),
3051                    frame.changing.y(),
3052                    frame.changing.width() - 1,
3053                    frame.changing.height() - 1);
3054   }
3055
3056   screen->showPosition(dx, dy);
3057 }
3058
3059
3060 bool BlackboxWindow::doWorkspaceWarping(int x_root, int y_root,
3061                                         int dx, int dy) {
3062   // workspace warping
3063   bool warp = False;
3064   unsigned int dest = screen->getCurrentWorkspaceID();
3065   if (x_root <= 0) {
3066     warp = True;
3067
3068     if (dest > 0) dest--;
3069     else dest = screen->getNumberOfWorkspaces() - 1;
3070
3071   } else if (x_root >= screen->getRect().right()) {
3072     warp = True;
3073
3074     if (dest < screen->getNumberOfWorkspaces() - 1) dest++;
3075     else dest = 0;
3076   }
3077   if (! warp)
3078     return false;
3079
3080   endMove();
3081   bool focus = flags.focused; // had focus while moving?
3082   if (! flags.stuck)
3083     screen->reassociateWindow(this, dest, False);
3084   screen->changeWorkspaceID(dest);
3085   if (focus)
3086     setInputFocus();
3087
3088   /*
3089      If the XWarpPointer is done after the configure, we can end up
3090      grabbing another window, so made sure you do it first.
3091      */
3092   int dest_x;
3093   if (x_root <= 0) {
3094     dest_x = screen->getRect().right() - 1;
3095     XWarpPointer(blackbox->getXDisplay(), None, 
3096                  screen->getRootWindow(), 0, 0, 0, 0,
3097                  dest_x, y_root);
3098
3099     configure(dx + (screen->getRect().width() - 1), dy,
3100               frame.rect.width(), frame.rect.height());
3101   } else {
3102     dest_x = 0;
3103     XWarpPointer(blackbox->getXDisplay(), None, 
3104                  screen->getRootWindow(), 0, 0, 0, 0,
3105                  dest_x, y_root);
3106
3107     configure(dx - (screen->getRect().width() - 1), dy,
3108               frame.rect.width(), frame.rect.height());
3109   }
3110
3111   beginMove(dest_x, y_root);
3112   return true;
3113 }
3114
3115
3116 void BlackboxWindow::doWindowSnapping(int &dx, int &dy) {
3117   // how much resistance to edges to provide
3118   const int resistance_size = screen->getEdgeSnapThreshold();
3119
3120   // how far away to snap
3121   const int snap_distance = screen->getEdgeSnapThreshold();
3122
3123   // how to snap windows
3124   const int snap_to_windows = BScreen::WindowResistance;
3125   const int snap_to_edges = BScreen::WindowResistance;
3126   // the amount of space away from the edge to provide resistance/snap
3127   const int snap_offset = screen->getEdgeSnapThreshold();
3128
3129   // find the geomeetery where the moving window currently is
3130   const Rect &moving = screen->doOpaqueMove() ? frame.rect : frame.changing;
3131
3132   // window corners
3133   const int wleft = dx,
3134            wright = dx + frame.rect.width() - 1,
3135              wtop = dy,
3136           wbottom = dy + frame.rect.height() - 1;
3137
3138   if (snap_to_windows) {
3139     RectList rectlist;
3140
3141     Workspace *w = screen->getWorkspace(getWorkspaceNumber());
3142     assert(w);
3143
3144     // add windows on the workspace to the rect list
3145     const BlackboxWindowList& stack_list = w->getStackingList();
3146     BlackboxWindowList::const_iterator st_it, st_end = stack_list.end();
3147     for (st_it = stack_list.begin(); st_it != st_end; ++st_it)
3148       rectlist.push_back( (*st_it)->frameRect() );
3149
3150     // add the toolbar and the slit to the rect list.
3151     // (only if they are not hidden)
3152     Toolbar *tbar = screen->getToolbar();
3153     Slit *slit = screen->getSlit();
3154     Rect tbar_rect, slit_rect;
3155     unsigned int bwidth = screen->getBorderWidth() * 2;
3156
3157     if (! (screen->doHideToolbar() || tbar->isHidden())) {
3158       tbar_rect.setRect(tbar->getX(), tbar->getY(), tbar->getWidth() + bwidth,
3159                         tbar->getHeight() + bwidth);
3160       rectlist.push_back(tbar_rect);
3161     }
3162
3163     if (! slit->isHidden()) {
3164       slit_rect.setRect(slit->getX(), slit->getY(), slit->getWidth() + bwidth,
3165                         slit->getHeight() + bwidth);
3166       rectlist.push_back(slit_rect);
3167     }
3168
3169     RectList::const_iterator it, end = rectlist.end();
3170     for (it = rectlist.begin(); it != end; ++it) {
3171       bool snapped = False;
3172       const Rect &winrect = *it;
3173
3174       if (snap_to_windows == BScreen::WindowResistance)
3175         // if the window is already over top of this snap target, then
3176         // resistance is futile, so just ignore it
3177         if (winrect.intersects(moving))
3178           continue;
3179
3180       int dleft, dright, dtop, dbottom;
3181
3182       // if the windows are in the same plane vertically
3183       if (wtop >= (signed)(winrect.y() - frame.rect.height() + 1) &&
3184           wtop < (signed)(winrect.y() + winrect.height() - 1)) {
3185
3186         if (snap_to_windows == BScreen::WindowResistance) {
3187           dleft = wright - winrect.left();
3188           dright = winrect.right() - wleft;
3189
3190           // snap left of other window?
3191           if (dleft >= 0 && dleft < resistance_size) {
3192             dx = winrect.left() - frame.rect.width();
3193             snapped = True;
3194           }
3195           // snap right of other window?
3196           else if (dright >= 0 && dright < resistance_size) {
3197             dx = winrect.right() + 1;
3198             snapped = True;
3199           }
3200         } else { // BScreen::WindowSnap
3201           dleft = abs(wright - winrect.left());
3202           dright = abs(wleft - winrect.right());
3203
3204           // snap left of other window?
3205           if (dleft < snap_distance && dleft <= dright) {
3206             dx = winrect.left() - frame.rect.width();
3207             snapped = True;
3208           }
3209           // snap right of other window?
3210           else if (dright < snap_distance) {
3211             dx = winrect.right() + 1;
3212             snapped = True;
3213           }            
3214         }
3215
3216         if (snapped) {
3217           if (screen->getWindowCornerSnap()) {
3218             // try corner-snap to its other sides
3219             if (snap_to_windows == BScreen::WindowResistance) {
3220               dtop = winrect.top() - wtop;
3221               dbottom = wbottom - winrect.bottom();
3222               if (dtop > 0 && dtop < resistance_size) {
3223                 // if we're already past the top edge, then don't provide
3224                 // resistance
3225                 if (moving.top() >= winrect.top())
3226                   dy = winrect.top();
3227               } else if (dbottom > 0 && dbottom < resistance_size) {
3228                 // if we're already past the bottom edge, then don't provide
3229                 // resistance
3230                 if (moving.bottom() <= winrect.bottom())
3231                   dy = winrect.bottom() - frame.rect.height() + 1;
3232               }
3233             } else { // BScreen::WindowSnap
3234               dtop = abs(wtop - winrect.top());
3235               dbottom = abs(wbottom - winrect.bottom());
3236               if (dtop < snap_distance && dtop <= dbottom)
3237                 dy = winrect.top();
3238               else if (dbottom < snap_distance)
3239                 dy = winrect.bottom() - frame.rect.height() + 1;
3240             }
3241           }
3242
3243           continue;
3244         }
3245       }
3246
3247       // if the windows are on the same plane horizontally
3248       if (wleft >= (signed)(winrect.x() - frame.rect.width() + 1) &&
3249           wleft < (signed)(winrect.x() + winrect.width() - 1)) {
3250
3251         if (snap_to_windows == BScreen::WindowResistance) {
3252           dtop = wbottom - winrect.top();
3253           dbottom = winrect.bottom() - wtop;
3254
3255           // snap top of other window?
3256           if (dtop >= 0 && dtop < resistance_size) {
3257             dy = winrect.top() - frame.rect.height();
3258             snapped = True;
3259           }
3260           // snap bottom of other window?
3261           else if (dbottom >= 0 && dbottom < resistance_size) {
3262             dy = winrect.bottom() + 1;
3263             snapped = True;
3264           }
3265         } else { // BScreen::WindowSnap
3266           dtop = abs(wbottom - winrect.top());
3267           dbottom = abs(wtop - winrect.bottom());
3268
3269           // snap top of other window?
3270           if (dtop < snap_distance && dtop <= dbottom) {
3271             dy = winrect.top() - frame.rect.height();
3272             snapped = True;
3273           }
3274           // snap bottom of other window?
3275           else if (dbottom < snap_distance) {
3276             dy = winrect.bottom() + 1;
3277             snapped = True;
3278           }
3279
3280         }
3281
3282         if (snapped) {
3283           if (screen->getWindowCornerSnap()) {
3284             // try corner-snap to its other sides
3285             if (snap_to_windows == BScreen::WindowResistance) {
3286               dleft = winrect.left() - wleft;
3287               dright = wright - winrect.right();
3288               if (dleft > 0 && dleft < resistance_size) {
3289                 // if we're already past the left edge, then don't provide
3290                 // resistance
3291                 if (moving.left() >= winrect.left())
3292                   dx = winrect.left();
3293               } else if (dright > 0 && dright < resistance_size) {
3294                 // if we're already past the right edge, then don't provide
3295                 // resistance
3296                 if (moving.right() <= winrect.right())
3297                   dx = winrect.right() - frame.rect.width() + 1;
3298               }
3299             } else { // BScreen::WindowSnap
3300               dleft = abs(wleft - winrect.left());
3301               dright = abs(wright - winrect.right());
3302               if (dleft < snap_distance && dleft <= dright)
3303                 dx = winrect.left();
3304               else if (dright < snap_distance)
3305                 dx = winrect.right() - frame.rect.width() + 1;
3306             }
3307           }
3308
3309           continue;
3310         }
3311       }
3312     }
3313   }
3314
3315   if (snap_to_edges) {
3316     RectList rectlist;
3317
3318     // snap to the screen edges (and screen boundaries for xinerama)
3319 #ifdef    XINERAMA
3320     if (screen->isXineramaActive() && blackbox->doXineramaSnapping()) {
3321       rectlist.insert(rectlist.begin(),
3322                       screen->getXineramaAreas().begin(),
3323                       screen->getXineramaAreas().end());
3324     } else
3325 #endif // XINERAMA
3326       rectlist.push_back(screen->getRect());
3327
3328     RectList::const_iterator it, end = rectlist.end();
3329     for (it = rectlist.begin(); it != end; ++it) {
3330       const Rect &srect = *it;
3331
3332       if (snap_to_edges == BScreen::WindowResistance) {
3333         // if we're not in the rectangle then don't snap to it.
3334         if (! srect.contains(moving))
3335           continue;
3336       } else { // BScreen::WindowSnap
3337         // if we're not in the rectangle then don't snap to it.
3338         if (! srect.intersects(Rect(wleft, wtop, frame.rect.width(),
3339                                     frame.rect.height())))
3340           continue;
3341       }
3342
3343       if (snap_to_edges == BScreen::WindowResistance) {
3344       int dleft = srect.left() - wleft,
3345          dright = wright - srect.right(),
3346            dtop = srect.top() - wtop,
3347         dbottom = wbottom - srect.bottom();
3348
3349         // snap left?
3350         if (dleft > 0 && dleft < resistance_size)
3351           dx = srect.left();
3352         // snap right?
3353         else if (dright > 0 && dright < resistance_size)
3354           dx = srect.right() - frame.rect.width() + 1;
3355
3356         // snap top?
3357         if (dtop > 0 && dtop < resistance_size)
3358           dy = srect.top();
3359         // snap bottom?
3360         else if (dbottom > 0 && dbottom < resistance_size)
3361           dy = srect.bottom() - frame.rect.height() + 1;
3362       } else { // BScreen::WindowSnap
3363         int dleft = abs(wleft - srect.left()),
3364            dright = abs(wright - srect.right()),
3365              dtop = abs(wtop - srect.top()),
3366           dbottom = abs(wbottom - srect.bottom());
3367
3368         // snap left?
3369         if (dleft < snap_distance && dleft <= dright)
3370           dx = srect.left();
3371         // snap right?
3372         else if (dright < snap_distance)
3373           dx = srect.right() - frame.rect.width() + 1;
3374
3375         // snap top?
3376         if (dtop < snap_distance && dtop <= dbottom)
3377           dy = srect.top();
3378         // snap bottom?
3379         else if (dbottom < snap_distance)
3380           dy = srect.bottom() - frame.rect.height() + 1;
3381       }
3382     }
3383   }
3384 }
3385
3386
3387 void BlackboxWindow::endMove(void) {
3388   assert(flags.moving);
3389   assert(blackbox->getChangingWindow() == this);
3390
3391   flags.moving = False;
3392   blackbox->setChangingWindow(0);
3393
3394   if (! screen->doOpaqueMove()) {
3395     /* when drawing the rubber band, we need to make sure we only draw inside
3396      * the frame... frame.changing_* contain the new coords for the window,
3397      * so we need to subtract 1 from changing_w/changing_h every where we
3398      * draw the rubber band (for both moving and resizing)
3399      */
3400     XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
3401                    screen->getOpGC(), frame.changing.x(), frame.changing.y(),
3402                    frame.changing.width() - 1, frame.changing.height() - 1);
3403       XUngrabServer(blackbox->getXDisplay());
3404   
3405       configure(frame.changing.x(), frame.changing.y(),
3406                 frame.changing.width(), frame.changing.height());
3407   } else {
3408     configure(frame.rect.x(), frame.rect.y(),
3409               frame.rect.width(), frame.rect.height());
3410   }
3411   screen->hideGeometry();
3412
3413   XUngrabPointer(blackbox->getXDisplay(), CurrentTime);
3414
3415   // if there are any left over motions from the move, drop them now
3416   XSync(blackbox->getXDisplay(), false); // make sure we don't miss any
3417   XEvent e;
3418   while (XCheckTypedWindowEvent(blackbox->getXDisplay(), frame.window,
3419                                 MotionNotify, &e));
3420 }
3421
3422
3423 void BlackboxWindow::beginResize(int x_root, int y_root, Corner dir) {
3424   assert(! (flags.resizing || flags.moving));
3425
3426   /*
3427     Only one window can be moved/resized at a time. If another window is already
3428     being moved or resized, then stop it before whating to work with this one.
3429   */
3430   BlackboxWindow *changing = blackbox->getChangingWindow();
3431   if (changing && changing != this) {
3432     if (changing->flags.moving)
3433       changing->endMove();
3434     else // if (changing->flags.resizing)
3435       changing->endResize();
3436   }
3437
3438   resize_dir = dir;
3439
3440   Cursor cursor;
3441   Corner anchor;
3442   
3443   switch (resize_dir) {
3444   case BottomLeft:
3445     anchor = TopRight;
3446     cursor = blackbox->getLowerLeftAngleCursor();
3447     break;
3448
3449   case BottomRight:
3450     anchor = TopLeft;
3451     cursor = blackbox->getLowerRightAngleCursor();
3452     break;
3453
3454   case TopLeft:
3455     anchor = BottomRight;
3456     cursor = blackbox->getUpperLeftAngleCursor();
3457     break;
3458
3459   case TopRight:
3460     anchor = BottomLeft;
3461     cursor = blackbox->getUpperRightAngleCursor();
3462     break;
3463
3464   default:
3465     assert(false); // unhandled Corner
3466     return;        // unreachable, for the compiler
3467   }
3468   
3469   XGrabServer(blackbox->getXDisplay());
3470   XGrabPointer(blackbox->getXDisplay(), frame.window, False,
3471                PointerMotionMask | ButtonReleaseMask,
3472                GrabModeAsync, GrabModeAsync, None, cursor, CurrentTime);
3473
3474   flags.resizing = True;
3475   blackbox->setChangingWindow(this);
3476
3477   int gw, gh;
3478   frame.changing = frame.rect;
3479
3480   constrain(anchor,  &gw, &gh);
3481
3482   XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
3483                  screen->getOpGC(), frame.changing.x(), frame.changing.y(),
3484                  frame.changing.width() - 1, frame.changing.height() - 1);
3485
3486   screen->showGeometry(gw, gh);
3487   
3488   frame.grab_x = x_root;
3489   frame.grab_y = y_root;
3490 }
3491
3492
3493 void BlackboxWindow::doResize(int x_root, int y_root) {
3494   assert(flags.resizing);
3495   assert(blackbox->getChangingWindow() == this);
3496
3497   XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
3498                  screen->getOpGC(), frame.changing.x(), frame.changing.y(),
3499                  frame.changing.width() - 1, frame.changing.height() - 1);
3500
3501   int gw, gh;
3502   Corner anchor;
3503
3504   switch (resize_dir) {
3505   case BottomLeft:
3506     anchor = TopRight;
3507     frame.changing.setSize(frame.rect.width() - (x_root - frame.grab_x),
3508                            frame.rect.height() + (y_root - frame.grab_y));
3509     break;
3510   case BottomRight:
3511     anchor = TopLeft;
3512     frame.changing.setSize(frame.rect.width() + (x_root - frame.grab_x),
3513                            frame.rect.height() + (y_root - frame.grab_y));
3514     break;
3515   case TopLeft:
3516     anchor = BottomRight;
3517     frame.changing.setSize(frame.rect.width() - (x_root - frame.grab_x),
3518                            frame.rect.height() - (y_root - frame.grab_y));
3519     break;
3520   case TopRight:
3521     anchor = BottomLeft;
3522     frame.changing.setSize(frame.rect.width() + (x_root - frame.grab_x),
3523                            frame.rect.height() - (y_root - frame.grab_y));
3524     break;
3525
3526   default:
3527     assert(false); // unhandled Corner
3528     return;        // unreachable, for the compiler
3529   }
3530   
3531   constrain(anchor, &gw, &gh);
3532
3533   XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
3534                  screen->getOpGC(), frame.changing.x(), frame.changing.y(),
3535                  frame.changing.width() - 1, frame.changing.height() - 1);
3536
3537   screen->showGeometry(gw, gh);
3538 }
3539
3540
3541 void BlackboxWindow::endResize(void) {
3542   assert(flags.resizing);
3543   assert(blackbox->getChangingWindow() == this);
3544
3545   XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
3546                  screen->getOpGC(), frame.changing.x(), frame.changing.y(),
3547                  frame.changing.width() - 1, frame.changing.height() - 1);
3548   XUngrabServer(blackbox->getXDisplay());
3549
3550   // unset maximized state after resized when fully maximized
3551   if (flags.maximized == 1)
3552     maximize(0);
3553   
3554   flags.resizing = False;
3555   blackbox->setChangingWindow(0);
3556
3557   configure(frame.changing.x(), frame.changing.y(),
3558             frame.changing.width(), frame.changing.height());
3559   screen->hideGeometry();
3560
3561   XUngrabPointer(blackbox->getXDisplay(), CurrentTime);
3562   
3563   // if there are any left over motions from the resize, drop them now
3564   XSync(blackbox->getXDisplay(), false); // make sure we don't miss any
3565   XEvent e;
3566   while (XCheckTypedWindowEvent(blackbox->getXDisplay(), frame.window,
3567                                 MotionNotify, &e));
3568 }
3569
3570
3571 void BlackboxWindow::motionNotifyEvent(const XMotionEvent *me) {
3572 #ifdef DEBUG
3573   fprintf(stderr, "BlackboxWindow::motionNotifyEvent() for 0x%lx\n",
3574           client.window);
3575 #endif
3576
3577   if (flags.moving) {
3578     doMove(me->x_root, me->y_root);
3579   } else if (flags.resizing) {
3580     doResize(me->x_root, me->y_root);
3581   } else {
3582     if (!flags.resizing && me->state & Button1Mask && (functions & Func_Move) &&
3583         (frame.title == me->window || frame.label == me->window ||
3584          frame.handle == me->window || frame.window == me->window)) {
3585       beginMove(me->x_root, me->y_root);
3586     } else if ((functions & Func_Resize) &&
3587                (me->state & Button1Mask && (me->window == frame.right_grip ||
3588                                             me->window == frame.left_grip)) ||
3589                (me->state & Button3Mask && me->state & ModMask &&
3590                 me->window == frame.window)) {
3591       unsigned int zones = screen->getResizeZones();
3592       Corner corner;
3593       
3594       if (me->window == frame.left_grip) {
3595         corner = BottomLeft;
3596       } else if (me->window == frame.right_grip || zones == 1) {
3597         corner = BottomRight;
3598       } else {
3599         bool top;
3600         bool left = (me->x_root - frame.rect.x() <=
3601                      static_cast<signed>(frame.rect.width() / 2));
3602         if (zones == 2)
3603           top = False;
3604         else // (zones == 4)
3605           top = (me->y_root - frame.rect.y() <=
3606                  static_cast<signed>(frame.rect.height() / 2));
3607         corner = (top ? (left ? TopLeft : TopRight) :
3608                         (left ? BottomLeft : BottomRight));
3609       }
3610
3611       beginResize(me->x_root, me->y_root, corner);
3612     }
3613   }
3614 }
3615
3616
3617 #ifdef    SHAPE
3618 void BlackboxWindow::shapeEvent(XShapeEvent *) {
3619   if (blackbox->hasShapeExtensions() && flags.shaped) {
3620     configureShape();
3621   }
3622 }
3623 #endif // SHAPE
3624
3625
3626 bool BlackboxWindow::validateClient(void) const {
3627   XSync(blackbox->getXDisplay(), False);
3628
3629   XEvent e;
3630   if (XCheckTypedWindowEvent(blackbox->getXDisplay(), client.window,
3631                              DestroyNotify, &e) ||
3632       XCheckTypedWindowEvent(blackbox->getXDisplay(), client.window,
3633                              UnmapNotify, &e)) {
3634     XPutBackEvent(blackbox->getXDisplay(), &e);
3635
3636     return False;
3637   }
3638
3639   return True;
3640 }
3641
3642
3643 void BlackboxWindow::restore(bool remap) {
3644   XChangeSaveSet(blackbox->getXDisplay(), client.window, SetModeDelete);
3645   XSelectInput(blackbox->getXDisplay(), client.window, NoEventMask);
3646   XSelectInput(blackbox->getXDisplay(), frame.plate, NoEventMask);
3647
3648   // do not leave a shaded window as an icon unless it was an icon
3649   if (flags.shaded && ! flags.iconic)
3650     setState(NormalState);
3651
3652   restoreGravity(client.rect);
3653
3654   XUnmapWindow(blackbox->getXDisplay(), frame.window);
3655   XUnmapWindow(blackbox->getXDisplay(), client.window);
3656
3657   XSetWindowBorderWidth(blackbox->getXDisplay(), client.window, client.old_bw);
3658
3659   XEvent ev;
3660   if (XCheckTypedWindowEvent(blackbox->getXDisplay(), client.window,
3661                              ReparentNotify, &ev)) {
3662     remap = True;
3663   } else {
3664     // according to the ICCCM - if the client doesn't reparent to
3665     // root, then we have to do it for them
3666     XReparentWindow(blackbox->getXDisplay(), client.window,
3667                     screen->getRootWindow(),
3668                     client.rect.x(), client.rect.y());
3669   }
3670
3671   if (remap) XMapWindow(blackbox->getXDisplay(), client.window);
3672 }
3673
3674
3675 // timer for autoraise
3676 void BlackboxWindow::timeout(void) {
3677   screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
3678 }
3679
3680
3681 void BlackboxWindow::changeBlackboxHints(const BlackboxHints *net) {
3682   if ((net->flags & AttribShaded) &&
3683       ((blackbox_attrib.attrib & AttribShaded) !=
3684        (net->attrib & AttribShaded)))
3685     shade();
3686
3687   if (flags.visible && // watch out for requests when we can not be seen
3688       (net->flags & (AttribMaxVert | AttribMaxHoriz)) &&
3689       ((blackbox_attrib.attrib & (AttribMaxVert | AttribMaxHoriz)) !=
3690        (net->attrib & (AttribMaxVert | AttribMaxHoriz)))) {
3691     if (flags.maximized) {
3692       maximize(0);
3693     } else {
3694       int button = 0;
3695
3696       if ((net->flags & AttribMaxHoriz) && (net->flags & AttribMaxVert))
3697         button = ((net->attrib & (AttribMaxHoriz | AttribMaxVert)) ?  1 : 0);
3698       else if (net->flags & AttribMaxVert)
3699         button = ((net->attrib & AttribMaxVert) ? 2 : 0);
3700       else if (net->flags & AttribMaxHoriz)
3701         button = ((net->attrib & AttribMaxHoriz) ? 3 : 0);
3702
3703       maximize(button);
3704     }
3705   }
3706
3707   if ((net->flags & AttribOmnipresent) &&
3708       ((blackbox_attrib.attrib & AttribOmnipresent) !=
3709        (net->attrib & AttribOmnipresent)))
3710     stick();
3711
3712   if ((net->flags & AttribWorkspace) &&
3713       (blackbox_attrib.workspace != net->workspace)) {
3714     screen->reassociateWindow(this, net->workspace, True);
3715
3716     if (screen->getCurrentWorkspaceID() != net->workspace) {
3717       withdraw();
3718     } else {
3719       show();
3720       screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
3721     }
3722   }
3723
3724   if (net->flags & AttribDecoration) {
3725     switch (net->decoration) {
3726     case DecorNone:
3727       decorations = 0;
3728
3729       break;
3730
3731     default:
3732     case DecorNormal:
3733       decorations |= Decor_Titlebar | Decor_Border | Decor_Iconify;
3734   
3735       decorations = ((functions & Func_Resize) && !isTransient() ?
3736                      decorations | Decor_Handle :
3737                      decorations &= ~Decor_Handle);
3738       decorations = (functions & Func_Maximize ?
3739                      decorations | Decor_Maximize :
3740                      decorations &= ~Decor_Maximize);
3741
3742       break;
3743
3744     case DecorTiny:
3745       decorations |= Decor_Titlebar | Decor_Iconify;
3746       decorations &= ~(Decor_Border | Decor_Handle);
3747       
3748       decorations = (functions & Func_Maximize ?
3749                      decorations | Decor_Maximize :
3750                      decorations &= ~Decor_Maximize);
3751
3752       break;
3753
3754     case DecorTool:
3755       decorations |= Decor_Titlebar;
3756       decorations &= ~(Decor_Iconify | Decor_Border);
3757
3758       decorations = ((functions & Func_Resize) && !isTransient() ?
3759                      decorations | Decor_Handle :
3760                      decorations &= ~Decor_Handle);
3761       decorations = (functions & Func_Maximize ?
3762                      decorations | Decor_Maximize :
3763                      decorations &= ~Decor_Maximize);
3764
3765       break;
3766     }
3767
3768     // we can not be shaded if we lack a titlebar
3769     if (flags.shaded && ! (decorations & Decor_Titlebar))
3770       shade();
3771
3772     if (flags.visible && frame.window) {
3773       XMapSubwindows(blackbox->getXDisplay(), frame.window);
3774       XMapWindow(blackbox->getXDisplay(), frame.window);
3775     }
3776
3777     reconfigure();
3778     setState(current_state);
3779   }
3780 }
3781
3782
3783 /*
3784  * Set the sizes of all components of the window frame
3785  * (the window decorations).
3786  * These values are based upon the current style settings and the client
3787  * window's dimensions.
3788  */
3789 void BlackboxWindow::upsize(void) {
3790   frame.bevel_w = screen->getBevelWidth();
3791
3792   if (decorations & Decor_Border) {
3793     frame.border_w = screen->getBorderWidth();
3794     if (! isTransient())
3795       frame.mwm_border_w = screen->getFrameWidth();
3796     else
3797       frame.mwm_border_w = 0;
3798   } else {
3799     frame.mwm_border_w = frame.border_w = 0;
3800   }
3801
3802   if (decorations & Decor_Titlebar) {
3803     // the height of the titlebar is based upon the height of the font being
3804     // used to display the window's title
3805     WindowStyle *style = screen->getWindowStyle();
3806     frame.title_h = style->font->height() + (frame.bevel_w * 2) + 2;
3807
3808     frame.label_h = frame.title_h - (frame.bevel_w * 2);
3809     frame.button_w = (frame.label_h - 2);
3810
3811     // set the top frame margin
3812     frame.margin.top = frame.border_w + frame.title_h +
3813                        frame.border_w + frame.mwm_border_w;
3814   } else {
3815     frame.title_h = 0;
3816     frame.label_h = 0;
3817     frame.button_w = 0;
3818
3819     // set the top frame margin
3820     frame.margin.top = frame.border_w + frame.mwm_border_w;
3821   }
3822
3823   // set the left/right frame margin
3824   frame.margin.left = frame.margin.right = frame.border_w + frame.mwm_border_w;
3825
3826   if (decorations & Decor_Handle) {
3827     frame.grip_w = frame.button_w * 2;
3828     frame.handle_h = screen->getHandleWidth();
3829
3830     // set the bottom frame margin
3831     frame.margin.bottom = frame.border_w + frame.handle_h +
3832                           frame.border_w + frame.mwm_border_w;
3833   } else {
3834     frame.handle_h = 0;
3835     frame.grip_w = 0;
3836
3837     // set the bottom frame margin
3838     frame.margin.bottom = frame.border_w + frame.mwm_border_w;
3839   }
3840
3841   /*
3842     We first get the normal dimensions and use this to define the inside_w/h
3843     then we modify the height if shading is in effect.
3844     If the shade state is not considered then frame.rect gets reset to the
3845     normal window size on a reconfigure() call resulting in improper
3846     dimensions appearing in move/resize and other events.
3847   */
3848   unsigned int
3849     height = client.rect.height() + frame.margin.top + frame.margin.bottom,
3850     width = client.rect.width() + frame.margin.left + frame.margin.right;
3851
3852   frame.inside_w = width - (frame.border_w * 2);
3853   frame.inside_h = height - (frame.border_w * 2);
3854
3855   if (flags.shaded)
3856     height = frame.title_h + (frame.border_w * 2);
3857   frame.rect.setSize(width, height);
3858 }
3859
3860
3861 /*
3862  * Calculate the size of the client window and constrain it to the
3863  * size specified by the size hints of the client window.
3864  *
3865  * The logical width and height are placed into pw and ph, if they
3866  * are non-zero.  Logical size refers to the users perception of
3867  * the window size (for example an xterm resizes in cells, not in pixels).
3868  *
3869  * The physical geometry is placed into frame.changing_{x,y,width,height}.
3870  * Physical geometry refers to the geometry of the window in pixels.
3871  */
3872 void BlackboxWindow::constrain(Corner anchor, int *pw, int *ph) {
3873   // frame.changing represents the requested frame size, we need to
3874   // strip the frame margin off and constrain the client size
3875   frame.changing.setCoords(frame.changing.left() + frame.margin.left,
3876                            frame.changing.top() + frame.margin.top,
3877                            frame.changing.right() - frame.margin.right,
3878                            frame.changing.bottom() - frame.margin.bottom);
3879
3880   int dw = frame.changing.width(), dh = frame.changing.height(),
3881     base_width = (client.base_width) ? client.base_width : client.min_width,
3882     base_height = (client.base_height) ? client.base_height :
3883                                          client.min_height;
3884
3885   // constrain
3886   if (dw < static_cast<signed>(client.min_width)) dw = client.min_width;
3887   if (dh < static_cast<signed>(client.min_height)) dh = client.min_height;
3888   if (dw > static_cast<signed>(client.max_width)) dw = client.max_width;
3889   if (dh > static_cast<signed>(client.max_height)) dh = client.max_height;
3890
3891   dw -= base_width;
3892   dw /= client.width_inc;
3893   dh -= base_height;
3894   dh /= client.height_inc;
3895
3896   if (pw) {
3897     if (client.width_inc == 1)
3898       *pw = dw + base_width;
3899     else
3900       *pw = dw;
3901   }
3902   if (ph) {
3903     if (client.height_inc == 1)
3904       *ph = dh + base_height;
3905     else
3906       *ph = dh;
3907   }
3908
3909   dw *= client.width_inc;
3910   dw += base_width;
3911   dh *= client.height_inc;
3912   dh += base_height;
3913
3914   frame.changing.setSize(dw, dh);
3915
3916   // add the frame margin back onto frame.changing
3917   frame.changing.setCoords(frame.changing.left() - frame.margin.left,
3918                            frame.changing.top() - frame.margin.top,
3919                            frame.changing.right() + frame.margin.right,
3920                            frame.changing.bottom() + frame.margin.bottom);
3921
3922   // move frame.changing to the specified anchor
3923   int dx = 0,
3924       dy = 0;
3925   switch (anchor) {
3926   case TopLeft:
3927     break;
3928
3929   case TopRight:
3930     dx = frame.rect.right() - frame.changing.right();
3931     break;
3932
3933   case BottomLeft:
3934     dy = frame.rect.bottom() - frame.changing.bottom();
3935     break;
3936
3937   case BottomRight:
3938     dx = frame.rect.right() - frame.changing.right();
3939     dy = frame.rect.bottom() - frame.changing.bottom();
3940     break;
3941
3942   default:
3943     assert(false);  // unhandled corner
3944   }
3945   frame.changing.setPos(frame.changing.x() + dx, frame.changing.y() + dy);
3946 }
3947
3948
3949 void WindowStyle::doJustify(const std::string &text, int &start_pos,
3950                             unsigned int max_length,
3951                             unsigned int modifier) const {
3952   size_t text_len = text.size();
3953   unsigned int length;
3954
3955   do {
3956     length = font->measureString(string(text, 0, text_len)) + modifier;
3957   } while (length > max_length && text_len-- > 0);
3958
3959   switch (justify) {
3960   case RightJustify:
3961     start_pos += max_length - length;
3962     break;
3963
3964   case CenterJustify:
3965     start_pos += (max_length - length) / 2;
3966     break;
3967
3968   case LeftJustify:
3969   default:
3970     break;
3971   }
3972 }
3973
3974
3975 BWindowGroup::BWindowGroup(Blackbox *b, Window _group)
3976   : blackbox(b), group(_group) {
3977   XWindowAttributes wattrib;
3978   if (! XGetWindowAttributes(blackbox->getXDisplay(), group, &wattrib)) {
3979     // group window doesn't seem to exist anymore
3980     delete this;
3981     return;
3982   }
3983
3984   XSelectInput(blackbox->getXDisplay(), group,
3985                PropertyChangeMask | FocusChangeMask | StructureNotifyMask);
3986
3987   blackbox->saveGroupSearch(group, this);
3988 }
3989
3990
3991 BWindowGroup::~BWindowGroup(void) {
3992   blackbox->removeGroupSearch(group);
3993 }
3994
3995
3996 BlackboxWindow *
3997 BWindowGroup::find(BScreen *screen, bool allow_transients) const {
3998   BlackboxWindow *ret = blackbox->getFocusedWindow();
3999
4000   // does the focus window match (or any transient_fors)?
4001   while (ret) {
4002     if (ret->getScreen() == screen && ret->getGroupWindow() == group) {
4003       if (ret->isTransient() && allow_transients) break;
4004       else if (! ret->isTransient()) break;
4005     }
4006
4007     ret = ret->getTransientFor();
4008   }
4009
4010   if (ret) return ret;
4011
4012   // the focus window didn't match, look in the group's window list
4013   BlackboxWindowList::const_iterator it, end = windowList.end();
4014   for (it = windowList.begin(); it != end; ++it) {
4015     ret = *it;
4016     if (ret->getScreen() == screen && ret->getGroupWindow() == group) {
4017       if (ret->isTransient() && allow_transients) break;
4018       else if (! ret->isTransient()) break;
4019     }
4020   }
4021
4022   return ret;
4023 }