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