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