]> icculus.org git repositories - mikachu/openbox.git/blob - src/frame.cc
i think im handling reparent events correctly now, by unmanaging the window and then...
[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     /*
440       If the app reparented itself, then we unmanage the window. This causes
441       the window to be unmapped, so to be nice to it, we remap the window
442       here. We don't put the event back onto the stack because we put it there
443       in the first place.
444     */
445     XMapWindow(otk::OBDisplay::display, _client->window());
446   } else {
447     // according to the ICCCM - if the client doesn't reparent to
448     // root, then we have to do it for them
449     XReparentWindow(otk::OBDisplay::display, _client->window(),
450                     _screen->rootWindow(),
451                     _client->area().x(), _client->area().y());
452   }
453
454   // do an extra map here .. ? XXX
455 //  XMapWindow(otk::OBDisplay::display, _client->window());
456 }
457
458
459 void OBFrame::clientGravity(int &x, int &y)
460 {
461   x = _client->area().x();
462   y = _client->area().y();
463
464   // horizontal
465   switch (_client->gravity()) {
466   default:
467   case NorthWestGravity:
468   case SouthWestGravity:
469   case WestGravity:
470     break;
471
472   case NorthGravity:
473   case SouthGravity:
474   case CenterGravity:
475     x -= (_size.left + _size.right) / 2;
476     break;
477
478   case NorthEastGravity:
479   case SouthEastGravity:
480   case EastGravity:
481     x -= _size.left + _size.right;
482     break;
483
484   case ForgetGravity:
485   case StaticGravity:
486     x -= _size.left;
487     break;
488   }
489
490   // vertical
491   switch (_client->gravity()) {
492   default:
493   case NorthWestGravity:
494   case NorthEastGravity:
495   case NorthGravity:
496     break;
497
498   case CenterGravity:
499   case EastGravity:
500   case WestGravity:
501     y -= (_size.top + _size.bottom) / 2;
502     break;
503
504   case SouthWestGravity:
505   case SouthEastGravity:
506   case SouthGravity:
507     y -= _size.top + _size.bottom;
508     break;
509
510   case ForgetGravity:
511   case StaticGravity:
512     y -= _size.top;
513     break;
514   }
515 }
516
517
518 void OBFrame::frameGravity(int &x, int &y)
519 {
520   x = rect().x();
521   y = rect().y();
522   
523   // horizontal
524   switch (_client->gravity()) {
525   default:
526   case NorthWestGravity:
527   case WestGravity:
528   case SouthWestGravity:
529     break;
530   case NorthGravity:
531   case CenterGravity:
532   case SouthGravity:
533     x += (_size.left + _size.right) / 2;
534     break;
535   case NorthEastGravity:
536   case EastGravity:
537   case SouthEastGravity:
538     x += _size.left + _size.right;
539     break;
540   case StaticGravity:
541   case ForgetGravity:
542     x += _size.left;
543     break;
544   }
545
546   // vertical
547   switch (_client->gravity()) {
548   default:
549   case NorthWestGravity:
550   case WestGravity:
551   case SouthWestGravity:
552     break;
553   case NorthGravity:
554   case CenterGravity:
555   case SouthGravity:
556     y += (_size.top + _size.bottom) / 2;
557     break;
558   case NorthEastGravity:
559   case EastGravity:
560   case SouthEastGravity:
561     y += _size.top + _size.bottom;
562     break;
563   case StaticGravity:
564   case ForgetGravity:
565     y += _size.top;
566     break;
567   }
568 }
569
570
571 }