fix off-by-ones in window-to-window snapping. move the property from the blackbox...
[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
43 #include <cstdlib>
44
45 #include "i18n.hh"
46 #include "blackbox.hh"
47 #include "GCCache.hh"
48 #include "Iconmenu.hh"
49 #include "Image.hh"
50 #include "Screen.hh"
51 #include "Toolbar.hh"
52 #include "Util.hh"
53 #include "Window.hh"
54 #include "Windowmenu.hh"
55 #include "Workspace.hh"
56 #include "Slit.hh"
57
58 using std::string;
59
60 /*
61  * Initializes the class with default values/the window's set initial values.
62  */
63 BlackboxWindow::BlackboxWindow(Blackbox *b, Window w, BScreen *s) {
64   // fprintf(stderr, "BlackboxWindow size: %d bytes\n",
65   // sizeof(BlackboxWindow));
66
67 #ifdef    DEBUG
68   fprintf(stderr, "BlackboxWindow::BlackboxWindow(): creating 0x%lx\n", w);
69 #endif // DEBUG
70
71   // set timer to zero... it is initialized properly later, so we check
72   // if timer is zero in the destructor, and assume that the window is not
73   // fully constructed if timer is zero...
74   timer = 0;
75   blackbox = b;
76   client.window = w;
77   screen = s;
78   xatom = blackbox->getXAtom();
79
80   if (! validateClient()) {
81     delete this;
82     return;
83   }
84
85   // set the eventmask early in the game so that we make sure we get
86   // all the events we are interested in
87   XSetWindowAttributes attrib_set;
88   attrib_set.event_mask = PropertyChangeMask | FocusChangeMask |
89                           StructureNotifyMask;
90   attrib_set.do_not_propagate_mask = ButtonPressMask | ButtonReleaseMask |
91                                      ButtonMotionMask;
92   XChangeWindowAttributes(blackbox->getXDisplay(), client.window,
93                           CWEventMask|CWDontPropagate, &attrib_set);
94
95   // fetch client size and placement
96   XWindowAttributes wattrib;
97   if ((! XGetWindowAttributes(blackbox->getXDisplay(),
98                               client.window, &wattrib)) ||
99       (! wattrib.screen) || wattrib.override_redirect) {
100 #ifdef    DEBUG
101     fprintf(stderr,
102             "BlackboxWindow::BlackboxWindow(): XGetWindowAttributes failed\n");
103 #endif // DEBUG
104
105     delete this;
106     return;
107   }
108
109   flags.moving = flags.resizing = flags.shaded = flags.visible =
110     flags.iconic = flags.focused = flags.stuck = flags.modal =
111     flags.send_focus_message = flags.shaped = False;
112   flags.maximized = 0;
113
114   blackbox_attrib.workspace = window_number = BSENTINEL;
115
116   blackbox_attrib.flags = blackbox_attrib.attrib = blackbox_attrib.stack
117     = blackbox_attrib.decoration = 0l;
118   blackbox_attrib.premax_x = blackbox_attrib.premax_y = 0;
119   blackbox_attrib.premax_w = blackbox_attrib.premax_h = 0;
120
121   frame.border_w = 1;
122   frame.window = frame.plate = frame.title = frame.handle = None;
123   frame.close_button = frame.iconify_button = frame.maximize_button = None;
124   frame.right_grip = frame.left_grip = None;
125
126   frame.ulabel_pixel = frame.flabel_pixel = frame.utitle_pixel =
127   frame.ftitle_pixel = frame.uhandle_pixel = frame.fhandle_pixel =
128     frame.ubutton_pixel = frame.fbutton_pixel = frame.pbutton_pixel =
129     frame.uborder_pixel = frame.fborder_pixel = frame.ugrip_pixel =
130     frame.fgrip_pixel = 0;
131   frame.utitle = frame.ftitle = frame.uhandle = frame.fhandle = None;
132   frame.ulabel = frame.flabel = frame.ubutton = frame.fbutton = None;
133   frame.pbutton = frame.ugrip = frame.fgrip = decorations;
134
135   decorations = Decor_Titlebar | Decor_Border | Decor_Handle |
136                 Decor_Iconify | Decor_Maximize;
137   functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize;
138
139   client.wm_hint_flags = client.normal_hint_flags = 0;
140   client.transient_for = 0;
141
142   // get the initial size and location of client window (relative to the
143   // _root window_). This position is the reference point used with the
144   // window's gravity to find the window's initial position.
145   client.rect.setRect(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
146   client.old_bw = wattrib.border_width;
147
148   windowmenu = 0;
149   lastButtonPressTime = 0;
150
151   timer = new BTimer(blackbox, this);
152   timer->setTimeout(blackbox->getAutoRaiseDelay());
153
154   if (! getBlackboxHints())
155     getMWMHints();
156
157   // get size, aspect, minimum/maximum size and other hints set by the
158   // client
159   getWMProtocols();
160   getWMHints();
161   getWMNormalHints();
162
163   if (client.initial_state == WithdrawnState) {
164     screen->getSlit()->addClient(client.window);
165     delete this;
166     return;
167   }
168
169   frame.window = createToplevelWindow();
170   frame.plate = createChildWindow(frame.window);
171   associateClientWindow();
172
173   blackbox->saveWindowSearch(frame.window, this);
174   blackbox->saveWindowSearch(frame.plate, this);
175   blackbox->saveWindowSearch(client.window, this);
176
177   // determine if this is a transient window
178   getTransientInfo();
179
180   // adjust the window decorations based on transience and window sizes
181   if (isTransient()) {
182     decorations &= ~(Decor_Maximize | Decor_Handle);
183     functions &= ~Func_Maximize;
184   }
185
186   if ((client.normal_hint_flags & PMinSize) &&
187       (client.normal_hint_flags & PMaxSize) &&
188       client.max_width <= client.min_width &&
189       client.max_height <= client.min_height) {
190     decorations &= ~(Decor_Maximize | Decor_Handle);
191     functions &= ~(Func_Resize | Func_Maximize);
192   }
193   upsize();
194
195   bool place_window = True;
196   if (blackbox->isStartup() || isTransient() ||
197       client.normal_hint_flags & (PPosition|USPosition)) {
198     setGravityOffsets();
199
200
201     if (blackbox->isStartup() ||
202         client.rect.intersects(screen->availableArea()))
203       place_window = False;
204   }
205
206   if (decorations & Decor_Titlebar)
207     createTitlebar();
208
209   if (decorations & Decor_Handle)
210     createHandle();
211
212 #ifdef    SHAPE
213   if (blackbox->hasShapeExtensions() && flags.shaped) {
214     configureShape();
215   }
216 #endif // SHAPE
217
218   if ((! screen->isSloppyFocus()) || screen->doClickRaise()) {
219     // grab button 1 for changing focus/raising
220     blackbox->grabButton(Button1, 0, frame.plate, True, ButtonPressMask,
221                          GrabModeSync, GrabModeSync, frame.plate, None);
222   }
223
224   blackbox->grabButton(Button1, Mod1Mask, frame.window, True,
225                        ButtonReleaseMask | ButtonMotionMask, GrabModeAsync,
226                        GrabModeAsync, frame.window, blackbox->getMoveCursor());
227   blackbox->grabButton(Button2, Mod1Mask, frame.window, True,
228                        ButtonReleaseMask, GrabModeAsync, GrabModeAsync,
229                        frame.window, None);
230   blackbox->grabButton(Button3, Mod1Mask, frame.window, True,
231                        ButtonReleaseMask | ButtonMotionMask, GrabModeAsync,
232                        GrabModeAsync, frame.window,
233                        blackbox->getLowerRightAngleCursor());
234
235   positionWindows();
236   decorate();
237
238   if (decorations & Decor_Titlebar)
239     XMapSubwindows(blackbox->getXDisplay(), frame.title);
240   XMapSubwindows(blackbox->getXDisplay(), frame.window);
241
242   windowmenu = new Windowmenu(this);
243
244   if (blackbox_attrib.workspace >= screen->getWorkspaceCount())
245     screen->getCurrentWorkspace()->addWindow(this, place_window);
246   else
247     screen->getWorkspace(blackbox_attrib.workspace)->
248       addWindow(this, place_window);
249
250   if (! place_window) {
251     // don't need to call configure if we are letting the workspace
252     // place the window
253     configure(frame.rect.x(), frame.rect.y(),
254               frame.rect.width(), frame.rect.height());
255   }
256
257   if (flags.shaded) {
258     flags.shaded = False;
259     shade();
260   }
261
262   if (flags.maximized && (functions & Func_Maximize)) {
263     remaximize();
264   }
265
266   setFocusFlag(False);
267 }
268
269
270 BlackboxWindow::~BlackboxWindow(void) {
271 #ifdef    DEBUG
272   fprintf(stderr, "BlackboxWindow::~BlackboxWindow: destroying 0x%lx\n",
273           client.window);
274 #endif // DEBUG
275
276   if (! timer) // window not managed...
277     return;
278
279   if (flags.moving || flags.resizing) {
280     screen->hideGeometry();
281     XUngrabPointer(blackbox->getXDisplay(), CurrentTime);
282   }
283
284   delete timer;
285
286   delete windowmenu;
287
288   if (client.window_group) {
289     BWindowGroup *group = blackbox->searchGroup(client.window_group);
290     if (group) group->removeWindow(this);
291   }
292
293   // remove ourselves from our transient_for
294   if (isTransient()) {
295     if (client.transient_for != (BlackboxWindow *) ~0ul) {
296       client.transient_for->client.transientList.remove(this);
297     }
298     client.transient_for = (BlackboxWindow*) 0;
299   }
300
301   if (client.transientList.size() > 0) {
302     // reset transient_for for all transients
303     BlackboxWindowList::iterator it, end = client.transientList.end();
304     for (it = client.transientList.begin(); it != end; ++it) {
305       (*it)->client.transient_for = (BlackboxWindow*) 0;
306     }
307   }
308
309   if (frame.title)
310     destroyTitlebar();
311
312   if (frame.handle)
313     destroyHandle();
314
315   if (frame.plate) {
316     blackbox->removeWindowSearch(frame.plate);
317     XDestroyWindow(blackbox->getXDisplay(), frame.plate);
318   }
319
320   if (frame.window) {
321     blackbox->removeWindowSearch(frame.window);
322     XDestroyWindow(blackbox->getXDisplay(), frame.window);
323   }
324
325   blackbox->removeWindowSearch(client.window);
326 }
327
328
329 /*
330  * Creates a new top level window, with a given location, size, and border
331  * width.
332  * Returns: the newly created window
333  */
334 Window BlackboxWindow::createToplevelWindow(void) {
335   XSetWindowAttributes attrib_create;
336   unsigned long create_mask = CWBackPixmap | CWBorderPixel | CWColormap |
337                               CWOverrideRedirect | CWEventMask;
338
339   attrib_create.background_pixmap = None;
340   attrib_create.colormap = screen->getColormap();
341   attrib_create.override_redirect = True;
342   attrib_create.event_mask = ButtonPressMask | ButtonReleaseMask |
343                              ButtonMotionMask | EnterWindowMask;
344
345   return XCreateWindow(blackbox->getXDisplay(), screen->getRootWindow(),
346                        -1, -1, 1, 1, frame.border_w, screen->getDepth(),
347                        InputOutput, screen->getVisual(), create_mask,
348                        &attrib_create);
349 }
350
351
352 /*
353  * Creates a child window, and optionally associates a given cursor with
354  * the new window.
355  */
356 Window BlackboxWindow::createChildWindow(Window parent, Cursor cursor) {
357   XSetWindowAttributes attrib_create;
358   unsigned long create_mask = CWBackPixmap | CWBorderPixel |
359                               CWEventMask;
360
361   attrib_create.background_pixmap = None;
362   attrib_create.event_mask = ButtonPressMask | ButtonReleaseMask |
363                              ButtonMotionMask | ExposureMask;
364
365   if (cursor) {
366     create_mask |= CWCursor;
367     attrib_create.cursor = cursor;
368   }
369
370   return XCreateWindow(blackbox->getXDisplay(), parent, 0, 0, 1, 1, 0,
371                        screen->getDepth(), InputOutput, screen->getVisual(),
372                        create_mask, &attrib_create);
373 }
374
375
376 void BlackboxWindow::associateClientWindow(void) {
377   XSetWindowBorderWidth(blackbox->getXDisplay(), client.window, 0);
378   getWMName();
379   getWMIconName();
380
381   XChangeSaveSet(blackbox->getXDisplay(), client.window, SetModeInsert);
382
383   XSelectInput(blackbox->getXDisplay(), frame.plate, SubstructureRedirectMask);
384
385   XGrabServer(blackbox->getXDisplay());
386   XSelectInput(blackbox->getXDisplay(), client.window, NoEventMask);
387   XReparentWindow(blackbox->getXDisplay(), client.window, frame.plate, 0, 0);
388   XSelectInput(blackbox->getXDisplay(), client.window,
389                PropertyChangeMask | FocusChangeMask | StructureNotifyMask);
390   XUngrabServer(blackbox->getXDisplay());
391
392   XRaiseWindow(blackbox->getXDisplay(), frame.plate);
393   XMapSubwindows(blackbox->getXDisplay(), frame.plate);
394
395
396 #ifdef    SHAPE
397   if (blackbox->hasShapeExtensions()) {
398     XShapeSelectInput(blackbox->getXDisplay(), client.window,
399                       ShapeNotifyMask);
400
401     Bool shaped = False;
402     int foo;
403     unsigned int ufoo;
404
405     XShapeQueryExtents(blackbox->getXDisplay(), client.window, &shaped,
406                        &foo, &foo, &ufoo, &ufoo, &foo, &foo, &foo,
407                        &ufoo, &ufoo);
408     flags.shaped = shaped;
409   }
410 #endif // SHAPE
411 }
412
413
414 void BlackboxWindow::decorate(void) {
415   BTexture* texture;
416
417   texture = &(screen->getWindowStyle()->b_focus);
418   frame.fbutton = texture->render(frame.button_w, frame.button_w,
419                                   frame.fbutton);
420   if (! frame.fbutton)
421     frame.fbutton_pixel = texture->color().pixel();
422
423   texture = &(screen->getWindowStyle()->b_unfocus);
424   frame.ubutton = texture->render(frame.button_w, frame.button_w,
425                                   frame.ubutton);
426   if (! frame.ubutton)
427     frame.ubutton_pixel = texture->color().pixel();
428
429   texture = &(screen->getWindowStyle()->b_pressed);
430   frame.pbutton = texture->render(frame.button_w, frame.button_w,
431                                   frame.pbutton);
432   if (! frame.pbutton)
433     frame.pbutton_pixel = texture->color().pixel();
434
435   if (decorations & Decor_Titlebar) {
436     texture = &(screen->getWindowStyle()->t_focus);
437     frame.ftitle = texture->render(frame.inside_w, frame.title_h,
438                                    frame.ftitle);
439     if (! frame.ftitle)
440       frame.ftitle_pixel = texture->color().pixel();
441
442     texture = &(screen->getWindowStyle()->t_unfocus);
443     frame.utitle = texture->render(frame.inside_w, frame.title_h,
444                                    frame.utitle);
445     if (! frame.utitle)
446       frame.utitle_pixel = texture->color().pixel();
447
448     XSetWindowBorder(blackbox->getXDisplay(), frame.title,
449                      screen->getBorderColor()->pixel());
450
451     decorateLabel();
452   }
453
454   if (decorations & Decor_Border) {
455     frame.fborder_pixel = screen->getWindowStyle()->f_focus.pixel();
456     frame.uborder_pixel = screen->getWindowStyle()->f_unfocus.pixel();
457     blackbox_attrib.flags |= AttribDecoration;
458     blackbox_attrib.decoration = DecorNormal;
459   } else {
460     blackbox_attrib.flags |= AttribDecoration;
461     blackbox_attrib.decoration = DecorNone;
462   }
463
464   if (decorations & Decor_Handle) {
465     texture = &(screen->getWindowStyle()->h_focus);
466     frame.fhandle = texture->render(frame.inside_w, frame.handle_h,
467                                     frame.fhandle);
468     if (! frame.fhandle)
469       frame.fhandle_pixel = texture->color().pixel();
470
471     texture = &(screen->getWindowStyle()->h_unfocus);
472     frame.uhandle = texture->render(frame.inside_w, frame.handle_h,
473                                     frame.uhandle);
474     if (! frame.uhandle)
475       frame.uhandle_pixel = texture->color().pixel();
476
477     texture = &(screen->getWindowStyle()->g_focus);
478     frame.fgrip = texture->render(frame.grip_w, frame.handle_h, frame.fgrip);
479     if (! frame.fgrip)
480       frame.fgrip_pixel = texture->color().pixel();
481
482     texture = &(screen->getWindowStyle()->g_unfocus);
483     frame.ugrip = texture->render(frame.grip_w, frame.handle_h, frame.ugrip);
484     if (! frame.ugrip)
485       frame.ugrip_pixel = texture->color().pixel();
486
487     XSetWindowBorder(blackbox->getXDisplay(), frame.handle,
488                      screen->getBorderColor()->pixel());
489     XSetWindowBorder(blackbox->getXDisplay(), frame.left_grip,
490                      screen->getBorderColor()->pixel());
491     XSetWindowBorder(blackbox->getXDisplay(), frame.right_grip,
492                      screen->getBorderColor()->pixel());
493   }
494
495   XSetWindowBorder(blackbox->getXDisplay(), frame.window,
496                    screen->getBorderColor()->pixel());
497 }
498
499
500 void BlackboxWindow::decorateLabel(void) {
501   BTexture *texture;
502
503   texture = &(screen->getWindowStyle()->l_focus);
504   frame.flabel = texture->render(frame.label_w, frame.label_h, frame.flabel);
505   if (! frame.flabel)
506     frame.flabel_pixel = texture->color().pixel();
507
508   texture = &(screen->getWindowStyle()->l_unfocus);
509   frame.ulabel = texture->render(frame.label_w, frame.label_h, frame.ulabel);
510   if (! frame.ulabel)
511     frame.ulabel_pixel = texture->color().pixel();
512 }
513
514
515 void BlackboxWindow::createHandle(void) {
516   frame.handle = createChildWindow(frame.window);
517   blackbox->saveWindowSearch(frame.handle, this);
518
519   frame.left_grip =
520     createChildWindow(frame.handle, blackbox->getLowerLeftAngleCursor());
521   blackbox->saveWindowSearch(frame.left_grip, this);
522
523   frame.right_grip =
524     createChildWindow(frame.handle, blackbox->getLowerRightAngleCursor());
525   blackbox->saveWindowSearch(frame.right_grip, this);
526 }
527
528
529 void BlackboxWindow::destroyHandle(void) {
530   if (frame.fhandle)
531     screen->getImageControl()->removeImage(frame.fhandle);
532
533   if (frame.uhandle)
534     screen->getImageControl()->removeImage(frame.uhandle);
535
536   if (frame.fgrip)
537     screen->getImageControl()->removeImage(frame.fgrip);
538
539   if (frame.ugrip)
540     screen->getImageControl()->removeImage(frame.ugrip);
541
542   blackbox->removeWindowSearch(frame.left_grip);
543   blackbox->removeWindowSearch(frame.right_grip);
544
545   XDestroyWindow(blackbox->getXDisplay(), frame.left_grip);
546   XDestroyWindow(blackbox->getXDisplay(), frame.right_grip);
547   frame.left_grip = frame.right_grip = None;
548
549   blackbox->removeWindowSearch(frame.handle);
550   XDestroyWindow(blackbox->getXDisplay(), frame.handle);
551   frame.handle = None;
552 }
553
554
555 void BlackboxWindow::createTitlebar(void) {
556   frame.title = createChildWindow(frame.window);
557   frame.label = createChildWindow(frame.title);
558   blackbox->saveWindowSearch(frame.title, this);
559   blackbox->saveWindowSearch(frame.label, this);
560
561   if (decorations & Decor_Iconify) createIconifyButton();
562   if (decorations & Decor_Maximize) createMaximizeButton();
563   if (decorations & Decor_Close) createCloseButton();
564 }
565
566
567 void BlackboxWindow::destroyTitlebar(void) {
568   if (frame.close_button)
569     destroyCloseButton();
570
571   if (frame.iconify_button)
572     destroyIconifyButton();
573
574   if (frame.maximize_button)
575     destroyMaximizeButton();
576
577   if (frame.ftitle)
578     screen->getImageControl()->removeImage(frame.ftitle);
579
580   if (frame.utitle)
581     screen->getImageControl()->removeImage(frame.utitle);
582
583   if (frame.flabel)
584     screen->getImageControl()->removeImage(frame.flabel);
585
586   if( frame.ulabel)
587     screen->getImageControl()->removeImage(frame.ulabel);
588
589   if (frame.fbutton)
590     screen->getImageControl()->removeImage(frame.fbutton);
591
592   if (frame.ubutton)
593     screen->getImageControl()->removeImage(frame.ubutton);
594
595   if (frame.pbutton)
596     screen->getImageControl()->removeImage(frame.pbutton);
597
598   blackbox->removeWindowSearch(frame.title);
599   blackbox->removeWindowSearch(frame.label);
600
601   XDestroyWindow(blackbox->getXDisplay(), frame.label);
602   XDestroyWindow(blackbox->getXDisplay(), frame.title);
603   frame.title = frame.label = None;
604 }
605
606
607 void BlackboxWindow::createCloseButton(void) {
608   if (frame.title != None) {
609     frame.close_button = createChildWindow(frame.title);
610     blackbox->saveWindowSearch(frame.close_button, this);
611   }
612 }
613
614
615 void BlackboxWindow::destroyCloseButton(void) {
616   blackbox->removeWindowSearch(frame.close_button);
617   XDestroyWindow(blackbox->getXDisplay(), frame.close_button);
618   frame.close_button = None;
619 }
620
621
622 void BlackboxWindow::createIconifyButton(void) {
623   if (frame.title != None) {
624     frame.iconify_button = createChildWindow(frame.title);
625     blackbox->saveWindowSearch(frame.iconify_button, this);
626   }
627 }
628
629
630 void BlackboxWindow::destroyIconifyButton(void) {
631   blackbox->removeWindowSearch(frame.iconify_button);
632   XDestroyWindow(blackbox->getXDisplay(), frame.iconify_button);
633   frame.iconify_button = None;
634 }
635
636
637 void BlackboxWindow::createMaximizeButton(void) {
638   if (frame.title != None) {
639     frame.maximize_button = createChildWindow(frame.title);
640     blackbox->saveWindowSearch(frame.maximize_button, this);
641   }
642 }
643
644
645 void BlackboxWindow::destroyMaximizeButton(void) {
646   blackbox->removeWindowSearch(frame.maximize_button);
647   XDestroyWindow(blackbox->getXDisplay(), frame.maximize_button);
648   frame.maximize_button = None;
649 }
650
651
652 void BlackboxWindow::positionButtons(bool redecorate_label) {
653   string layout = blackbox->getTitlebarLayout();
654   string parsed;
655
656   bool hasclose, hasiconify, hasmaximize, haslabel;
657   hasclose = hasiconify = hasmaximize = haslabel = false;
658
659   string::const_iterator it, end;
660   for (it = layout.begin(), end = layout.end(); it != end; ++it) {
661     switch(*it) {
662     case 'C':
663       if (! hasclose && (decorations & Decor_Close)) {
664         hasclose = true;
665         parsed += *it;
666       }
667       break;
668     case 'I':
669       if (! hasiconify && (decorations & Decor_Iconify)) {
670         hasiconify = true;
671         parsed += *it;
672       }
673       break;
674     case 'M':
675       if (! hasmaximize && (decorations & Decor_Maximize)) {
676         hasmaximize = true;
677         parsed += *it;
678       }
679       break;
680     case 'L':
681       if (! haslabel) {
682         haslabel = true;
683         parsed += *it;
684       }
685     }
686   }
687   if (! hasclose && frame.close_button)
688     destroyCloseButton();
689   if (! hasiconify && frame.iconify_button)
690     destroyIconifyButton();
691   if (! hasmaximize && frame.maximize_button)
692     destroyMaximizeButton();
693   if (! haslabel)
694     parsed += 'L';      // require that the label be in the layout
695
696   const unsigned int bsep = frame.bevel_w + 1;  // separation between elements
697   const unsigned int by = frame.bevel_w + 1;
698   const unsigned int ty = frame.bevel_w;
699
700   frame.label_w = frame.inside_w - bsep * 2 -
701     (frame.button_w + bsep) * (parsed.size() - 1);
702
703   unsigned int x = bsep;
704   for (it = parsed.begin(), end = parsed.end(); it != end; ++it) {
705     switch(*it) {
706     case 'C':
707       if (!frame.close_button) createCloseButton();
708       XMoveResizeWindow(blackbox->getXDisplay(), frame.close_button, x, by,
709                         frame.button_w, frame.button_w);
710       x += frame.button_w + bsep;
711       break;
712     case 'I':
713       if (!frame.iconify_button) createIconifyButton();
714       XMoveResizeWindow(blackbox->getXDisplay(), frame.iconify_button, x, by,
715                         frame.button_w, frame.button_w);
716       x += frame.button_w + bsep;
717       break;
718     case 'M':
719       if (!frame.maximize_button) createMaximizeButton();
720       XMoveResizeWindow(blackbox->getXDisplay(), frame.maximize_button, x, by,
721                         frame.button_w, frame.button_w);
722       x += frame.button_w + bsep;
723       break;
724     case 'L':
725       XMoveResizeWindow(blackbox->getXDisplay(), frame.label, x, ty,
726                         frame.label_w, frame.label_h);
727       x += frame.label_w + bsep;
728       break;
729     }
730   }
731
732   if (redecorate_label) decorateLabel();
733   redrawLabel();
734   redrawAllButtons();
735 }
736
737
738 void BlackboxWindow::reconfigure(void) {
739   upsize();
740
741   client.rect.setPos(frame.rect.left() + frame.margin.left,
742                      frame.rect.top() + frame.margin.top);
743
744   positionWindows();
745   decorate();
746
747   XClearWindow(blackbox->getXDisplay(), frame.window);
748   setFocusFlag(flags.focused);
749
750   configure(frame.rect.x(), frame.rect.y(),
751             frame.rect.width(), frame.rect.height());
752
753   if (windowmenu) {
754     windowmenu->move(windowmenu->getX(), frame.rect.y() + frame.title_h);
755     windowmenu->reconfigure();
756   }
757 }
758
759
760 void BlackboxWindow::updateFocusModel(void) {
761   if ((! screen->isSloppyFocus()) || screen->doClickRaise()) {
762     // grab button 1 for changing focus/raising
763     blackbox->grabButton(Button1, 0, frame.plate, True, ButtonPressMask,
764                          GrabModeSync, GrabModeSync, None, None);
765   } else {
766     blackbox->ungrabButton(Button1, 0, frame.plate);
767   }
768 }
769
770
771 void BlackboxWindow::positionWindows(void) {
772   XMoveResizeWindow(blackbox->getXDisplay(), frame.window,
773                     frame.rect.x(), frame.rect.y(), frame.inside_w,
774                     (flags.shaded) ? frame.title_h : frame.inside_h);
775   XSetWindowBorderWidth(blackbox->getXDisplay(), frame.window, frame.border_w);
776   XSetWindowBorderWidth(blackbox->getXDisplay(), frame.plate,
777                         frame.mwm_border_w);
778   XMoveResizeWindow(blackbox->getXDisplay(), frame.plate,
779                     frame.margin.left - frame.mwm_border_w - frame.border_w,
780                     frame.margin.top - frame.mwm_border_w - frame.border_w,
781                     client.rect.width(), client.rect.height());
782   XMoveResizeWindow(blackbox->getXDisplay(), client.window,
783                     0, 0, client.rect.width(), client.rect.height());
784
785   if (decorations & Decor_Titlebar) {
786     if (frame.title == None) createTitlebar();
787
788     XSetWindowBorderWidth(blackbox->getXDisplay(), frame.title,
789                           frame.border_w);
790     XMoveResizeWindow(blackbox->getXDisplay(), frame.title, -frame.border_w,
791                       -frame.border_w, frame.inside_w, frame.title_h);
792
793     positionButtons();
794     XMapSubwindows(blackbox->getXDisplay(), frame.title);
795     XMapWindow(blackbox->getXDisplay(), frame.title);
796   } else if (frame.title) {
797     destroyTitlebar();
798   }
799   if (decorations & Decor_Handle) {
800     if (frame.handle == None) createHandle();
801     XSetWindowBorderWidth(blackbox->getXDisplay(), frame.handle,
802                           frame.border_w);
803     XSetWindowBorderWidth(blackbox->getXDisplay(), frame.left_grip,
804                           frame.border_w);
805     XSetWindowBorderWidth(blackbox->getXDisplay(), frame.right_grip,
806                           frame.border_w);
807
808     // use client.rect here so the value is correct even if shaded
809     XMoveResizeWindow(blackbox->getXDisplay(), frame.handle,
810                       -frame.border_w,
811                       client.rect.height() + frame.margin.top +
812                       frame.mwm_border_w - frame.border_w,
813                       frame.inside_w, frame.handle_h);
814     XMoveResizeWindow(blackbox->getXDisplay(), frame.left_grip,
815                       -frame.border_w, -frame.border_w,
816                       frame.grip_w, frame.handle_h);
817     XMoveResizeWindow(blackbox->getXDisplay(), frame.right_grip,
818                       frame.inside_w - frame.grip_w - frame.border_w,
819                       -frame.border_w, frame.grip_w, frame.handle_h);
820
821     XMapSubwindows(blackbox->getXDisplay(), frame.handle);
822     XMapWindow(blackbox->getXDisplay(), frame.handle);
823   } else if (frame.handle) {
824     destroyHandle();
825   }
826 }
827
828
829 void BlackboxWindow::getWMName(void) {
830   XTextProperty text_prop;
831
832   if (XGetWMName(blackbox->getXDisplay(), client.window, &text_prop)) {
833     client.title = textPropertyToString(blackbox->getXDisplay(), text_prop);
834     if (client.title.empty())
835       client.title = i18n(WindowSet, WindowUnnamed, "Unnamed");
836     XFree((char *) text_prop.value);
837   } else {
838     client.title = i18n(WindowSet, WindowUnnamed, "Unnamed");
839   }
840 }
841
842
843 void BlackboxWindow::getWMIconName(void) {
844   XTextProperty text_prop;
845
846   if (XGetWMIconName(blackbox->getXDisplay(), client.window, &text_prop)) {
847     client.icon_title =
848       textPropertyToString(blackbox->getXDisplay(), text_prop);
849     if (client.icon_title.empty())
850       client.icon_title = client.title;
851     XFree((char *) text_prop.value);
852   } else {
853     client.icon_title = client.title;
854   }
855 }
856
857
858 /*
859  * Retrieve which WM Protocols are supported by the client window.
860  * If the WM_DELETE_WINDOW protocol is supported, add the close button to the
861  * window's decorations and allow the close behavior.
862  * If the WM_TAKE_FOCUS protocol is supported, save a value that indicates
863  * this.
864  */
865 void BlackboxWindow::getWMProtocols(void) {
866   Atom *proto;
867   int num_return = 0;
868
869   if (XGetWMProtocols(blackbox->getXDisplay(), client.window,
870                       &proto, &num_return)) {
871     for (int i = 0; i < num_return; ++i) {
872       if (proto[i] == blackbox->getWMDeleteAtom()) {
873         decorations |= Decor_Close;
874         functions |= Func_Close;
875       } else if (proto[i] == blackbox->getWMTakeFocusAtom())
876         flags.send_focus_message = True;
877       else if (proto[i] == blackbox->getBlackboxStructureMessagesAtom())
878         screen->addNetizen(new Netizen(screen, client.window));
879     }
880
881     XFree(proto);
882   }
883 }
884
885
886 /*
887  * Gets the value of the WM_HINTS property.
888  * If the property is not set, then use a set of default values.
889  */
890 void BlackboxWindow::getWMHints(void) {
891   focus_mode = F_Passive;
892   client.initial_state = NormalState;
893
894   // remove from current window group
895   if (client.window_group) {
896     BWindowGroup *group = blackbox->searchGroup(client.window_group);
897     if (group) group->removeWindow(this);
898   }
899   client.window_group = None;
900
901   XWMHints *wmhint = XGetWMHints(blackbox->getXDisplay(), client.window);
902   if (! wmhint) {
903     return;
904   }
905
906   if (wmhint->flags & InputHint) {
907     if (wmhint->input == True) {
908       if (flags.send_focus_message)
909         focus_mode = F_LocallyActive;
910     } else {
911       if (flags.send_focus_message)
912         focus_mode = F_GloballyActive;
913       else
914         focus_mode = F_NoInput;
915     }
916   }
917
918   if (wmhint->flags & StateHint)
919     client.initial_state = wmhint->initial_state;
920
921   if (wmhint->flags & WindowGroupHint) {
922     client.window_group = wmhint->window_group;
923
924     // add window to the appropriate group
925     BWindowGroup *group = blackbox->searchGroup(client.window_group);
926     if (! group) // no group found, create it!
927       group = new BWindowGroup(blackbox, client.window_group);
928     group->addWindow(this);
929   }
930
931   client.wm_hint_flags = wmhint->flags;
932   XFree(wmhint);
933 }
934
935
936 /*
937  * Gets the value of the WM_NORMAL_HINTS property.
938  * If the property is not set, then use a set of default values.
939  */
940 void BlackboxWindow::getWMNormalHints(void) {
941   long icccm_mask;
942   XSizeHints sizehint;
943
944   client.min_width = client.min_height =
945     client.width_inc = client.height_inc = 1;
946   client.base_width = client.base_height = 0;
947
948   /*
949     use the full screen, not the strut modified size. otherwise when the
950     availableArea changes max_width/height will be incorrect and lead to odd
951     rendering bugs.
952   */
953   const Rect& screen_area = screen->getRect();
954   client.max_width = screen_area.width();
955
956   client.max_height = screen_area.height();
957   client.min_aspect_x = client.min_aspect_y =
958     client.max_aspect_x = client.max_aspect_y = 1;
959   client.win_gravity = NorthWestGravity;
960
961   if (! XGetWMNormalHints(blackbox->getXDisplay(), client.window,
962                           &sizehint, &icccm_mask))
963     return;
964
965   client.normal_hint_flags = sizehint.flags;
966
967   if (sizehint.flags & PMinSize) {
968     client.min_width = sizehint.min_width;
969     client.min_height = sizehint.min_height;
970   }
971
972   if (sizehint.flags & PMaxSize) {
973     client.max_width = sizehint.max_width;
974     client.max_height = sizehint.max_height;
975   }
976
977   if (sizehint.flags & PResizeInc) {
978     client.width_inc = sizehint.width_inc;
979     client.height_inc = sizehint.height_inc;
980   }
981
982   if (sizehint.flags & PAspect) {
983     client.min_aspect_x = sizehint.min_aspect.x;
984     client.min_aspect_y = sizehint.min_aspect.y;
985     client.max_aspect_x = sizehint.max_aspect.x;
986     client.max_aspect_y = sizehint.max_aspect.y;
987   }
988
989   if (sizehint.flags & PBaseSize) {
990     client.base_width = sizehint.base_width;
991     client.base_height = sizehint.base_height;
992   }
993
994   if (sizehint.flags & PWinGravity)
995     client.win_gravity = sizehint.win_gravity;
996 }
997
998
999 /*
1000  * Gets the MWM hints for the class' contained window.
1001  * This is used while initializing the window to its first state, and not
1002  * thereafter.
1003  * Returns: true if the MWM hints are successfully retreived and applied;
1004  * false if they are not.
1005  */
1006 void BlackboxWindow::getMWMHints(void) {
1007   int format;
1008   Atom atom_return;
1009   unsigned long num, len;
1010   MwmHints *mwm_hint = 0;
1011
1012   int ret = XGetWindowProperty(blackbox->getXDisplay(), client.window,
1013                                blackbox->getMotifWMHintsAtom(), 0,
1014                                PropMwmHintsElements, False,
1015                                blackbox->getMotifWMHintsAtom(), &atom_return,
1016                                &format, &num, &len,
1017                                (unsigned char **) &mwm_hint);
1018
1019   if (ret != Success || ! mwm_hint || num != PropMwmHintsElements)
1020     return;
1021
1022   if (mwm_hint->flags & MwmHintsDecorations) {
1023     if (mwm_hint->decorations & MwmDecorAll) {
1024       decorations = Decor_Titlebar | Decor_Handle | Decor_Border |
1025                     Decor_Iconify | Decor_Maximize | Decor_Close;
1026     } else {
1027       decorations = 0;
1028
1029       if (mwm_hint->decorations & MwmDecorBorder)
1030         decorations |= Decor_Border;
1031       if (mwm_hint->decorations & MwmDecorHandle)
1032         decorations |= Decor_Handle;
1033       if (mwm_hint->decorations & MwmDecorTitle)
1034         decorations |= Decor_Titlebar;
1035       if (mwm_hint->decorations & MwmDecorIconify)
1036         decorations |= Decor_Iconify;
1037       if (mwm_hint->decorations & MwmDecorMaximize)
1038         decorations |= Decor_Maximize;
1039     }
1040   }
1041
1042   if (mwm_hint->flags & MwmHintsFunctions) {
1043     if (mwm_hint->functions & MwmFuncAll) {
1044       functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize |
1045                   Func_Close;
1046     } else {
1047       functions = 0;
1048
1049       if (mwm_hint->functions & MwmFuncResize)
1050         functions |= Func_Resize;
1051       if (mwm_hint->functions & MwmFuncMove)
1052         functions |= Func_Move;
1053       if (mwm_hint->functions & MwmFuncIconify)
1054         functions |= Func_Iconify;
1055       if (mwm_hint->functions & MwmFuncMaximize)
1056         functions |= Func_Maximize;
1057       if (mwm_hint->functions & MwmFuncClose)
1058         functions |= Func_Close;
1059     }
1060   }
1061   XFree(mwm_hint);
1062 }
1063
1064
1065 /*
1066  * Gets the blackbox hints from the class' contained window.
1067  * This is used while initializing the window to its first state, and not
1068  * thereafter.
1069  * Returns: true if the hints are successfully retreived and applied; false if
1070  * they are not.
1071  */
1072 bool BlackboxWindow::getBlackboxHints(void) {
1073   int format;
1074   Atom atom_return;
1075   unsigned long num, len;
1076   BlackboxHints *blackbox_hint = 0;
1077
1078   int ret = XGetWindowProperty(blackbox->getXDisplay(), client.window,
1079                                blackbox->getBlackboxHintsAtom(), 0,
1080                                PropBlackboxHintsElements, False,
1081                                blackbox->getBlackboxHintsAtom(), &atom_return,
1082                                &format, &num, &len,
1083                                (unsigned char **) &blackbox_hint);
1084   if (ret != Success || ! blackbox_hint || num != PropBlackboxHintsElements)
1085     return False;
1086
1087   if (blackbox_hint->flags & AttribShaded)
1088     flags.shaded = (blackbox_hint->attrib & AttribShaded);
1089
1090   if ((blackbox_hint->flags & AttribMaxHoriz) &&
1091       (blackbox_hint->flags & AttribMaxVert))
1092     flags.maximized = (blackbox_hint->attrib &
1093                        (AttribMaxHoriz | AttribMaxVert)) ? 1 : 0;
1094   else if (blackbox_hint->flags & AttribMaxVert)
1095     flags.maximized = (blackbox_hint->attrib & AttribMaxVert) ? 2 : 0;
1096   else if (blackbox_hint->flags & AttribMaxHoriz)
1097     flags.maximized = (blackbox_hint->attrib & AttribMaxHoriz) ? 3 : 0;
1098
1099   if (blackbox_hint->flags & AttribOmnipresent)
1100     flags.stuck = (blackbox_hint->attrib & AttribOmnipresent);
1101
1102   if (blackbox_hint->flags & AttribWorkspace)
1103     blackbox_attrib.workspace = blackbox_hint->workspace;
1104
1105   // if (blackbox_hint->flags & AttribStack)
1106   //   don't yet have always on top/bottom for blackbox yet... working
1107   //   on that
1108
1109   if (blackbox_hint->flags & AttribDecoration) {
1110     switch (blackbox_hint->decoration) {
1111     case DecorNone:
1112       // clear all decorations except close
1113       decorations &= Decor_Close;
1114       // clear all functions except close
1115       functions &= Func_Close;
1116
1117       break;
1118
1119     case DecorTiny:
1120       decorations |= Decor_Titlebar | Decor_Iconify;
1121       decorations &= ~(Decor_Border | Decor_Handle | Decor_Maximize);
1122       functions |= Func_Move | Func_Iconify;
1123       functions &= ~(Func_Resize | Func_Maximize);
1124
1125       break;
1126
1127     case DecorTool:
1128       decorations |= Decor_Titlebar;
1129       decorations &= ~(Decor_Iconify | Decor_Border | Decor_Handle);
1130       functions |= Func_Move;
1131       functions &= ~(Func_Resize | Func_Maximize | Func_Iconify);
1132
1133       break;
1134
1135     case DecorNormal:
1136     default:
1137       decorations |= Decor_Titlebar | Decor_Border | Decor_Handle |
1138                      Decor_Iconify | Decor_Maximize;
1139       functions |= Func_Resize | Func_Move | Func_Iconify | Func_Maximize;
1140
1141       break;
1142     }
1143
1144     reconfigure();
1145   }
1146   XFree(blackbox_hint);
1147   return True;
1148 }
1149
1150
1151 void BlackboxWindow::getTransientInfo(void) {
1152   if (client.transient_for &&
1153       client.transient_for != (BlackboxWindow *) ~0ul) {
1154     // the transient for hint was removed, so we need to tell our
1155     // previous transient_for that we are going away
1156     client.transient_for->client.transientList.remove(this);
1157   }
1158
1159   // we have no transient_for until we find a new one
1160   client.transient_for = 0;
1161
1162   Window trans_for;
1163   if (! XGetTransientForHint(blackbox->getXDisplay(), client.window,
1164                              &trans_for)) {
1165     // transient_for hint not set
1166     return;
1167   }
1168
1169   if (trans_for == client.window) {
1170     // wierd client... treat this window as a normal window
1171     return;
1172   }
1173
1174   if (trans_for == None || trans_for == screen->getRootWindow()) {
1175     // this is an undocumented interpretation of the ICCCM. a transient
1176     // associated with None/Root/itself is assumed to be a modal root
1177     // transient.  we don't support the concept of a global transient,
1178     // so we just associate this transient with nothing, and perhaps
1179     // we will add support later for global modality.
1180     client.transient_for = (BlackboxWindow *) ~0ul;
1181     flags.modal = True;
1182     return;
1183   }
1184
1185   client.transient_for = blackbox->searchWindow(trans_for);
1186   if (! client.transient_for &&
1187       client.window_group && trans_for == client.window_group) {
1188     // no direct transient_for, perhaps this is a group transient?
1189     BWindowGroup *group = blackbox->searchGroup(client.window_group);
1190     if (group) client.transient_for = group->find(screen);
1191   }
1192
1193   if (! client.transient_for || client.transient_for == this) {
1194     // no transient_for found, or we have a wierd client that wants to be
1195     // a transient for itself, so we treat this window as a normal window
1196     client.transient_for = (BlackboxWindow*) 0;
1197     return;
1198   }
1199
1200   // register ourselves with our new transient_for
1201   client.transient_for->client.transientList.push_back(this);
1202   flags.stuck = client.transient_for->flags.stuck;
1203 }
1204
1205
1206 BlackboxWindow *BlackboxWindow::getTransientFor(void) const {
1207   if (client.transient_for &&
1208       client.transient_for != (BlackboxWindow*) ~0ul)
1209     return client.transient_for;
1210   return 0;
1211 }
1212
1213
1214 void BlackboxWindow::configure(int dx, int dy,
1215                                unsigned int dw, unsigned int dh) {
1216   bool send_event = (frame.rect.x() != dx || frame.rect.y() != dy);
1217
1218   if ((dw != frame.rect.width()) || (dh != frame.rect.height())) {
1219     frame.rect.setRect(dx, dy, dw, dh);
1220     frame.inside_w = frame.rect.width() - (frame.border_w * 2);
1221     frame.inside_h = frame.rect.height() - (frame.border_w * 2);
1222
1223     if (frame.rect.right() <= 0 || frame.rect.bottom() <= 0)
1224       frame.rect.setPos(0, 0);
1225
1226     client.rect.setCoords(frame.rect.left() + frame.margin.left,
1227                           frame.rect.top() + frame.margin.top,
1228                           frame.rect.right() - frame.margin.right,
1229                           frame.rect.bottom() - frame.margin.bottom);
1230
1231 #ifdef    SHAPE
1232     if (blackbox->hasShapeExtensions() && flags.shaped) {
1233       configureShape();
1234     }
1235 #endif // SHAPE
1236
1237     positionWindows();
1238     decorate();
1239     setFocusFlag(flags.focused);
1240     redrawAllButtons();
1241   } else {
1242     frame.rect.setPos(dx, dy);
1243
1244     XMoveWindow(blackbox->getXDisplay(), frame.window,
1245                 frame.rect.x(), frame.rect.y());
1246
1247     if (! flags.moving) send_event = True;
1248   }
1249
1250   if (send_event && ! flags.moving) {
1251     client.rect.setPos(frame.rect.left() + frame.margin.left,
1252                        frame.rect.top() + frame.margin.top);
1253
1254     XEvent event;
1255     event.type = ConfigureNotify;
1256
1257     event.xconfigure.display = blackbox->getXDisplay();
1258     event.xconfigure.event = client.window;
1259     event.xconfigure.window = client.window;
1260     event.xconfigure.x = client.rect.x();
1261     event.xconfigure.y = client.rect.y();
1262     event.xconfigure.width = client.rect.width();
1263     event.xconfigure.height = client.rect.height();
1264     event.xconfigure.border_width = client.old_bw;
1265     event.xconfigure.above = frame.window;
1266     event.xconfigure.override_redirect = False;
1267
1268     XSendEvent(blackbox->getXDisplay(), client.window, True,
1269                NoEventMask, &event);
1270
1271     screen->updateNetizenConfigNotify(&event);
1272   }
1273 }
1274
1275
1276 #ifdef SHAPE
1277 void BlackboxWindow::configureShape(void) {
1278   XShapeCombineShape(blackbox->getXDisplay(), frame.window, ShapeBounding,
1279                      frame.margin.left - frame.border_w,
1280                      frame.margin.top - frame.border_w,
1281                      client.window, ShapeBounding, ShapeSet);
1282
1283   int num = 0;
1284   XRectangle xrect[2];
1285
1286   if (decorations & Decor_Titlebar) {
1287     xrect[0].x = xrect[0].y = -frame.border_w;
1288     xrect[0].width = frame.rect.width();
1289     xrect[0].height = frame.title_h + (frame.border_w * 2);
1290     ++num;
1291   }
1292
1293   if (decorations & Decor_Handle) {
1294     xrect[1].x = -frame.border_w;
1295     xrect[1].y = frame.rect.height() - frame.margin.bottom +
1296                  frame.mwm_border_w - frame.border_w;
1297     xrect[1].width = frame.rect.width();
1298     xrect[1].height = frame.handle_h + (frame.border_w * 2);
1299     ++num;
1300   }
1301
1302   XShapeCombineRectangles(blackbox->getXDisplay(), frame.window,
1303                           ShapeBounding, 0, 0, xrect, num,
1304                           ShapeUnion, Unsorted);
1305 }
1306 #endif // SHAPE
1307
1308
1309 bool BlackboxWindow::setInputFocus(void) {
1310   if (flags.focused) return True;
1311
1312   if (! client.rect.intersects(screen->getRect())) {
1313     // client is outside the screen, move it to the center
1314     configure((screen->getWidth() - frame.rect.width()) / 2,
1315               (screen->getHeight() - frame.rect.height()) / 2,
1316               frame.rect.width(), frame.rect.height());
1317   }
1318
1319   if (client.transientList.size() > 0) {
1320     // transfer focus to any modal transients
1321     BlackboxWindowList::iterator it, end = client.transientList.end();
1322     for (it = client.transientList.begin(); it != end; ++it) {
1323       if ((*it)->flags.modal) return (*it)->setInputFocus();
1324     }
1325   }
1326
1327   bool ret = True;
1328   if (focus_mode == F_LocallyActive || focus_mode == F_Passive) {
1329     XSetInputFocus(blackbox->getXDisplay(), client.window,
1330                    RevertToPointerRoot, CurrentTime);
1331
1332     blackbox->setFocusedWindow(this);
1333   } else {
1334     /* we could set the focus to none, since the window doesn't accept focus,
1335      * but we shouldn't set focus to nothing since this would surely make
1336      * someone angry
1337      */
1338     ret = False;
1339   }
1340
1341   if (flags.send_focus_message) {
1342     XEvent ce;
1343     ce.xclient.type = ClientMessage;
1344     ce.xclient.message_type = blackbox->getWMProtocolsAtom();
1345     ce.xclient.display = blackbox->getXDisplay();
1346     ce.xclient.window = client.window;
1347     ce.xclient.format = 32;
1348     ce.xclient.data.l[0] = blackbox->getWMTakeFocusAtom();
1349     ce.xclient.data.l[1] = blackbox->getLastTime();
1350     ce.xclient.data.l[2] = 0l;
1351     ce.xclient.data.l[3] = 0l;
1352     ce.xclient.data.l[4] = 0l;
1353     XSendEvent(blackbox->getXDisplay(), client.window, False,
1354                NoEventMask, &ce);
1355   }
1356
1357   return ret;
1358 }
1359
1360
1361 void BlackboxWindow::iconify(void) {
1362   if (flags.iconic) return;
1363
1364   if (windowmenu) windowmenu->hide();
1365
1366   setState(IconicState);
1367
1368   /*
1369    * we don't want this XUnmapWindow call to generate an UnmapNotify event, so
1370    * we need to clear the event mask on client.window for a split second.
1371    * HOWEVER, since X11 is asynchronous, the window could be destroyed in that
1372    * split second, leaving us with a ghost window... so, we need to do this
1373    * while the X server is grabbed
1374    */
1375   XGrabServer(blackbox->getXDisplay());
1376   XSelectInput(blackbox->getXDisplay(), client.window, NoEventMask);
1377   XUnmapWindow(blackbox->getXDisplay(), client.window);
1378   XSelectInput(blackbox->getXDisplay(), client.window,
1379                PropertyChangeMask | FocusChangeMask | StructureNotifyMask);
1380   XUngrabServer(blackbox->getXDisplay());
1381
1382   XUnmapWindow(blackbox->getXDisplay(), frame.window);
1383   flags.visible = False;
1384   flags.iconic = True;
1385
1386   screen->getWorkspace(blackbox_attrib.workspace)->removeWindow(this);
1387
1388   if (isTransient()) {
1389     if (client.transient_for != (BlackboxWindow *) ~0ul &&
1390         ! client.transient_for->flags.iconic) {
1391       // iconify our transient_for
1392       client.transient_for->iconify();
1393     }
1394   }
1395
1396   screen->addIcon(this);
1397
1398   if (client.transientList.size() > 0) {
1399     // iconify all transients
1400     BlackboxWindowList::iterator it, end = client.transientList.end();
1401     for (it = client.transientList.begin(); it != end; ++it) {
1402       if (! (*it)->flags.iconic) (*it)->iconify();
1403     }
1404   }
1405 }
1406
1407
1408 void BlackboxWindow::show(void) {
1409   setState(NormalState);
1410
1411   XMapWindow(blackbox->getXDisplay(), client.window);
1412   XMapSubwindows(blackbox->getXDisplay(), frame.window);
1413   XMapWindow(blackbox->getXDisplay(), frame.window);
1414
1415   flags.visible = True;
1416   flags.iconic = False;
1417 }
1418
1419
1420 void BlackboxWindow::deiconify(bool reassoc, bool raise) {
1421   if (flags.iconic || reassoc)
1422     screen->reassociateWindow(this, BSENTINEL, False);
1423   else if (blackbox_attrib.workspace != screen->getCurrentWorkspace()->getID())
1424     return;
1425
1426   show();
1427
1428   // reassociate and deiconify all transients
1429   if (reassoc && client.transientList.size() > 0) {
1430     BlackboxWindowList::iterator it, end = client.transientList.end();
1431     for (it = client.transientList.begin(); it != end; ++it) {
1432       (*it)->deiconify(True, False);
1433     }
1434   }
1435
1436   if (raise)
1437     screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
1438 }
1439
1440
1441 void BlackboxWindow::close(void) {
1442   XEvent ce;
1443   ce.xclient.type = ClientMessage;
1444   ce.xclient.message_type = blackbox->getWMProtocolsAtom();
1445   ce.xclient.display = blackbox->getXDisplay();
1446   ce.xclient.window = client.window;
1447   ce.xclient.format = 32;
1448   ce.xclient.data.l[0] = blackbox->getWMDeleteAtom();
1449   ce.xclient.data.l[1] = CurrentTime;
1450   ce.xclient.data.l[2] = 0l;
1451   ce.xclient.data.l[3] = 0l;
1452   ce.xclient.data.l[4] = 0l;
1453   XSendEvent(blackbox->getXDisplay(), client.window, False, NoEventMask, &ce);
1454 }
1455
1456
1457 void BlackboxWindow::withdraw(void) {
1458   setState(current_state);
1459
1460   flags.visible = False;
1461   flags.iconic = False;
1462
1463   XUnmapWindow(blackbox->getXDisplay(), frame.window);
1464
1465   XGrabServer(blackbox->getXDisplay());
1466   XSelectInput(blackbox->getXDisplay(), client.window, NoEventMask);
1467   XUnmapWindow(blackbox->getXDisplay(), client.window);
1468   XSelectInput(blackbox->getXDisplay(), client.window,
1469                PropertyChangeMask | FocusChangeMask | StructureNotifyMask);
1470     XUngrabServer(blackbox->getXDisplay());
1471
1472   if (windowmenu) windowmenu->hide();
1473 }
1474
1475
1476 void BlackboxWindow::maximize(unsigned int button) {
1477   // handle case where menu is open then the max button is used instead
1478   if (windowmenu && windowmenu->isVisible()) windowmenu->hide();
1479
1480   if (flags.maximized) {
1481     flags.maximized = 0;
1482
1483     blackbox_attrib.flags &= ! (AttribMaxHoriz | AttribMaxVert);
1484     blackbox_attrib.attrib &= ! (AttribMaxHoriz | AttribMaxVert);
1485
1486     /*
1487       when a resize is begun, maximize(0) is called to clear any maximization
1488       flags currently set.  Otherwise it still thinks it is maximized.
1489       so we do not need to call configure() because resizing will handle it
1490     */
1491     if (! flags.resizing)
1492       configure(blackbox_attrib.premax_x, blackbox_attrib.premax_y,
1493                 blackbox_attrib.premax_w, blackbox_attrib.premax_h);
1494
1495     blackbox_attrib.premax_x = blackbox_attrib.premax_y = 0;
1496     blackbox_attrib.premax_w = blackbox_attrib.premax_h = 0;
1497
1498     redrawAllButtons();
1499     setState(current_state);
1500     return;
1501   }
1502
1503   blackbox_attrib.premax_x = frame.rect.x();
1504   blackbox_attrib.premax_y = frame.rect.y();
1505   blackbox_attrib.premax_w = frame.rect.width();
1506   // use client.rect so that clients can be restored even if shaded
1507   blackbox_attrib.premax_h =
1508     client.rect.height() + frame.margin.top + frame.margin.bottom;
1509
1510   const Rect &screen_area = screen->availableArea();
1511   frame.changing = screen_area;
1512   constrain(TopLeft);
1513
1514   switch(button) {
1515   case 1:
1516     blackbox_attrib.flags |= AttribMaxHoriz | AttribMaxVert;
1517     blackbox_attrib.attrib |= AttribMaxHoriz | AttribMaxVert;
1518     break;
1519
1520   case 2:
1521     blackbox_attrib.flags |= AttribMaxVert;
1522     blackbox_attrib.attrib |= AttribMaxVert;
1523
1524     frame.changing.setX(frame.rect.x());
1525     frame.changing.setWidth(frame.rect.width());
1526     break;
1527
1528   case 3:
1529     blackbox_attrib.flags |= AttribMaxHoriz;
1530     blackbox_attrib.attrib |= AttribMaxHoriz;
1531
1532     frame.changing.setY(frame.rect.y());
1533     frame.changing.setHeight(frame.rect.height());
1534     break;
1535   }
1536
1537   if (flags.shaded) {
1538     blackbox_attrib.flags ^= AttribShaded;
1539     blackbox_attrib.attrib ^= AttribShaded;
1540     flags.shaded = False;
1541   }
1542
1543   flags.maximized = button;
1544
1545   configure(frame.changing.x(), frame.changing.y(),
1546             frame.changing.width(), frame.changing.height());
1547   if (flags.focused)
1548     screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
1549   redrawAllButtons();
1550   setState(current_state);
1551 }
1552
1553
1554 // re-maximizes the window to take into account availableArea changes
1555 void BlackboxWindow::remaximize(void) {
1556   // save the original dimensions because maximize will wipe them out
1557   int premax_x = blackbox_attrib.premax_x,
1558     premax_y = blackbox_attrib.premax_y,
1559     premax_w = blackbox_attrib.premax_w,
1560     premax_h = blackbox_attrib.premax_h;
1561
1562   unsigned int button = flags.maximized;
1563   flags.maximized = 0; // trick maximize() into working
1564   maximize(button);
1565
1566   // restore saved values
1567   blackbox_attrib.premax_x = premax_x;
1568   blackbox_attrib.premax_y = premax_y;
1569   blackbox_attrib.premax_w = premax_w;
1570   blackbox_attrib.premax_h = premax_h;
1571 }
1572
1573
1574 void BlackboxWindow::setWorkspace(unsigned int n) {
1575   blackbox_attrib.flags |= AttribWorkspace;
1576   blackbox_attrib.workspace = n;
1577 }
1578
1579
1580 void BlackboxWindow::shade(void) {
1581   if (flags.shaded) {
1582     XResizeWindow(blackbox->getXDisplay(), frame.window,
1583                   frame.inside_w, frame.inside_h);
1584     flags.shaded = False;
1585     blackbox_attrib.flags ^= AttribShaded;
1586     blackbox_attrib.attrib ^= AttribShaded;
1587
1588     setState(NormalState);
1589
1590     // set the frame rect to the normal size
1591     frame.rect.setHeight(client.rect.height() + frame.margin.top +
1592                          frame.margin.bottom);
1593   } else {
1594     if (! (decorations & Decor_Titlebar))
1595       return;
1596
1597     XResizeWindow(blackbox->getXDisplay(), frame.window,
1598                   frame.inside_w, frame.title_h);
1599     flags.shaded = True;
1600     blackbox_attrib.flags |= AttribShaded;
1601     blackbox_attrib.attrib |= AttribShaded;
1602
1603     setState(IconicState);
1604
1605     // set the frame rect to the shaded size
1606     frame.rect.setHeight(frame.title_h + (frame.border_w * 2));
1607   }
1608 }
1609
1610
1611 void BlackboxWindow::stick(void) {
1612   if (flags.stuck) {
1613     blackbox_attrib.flags ^= AttribOmnipresent;
1614     blackbox_attrib.attrib ^= AttribOmnipresent;
1615
1616     flags.stuck = False;
1617
1618     if (! flags.iconic)
1619       screen->reassociateWindow(this, BSENTINEL, True);
1620
1621     setState(current_state);
1622   } else {
1623     flags.stuck = True;
1624
1625     blackbox_attrib.flags |= AttribOmnipresent;
1626     blackbox_attrib.attrib |= AttribOmnipresent;
1627
1628     setState(current_state);
1629   }
1630 }
1631
1632
1633 void BlackboxWindow::setFocusFlag(bool focus) {
1634   flags.focused = focus;
1635
1636   if (decorations & Decor_Titlebar) {
1637     if (flags.focused) {
1638       if (frame.ftitle)
1639         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
1640                                    frame.title, frame.ftitle);
1641       else
1642         XSetWindowBackground(blackbox->getXDisplay(),
1643                              frame.title, frame.ftitle_pixel);
1644     } else {
1645       if (frame.utitle)
1646         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
1647                                    frame.title, frame.utitle);
1648       else
1649         XSetWindowBackground(blackbox->getXDisplay(),
1650                              frame.title, frame.utitle_pixel);
1651     }
1652     XClearWindow(blackbox->getXDisplay(), frame.title);
1653
1654     redrawLabel();
1655     redrawAllButtons();
1656   }
1657
1658   if (decorations & Decor_Handle) {
1659     if (flags.focused) {
1660       if (frame.fhandle)
1661         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
1662                                    frame.handle, frame.fhandle);
1663       else
1664         XSetWindowBackground(blackbox->getXDisplay(),
1665                              frame.handle, frame.fhandle_pixel);
1666
1667       if (frame.fgrip) {
1668         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
1669                                    frame.left_grip, frame.fgrip);
1670         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
1671                                    frame.right_grip, frame.fgrip);
1672       } else {
1673         XSetWindowBackground(blackbox->getXDisplay(),
1674                              frame.left_grip, frame.fgrip_pixel);
1675         XSetWindowBackground(blackbox->getXDisplay(),
1676                              frame.right_grip, frame.fgrip_pixel);
1677       }
1678     } else {
1679       if (frame.uhandle)
1680         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
1681                                    frame.handle, frame.uhandle);
1682       else
1683         XSetWindowBackground(blackbox->getXDisplay(),
1684                              frame.handle, frame.uhandle_pixel);
1685
1686       if (frame.ugrip) {
1687         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
1688                                    frame.left_grip, frame.ugrip);
1689         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
1690                                    frame.right_grip, frame.ugrip);
1691       } else {
1692         XSetWindowBackground(blackbox->getXDisplay(),
1693                              frame.left_grip, frame.ugrip_pixel);
1694         XSetWindowBackground(blackbox->getXDisplay(),
1695                              frame.right_grip, frame.ugrip_pixel);
1696       }
1697     }
1698     XClearWindow(blackbox->getXDisplay(), frame.handle);
1699     XClearWindow(blackbox->getXDisplay(), frame.left_grip);
1700     XClearWindow(blackbox->getXDisplay(), frame.right_grip);
1701   }
1702
1703   if (decorations & Decor_Border) {
1704     if (flags.focused)
1705       XSetWindowBorder(blackbox->getXDisplay(),
1706                        frame.plate, frame.fborder_pixel);
1707     else
1708       XSetWindowBorder(blackbox->getXDisplay(),
1709                        frame.plate, frame.uborder_pixel);
1710   }
1711
1712   if (screen->isSloppyFocus() && screen->doAutoRaise()) {
1713     if (isFocused()) timer->start();
1714     else timer->stop();
1715   }
1716
1717   if (isFocused())
1718     blackbox->setFocusedWindow(this);
1719 }
1720
1721
1722 void BlackboxWindow::installColormap(bool install) {
1723   int i = 0, ncmap = 0;
1724   Colormap *cmaps = XListInstalledColormaps(blackbox->getXDisplay(),
1725                                             client.window, &ncmap);
1726   XWindowAttributes wattrib;
1727   if (cmaps) {
1728     if (XGetWindowAttributes(blackbox->getXDisplay(),
1729                              client.window, &wattrib)) {
1730       if (install) {
1731         // install the window's colormap
1732         for (i = 0; i < ncmap; i++) {
1733           if (*(cmaps + i) == wattrib.colormap)
1734             // this window is using an installed color map... do not install
1735             install = False;
1736         }
1737         // otherwise, install the window's colormap
1738         if (install)
1739           XInstallColormap(blackbox->getXDisplay(), wattrib.colormap);
1740       } else {
1741         // uninstall the window's colormap
1742         for (i = 0; i < ncmap; i++) {
1743           if (*(cmaps + i) == wattrib.colormap)
1744             // we found the colormap to uninstall
1745             XUninstallColormap(blackbox->getXDisplay(), wattrib.colormap);
1746         }
1747       }
1748     }
1749
1750     XFree(cmaps);
1751   }
1752 }
1753
1754
1755 void BlackboxWindow::setState(unsigned long new_state) {
1756   current_state = new_state;
1757
1758   unsigned long state[2];
1759   state[0] = current_state;
1760   state[1] = None;
1761   xatom->setValue(client.window, XAtom::wm_state, XAtom::wm_state, state, 2);
1762  
1763   xatom->setValue(client.window, XAtom::blackbox_attributes,
1764                   XAtom::blackbox_attributes, (unsigned long *)&blackbox_attrib,
1765                   PropBlackboxAttributesElements);
1766 }
1767
1768
1769 bool BlackboxWindow::getState(void) {
1770   current_state = 0;
1771
1772   Atom atom_return;
1773   bool ret = False;
1774   unsigned long *state, nitems;
1775
1776   if (! xatom->getValue(client.window, XAtom::wm_state, XAtom::wm_state, nitems,
1777                         &state))
1778     return False;
1779
1780   current_state = static_cast<unsigned long>(state[0]);
1781   delete state;
1782
1783   return True;
1784 }
1785
1786
1787 void BlackboxWindow::restoreAttributes(void) {
1788   if (! getState()) current_state = NormalState;
1789
1790   Atom atom_return;
1791   int foo;
1792   unsigned long ulfoo, nitems;
1793
1794   BlackboxAttributes *net;
1795   int ret = XGetWindowProperty(blackbox->getXDisplay(), client.window,
1796                                blackbox->getBlackboxAttributesAtom(), 0l,
1797                                PropBlackboxAttributesElements, False,
1798                                blackbox->getBlackboxAttributesAtom(),
1799                                &atom_return, &foo, &nitems, &ulfoo,
1800                                (unsigned char **) &net);
1801   if (ret != Success || ! net || nitems != PropBlackboxAttributesElements)
1802     return;
1803
1804   if (net->flags & AttribShaded &&
1805       net->attrib & AttribShaded) {
1806     int save_state =
1807       ((current_state == IconicState) ? NormalState : current_state);
1808
1809     flags.shaded = False;
1810     shade();
1811
1812     current_state = save_state;
1813   }
1814
1815   if ((net->workspace != screen->getCurrentWorkspaceID()) &&
1816       (net->workspace < screen->getWorkspaceCount())) {
1817     screen->reassociateWindow(this, net->workspace, True);
1818
1819     if (current_state == NormalState) current_state = WithdrawnState;
1820   } else if (current_state == WithdrawnState) {
1821     current_state = NormalState;
1822   }
1823
1824   if (net->flags & AttribOmnipresent &&
1825       net->attrib & AttribOmnipresent) {
1826     flags.stuck = False;
1827     stick();
1828
1829     current_state = NormalState;
1830   }
1831
1832   if ((net->flags & AttribMaxHoriz) ||
1833       (net->flags & AttribMaxVert)) {
1834     int x = net->premax_x, y = net->premax_y;
1835     unsigned int w = net->premax_w, h = net->premax_h;
1836     flags.maximized = 0;
1837
1838     unsigned int m = 0;
1839     if ((net->flags & AttribMaxHoriz) &&
1840         (net->flags & AttribMaxVert))
1841       m = (net->attrib & (AttribMaxHoriz | AttribMaxVert)) ? 1 : 0;
1842     else if (net->flags & AttribMaxVert)
1843       m = (net->attrib & AttribMaxVert) ? 2 : 0;
1844     else if (net->flags & AttribMaxHoriz)
1845       m = (net->attrib & AttribMaxHoriz) ? 3 : 0;
1846
1847     if (m) maximize(m);
1848
1849     blackbox_attrib.premax_x = x;
1850     blackbox_attrib.premax_y = y;
1851     blackbox_attrib.premax_w = w;
1852     blackbox_attrib.premax_h = h;
1853   }
1854
1855   setState(current_state);
1856
1857   XFree((void *) net);
1858 }
1859
1860
1861 /*
1862  * Positions the frame according the the client window position and window
1863  * gravity.
1864  */
1865 void BlackboxWindow::setGravityOffsets(void) {
1866   // x coordinates for each gravity type
1867   const int x_west = client.rect.x();
1868   const int x_east = client.rect.right() - frame.inside_w + 1;
1869   const int x_center = client.rect.left() +
1870     ((client.rect.width() - frame.rect.width()) / 2);
1871   // y coordinates for each gravity type
1872   const int y_north = client.rect.y();
1873   const int y_south = client.rect.bottom() - frame.inside_h + 1;
1874   const int y_center = client.rect.top() +
1875     ((client.rect.height() - frame.rect.height()) / 2);
1876
1877   switch (client.win_gravity) {
1878   default:
1879   case NorthWestGravity: frame.rect.setPos(x_west,   y_north);  break;
1880   case NorthGravity:     frame.rect.setPos(x_center, y_north);  break;
1881   case NorthEastGravity: frame.rect.setPos(x_east,   y_north);  break;
1882   case SouthWestGravity: frame.rect.setPos(x_west,   y_south);  break;
1883   case SouthGravity:     frame.rect.setPos(x_center, y_south);  break;
1884   case SouthEastGravity: frame.rect.setPos(x_east,   y_south);  break;
1885   case WestGravity:      frame.rect.setPos(x_west,   y_center); break;
1886   case CenterGravity:    frame.rect.setPos(x_center, y_center); break;
1887   case EastGravity:      frame.rect.setPos(x_east,   y_center); break;
1888
1889   case ForgetGravity:
1890   case StaticGravity:
1891     frame.rect.setPos(client.rect.x() - frame.margin.left,
1892                       client.rect.y() - frame.margin.top);
1893     break;
1894   }
1895 }
1896
1897
1898 /*
1899  * The reverse of the setGravityOffsets function. Uses the frame window's
1900  * position to find the window's reference point.
1901  */
1902 void BlackboxWindow::restoreGravity(void) {
1903   // x coordinates for each gravity type
1904   const int x_west = frame.rect.x();
1905   const int x_east = frame.rect.x() + frame.inside_w - client.rect.width();
1906   const int x_center = frame.rect.x() -
1907     ((client.rect.width() - frame.rect.width()) / 2);
1908   // y coordinates for each gravity type
1909   const int y_north = frame.rect.y();
1910   const int y_south = frame.rect.y() + frame.inside_h - client.rect.height();
1911   const int y_center = frame.rect.y() -
1912     ((client.rect.height() - frame.rect.height()) / 2);
1913
1914   switch(client.win_gravity) {
1915   default:
1916   case NorthWestGravity: client.rect.setPos(x_west,   y_north);  break;
1917   case NorthGravity:     client.rect.setPos(x_center, y_north);  break;
1918   case NorthEastGravity: client.rect.setPos(x_east,   y_north);  break;
1919   case SouthWestGravity: client.rect.setPos(x_west,   y_south);  break;
1920   case SouthGravity:     client.rect.setPos(x_center, y_south);  break;
1921   case SouthEastGravity: client.rect.setPos(x_east,   y_south);  break;
1922   case WestGravity:      client.rect.setPos(x_west,   y_center); break;
1923   case CenterGravity:    client.rect.setPos(x_center, y_center); break;
1924   case EastGravity:      client.rect.setPos(x_east,   y_center); break;
1925
1926   case ForgetGravity:
1927   case StaticGravity:
1928     client.rect.setPos(frame.rect.left() + frame.margin.left,
1929                        frame.rect.top() + frame.margin.top);
1930     break;
1931   }
1932 }
1933
1934
1935 void BlackboxWindow::redrawLabel(void) {
1936   if (flags.focused) {
1937     if (frame.flabel)
1938       XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
1939                                  frame.label, frame.flabel);
1940     else
1941       XSetWindowBackground(blackbox->getXDisplay(),
1942                            frame.label, frame.flabel_pixel);
1943   } else {
1944     if (frame.ulabel)
1945       XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
1946                                  frame.label, frame.ulabel);
1947     else
1948       XSetWindowBackground(blackbox->getXDisplay(),
1949                            frame.label, frame.ulabel_pixel);
1950   }
1951   XClearWindow(blackbox->getXDisplay(), frame.label);
1952
1953   WindowStyle *style = screen->getWindowStyle();
1954
1955   int pos = frame.bevel_w * 2,
1956     dlen = style->doJustify(client.title.c_str(), pos, frame.label_w,
1957                             frame.bevel_w * 4, i18n.multibyte());
1958
1959   BPen pen((flags.focused) ? style->l_text_focus : style->l_text_unfocus,
1960            style->font);
1961   if (i18n.multibyte())
1962     XmbDrawString(blackbox->getXDisplay(), frame.label, style->fontset,
1963                   pen.gc(), pos,
1964                   (1 - style->fontset_extents->max_ink_extent.y),
1965                   client.title.c_str(), dlen);
1966   else
1967     XDrawString(blackbox->getXDisplay(), frame.label, pen.gc(), pos,
1968                 (style->font->ascent + 1), client.title.c_str(), dlen);
1969 }
1970
1971
1972 void BlackboxWindow::redrawAllButtons(void) {
1973   if (frame.iconify_button) redrawIconifyButton(False);
1974   if (frame.maximize_button) redrawMaximizeButton(flags.maximized);
1975   if (frame.close_button) redrawCloseButton(False);
1976 }
1977
1978
1979 void BlackboxWindow::redrawIconifyButton(bool pressed) {
1980   if (! pressed) {
1981     if (flags.focused) {
1982       if (frame.fbutton)
1983         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
1984                                    frame.iconify_button, frame.fbutton);
1985       else
1986         XSetWindowBackground(blackbox->getXDisplay(),
1987                              frame.iconify_button, frame.fbutton_pixel);
1988     } else {
1989       if (frame.ubutton)
1990         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
1991                                    frame.iconify_button, frame.ubutton);
1992       else
1993         XSetWindowBackground(blackbox->getXDisplay(), frame.iconify_button,
1994                              frame.ubutton_pixel);
1995     }
1996   } else {
1997     if (frame.pbutton)
1998       XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
1999                                  frame.iconify_button, frame.pbutton);
2000     else
2001       XSetWindowBackground(blackbox->getXDisplay(),
2002                            frame.iconify_button, frame.pbutton_pixel);
2003   }
2004   XClearWindow(blackbox->getXDisplay(), frame.iconify_button);
2005
2006   BPen pen((flags.focused) ? screen->getWindowStyle()->b_pic_focus :
2007            screen->getWindowStyle()->b_pic_unfocus);
2008   XDrawRectangle(blackbox->getXDisplay(), frame.iconify_button, pen.gc(),
2009                  2, (frame.button_w - 5), (frame.button_w - 5), 2);
2010 }
2011
2012
2013 void BlackboxWindow::redrawMaximizeButton(bool pressed) {
2014   if (! pressed) {
2015     if (flags.focused) {
2016       if (frame.fbutton)
2017         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2018                                    frame.maximize_button, frame.fbutton);
2019       else
2020         XSetWindowBackground(blackbox->getXDisplay(), frame.maximize_button,
2021                              frame.fbutton_pixel);
2022     } else {
2023       if (frame.ubutton)
2024         XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2025                                    frame.maximize_button, frame.ubutton);
2026       else
2027         XSetWindowBackground(blackbox->getXDisplay(), frame.maximize_button,
2028                              frame.ubutton_pixel);
2029     }
2030   } else {
2031     if (frame.pbutton)
2032       XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2033                                  frame.maximize_button, frame.pbutton);
2034     else
2035       XSetWindowBackground(blackbox->getXDisplay(), frame.maximize_button,
2036                            frame.pbutton_pixel);
2037   }
2038   XClearWindow(blackbox->getXDisplay(), frame.maximize_button);
2039
2040   BPen pen((flags.focused) ? screen->getWindowStyle()->b_pic_focus :
2041            screen->getWindowStyle()->b_pic_unfocus);
2042   XDrawRectangle(blackbox->getXDisplay(), frame.maximize_button, pen.gc(),
2043                  2, 2, (frame.button_w - 5), (frame.button_w - 5));
2044   XDrawLine(blackbox->getXDisplay(), frame.maximize_button, pen.gc(),
2045             2, 3, (frame.button_w - 3), 3);
2046 }
2047
2048
2049 void BlackboxWindow::redrawCloseButton(bool pressed) {
2050   if (! pressed) {
2051     if (flags.focused) {
2052       if (frame.fbutton)
2053         XSetWindowBackgroundPixmap(blackbox->getXDisplay(), frame.close_button,
2054                                    frame.fbutton);
2055       else
2056         XSetWindowBackground(blackbox->getXDisplay(), frame.close_button,
2057                              frame.fbutton_pixel);
2058     } else {
2059       if (frame.ubutton)
2060         XSetWindowBackgroundPixmap(blackbox->getXDisplay(), frame.close_button,
2061                                    frame.ubutton);
2062       else
2063         XSetWindowBackground(blackbox->getXDisplay(), frame.close_button,
2064                              frame.ubutton_pixel);
2065     }
2066   } else {
2067     if (frame.pbutton)
2068       XSetWindowBackgroundPixmap(blackbox->getXDisplay(),
2069                                  frame.close_button, frame.pbutton);
2070     else
2071       XSetWindowBackground(blackbox->getXDisplay(),
2072                            frame.close_button, frame.pbutton_pixel);
2073   }
2074   XClearWindow(blackbox->getXDisplay(), frame.close_button);
2075
2076   BPen pen((flags.focused) ? screen->getWindowStyle()->b_pic_focus :
2077            screen->getWindowStyle()->b_pic_unfocus);
2078   XDrawLine(blackbox->getXDisplay(), frame.close_button, pen.gc(),
2079             2, 2, (frame.button_w - 3), (frame.button_w - 3));
2080   XDrawLine(blackbox->getXDisplay(), frame.close_button, pen.gc(),
2081             2, (frame.button_w - 3), (frame.button_w - 3), 2);
2082 }
2083
2084
2085 void BlackboxWindow::mapRequestEvent(XMapRequestEvent *re) {
2086   if (re->window != client.window)
2087     return;
2088
2089 #ifdef    DEBUG
2090   fprintf(stderr, "BlackboxWindow::mapRequestEvent() for 0x%lx\n",
2091           client.window);
2092 #endif // DEBUG
2093
2094   bool get_state_ret = getState();
2095   if (! (get_state_ret && blackbox->isStartup())) {
2096     if ((client.wm_hint_flags & StateHint) &&
2097         (! (current_state == NormalState || current_state == IconicState)))
2098       current_state = client.initial_state;
2099     else
2100       current_state = NormalState;
2101   } else if (flags.iconic) {
2102     current_state = NormalState;
2103   }
2104
2105   switch (current_state) {
2106   case IconicState:
2107     iconify();
2108     break;
2109
2110   case WithdrawnState:
2111     withdraw();
2112     break;
2113
2114   case NormalState:
2115   case InactiveState:
2116   case ZoomState:
2117   default:
2118     show();
2119     screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
2120     if (! blackbox->isStartup() && (isTransient() || screen->doFocusNew())) {
2121       XSync(blackbox->getXDisplay(), False); // make sure the frame is mapped..
2122       setInputFocus();
2123     }
2124     break;
2125   }
2126 }
2127
2128
2129 void BlackboxWindow::unmapNotifyEvent(XUnmapEvent *ue) {
2130   if (ue->window != client.window)
2131     return;
2132
2133 #ifdef    DEBUG
2134   fprintf(stderr, "BlackboxWindow::unmapNotifyEvent() for 0x%lx\n",
2135           client.window);
2136 #endif // DEBUG
2137
2138   screen->unmanageWindow(this, False);
2139 }
2140
2141
2142 void BlackboxWindow::destroyNotifyEvent(XDestroyWindowEvent *de) {
2143   if (de->window != client.window)
2144     return;
2145
2146 #ifdef    DEBUG
2147   fprintf(stderr, "BlackboxWindow::destroyNotifyEvent() for 0x%lx\n",
2148           client.window);
2149 #endif // DEBUG
2150
2151   screen->unmanageWindow(this, False);
2152 }
2153
2154
2155 void BlackboxWindow::reparentNotifyEvent(XReparentEvent *re) {
2156   if (re->window != client.window || re->parent == frame.plate)
2157     return;
2158
2159 #ifdef    DEBUG
2160   fprintf(stderr, "BlackboxWindow::reparentNotifyEvent(): reparent 0x%lx to "
2161           "0x%lx.\n", client.window, re->parent);
2162 #endif // DEBUG
2163
2164   XEvent ev;
2165   ev.xreparent = *re;
2166   XPutBackEvent(blackbox->getXDisplay(), &ev);
2167   screen->unmanageWindow(this, True);
2168 }
2169
2170
2171 void BlackboxWindow::propertyNotifyEvent(Atom atom) {
2172   switch(atom) {
2173   case XA_WM_CLASS:
2174   case XA_WM_CLIENT_MACHINE:
2175   case XA_WM_COMMAND:
2176     break;
2177
2178   case XA_WM_TRANSIENT_FOR: {
2179     // determine if this is a transient window
2180     getTransientInfo();
2181
2182     // adjust the window decorations based on transience
2183     if (isTransient()) {
2184       decorations &= ~(Decor_Maximize | Decor_Handle);
2185       functions &= ~Func_Maximize;
2186     }
2187
2188     reconfigure();
2189   }
2190     break;
2191
2192   case XA_WM_HINTS:
2193     getWMHints();
2194     break;
2195
2196   case XA_WM_ICON_NAME:
2197     getWMIconName();
2198     if (flags.iconic) screen->propagateWindowName(this);
2199     break;
2200
2201   case XA_WM_NAME:
2202     getWMName();
2203
2204     if (decorations & Decor_Titlebar)
2205       redrawLabel();
2206
2207     screen->propagateWindowName(this);
2208     break;
2209
2210   case XA_WM_NORMAL_HINTS: {
2211     getWMNormalHints();
2212
2213     if ((client.normal_hint_flags & PMinSize) &&
2214         (client.normal_hint_flags & PMaxSize)) {
2215       if (client.max_width <= client.min_width &&
2216           client.max_height <= client.min_height) {
2217         decorations &= ~(Decor_Maximize | Decor_Handle);
2218         functions &= ~(Func_Resize | Func_Maximize);
2219       } else {
2220         decorations |= Decor_Maximize | Decor_Handle;
2221         functions |= Func_Resize | Func_Maximize;
2222       }
2223     }
2224
2225     Rect old_rect = frame.rect;
2226
2227     upsize();
2228
2229     if (old_rect != frame.rect)
2230       reconfigure();
2231
2232     break;
2233   }
2234
2235   default:
2236     if (atom == blackbox->getWMProtocolsAtom()) {
2237       getWMProtocols();
2238
2239       if ((decorations & Decor_Close) && (! frame.close_button)) {
2240         createCloseButton();
2241         if (decorations & Decor_Titlebar) {
2242           positionButtons(True);
2243           XMapSubwindows(blackbox->getXDisplay(), frame.title);
2244         }
2245         if (windowmenu) windowmenu->reconfigure();
2246       }
2247     }
2248
2249     break;
2250   }
2251 }
2252
2253
2254 void BlackboxWindow::exposeEvent(XExposeEvent *ee) {
2255   if (frame.label == ee->window && (decorations & Decor_Titlebar))
2256     redrawLabel();
2257   else if (frame.close_button == ee->window)
2258     redrawCloseButton(False);
2259   else if (frame.maximize_button == ee->window)
2260     redrawMaximizeButton(flags.maximized);
2261   else if (frame.iconify_button == ee->window)
2262     redrawIconifyButton(False);
2263 }
2264
2265
2266 void BlackboxWindow::configureRequestEvent(XConfigureRequestEvent *cr) {
2267   if (cr->window != client.window || flags.iconic)
2268     return;
2269
2270   int cx = frame.rect.x(), cy = frame.rect.y();
2271   unsigned int cw = frame.rect.width(), ch = frame.rect.height();
2272
2273   if (cr->value_mask & CWBorderWidth)
2274     client.old_bw = cr->border_width;
2275
2276   if (cr->value_mask & CWX)
2277     cx = cr->x - frame.margin.left;
2278
2279   if (cr->value_mask & CWY)
2280     cy = cr->y - frame.margin.top;
2281
2282   if (cr->value_mask & CWWidth)
2283     cw = cr->width + frame.margin.left + frame.margin.right;
2284
2285   if (cr->value_mask & CWHeight)
2286     ch = cr->height + frame.margin.top + frame.margin.bottom;
2287
2288   if (frame.rect != Rect(cx, cy, cw, ch))
2289     configure(cx, cy, cw, ch);
2290
2291   if (cr->value_mask & CWStackMode) {
2292     switch (cr->detail) {
2293     case Below:
2294     case BottomIf:
2295       screen->getWorkspace(blackbox_attrib.workspace)->lowerWindow(this);
2296       break;
2297
2298     case Above:
2299     case TopIf:
2300     default:
2301       screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
2302       break;
2303     }
2304   }
2305 }
2306
2307
2308 void BlackboxWindow::buttonPressEvent(XButtonEvent *be) {
2309   if (frame.maximize_button == be->window) {
2310     redrawMaximizeButton(True);
2311   } else if (be->button == 1 || (be->button == 3 && be->state == Mod1Mask)) {
2312     if (! flags.focused)
2313       setInputFocus();
2314
2315     if (frame.iconify_button == be->window) {
2316       redrawIconifyButton(True);
2317     } else if (frame.close_button == be->window) {
2318       redrawCloseButton(True);
2319     } else if (frame.plate == be->window) {
2320       if (windowmenu && windowmenu->isVisible()) windowmenu->hide();
2321
2322       screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
2323
2324       XAllowEvents(blackbox->getXDisplay(), ReplayPointer, be->time);
2325     } else {
2326       if (frame.title == be->window || frame.label == be->window) {
2327         if (((be->time - lastButtonPressTime) <=
2328              blackbox->getDoubleClickInterval()) ||
2329             (be->state & ControlMask)) {
2330           lastButtonPressTime = 0;
2331           shade();
2332         } else {
2333           lastButtonPressTime = be->time;
2334         }
2335       }
2336
2337       frame.grab_x = be->x_root - frame.rect.x() - frame.border_w;
2338       frame.grab_y = be->y_root - frame.rect.y() - frame.border_w;
2339
2340       if (windowmenu && windowmenu->isVisible()) windowmenu->hide();
2341
2342       screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
2343     }
2344   } else if (be->button == 2 && (be->window != frame.iconify_button) &&
2345              (be->window != frame.close_button)) {
2346     screen->getWorkspace(blackbox_attrib.workspace)->lowerWindow(this);
2347   } else if (windowmenu && be->button == 3 &&
2348              (frame.title == be->window || frame.label == be->window ||
2349               frame.handle == be->window || frame.window == be->window)) {
2350     int mx = 0, my = 0;
2351
2352     if (frame.title == be->window || frame.label == be->window) {
2353       mx = be->x_root - (windowmenu->getWidth() / 2);
2354       my = frame.rect.y() + frame.title_h + frame.border_w;
2355     } else if (frame.handle == be->window) {
2356       mx = be->x_root - (windowmenu->getWidth() / 2);
2357       my = frame.rect.bottom() - frame.handle_h - (frame.border_w * 3) -
2358            windowmenu->getHeight();
2359     } else {
2360       mx = be->x_root - (windowmenu->getWidth() / 2);
2361
2362       if (be->y <= static_cast<signed>(frame.bevel_w))
2363         my = frame.rect.y() + frame.title_h;
2364       else
2365         my = be->y_root - (windowmenu->getHeight() / 2);
2366     }
2367
2368     // snap the window menu into a corner if necessary - we check the
2369     // position of the menu with the coordinates of the client to
2370     // make the comparisions easier.
2371     // XXX: this needs some work!
2372     if (mx > client.rect.right() -
2373         static_cast<signed>(windowmenu->getWidth()))
2374       mx = frame.rect.right() - windowmenu->getWidth() - frame.border_w + 1;
2375     if (mx < client.rect.left())
2376       mx = frame.rect.x();
2377
2378     if (my > client.rect.bottom() -
2379         static_cast<signed>(windowmenu->getHeight()))
2380       my = frame.rect.bottom() - windowmenu->getHeight() - frame.border_w + 1;
2381     if (my < client.rect.top())
2382       my = frame.rect.y() + ((decorations & Decor_Titlebar) ?
2383                              frame.title_h : 0);
2384
2385     if (windowmenu) {
2386       if (! windowmenu->isVisible()) {
2387         windowmenu->move(mx, my);
2388         windowmenu->show();
2389         XRaiseWindow(blackbox->getXDisplay(), windowmenu->getWindowID());
2390         XRaiseWindow(blackbox->getXDisplay(),
2391                      windowmenu->getSendToMenu()->getWindowID());
2392       } else {
2393         windowmenu->hide();
2394       }
2395     }
2396   // mouse wheel up
2397   } else if (be->button == 4) {
2398     if ((be->window == frame.label ||
2399          be->window == frame.title) &&
2400         ! flags.shaded)
2401       shade();
2402   // mouse wheel down
2403   } else if (be->button == 5) {
2404     if ((be->window == frame.label ||
2405          be->window == frame.title) &&
2406         flags.shaded)
2407       shade();
2408   }
2409 }
2410
2411
2412 void BlackboxWindow::buttonReleaseEvent(XButtonEvent *re) {
2413   if (re->window == frame.maximize_button) {
2414     if ((re->x >= 0 && re->x <= static_cast<signed>(frame.button_w)) &&
2415         (re->y >= 0 && re->y <= static_cast<signed>(frame.button_w))) {
2416       maximize(re->button);
2417     } else {
2418       redrawMaximizeButton(flags.maximized);
2419     }
2420   } else if (re->window == frame.iconify_button) {
2421     if ((re->x >= 0 && re->x <= static_cast<signed>(frame.button_w)) &&
2422         (re->y >= 0 && re->y <= static_cast<signed>(frame.button_w))) {
2423       iconify();
2424     } else {
2425       redrawIconifyButton(False);
2426     }
2427   } else if (re->window == frame.close_button) {
2428     if ((re->x >= 0 && re->x <= static_cast<signed>(frame.button_w)) &&
2429         (re->y >= 0 && re->y <= static_cast<signed>(frame.button_w)))
2430       close();
2431     redrawCloseButton(False);
2432   } else if (flags.moving) {
2433     flags.moving = False;
2434
2435     if (! screen->doOpaqueMove()) {
2436       /* when drawing the rubber band, we need to make sure we only draw inside
2437        * the frame... frame.changing_* contain the new coords for the window,
2438        * so we need to subtract 1 from changing_w/changing_h every where we
2439        * draw the rubber band (for both moving and resizing)
2440        */
2441       XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
2442                      screen->getOpGC(), frame.changing.x(), frame.changing.y(),
2443                      frame.changing.width() - 1, frame.changing.height() - 1);
2444       XUngrabServer(blackbox->getXDisplay());
2445
2446       configure(frame.changing.x(), frame.changing.y(),
2447                 frame.changing.width(), frame.changing.height());
2448     } else {
2449       configure(frame.rect.x(), frame.rect.y(),
2450                 frame.rect.width(), frame.rect.height());
2451     }
2452     screen->hideGeometry();
2453     XUngrabPointer(blackbox->getXDisplay(), CurrentTime);
2454   } else if (flags.resizing) {
2455     XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
2456                    screen->getOpGC(), frame.changing.x(), frame.changing.y(),
2457                    frame.changing.width() - 1, frame.changing.height() - 1);
2458     XUngrabServer(blackbox->getXDisplay());
2459
2460     screen->hideGeometry();
2461
2462     constrain((re->window == frame.left_grip) ? TopRight : TopLeft);
2463
2464     // unset maximized state when resized after fully maximized
2465     if (flags.maximized == 1)
2466       maximize(0);
2467     flags.resizing = False;
2468     configure(frame.changing.x(), frame.changing.y(),
2469               frame.changing.width(), frame.changing.height());
2470
2471     XUngrabPointer(blackbox->getXDisplay(), CurrentTime);
2472   } else if (re->window == frame.window) {
2473     if (re->button == 2 && re->state == Mod1Mask)
2474       XUngrabPointer(blackbox->getXDisplay(), CurrentTime);
2475   }
2476 }
2477
2478
2479 void BlackboxWindow::motionNotifyEvent(XMotionEvent *me) {
2480   if (! flags.resizing && (me->state & Button1Mask) &&
2481       (functions & Func_Move) &&
2482       (frame.title == me->window || frame.label == me->window ||
2483        frame.handle == me->window || frame.window == me->window)) {
2484     if (! flags.moving) {
2485       XGrabPointer(blackbox->getXDisplay(), me->window, False,
2486                    Button1MotionMask | ButtonReleaseMask,
2487                    GrabModeAsync, GrabModeAsync,
2488                    None, blackbox->getMoveCursor(), CurrentTime);
2489
2490       if (windowmenu && windowmenu->isVisible())
2491         windowmenu->hide();
2492
2493       flags.moving = True;
2494
2495       if (! screen->doOpaqueMove()) {
2496         XGrabServer(blackbox->getXDisplay());
2497
2498         frame.changing = frame.rect;
2499         screen->showPosition(frame.changing.x(), frame.changing.y());
2500
2501         XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
2502                        screen->getOpGC(),
2503                        frame.changing.x(),
2504                        frame.changing.y(),
2505                        frame.changing.width() - 1,
2506                        frame.changing.height() - 1);
2507       }
2508     } else {
2509       int dx = me->x_root - frame.grab_x, dy = me->y_root - frame.grab_y;
2510       dx -= frame.border_w;
2511       dy -= frame.border_w;
2512
2513       const int snap_distance = screen->getEdgeSnapThreshold();
2514
2515       if (snap_distance) {
2516         // window corners
2517         const int wleft = dx,
2518                  wright = dx + frame.rect.width() - 1,
2519                    wtop = dy,
2520                 wbottom = dy + frame.rect.height() - 1;
2521
2522         // Maybe this should be saved in the class, and set in the setWorkspace
2523         // function!!
2524         Workspace *w = screen->getWorkspace(getWorkspaceNumber());
2525         assert(w);
2526
2527         if (screen->getWindowToWindowSnap()) {
2528           // try snap to another window
2529           for (unsigned int i = 0, c = w->getCount(); i < c; ++i) {
2530             BlackboxWindow *snapwin = w->getWindow(i);
2531             if (snapwin == this)
2532               continue;   // don't snap to self
2533
2534             const Rect &winrect = snapwin->frameRect();
2535             int dleft = std::abs(wright - winrect.left()),
2536                dright = std::abs(wleft - winrect.right()),
2537                  dtop = std::abs(wbottom - winrect.top()),
2538               dbottom = std::abs(wtop - winrect.bottom());
2539
2540             // snap top of other window?
2541             if (dtop < snap_distance && dtop <= dbottom) {
2542               dy = winrect.top() - frame.rect.height();
2543
2544               if (screen->getWindowCornerSnap()) {
2545                 // try corner-snap to its other sides
2546                 dleft = std::abs(wleft - winrect.left());
2547                 dright = std::abs(wright - winrect.right());
2548                 if (dleft < snap_distance && dleft <= dright)
2549                   dx = winrect.left();
2550                 else if (dright < snap_distance)
2551                   dx = winrect.right() - frame.rect.width() + 1;
2552               }
2553
2554               continue;
2555             }
2556             // snap bottom of other window?
2557             else if (dbottom < snap_distance) {
2558               dy = winrect.bottom() + 1;
2559
2560               if (screen->getWindowCornerSnap()) {
2561                 // try corner-snap to its other sides
2562                 dleft = std::abs(wleft - winrect.left());
2563                 dright = std::abs(wright - winrect.right());
2564                 if (dleft < snap_distance && dleft <= dright)
2565                   dx = winrect.left();
2566                 else if (dright < snap_distance)
2567                   dx = winrect.right() - frame.rect.width() + 1;
2568               }
2569
2570               continue;
2571             }
2572
2573             // snap left of other window?
2574             if (dleft < snap_distance && dleft <= dright) {
2575               dx = winrect.left() - frame.rect.width();
2576
2577               if (screen->getWindowCornerSnap()) {
2578                 // try corner-snap to its other sides
2579                 dtop = std::abs(wtop - winrect.top());
2580                 dbottom = std::abs(wbottom - winrect.bottom());
2581                 if (dtop < snap_distance && dtop <= dbottom)
2582                   dy = winrect.top();
2583                 else if (dbottom < snap_distance)
2584                   dy = winrect.bottom() - frame.rect.height() + 1;
2585               }
2586
2587               continue;
2588             }
2589             // snap right of other window?
2590             else if (dright < snap_distance) {
2591               dx = winrect.right() + 1;
2592
2593               if (screen->getWindowCornerSnap()) {
2594                 // try corner-snap to its other sides
2595                 dtop = std::abs(wtop - winrect.top());
2596                 dbottom = std::abs(wbottom - winrect.bottom());
2597                 if (dtop < snap_distance && dtop <= dbottom)
2598                   dy = winrect.top();
2599                 else if (dbottom < snap_distance)
2600                   dy = winrect.bottom() - frame.rect.height() + 1;
2601               }
2602
2603               continue;
2604             }
2605           }
2606         }
2607                 
2608         // try snap to the screen's available area
2609         Rect srect = screen->availableArea();
2610
2611         int dleft = std::abs(wleft - srect.left()),
2612            dright = std::abs(wright - srect.right()),
2613              dtop = std::abs(wtop - srect.top()),
2614           dbottom = std::abs(wbottom - srect.bottom());
2615
2616         // snap left?
2617         if (dleft < snap_distance && dleft <= dright)
2618           dx = srect.left();
2619         // snap right?
2620         else if (dright < snap_distance)
2621           dx = srect.right() - frame.rect.width() + 1;
2622
2623         // snap top?
2624         if (dtop < snap_distance && dtop <= dbottom)
2625           dy = srect.top();
2626         // snap bottom?
2627         else if (dbottom < snap_distance)
2628           dy = srect.bottom() - frame.rect.height() + 1;
2629
2630         if (! screen->doFullMax()) {
2631           srect = screen->getRect(); // now get the full screen
2632
2633           dleft = std::abs(wleft - srect.left()),
2634           dright = std::abs(wright - srect.right()),
2635           dtop = std::abs(wtop - srect.top()),
2636           dbottom = std::abs(wbottom - srect.bottom());
2637
2638           // snap left?
2639           if (dleft < snap_distance && dleft <= dright)
2640             dx = srect.left();
2641           // snap right?
2642           else if (dright < snap_distance)
2643             dx = srect.right() - frame.rect.width() + 1;
2644
2645           // snap top?
2646           if (dtop < snap_distance && dtop <= dbottom)
2647             dy = srect.top();
2648           // snap bottom?
2649           else if (dbottom < snap_distance)
2650             dy = srect.bottom() - frame.rect.height() + 1;
2651         }
2652       }
2653
2654       if (screen->doOpaqueMove()) {
2655         configure(dx, dy, frame.rect.width(), frame.rect.height());
2656       } else {
2657         XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
2658                        screen->getOpGC(),
2659                        frame.changing.x(),
2660                        frame.changing.y(),
2661                        frame.changing.width() - 1,
2662                        frame.changing.height() - 1);
2663
2664         frame.changing.setPos(dx, dy);
2665
2666         XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
2667                        screen->getOpGC(),
2668                        frame.changing.x(),
2669                        frame.changing.y(),
2670                        frame.changing.width() - 1,
2671                        frame.changing.height() - 1);
2672       }
2673
2674       screen->showPosition(dx, dy);
2675     }
2676   } else if ((functions & Func_Resize) &&
2677              (((me->state & Button1Mask) &&
2678                (me->window == frame.right_grip ||
2679                 me->window == frame.left_grip)) ||
2680               (me->state & (Mod1Mask | Button3Mask) &&
2681                me->window == frame.window))) {
2682     bool left = (me->window == frame.left_grip);
2683
2684     if (! flags.resizing) {
2685       XGrabServer(blackbox->getXDisplay());
2686       XGrabPointer(blackbox->getXDisplay(), me->window, False,
2687                    ButtonMotionMask | ButtonReleaseMask,
2688                    GrabModeAsync, GrabModeAsync, None,
2689                    ((left) ? blackbox->getLowerLeftAngleCursor() :
2690                     blackbox->getLowerRightAngleCursor()),
2691                    CurrentTime);
2692
2693       flags.resizing = True;
2694
2695       int gw, gh;
2696       frame.grab_x = me->x;
2697       frame.grab_y = me->y;
2698       frame.changing = frame.rect;
2699
2700       constrain((left) ? TopRight : TopLeft, &gw, &gh);
2701
2702       XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
2703                      screen->getOpGC(), frame.changing.x(), frame.changing.y(),
2704                      frame.changing.width() - 1, frame.changing.height() - 1);
2705
2706       screen->showGeometry(gw, gh);
2707     } else {
2708       XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
2709                      screen->getOpGC(), frame.changing.x(), frame.changing.y(),
2710                      frame.changing.width() - 1, frame.changing.height() - 1);
2711
2712       int gw, gh;
2713
2714       Corner anchor;
2715
2716       if (left) {
2717         anchor = TopRight;
2718         frame.changing.setCoords(me->x_root - frame.grab_x, frame.rect.top(),
2719                                  frame.rect.right(), frame.rect.bottom());
2720         frame.changing.setHeight(frame.rect.height() + (me->y - frame.grab_y));
2721       } else {
2722         anchor = TopLeft;
2723         frame.changing.setSize(frame.rect.width() + (me->x - frame.grab_x),
2724                                frame.rect.height() + (me->y - frame.grab_y));
2725       }
2726
2727       constrain(anchor, &gw, &gh);
2728
2729       XDrawRectangle(blackbox->getXDisplay(), screen->getRootWindow(),
2730                      screen->getOpGC(), frame.changing.x(), frame.changing.y(),
2731                      frame.changing.width() - 1, frame.changing.height() - 1);
2732
2733       screen->showGeometry(gw, gh);
2734     }
2735   }
2736 }
2737
2738
2739 #ifdef    SHAPE
2740 void BlackboxWindow::shapeEvent(XShapeEvent *) {
2741   if (blackbox->hasShapeExtensions() && flags.shaped) {
2742     configureShape();
2743   }
2744 }
2745 #endif // SHAPE
2746
2747
2748 bool BlackboxWindow::validateClient(void) const {
2749   XSync(blackbox->getXDisplay(), False);
2750
2751   XEvent e;
2752   if (XCheckTypedWindowEvent(blackbox->getXDisplay(), client.window,
2753                              DestroyNotify, &e) ||
2754       XCheckTypedWindowEvent(blackbox->getXDisplay(), client.window,
2755                              UnmapNotify, &e)) {
2756     XPutBackEvent(blackbox->getXDisplay(), &e);
2757
2758     return False;
2759   }
2760
2761   return True;
2762 }
2763
2764
2765 void BlackboxWindow::restore(bool remap) {
2766   XChangeSaveSet(blackbox->getXDisplay(), client.window, SetModeDelete);
2767   XSelectInput(blackbox->getXDisplay(), client.window, NoEventMask);
2768   XSelectInput(blackbox->getXDisplay(), frame.plate, NoEventMask);
2769
2770   restoreGravity();
2771
2772   XUnmapWindow(blackbox->getXDisplay(), frame.window);
2773   XUnmapWindow(blackbox->getXDisplay(), client.window);
2774
2775   XSetWindowBorderWidth(blackbox->getXDisplay(), client.window, client.old_bw);
2776
2777   XEvent ev;
2778   if (XCheckTypedWindowEvent(blackbox->getXDisplay(), client.window,
2779                              ReparentNotify, &ev)) {
2780     remap = True;
2781   } else {
2782     // according to the ICCCM - if the client doesn't reparent to
2783     // root, then we have to do it for them
2784     XReparentWindow(blackbox->getXDisplay(), client.window,
2785                     screen->getRootWindow(),
2786                     client.rect.x(), client.rect.y());
2787   }
2788
2789   if (remap) XMapWindow(blackbox->getXDisplay(), client.window);
2790 }
2791
2792
2793 // timer for autoraise
2794 void BlackboxWindow::timeout(void) {
2795   screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
2796 }
2797
2798
2799 void BlackboxWindow::changeBlackboxHints(BlackboxHints *net) {
2800   if ((net->flags & AttribShaded) &&
2801       ((blackbox_attrib.attrib & AttribShaded) !=
2802        (net->attrib & AttribShaded)))
2803     shade();
2804
2805   if (flags.visible && // watch out for requests when we can not be seen
2806       (net->flags & (AttribMaxVert | AttribMaxHoriz)) &&
2807       ((blackbox_attrib.attrib & (AttribMaxVert | AttribMaxHoriz)) !=
2808        (net->attrib & (AttribMaxVert | AttribMaxHoriz)))) {
2809     if (flags.maximized) {
2810       maximize(0);
2811     } else {
2812       int button = 0;
2813
2814       if ((net->flags & AttribMaxHoriz) && (net->flags & AttribMaxVert))
2815         button = ((net->attrib & (AttribMaxHoriz | AttribMaxVert)) ?  1 : 0);
2816       else if (net->flags & AttribMaxVert)
2817         button = ((net->attrib & AttribMaxVert) ? 2 : 0);
2818       else if (net->flags & AttribMaxHoriz)
2819         button = ((net->attrib & AttribMaxHoriz) ? 3 : 0);
2820
2821       maximize(button);
2822     }
2823   }
2824
2825   if ((net->flags & AttribOmnipresent) &&
2826       ((blackbox_attrib.attrib & AttribOmnipresent) !=
2827        (net->attrib & AttribOmnipresent)))
2828     stick();
2829
2830   if ((net->flags & AttribWorkspace) &&
2831       (blackbox_attrib.workspace != net->workspace)) {
2832     screen->reassociateWindow(this, net->workspace, True);
2833
2834     if (screen->getCurrentWorkspaceID() != net->workspace) {
2835       withdraw();
2836     } else {
2837       show();
2838       screen->getWorkspace(blackbox_attrib.workspace)->raiseWindow(this);
2839     }
2840   }
2841
2842   if (net->flags & AttribDecoration) {
2843     switch (net->decoration) {
2844     case DecorNone:
2845       // clear all decorations except close
2846       decorations &= Decor_Close;
2847
2848       break;
2849
2850     default:
2851     case DecorNormal:
2852       decorations |= Decor_Titlebar | Decor_Border | Decor_Iconify;
2853   
2854       decorations = ((functions & Func_Resize) && !isTransient() ?
2855                      decorations | Decor_Handle :
2856                      decorations &= ~Decor_Handle);
2857       decorations = (functions & Func_Maximize ?
2858                      decorations | Decor_Maximize :
2859                      decorations &= ~Decor_Maximize);
2860
2861       break;
2862
2863     case DecorTiny:
2864       decorations |= Decor_Titlebar | Decor_Iconify;
2865       decorations &= ~(Decor_Border | Decor_Handle);
2866       
2867       decorations = (functions & Func_Maximize ?
2868                      decorations | Decor_Maximize :
2869                      decorations &= ~Decor_Maximize);
2870
2871       break;
2872
2873     case DecorTool:
2874       decorations |= Decor_Titlebar;
2875       decorations &= ~(Decor_Iconify | Decor_Border);
2876
2877       decorations = ((functions & Func_Resize) && !isTransient() ?
2878                      decorations | Decor_Handle :
2879                      decorations &= ~Decor_Handle);
2880       decorations = (functions & Func_Maximize ?
2881                      decorations | Decor_Maximize :
2882                      decorations &= ~Decor_Maximize);
2883
2884       break;
2885     }
2886
2887     // we can not be shaded if we lack a titlebar
2888     if (flags.shaded && ! (decorations & Decor_Titlebar))
2889       shade();
2890
2891     if (frame.window) {
2892       XMapSubwindows(blackbox->getXDisplay(), frame.window);
2893       XMapWindow(blackbox->getXDisplay(), frame.window);
2894     }
2895
2896     reconfigure();
2897     setState(current_state);
2898   }
2899 }
2900
2901
2902 /*
2903  * Set the sizes of all components of the window frame
2904  * (the window decorations).
2905  * These values are based upon the current style settings and the client
2906  * window's dimensions.
2907  */
2908 void BlackboxWindow::upsize(void) {
2909   frame.bevel_w = screen->getBevelWidth();
2910
2911   if (decorations & Decor_Border) {
2912     frame.border_w = screen->getBorderWidth();
2913     if (! isTransient())
2914       frame.mwm_border_w = screen->getFrameWidth();
2915     else
2916       frame.mwm_border_w = 0;
2917   } else {
2918     frame.mwm_border_w = frame.border_w = 0;
2919   }
2920
2921   if (decorations & Decor_Titlebar) {
2922     // the height of the titlebar is based upon the height of the font being
2923     // used to display the window's title
2924     WindowStyle *style = screen->getWindowStyle();
2925     if (i18n.multibyte())
2926       frame.title_h = (style->fontset_extents->max_ink_extent.height +
2927                        (frame.bevel_w * 2) + 2);
2928     else
2929       frame.title_h = (style->font->ascent + style->font->descent +
2930                        (frame.bevel_w * 2) + 2);
2931
2932     frame.label_h = frame.title_h - (frame.bevel_w * 2);
2933     frame.button_w = (frame.label_h - 2);
2934
2935     // set the top frame margin
2936     frame.margin.top = frame.border_w + frame.title_h +
2937                        frame.border_w + frame.mwm_border_w;
2938   } else {
2939     frame.title_h = 0;
2940     frame.label_h = 0;
2941     frame.button_w = 0;
2942
2943     // set the top frame margin
2944     frame.margin.top = frame.border_w + frame.mwm_border_w;
2945   }
2946
2947   // set the left/right frame margin
2948   frame.margin.left = frame.margin.right = frame.border_w + frame.mwm_border_w;
2949
2950   if (decorations & Decor_Handle) {
2951     frame.grip_w = frame.button_w * 2;
2952     frame.handle_h = screen->getHandleWidth();
2953
2954     // set the bottom frame margin
2955     frame.margin.bottom = frame.border_w + frame.handle_h +
2956                           frame.border_w + frame.mwm_border_w;
2957   } else {
2958     frame.handle_h = 0;
2959     frame.grip_w = 0;
2960
2961     // set the bottom frame margin
2962     frame.margin.bottom = frame.border_w + frame.mwm_border_w;
2963   }
2964
2965   /*
2966     We first get the normal dimensions and use this to define the inside_w/h
2967     then we modify the height if shading is in effect.
2968     If the shade state is not considered then frame.rect gets reset to the
2969     normal window size on a reconfigure() call resulting in improper
2970     dimensions appearing in move/resize and other events.
2971   */
2972   unsigned int
2973     height = client.rect.height() + frame.margin.top + frame.margin.bottom,
2974     width = client.rect.width() + frame.margin.left + frame.margin.right;
2975
2976   frame.inside_w = width - (frame.border_w * 2);
2977   frame.inside_h = height - (frame.border_w * 2);
2978
2979   if (flags.shaded)
2980     height = frame.title_h + (frame.border_w * 2);
2981   frame.rect.setSize(width, height);
2982 }
2983
2984
2985 /*
2986  * Calculate the size of the client window and constrain it to the
2987  * size specified by the size hints of the client window.
2988  *
2989  * The logical width and height are placed into pw and ph, if they
2990  * are non-zero.  Logical size refers to the users perception of
2991  * the window size (for example an xterm resizes in cells, not in pixels).
2992  *
2993  * The physical geometry is placed into frame.changing_{x,y,width,height}.
2994  * Physical geometry refers to the geometry of the window in pixels.
2995  */
2996 void BlackboxWindow::constrain(Corner anchor, int *pw, int *ph) {
2997   // frame.changing represents the requested frame size, we need to
2998   // strip the frame margin off and constrain the client size
2999   frame.changing.setCoords(frame.changing.left() + frame.margin.left,
3000                            frame.changing.top() + frame.margin.top,
3001                            frame.changing.right() - frame.margin.right,
3002                            frame.changing.bottom() - frame.margin.bottom);
3003
3004   int dw = frame.changing.width(), dh = frame.changing.height(),
3005     base_width = (client.base_width) ? client.base_width : client.min_width,
3006     base_height = (client.base_height) ? client.base_height :
3007                                          client.min_height;
3008
3009   // constrain
3010   if (dw < static_cast<signed>(client.min_width)) dw = client.min_width;
3011   if (dh < static_cast<signed>(client.min_height)) dh = client.min_height;
3012   if (dw > static_cast<signed>(client.max_width)) dw = client.max_width;
3013   if (dh > static_cast<signed>(client.max_height)) dh = client.max_height;
3014
3015   dw -= base_width;
3016   dw /= client.width_inc;
3017   dh -= base_height;
3018   dh /= client.height_inc;
3019
3020   if (pw) *pw = dw;
3021   if (ph) *ph = dh;
3022
3023   dw *= client.width_inc;
3024   dw += base_width;
3025   dh *= client.height_inc;
3026   dh += base_height;
3027
3028   frame.changing.setSize(dw, dh);
3029
3030   // add the frame margin back onto frame.changing
3031   frame.changing.setCoords(frame.changing.left() - frame.margin.left,
3032                            frame.changing.top() - frame.margin.top,
3033                            frame.changing.right() + frame.margin.right,
3034                            frame.changing.bottom() + frame.margin.bottom);
3035
3036   // move frame.changing to the specified anchor
3037   switch (anchor) {
3038   case TopLeft:
3039     // nothing to do
3040     break;
3041
3042   case TopRight:
3043     int dx = frame.rect.right() - frame.changing.right();
3044     frame.changing.setPos(frame.changing.x() + dx, frame.changing.y());
3045     break;
3046   }
3047 }
3048
3049
3050 int WindowStyle::doJustify(const char *text, int &start_pos,
3051                            unsigned int max_length, unsigned int modifier,
3052                            bool multibyte) const {
3053   size_t text_len = strlen(text);
3054   unsigned int length;
3055
3056   do {
3057     if (multibyte) {
3058       XRectangle ink, logical;
3059       XmbTextExtents(fontset, text, text_len, &ink, &logical);
3060       length = logical.width;
3061     } else {
3062       length = XTextWidth(font, text, text_len);
3063     }
3064     length += modifier;
3065   } while (length > max_length && text_len-- > 0);
3066
3067   switch (justify) {
3068   case RightJustify:
3069     start_pos += max_length - length;
3070     break;
3071
3072   case CenterJustify:
3073     start_pos += (max_length - length) / 2;
3074     break;
3075
3076   case LeftJustify:
3077   default:
3078     break;
3079   }
3080
3081   return text_len;
3082 }
3083
3084
3085 BWindowGroup::BWindowGroup(Blackbox *b, Window _group)
3086   : blackbox(b), group(_group) {
3087   XWindowAttributes wattrib;
3088   if (! XGetWindowAttributes(blackbox->getXDisplay(), group, &wattrib)) {
3089     // group window doesn't seem to exist anymore
3090     delete this;
3091     return;
3092   }
3093
3094   /*
3095     watch for destroy notify on the group window (in addition to
3096     any other events we are looking for)
3097
3098     since some managed windows can also be window group controllers,
3099     we need to make sure that we don't clobber the event mask for the
3100     managed window
3101   */
3102   XSelectInput(blackbox->getXDisplay(), group,
3103                wattrib.your_event_mask | StructureNotifyMask);
3104
3105   blackbox->saveGroupSearch(group, this);
3106 }
3107
3108
3109 BWindowGroup::~BWindowGroup(void) {
3110   blackbox->removeGroupSearch(group);
3111 }
3112
3113
3114 BlackboxWindow *
3115 BWindowGroup::find(BScreen *screen, bool allow_transients) const {
3116   BlackboxWindow *ret = blackbox->getFocusedWindow();
3117
3118   // does the focus window match (or any transient_fors)?
3119   while (ret) {
3120     if (ret->getScreen() == screen && ret->getGroupWindow() == group) {
3121       if (ret->isTransient() && allow_transients) break;
3122       else if (! ret->isTransient()) break;
3123     }
3124
3125     ret = ret->getTransientFor();
3126   }
3127
3128   if (ret) return ret;
3129
3130   // the focus window didn't match, look in the group's window list
3131   BlackboxWindowList::const_iterator it, end = windowList.end();
3132   for (it = windowList.begin(); it != end; ++it) {
3133     ret = *it;
3134     if (ret->getScreen() == screen && ret->getGroupWindow() == group) {
3135       if (ret->isTransient() && allow_transients) break;
3136       else if (! ret->isTransient()) break;
3137     }
3138   }
3139
3140   return ret;
3141 }