]> icculus.org git repositories - mikachu/openbox.git/blob - src/frame.cc
ignore the right about of unmap notifies
[mikachu/openbox.git] / src / frame.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #ifdef HAVE_CONFIG_H
4 # include "../config.h"
5 #endif
6
7 extern "C" {
8 #ifdef    SHAPE
9 #include <X11/extensions/shape.h>
10 #endif // SHAPE
11 }
12
13 #include "openbox.hh"
14 #include "frame.hh"
15 #include "client.hh"
16 #include "python.hh"
17 #include "bindings.hh"
18 #include "otk/display.hh"
19
20 #include <string>
21
22 namespace ob {
23
24 const long OBFrame::event_mask;
25
26 OBFrame::OBFrame(OBClient *client, otk::Style *style)
27   : otk::OtkWidget(Openbox::instance, style, Horizontal, 0, 1, true),
28     OBWidget(Type_Frame),
29     _client(client),
30     _screen(otk::OBDisplay::screenInfo(client->screen())),
31     _plate(this, OBWidget::Type_Plate),
32     _titlebar(this, OBWidget::Type_Titlebar),
33     _button_close(&_titlebar, OBWidget::Type_CloseButton),
34     _button_iconify(&_titlebar, OBWidget::Type_IconifyButton),
35     _button_max(&_titlebar, OBWidget::Type_MaximizeButton),
36     _button_stick(&_titlebar, OBWidget::Type_StickyButton),
37     _label(&_titlebar, OBWidget::Type_Label),
38     _handle(this, OBWidget::Type_Handle),
39     _grip_left(&_handle, OBWidget::Type_LeftGrip),
40     _grip_right(&_handle, OBWidget::Type_RightGrip),
41     _decorations(client->decorations())
42 {
43   assert(client);
44   assert(style);
45
46   XSelectInput(otk::OBDisplay::display, _window, OBFrame::event_mask);
47
48   _grip_left.setCursor(Openbox::instance->cursors().ll_angle);
49   _grip_right.setCursor(Openbox::instance->cursors().lr_angle);
50   
51   _label.setText(_client->title());
52
53   _style = 0;
54   setStyle(style);
55
56   otk::OtkWidget::unfocus(); // stuff starts out appearing focused in otk
57   
58   _plate.show(); // the other stuff is shown based on decor settings
59   
60   grabClient();
61 }
62
63
64 OBFrame::~OBFrame()
65 {
66   releaseClient();
67 }
68
69
70 void OBFrame::setTitle(const std::string &text)
71 {
72   _label.setText(text);
73   _label.update();
74 }
75
76
77 void OBFrame::setStyle(otk::Style *style)
78 {
79   assert(style);
80
81   otk::OtkWidget::setStyle(style);
82
83   // if a style was previously set, then 'replace' is true, cause we're
84   // replacing a style
85   bool replace = (_style);
86
87   if (replace) {
88     // XXX: do shit here whatever
89   }
90   
91   _style = style;
92
93   setBorderColor(_style->getBorderColor());
94
95   // if !replace, then adjust() will get called after the client is grabbed!
96   if (replace) {
97     // size/position everything
98     adjustSize();
99     adjustPosition();
100   }
101 }
102
103
104 void OBFrame::focus()
105 {
106   otk::OtkWidget::focus();
107   update();
108 }
109
110
111 void OBFrame::unfocus()
112 {
113   otk::OtkWidget::unfocus();
114   update();
115 }
116
117
118 void OBFrame::adjust()
119 {
120   // the party all happens in adjustSize
121 }
122
123
124 void OBFrame::adjustSize()
125 {
126   // XXX: only if not overridden or something!!! MORE LOGIC HERE!!
127   _decorations = _client->decorations();
128
129   // true/false for whether to show each element of the titlebar
130   bool tit_i = false, tit_m = false, tit_s = false, tit_c = false;
131   int width;   // the width of the client and its border
132   int bwidth;  // width to make borders
133   int cbwidth; // width of the inner client border
134   int butsize=0; // width and height of the titlebar buttons
135   const int bevel = _style->getBevelWidth();
136   
137   if (_decorations & OBClient::Decor_Border) {
138     bwidth = _style->getBorderWidth();
139     cbwidth = _style->getFrameWidth();
140   } else
141     bwidth = cbwidth = 0;
142   _innersize.left = _innersize.top = _innersize.bottom = _innersize.right =
143     cbwidth;
144   width = _client->area().width() + cbwidth * 2;
145
146   _plate.setBorderWidth(cbwidth);
147
148   setBorderWidth(bwidth);
149   _titlebar.setBorderWidth(bwidth);
150   _grip_left.setBorderWidth(bwidth);
151   _grip_right.setBorderWidth(bwidth);
152   _handle.setBorderWidth(bwidth);
153   
154   if (_decorations & OBClient::Decor_Titlebar) {
155     // set the titlebar size
156     _titlebar.setGeometry(-bwidth,
157                           -bwidth,
158                           width,
159                           _style->getFont()->height() + bevel * 2);
160     _innersize.top += _titlebar.height() + bwidth;
161
162     // set the label size
163     _label.setGeometry(0, bevel, width, _style->getFont()->height());
164     // set the buttons sizes
165     butsize = _label.height() - 2;
166     if (_decorations & OBClient::Decor_Iconify)
167       _button_iconify.setGeometry(0, bevel + 1, butsize, butsize);
168     if (_decorations & OBClient::Decor_Maximize)
169       _button_max.setGeometry(0, bevel + 1, butsize, butsize);
170     if (_decorations & OBClient::Decor_Sticky)
171       _button_stick.setGeometry(0, bevel + 1, butsize, butsize);
172     if (_decorations & OBClient::Decor_Close)
173       _button_close.setGeometry(0, bevel + 1, butsize, butsize);
174
175     // separation between titlebar elements
176     const int sep = bevel + 1;
177
178     std::string layout;
179     if (!python_get_string("titlebar_layout", &layout))
180       layout = "ILMC";
181
182     // this code ensures that the string only has one of each possible
183     // letter, all of the letters are valid, and L exists somewhere in the
184     // string!
185     bool tit_l = false;
186   
187     for (std::string::size_type i = 0; i < layout.size(); ++i) {
188       switch (layout[i]) {
189       case 'i':
190       case 'I':
191         if (!tit_i && (_decorations & OBClient::Decor_Iconify)) {
192           tit_i = true;
193           continue;
194         }
195         break;
196       case 'l':
197       case 'L':
198         if (!tit_l) {
199           tit_l = true;
200           continue;
201         }
202         break;
203       case 'm':
204       case 'M':
205         if (!tit_m && (_decorations & OBClient::Decor_Maximize)) {
206           tit_m = true;
207           continue;
208         }
209         break;
210       case 's':
211       case 'S':
212         if (!tit_s && (_decorations & OBClient::Decor_Sticky)) {
213           tit_s = true;
214           continue;
215         }
216         break;
217       case 'c':
218       case 'C':
219         if (!tit_c && (_decorations & OBClient::Decor_Close)) {
220           tit_c = true;
221           continue;
222         }
223         break;
224       }
225       // if we get here then we don't want the letter, kill it
226       layout.erase(i--, 1);
227     }
228     if (!tit_l)
229       layout.append(1, 'L');
230     
231     // the size of the label. this ASSUMES the layout has only buttons other
232     // that the ONE LABEL!!
233     // adds an extra sep so that there's a space on either side of the
234     // titlebar.. note: x = sep, below.
235     int lwidth = width - sep * 2 -
236       (butsize + sep) * (layout.size() - 1);
237     // quick sanity check for really small windows. if this is needed, its
238     // obviously not going to be displayed right...
239     // XXX: maybe we should make this look better somehow? constraints?
240     if (lwidth <= 0) lwidth = 1;
241     _label.setWidth(lwidth);
242
243     int x = sep;
244     for (std::string::size_type i = 0, len = layout.size(); i < len; ++i) {
245       switch (layout[i]) {
246       case 'i':
247       case 'I':
248         _button_iconify.move(x, _button_iconify.rect().y());
249         x += _button_iconify.width();
250         break;
251       case 'l':
252       case 'L':
253         _label.move(x, _label.rect().y());
254         x += _label.width();
255         break;
256       case 'm':
257       case 'M':
258         _button_max.move(x, _button_max.rect().y());
259         x += _button_max.width();
260         break;
261       case 's':
262       case 'S':
263         _button_stick.move(x, _button_stick.rect().y());
264         x += _button_stick.width();
265         break;
266       case 'c':
267       case 'C':
268         _button_close.move(x, _button_close.rect().y());
269         x += _button_close.width();
270         break;
271       default:
272         assert(false); // the layout string is invalid!
273       }
274       x += sep;
275     }
276   }
277
278   if (_decorations & OBClient::Decor_Handle) {
279     _handle.setGeometry(-bwidth,
280                         _innersize.top + _client->area().height() + cbwidth,
281                         width, _style->getHandleWidth());
282     _grip_left.setGeometry(-bwidth,
283                            -bwidth,
284                            // XXX: get a Point class in otk and use that for
285                            // the 'buttons size' since theyre all the same
286                            butsize * 2,
287                            _handle.height());
288     _grip_right.setGeometry(((_handle.rect().right() + 1) -
289                              butsize * 2),
290                             -bwidth,
291                             // XXX: get a Point class in otk and use that for
292                             // the 'buttons size' since theyre all the same
293                             butsize * 2,
294                             _handle.height());
295     _innersize.bottom += _handle.height() + bwidth;
296   }
297   
298
299   // position/size all the windows
300
301   if (_client->shaded())
302     resize(_innersize.left + _innersize.right + _client->area().width(),
303            _titlebar.height());
304   else
305     resize(_innersize.left + _innersize.right + _client->area().width(),
306            _innersize.top + _innersize.bottom + _client->area().height());
307
308   _plate.setGeometry(_innersize.left - cbwidth, _innersize.top - cbwidth,
309                      _client->area().width(), _client->area().height());
310
311   // map/unmap all the windows
312   if (_decorations & OBClient::Decor_Titlebar) {
313     _label.show();
314     if (tit_i)
315       _button_iconify.show();
316     else
317       _button_iconify.hide();
318     if (tit_m)
319       _button_max.show();
320     else
321       _button_max.hide();
322     if (tit_s)
323       _button_stick.show();
324     else
325       _button_stick.hide();
326     if (tit_c)
327       _button_close.show();
328     else
329       _button_close.hide();
330     _titlebar.show();
331   } else {
332     _titlebar.hide(true);
333   }
334
335   if (_decorations & OBClient::Decor_Handle)
336     _handle.show(true);
337   else
338     _handle.hide(true);
339   
340   _size.left   = _innersize.left + bwidth;
341   _size.right  = _innersize.right + bwidth;
342   _size.top    = _innersize.top + bwidth;
343   _size.bottom = _innersize.bottom + bwidth;
344
345   adjustShape();
346
347   update();
348 }
349
350
351 void OBFrame::adjustPosition()
352 {
353   int x, y;
354   clientGravity(x, y);
355   move(x, y);
356 }
357
358
359 void OBFrame::adjustShape()
360 {
361 #ifdef SHAPE
362   int bwidth = (_decorations & OBClient::Decor_Border) ?
363     _style->getBorderWidth() : 0;
364   
365   if (!_client->shaped()) {
366     // clear the shape on the frame window
367     XShapeCombineMask(otk::OBDisplay::display, _window, ShapeBounding,
368                       _innersize.left,
369                       _innersize.top,
370                       None, ShapeSet);
371   } else {
372     // make the frame's shape match the clients
373     XShapeCombineShape(otk::OBDisplay::display, _window, ShapeBounding,
374                        _innersize.left,
375                        _innersize.top,
376                        _client->window(), ShapeBounding, ShapeSet);
377
378     int num = 0;
379     XRectangle xrect[2];
380
381     if (_decorations & OBClient::Decor_Titlebar) {
382       xrect[0].x = _titlebar.rect().x();
383       xrect[0].y = _titlebar.rect().y();
384       xrect[0].width = _titlebar.width() + bwidth * 2; // XXX: this is useless once the widget handles borders!
385       xrect[0].height = _titlebar.height() + bwidth * 2;
386       ++num;
387     }
388
389     if (_decorations & OBClient::Decor_Handle) {
390       xrect[1].x = _handle.rect().x();
391       xrect[1].y = _handle.rect().y();
392       xrect[1].width = _handle.width() + bwidth * 2; // XXX: this is useless once the widget handles borders!
393       xrect[1].height = _handle.height() + bwidth * 2;
394       ++num;
395     }
396
397     XShapeCombineRectangles(otk::OBDisplay::display, window(),
398                             ShapeBounding, 0, 0, xrect, num,
399                             ShapeUnion, Unsorted);
400   }
401 #endif // SHAPE
402 }
403
404
405 void OBFrame::grabClient()
406 {
407   // reparent the client to the frame
408   XReparentWindow(otk::OBDisplay::display, _client->window(),
409                   _plate.window(), 0, 0);
410   /*
411     When reparenting the client window, it is usually not mapped yet, since
412     this occurs from a MapRequest. However, in the case where Openbox is
413     starting up, the window is already mapped, so we'll see unmap events for
414     it. There are 2 unmap events generated that we see, one with the 'event'
415     member set the root window, and one set to the client, but both get handled
416     and need to be ignored.
417   */
418   if (Openbox::instance->state() == Openbox::State_Starting)
419     _client->ignore_unmaps += 2;
420
421   // select the event mask on the client's parent (to receive config req's)
422   XSelectInput(otk::OBDisplay::display, _plate.window(),
423                SubstructureRedirectMask);
424
425   // map the client so it maps when the frame does
426   XMapWindow(otk::OBDisplay::display, _client->window());
427
428   adjustSize();
429   adjustPosition();
430 }
431
432
433 void OBFrame::releaseClient()
434 {
435   // check if the app has already reparented its window to the root window
436   XEvent ev;
437   if (XCheckTypedWindowEvent(otk::OBDisplay::display, _client->window(),
438                              ReparentNotify, &ev)) {
439     // XXX: ob2/bb didn't do this.. look up this process in other wm's!
440     //XPutBackEvent(otk::OBDisplay::display, &ev);
441     XMapWindow(otk::OBDisplay::display, _client->window());
442   } else {
443     // according to the ICCCM - if the client doesn't reparent to
444     // root, then we have to do it for them
445     XReparentWindow(otk::OBDisplay::display, _client->window(),
446                     _screen->rootWindow(),
447                     _client->area().x(), _client->area().y());
448   }
449
450   // do an extra map here .. ? XXX
451 //  XMapWindow(otk::OBDisplay::display, _client->window());
452 }
453
454
455 void OBFrame::clientGravity(int &x, int &y)
456 {
457   x = _client->area().x();
458   y = _client->area().y();
459
460   // horizontal
461   switch (_client->gravity()) {
462   default:
463   case NorthWestGravity:
464   case SouthWestGravity:
465   case WestGravity:
466     break;
467
468   case NorthGravity:
469   case SouthGravity:
470   case CenterGravity:
471     x -= (_size.left + _size.right) / 2;
472     break;
473
474   case NorthEastGravity:
475   case SouthEastGravity:
476   case EastGravity:
477     x -= _size.left + _size.right;
478     break;
479
480   case ForgetGravity:
481   case StaticGravity:
482     x -= _size.left;
483     break;
484   }
485
486   // vertical
487   switch (_client->gravity()) {
488   default:
489   case NorthWestGravity:
490   case NorthEastGravity:
491   case NorthGravity:
492     break;
493
494   case CenterGravity:
495   case EastGravity:
496   case WestGravity:
497     y -= (_size.top + _size.bottom) / 2;
498     break;
499
500   case SouthWestGravity:
501   case SouthEastGravity:
502   case SouthGravity:
503     y -= _size.top + _size.bottom;
504     break;
505
506   case ForgetGravity:
507   case StaticGravity:
508     y -= _size.top;
509     break;
510   }
511 }
512
513
514 void OBFrame::frameGravity(int &x, int &y)
515 {
516   x = rect().x();
517   y = rect().y();
518   
519   // horizontal
520   switch (_client->gravity()) {
521   default:
522   case NorthWestGravity:
523   case WestGravity:
524   case SouthWestGravity:
525     break;
526   case NorthGravity:
527   case CenterGravity:
528   case SouthGravity:
529     x += (_size.left + _size.right) / 2;
530     break;
531   case NorthEastGravity:
532   case EastGravity:
533   case SouthEastGravity:
534     x += _size.left + _size.right;
535     break;
536   case StaticGravity:
537   case ForgetGravity:
538     x += _size.left;
539     break;
540   }
541
542   // vertical
543   switch (_client->gravity()) {
544   default:
545   case NorthWestGravity:
546   case WestGravity:
547   case SouthWestGravity:
548     break;
549   case NorthGravity:
550   case CenterGravity:
551   case SouthGravity:
552     y += (_size.top + _size.bottom) / 2;
553     break;
554   case NorthEastGravity:
555   case EastGravity:
556   case SouthEastGravity:
557     y += _size.top + _size.bottom;
558     break;
559   case StaticGravity:
560   case ForgetGravity:
561     y += _size.top;
562     break;
563   }
564 }
565
566
567 }