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