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