]> icculus.org git repositories - dana/openbox.git/blob - otk/widget.cc
reset focus to root before exiting
[dana/openbox.git] / otk / widget.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #include "config.h"
4 #include "widget.hh"
5 #include "display.hh"
6 #include "surface.hh"
7 #include "rendertexture.hh"
8 #include "rendercolor.hh"
9 #include "eventdispatcher.hh"
10 #include "screeninfo.hh"
11
12 #include <climits>
13 #include <cassert>
14 #include <algorithm>
15
16 namespace otk {
17
18 Widget::Widget(int screen, EventDispatcher *ed, Direction direction, int bevel,
19                bool overrideredir)
20   : _texture(0),
21     _screen(screen),
22     _parent(0),
23     _window(0),
24     _surface(0),
25     _event_mask(ButtonPressMask | ButtonReleaseMask | ButtonMotionMask |
26                 ExposureMask | StructureNotifyMask),
27     _alignment(RenderStyle::CenterJustify),
28     _direction(direction),
29     _max_size(INT_MAX, INT_MAX),
30     _visible(false),
31     _bordercolor(0),
32     _borderwidth(0),
33     _bevel(bevel),
34     _dirty(true),
35     _dispatcher(ed),
36     _ignore_config(0)
37 {
38   createWindow(overrideredir);
39   _dispatcher->registerHandler(_window, this);
40 }
41
42 Widget::Widget(Widget *parent, Direction direction, int bevel)
43   : _texture(0),
44     _screen(parent->_screen),
45     _parent(parent),
46     _window(0),
47     _surface(0),
48     _event_mask(ButtonPressMask | ButtonReleaseMask | ButtonMotionMask |
49                 ExposureMask | StructureNotifyMask),
50     _alignment(RenderStyle::CenterJustify),
51     _direction(direction),
52     _max_size(INT_MAX, INT_MAX),
53     _visible(false),
54     _bordercolor(0),
55     _borderwidth(0),
56     _bevel(bevel),
57     _dirty(true),
58     _dispatcher(parent->_dispatcher),
59     _ignore_config(0)
60 {
61   assert(parent);
62   createWindow(false);
63   parent->addChild(this);
64   if (parent->visible()) parent->layout();
65   _dispatcher->registerHandler(_window, this);
66   styleChanged(*RenderStyle::style(_screen));
67 }
68
69 Widget::~Widget()
70 {
71   assert(_children.empty()); // this would be bad. theyd have a hanging _parent
72   
73   if (_surface) delete _surface;
74   if (_parent) _parent->removeChild(this);
75
76   _dispatcher->clearHandler(_window);
77   XDestroyWindow(**display, _window);
78 }
79
80 void Widget::show(bool children)
81 {
82   if (children) {
83     std::list<Widget*>::iterator it , end = _children.end();
84     for (it = _children.begin(); it != end; ++it) {
85       (*it)->show(true);
86     }
87   }
88   if (!_visible) {
89     if (_parent) _parent->calcDefaultSizes();
90     else resize(_area.size()); // constrain sizes
91     _visible = true;
92     XMapWindow(**display, _window);
93     update();
94   }
95 }
96
97 void Widget::hide()
98 {
99   if (_visible) {
100     _visible = false;
101     XUnmapWindow(**display, _window);
102     if (_parent) {
103       _parent->calcDefaultSizes();
104       _parent->layout();
105     }
106   }
107
108
109 void Widget::setEventMask(long e)
110 {
111   XSelectInput(**display, _window, e);
112   _event_mask = e;
113 }
114
115 void Widget::update()
116 {
117   if (!_visible) return;
118   _dirty = true;
119   if (_parent) {
120     _parent->calcDefaultSizes();
121     _parent->layout(); // relay-out us and our siblings
122   } else {
123     render();
124     layout();
125   }
126 }
127
128 void Widget::moveresize(const Rect &r)
129 {
130   int w, h;
131   w = std::max(std::min(r.width(), maxSize().width()), minSize().width());
132   h = std::max(std::min(r.height(), maxSize().height()), minSize().height());
133
134   bool sizechange = !(w == area().width() && h == area().height());
135
136   if (r.x() == area().x() && r.y() == area().y() && !sizechange)
137     return; // no change, don't cause a big layout chain to occur!
138   
139   internal_moveresize(r.x(), r.y(), w, h);
140
141   if (sizechange)
142     update();
143 }
144
145 void Widget::internal_moveresize(int x, int y, int w, int h)
146 {
147   assert(w > 0);
148   assert(h > 0);
149   assert(_borderwidth >= 0);
150   _dirty = true;
151   if (!(x == _area.x() && y == _area.y())) {
152     if (!(w == _area.width() && h == _area.height()))
153       XMoveResizeWindow(**display, _window, x, y,
154                         w - _borderwidth * 2,
155                         h - _borderwidth * 2);
156     else
157       XMoveWindow(**display, _window, x, y);
158   } else
159     XResizeWindow(**display, _window, w - _borderwidth*2, h - _borderwidth*2);
160   _ignore_config++;
161
162   _area = Rect(x, y, w, h);
163 }
164
165 void Widget::setAlignment(RenderStyle::Justify a)
166 {
167   _alignment = a;  
168   layout();
169 }
170   
171 void Widget::createWindow(bool overrideredir)
172 {
173   const ScreenInfo *info = display->screenInfo(_screen);
174   XSetWindowAttributes attrib;
175   unsigned long mask = CWEventMask | CWBorderPixel;
176
177   attrib.event_mask = _event_mask;
178   attrib.border_pixel = (_bordercolor ?
179                          _bordercolor->pixel():
180                          BlackPixel(**display, _screen));
181
182   if (overrideredir) {
183     mask |= CWOverrideRedirect;
184     attrib.override_redirect = true;
185   }
186   
187   _window = XCreateWindow(**display, (_parent ?
188                                       _parent->_window :
189                                       RootWindow(**display, _screen)),
190                           _area.x(), _area.y(),
191                           _area.width(), _area.height(),
192                           _borderwidth,
193                           info->depth(),
194                           InputOutput,
195                           info->visual(),
196                           mask,
197                           &attrib);
198   assert(_window != None);
199   ++_ignore_config;
200 }
201
202 void Widget::calcDefaultSizes()
203 {
204   std::list<Widget*>::const_iterator it, end = _children.end();
205   int min_biggest = 0, max_biggest = 0;
206   int min_sum = _bevel + _borderwidth * 2;
207   int max_sum = _bevel + _borderwidth * 2;
208   bool fullmax = false;
209
210   for (it = _children.begin(); it != end; ++it) {
211     const otk::Size &min = (*it)->minSize();
212     const otk::Size &max = (*it)->maxSize();
213     if (_direction == Horizontal) {
214       if (min.height() > min_biggest) min_biggest = min.height();
215       if (max.height() > max_biggest) max_biggest = max.height();
216       min_sum += _bevel + min.width();
217       if (max.width() == INT_MAX)
218         fullmax = true;
219       else if (!fullmax)
220         max_sum += _bevel + max.width();
221     } else {
222       if (min.width() > min_biggest) min_biggest = min.width();
223       if (max.width() > max_biggest) max_biggest = max.width();
224       min_sum += _bevel + min.height();
225       if (max.height() == INT_MAX)
226         fullmax = true;
227       else if (!fullmax)
228         max_sum += _bevel + max.height();
229     }
230   }
231   if (_direction == Horizontal) {
232     _min_size = otk::Size(min_sum + (_bevel + _borderwidth) * 2,
233                           min_biggest + (_bevel + _borderwidth) * 2);
234     _max_size = otk::Size((fullmax ? INT_MAX :
235                            max_sum  + (_bevel + _borderwidth) * 2),
236                           max_biggest);
237   } else {
238     _min_size = otk::Size(min_biggest + (_bevel + _borderwidth) * 2,
239                           min_sum + (_bevel + _borderwidth) * 2);
240     _max_size = otk::Size(max_biggest, (fullmax ? INT_MAX : max_sum +
241                                         (_bevel + _borderwidth) * 2));
242   }
243   update();
244 }
245
246 void Widget::setBorderWidth(int w)
247 {
248   assert(w >= 0);
249   if (!parent()) return; // top-level windows cannot have borders
250   if (w == borderWidth()) return; // no change
251   
252   _borderwidth = w;
253   XSetWindowBorderWidth(**display, _window, _borderwidth);
254
255   calcDefaultSizes();
256   update();
257 }
258
259 void Widget::setMinSize(const Size &s)
260 {
261   _min_size = s;
262   update();
263 }
264
265 void Widget::setMaxSize(const Size &s)
266 {
267   _max_size = s;
268   update();
269 }
270
271 void Widget::setBorderColor(const RenderColor *c)
272 {
273   _bordercolor = c;
274   XSetWindowBorder(**otk::display, _window,
275                    c ? c->pixel() : BlackPixel(**otk::display, _screen));
276 }
277
278 void Widget::setBevel(int b)
279 {
280   _bevel = b;
281   calcDefaultSizes();
282   layout();
283 }
284
285 void Widget::layout()
286 {
287   if (_children.empty() || !_visible) return;
288   if (_direction == Horizontal)
289     layoutHorz();
290   else
291     layoutVert();
292 }
293
294 void Widget::layoutHorz()
295 {
296   std::list<Widget*>::iterator it, end;
297
298   // work with just the visible children
299   std::list<Widget*> visible;
300   for (it = _children.begin(), end = _children.end(); it != end; ++it)
301     if ((*it)->visible())
302       visible.push_back(*it);
303
304   if (visible.empty()) return;
305
306   int x, y, w, h; // working area
307   x = y = _bevel;
308   w = _area.width() - _borderwidth * 2 - _bevel * 2;
309   h = _area.height() - _borderwidth * 2 - _bevel * 2;
310   if (w < 0 || h < 0) return; // not worth laying anything out!
311
312   int free = w - (visible.size() - 1) * _bevel;
313   if (free < 0) free = 0;
314   int each;
315   
316   std::list<Widget*> adjustable;
317
318   // find the 'free' space, and how many children will be using it
319   for (it = visible.begin(), end = visible.end(); it != end; ++it) {
320     free -= (*it)->minSize().width();
321     if (free < 0) free = 0;
322     if ((*it)->maxSize().width() - (*it)->minSize().width() > 0)
323       adjustable.push_back(*it);
324   }
325   // some widgets may have max widths that restrict them, find the 'true'
326   // amount of free space after these widgets are not included
327   if (!adjustable.empty()) {
328     do {
329       each = free / adjustable.size();
330       for (it = adjustable.begin(), end = adjustable.end(); it != end;) {
331         std::list<Widget*>::iterator next = it; ++next;
332         int m = (*it)->maxSize().width() - (*it)->minSize().width();
333         if (m > 0 && m < each) {
334           free -= m;
335           if (free < 0) free = 0;
336           adjustable.erase(it);
337           break; // if one is found to be fixed, then the free space needs to
338                  // change, and the rest need to be reexamined
339         }
340         it = next;
341       }
342     } while (it != end && !adjustable.empty());
343   }
344
345   // place/size the widgets
346   if (!adjustable.empty())
347     each = free / adjustable.size();
348   else
349     each = 0;
350   for (it = visible.begin(), end = visible.end(); it != end; ++it) {
351     int w;
352     // is the widget adjustable?
353     std::list<Widget*>::const_iterator
354       found = std::find(adjustable.begin(), adjustable.end(), *it);
355     if (found != adjustable.end()) {
356       // adjustable
357       w = (*it)->minSize().width() + each;
358     } else {
359       // fixed
360       w = (*it)->minSize().width();
361     }
362     // align it vertically
363     int yy = y;
364     int hh = std::max(std::min(h, (*it)->_max_size.height()),
365                       (*it)->_min_size.height());
366     if (hh < h) {
367       switch(_alignment) {
368       case RenderStyle::RightBottomJustify:
369         yy += h - hh;
370         break;
371       case RenderStyle::CenterJustify:
372         yy += (h - hh) / 2;
373         break;
374       case RenderStyle::LeftTopJustify:
375         break;
376       }
377     }
378     (*it)->internal_moveresize(x, yy, w, hh);
379     (*it)->render();
380     (*it)->layout();
381     x += w + _bevel;
382   }
383 }
384
385 void Widget::layoutVert()
386 {
387   std::list<Widget*>::iterator it, end;
388
389   // work with just the visible children
390   std::list<Widget*> visible;
391   for (it = _children.begin(), end = _children.end(); it != end; ++it)
392     if ((*it)->visible())
393       visible.push_back(*it);
394
395   if (visible.empty()) return;
396
397   int x, y, w, h; // working area
398   x = y = _bevel;
399   w = _area.width() - _borderwidth * 2 - _bevel * 2;
400   h = _area.height() - _borderwidth * 2 - _bevel * 2;
401   if (w < 0 || h < 0) return; // not worth laying anything out!
402
403   int free = h - (visible.size() - 1) * _bevel;
404   if (free < 0) free = 0;
405   int each;
406
407   std::list<Widget*> adjustable;
408
409   // find the 'free' space, and how many children will be using it
410   for (it = visible.begin(), end = visible.end(); it != end; ++it) {
411     free -= (*it)->minSize().height();
412     if (free < 0) free = 0;
413     if ((*it)->maxSize().height() - (*it)->minSize().height() > 0)
414       adjustable.push_back(*it);
415   }
416   // some widgets may have max heights that restrict them, find the 'true'
417   // amount of free space after these widgets are not included
418   if (!adjustable.empty()) {
419     do {
420       each = free / adjustable.size();
421       for (it = adjustable.begin(), end = adjustable.end(); it != end;) {
422         std::list<Widget*>::iterator next = it; ++next;
423         int m = (*it)->maxSize().height() - (*it)->minSize().height();
424         if (m > 0 && m < each) {
425           free -= m;
426           if (free < 0) free = 0;
427           adjustable.erase(it);
428           break; // if one is found to be fixed, then the free space needs to
429                  // change, and the rest need to be reexamined
430         }
431         it = next;
432       }
433     } while (it != end && !adjustable.empty());
434   }
435
436   // place/size the widgets
437   if (!adjustable.empty())
438     each = free / adjustable.size();
439   else
440     each = 0;
441   for (it = visible.begin(), end = visible.end(); it != end; ++it) {
442     int h;
443     // is the widget adjustable?
444     std::list<Widget*>::const_iterator
445       found = std::find(adjustable.begin(), adjustable.end(), *it);
446     if (found != adjustable.end()) {
447       // adjustable
448       h = (*it)->minSize().height() + each;
449     } else {
450       // fixed
451       h = (*it)->minSize().height();
452     }
453     // align it horizontally
454     int xx = x;
455     int ww = std::max(std::min(w, (*it)->_max_size.width()),
456                       (*it)->_min_size.width());
457     if (ww < w) {
458       switch(_alignment) {
459       case RenderStyle::RightBottomJustify:
460         xx += w - ww;
461         break;
462       case RenderStyle::CenterJustify:
463         xx += (w - ww) / 2;
464         break;
465       case RenderStyle::LeftTopJustify:
466         break;
467       }
468     }
469     (*it)->internal_moveresize(xx, y, ww, h);
470     (*it)->render();
471     (*it)->layout();
472     y += h + _bevel; 
473   }
474 }
475
476 void Widget::render()
477 {
478   if (!_dirty) return;
479   if (!_texture) {
480     // set a solid color as the default background
481     XSetWindowBackground(**display, _window,
482                          RenderStyle::style(_screen)->
483                          titlebarUnfocusBackground()->color().pixel());
484     return;
485   }
486   if (_borderwidth * 2 > _area.width() ||
487       _borderwidth * 2 > _area.height())
488     return; // no surface to draw on
489   
490   Surface *s = new Surface(_screen, Size(_area.width() - _borderwidth * 2,
491                                          _area.height() - _borderwidth * 2));
492   display->renderControl(_screen)->drawBackground(*s, *_texture);
493
494   renderForeground(*s); // for inherited types to render onto the _surface
495
496   XSetWindowBackgroundPixmap(**display, _window, s->pixmap());
497   XClearWindow(**display, _window);
498
499   // delete the old surface *after* its pixmap isn't in use anymore
500   if (_surface) delete _surface;
501
502   s->freePixelData(); // done rendering with this surface
503   _surface = s;
504
505   _dirty = false;
506 }
507
508 void Widget::renderChildren()
509 {
510   std::list<Widget*>::iterator it, end = _children.end();
511   for (it = _children.begin(); it != end; ++it)
512     (*it)->render();
513 }
514
515 void Widget::styleChanged(const RenderStyle &)
516 {
517   refresh();
518 }
519
520 void Widget::exposeHandler(const XExposeEvent &e)
521 {
522   EventHandler::exposeHandler(e);
523   XClearArea(**display, _window, e.x, e.y, e.width, e.height, false);
524 }
525
526 void Widget::configureHandler(const XConfigureEvent &e)
527 {
528   if (_ignore_config) {
529     _ignore_config--;
530   } else {
531     // only interested in these for top level windows
532     if (_parent) return;
533
534     XEvent ev;
535     ev.xconfigure.width = e.width;
536     ev.xconfigure.height = e.height;
537     while (XCheckTypedWindowEvent(**display, window(), ConfigureNotify, &ev));
538
539     if (!(ev.xconfigure.width == area().width() &&
540           ev.xconfigure.height == area().height())) {
541       _area = Rect(_area.position(), Size(e.width, e.height));
542       update();
543     }
544   }
545 }
546
547 }