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