]> icculus.org git repositories - mikachu/openbox.git/blob - src/frame.cc
working popups for moving/resizing
[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 Frame::event_mask;
25
26 Frame::Frame(Client *client, otk::RenderStyle *style)
27   : otk::Widget(openbox, style, Horizontal, 0, 1, true),
28     WidgetBase(WidgetBase::Type_Frame),
29     _client(client),
30     _screen(otk::display->screenInfo(client->screen())),
31     _plate(this, WidgetBase::Type_Plate),
32     _titlebar(this, WidgetBase::Type_Titlebar),
33     _button_close(&_titlebar, WidgetBase::Type_CloseButton, client),
34     _button_iconify(&_titlebar, WidgetBase::Type_IconifyButton, client),
35     _button_max(&_titlebar, WidgetBase::Type_MaximizeButton, client),
36     _button_alldesk(&_titlebar, WidgetBase::Type_AllDesktopsButton, client),
37     _label(&_titlebar, WidgetBase::Type_Label),
38     _handle(this, WidgetBase::Type_Handle),
39     _grip_left(&_handle, WidgetBase::Type_LeftGrip, client),
40     _grip_right(&_handle, WidgetBase::Type_RightGrip, client),
41     _decorations(client->decorations())
42 {
43   assert(client);
44   assert(style);
45
46   XSelectInput(**otk::display, _window, Frame::event_mask);
47
48   _grip_left.setCursor(openbox->cursors().ll_angle);
49   _grip_right.setCursor(openbox->cursors().lr_angle);
50   
51   _label.setText(_client->title());
52
53   _style = 0;
54   setStyle(style);
55
56   otk::Widget::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 Frame::~Frame()
63 {
64 }
65
66
67 void Frame::setTitle(const otk::ustring &text)
68 {
69   _label.setText(text);
70   _label.update();
71 }
72
73
74 void Frame::setStyle(otk::RenderStyle *style)
75 {
76   assert(style);
77
78   // if a style was previously set, then 'replace' is true, cause we're
79   // replacing a style
80   bool replace = (_style);
81
82   otk::Widget::setStyle(style);
83
84   if (replace) {
85     // XXX: do shit here whatever
86   }
87   
88   _style = style;
89
90   setBorderColor(_style->frameBorderColor());
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 Frame::focus()
102 {
103   otk::Widget::focus();
104   update();
105 }
106
107
108 void Frame::unfocus()
109 {
110   otk::Widget::unfocus();
111   update();
112 }
113
114
115 void Frame::adjust()
116 {
117   // the party all happens in adjustSize
118 }
119
120
121 void Frame::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 fontheight = _style->labelFont()->height(); // height of the font
132   int butsize = fontheight - 2; // width and height of the titlebar buttons
133   const int bevel = _style->bevelWidth();
134   
135   if (_decorations & Client::Decor_Border) {
136     bwidth = _style->frameBorderWidth();
137     cbwidth = _style->clientBorderWidth();
138   } else
139     bwidth = cbwidth = 0;
140   _innersize.left = _innersize.top = _innersize.bottom = _innersize.right =
141     cbwidth;
142   width = _client->area().width() + cbwidth * 2;
143
144   _plate.setBorderWidth(cbwidth);
145
146   setBorderWidth(bwidth);
147   _titlebar.setBorderWidth(bwidth);
148   _grip_left.setBorderWidth(bwidth);
149   _grip_right.setBorderWidth(bwidth);
150   _handle.setBorderWidth(bwidth);
151   
152   if (_decorations & Client::Decor_Titlebar) {
153     // set the titlebar size
154     _titlebar.setGeometry(-bwidth,
155                           -bwidth,
156                           width,
157                           _style->labelFont()->height() + (bevel * 2));
158     _innersize.top += _titlebar.height() + bwidth;
159
160     // set the label size
161     _label.setGeometry(0, bevel, width, fontheight);
162     // set the buttons sizes
163     if (_decorations & Client::Decor_Iconify)
164       _button_iconify.setGeometry(0, bevel + 1, butsize, butsize);
165     if (_decorations & Client::Decor_Maximize)
166       _button_max.setGeometry(0, bevel + 1, butsize, butsize);
167     if (_decorations & Client::Decor_AllDesktops)
168       _button_alldesk.setGeometry(0, bevel + 1, butsize, butsize);
169     if (_decorations & Client::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     otk::ustring 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 & Client::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 & Client::Decor_Maximize)) {
203           tit_m = true;
204           continue;
205         }
206         break;
207       case 'd':
208       case 'D':
209         if (!tit_s && (_decorations & Client::Decor_AllDesktops)) {
210           tit_s = true;
211           continue;
212         }
213         break;
214       case 'c':
215       case 'C':
216         if (!tit_c && (_decorations & Client::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 += "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 'd':
259       case 'D':
260         _button_alldesk.move(x, _button_alldesk.rect().y());
261         x += _button_alldesk.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 & Client::Decor_Handle) {
276     _handle.setGeometry(-bwidth,
277                         _innersize.top + _client->area().height() + cbwidth,
278                         width, _style->handleWidth());
279     _grip_left.setGeometry(-bwidth,
280                            -bwidth,
281                            butsize * 2,
282                            _handle.height());
283     _grip_right.setGeometry(((_handle.rect().right() + 1) -
284                              butsize * 2),
285                             -bwidth,
286                             butsize * 2,
287                             _handle.height());
288     _innersize.bottom += _handle.height() + bwidth;
289   }
290   
291
292   // position/size all the windows
293
294   if (_client->shaded())
295     resize(_innersize.left + _innersize.right + _client->area().width(),
296            _titlebar.height());
297   else
298     resize(_innersize.left + _innersize.right + _client->area().width(),
299            _innersize.top + _innersize.bottom + _client->area().height());
300
301   _plate.setGeometry(_innersize.left - cbwidth, _innersize.top - cbwidth,
302                      _client->area().width(), _client->area().height());
303
304   // map/unmap all the windows
305   if (_decorations & Client::Decor_Titlebar) {
306     _label.show();
307     if (tit_i)
308       _button_iconify.show();
309     else
310       _button_iconify.hide();
311     if (tit_m)
312       _button_max.show();
313     else
314       _button_max.hide();
315     if (tit_s)
316       _button_alldesk.show();
317     else
318       _button_alldesk.hide();
319     if (tit_c)
320       _button_close.show();
321     else
322       _button_close.hide();
323     _titlebar.show();
324   } else {
325     _titlebar.hide(true);
326   }
327
328   if (_decorations & Client::Decor_Handle)
329     _handle.show(true);
330   else
331     _handle.hide(true);
332   
333   _size.left   = _innersize.left + bwidth;
334   _size.right  = _innersize.right + bwidth;
335   _size.top    = _innersize.top + bwidth;
336   _size.bottom = _innersize.bottom + bwidth;
337
338   adjustShape();
339
340   update();
341 }
342
343
344 void Frame::adjustPosition()
345 {
346   int x, y;
347   clientGravity(x, y);
348   move(x, y);
349 }
350
351
352 void Frame::adjustShape()
353 {
354 #ifdef SHAPE
355   int bwidth = (_decorations & Client::Decor_Border) ?
356     _style->frameBorderWidth() : 0;
357   
358   if (!_client->shaped()) {
359     // clear the shape on the frame window
360     XShapeCombineMask(**otk::display, _window, ShapeBounding,
361                       _innersize.left,
362                       _innersize.top,
363                       None, ShapeSet);
364   } else {
365     // make the frame's shape match the clients
366     XShapeCombineShape(**otk::display, _window, ShapeBounding,
367                        _innersize.left,
368                        _innersize.top,
369                        _client->window(), ShapeBounding, ShapeSet);
370
371     int num = 0;
372     XRectangle xrect[2];
373
374     if (_decorations & Client::Decor_Titlebar) {
375       xrect[0].x = _titlebar.rect().x();
376       xrect[0].y = _titlebar.rect().y();
377       xrect[0].width = _titlebar.width() + bwidth * 2; // XXX: this is useless once the widget handles borders!
378       xrect[0].height = _titlebar.height() + bwidth * 2;
379       ++num;
380     }
381
382     if (_decorations & Client::Decor_Handle) {
383       xrect[1].x = _handle.rect().x();
384       xrect[1].y = _handle.rect().y();
385       xrect[1].width = _handle.width() + bwidth * 2; // XXX: this is useless once the widget handles borders!
386       xrect[1].height = _handle.height() + bwidth * 2;
387       ++num;
388     }
389
390     XShapeCombineRectangles(**otk::display, window(),
391                             ShapeBounding, 0, 0, xrect, num,
392                             ShapeUnion, Unsorted);
393   }
394 #endif // SHAPE
395 }
396
397
398 void Frame::adjustState()
399 {
400   _button_alldesk.update();
401   _button_max.update();
402 }
403
404
405 void Frame::grabClient()
406 {
407   // reparent the client to the frame
408   XReparentWindow(**otk::display, _client->window(), _plate.window(), 0, 0);
409   /*
410     When reparenting the client window, it is usually not mapped yet, since
411     this occurs from a MapRequest. However, in the case where Openbox is
412     starting up, the window is already mapped, so we'll see unmap events for
413     it. There are 2 unmap events generated that we see, one with the 'event'
414     member set the root window, and one set to the client, but both get handled
415     and need to be ignored.
416   */
417   if (openbox->state() == Openbox::State_Starting)
418     _client->ignore_unmaps += 2;
419
420   // select the event mask on the client's parent (to receive config/map req's)
421   XSelectInput(**otk::display, _plate.window(), SubstructureRedirectMask);
422
423   // map the client so it maps when the frame does
424   XMapWindow(**otk::display, _client->window());
425
426   adjustSize();
427   adjustPosition();
428 }
429
430
431 void Frame::releaseClient()
432 {
433   XEvent ev;
434
435   // check if the app has already reparented its window away
436   if (XCheckTypedWindowEvent(**otk::display, _client->window(),
437                              ReparentNotify, &ev)) {
438     XPutBackEvent(**otk::display, &ev);
439     // re-map the window since the unmanaging process unmaps it
440     XMapWindow(**otk::display, _client->window());  
441   } else {
442     // according to the ICCCM - if the client doesn't reparent itself, then we
443     // will reparent the window to root for them
444     XReparentWindow(**otk::display, _client->window(),
445                     _screen->rootWindow(),
446                     _client->area().x(), _client->area().y());
447   }
448 }
449
450
451 void Frame::clientGravity(int &x, int &y)
452 {
453   x = _client->area().x();
454   y = _client->area().y();
455
456   // horizontal
457   switch (_client->gravity()) {
458   default:
459   case NorthWestGravity:
460   case SouthWestGravity:
461   case WestGravity:
462     break;
463
464   case NorthGravity:
465   case SouthGravity:
466   case CenterGravity:
467     x -= (_size.left + _size.right) / 2;
468     break;
469
470   case NorthEastGravity:
471   case SouthEastGravity:
472   case EastGravity:
473     x -= _size.left + _size.right;
474     break;
475
476   case ForgetGravity:
477   case StaticGravity:
478     x -= _size.left;
479     break;
480   }
481
482   // vertical
483   switch (_client->gravity()) {
484   default:
485   case NorthWestGravity:
486   case NorthEastGravity:
487   case NorthGravity:
488     break;
489
490   case CenterGravity:
491   case EastGravity:
492   case WestGravity:
493     y -= (_size.top + _size.bottom) / 2;
494     break;
495
496   case SouthWestGravity:
497   case SouthEastGravity:
498   case SouthGravity:
499     y -= _size.top + _size.bottom;
500     break;
501
502   case ForgetGravity:
503   case StaticGravity:
504     y -= _size.top;
505     break;
506   }
507 }
508
509
510 void Frame::frameGravity(int &x, int &y)
511 {
512   x = rect().x();
513   y = rect().y();
514   
515   // horizontal
516   switch (_client->gravity()) {
517   default:
518   case NorthWestGravity:
519   case WestGravity:
520   case SouthWestGravity:
521     break;
522   case NorthGravity:
523   case CenterGravity:
524   case SouthGravity:
525     x += (_size.left + _size.right) / 2;
526     break;
527   case NorthEastGravity:
528   case EastGravity:
529   case SouthEastGravity:
530     x += _size.left + _size.right;
531     break;
532   case StaticGravity:
533   case ForgetGravity:
534     x += _size.left;
535     break;
536   }
537
538   // vertical
539   switch (_client->gravity()) {
540   default:
541   case NorthWestGravity:
542   case WestGravity:
543   case SouthWestGravity:
544     break;
545   case NorthGravity:
546   case CenterGravity:
547   case SouthGravity:
548     y += (_size.top + _size.bottom) / 2;
549     break;
550   case NorthEastGravity:
551   case EastGravity:
552   case SouthEastGravity:
553     y += _size.top + _size.bottom;
554     break;
555   case StaticGravity:
556   case ForgetGravity:
557     y += _size.top;
558     break;
559   }
560 }
561
562
563 }