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