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