use a solid color for the default background
[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     _visible = true;
90     if (_parent) _parent->calcDefaultSizes();
91     else {
92       resize(_min_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, 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, min_sum + (_bevel + _borderwidth) * 2);
239     _max_size = otk::Size(max_biggest, (fullmax ? INT_MAX : max_sum +
240                                         (_bevel + _borderwidth) * 2));
241   }
242   update();
243 }
244
245 void Widget::setBorderWidth(int w)
246 {
247   assert(w >= 0);
248   if (!parent()) return; // top-level windows cannot have borders
249   if (w == borderWidth()) return; // no change
250   
251   _borderwidth = w;
252   XSetWindowBorderWidth(**display, _window, _borderwidth);
253
254   calcDefaultSizes();
255   update();
256 }
257
258 void Widget::setMinSize(const Size &s)
259 {
260   _min_size = s;
261   update();
262 }
263
264 void Widget::setMaxSize(const Size &s)
265 {
266   _max_size = s;
267   update();
268 }
269
270 void Widget::setBorderColor(const RenderColor *c)
271 {
272   _bordercolor = c;
273   XSetWindowBorder(**otk::display, _window,
274                    c ? c->pixel() : BlackPixel(**otk::display, _screen));
275 }
276
277 void Widget::setBevel(int b)
278 {
279   _bevel = b;
280   calcDefaultSizes();
281   layout();
282 }
283
284 void Widget::layout()
285 {
286   if (_children.empty() || !_visible) return;
287   if (_direction == Horizontal)
288     layoutHorz();
289   else
290     layoutVert();
291 }
292
293 void Widget::layoutHorz()
294 {
295   std::list<Widget*>::iterator it, end;
296
297   // work with just the visible children
298   std::list<Widget*> visible = _children;
299   for (it = visible.begin(), end = visible.end(); it != end;) {
300     std::list<Widget*>::iterator next = it; ++next;
301     if (!(*it)->visible())
302       visible.erase(it);
303     it = next;
304   }
305
306   if (visible.empty()) return;
307
308   int x, y, w, h; // working area
309   x = y = _bevel;
310   w = _area.width() - _borderwidth * 2 - _bevel * 2;
311   h = _area.height() - _borderwidth * 2 - _bevel * 2;
312   if (w < 0 || h < 0) return; // not worth laying anything out!
313
314   int free = w - (visible.size() - 1) * _bevel;
315   if (free < 0) free = 0;
316   int each;
317   
318   std::list<Widget*> adjustable = visible;
319
320   // find the 'free' space, and how many children will be using it
321   for (it = adjustable.begin(), end = adjustable.end(); it != end;) {
322     std::list<Widget*>::iterator next = it; ++next;
323     free -= (*it)->minSize().width();
324     if (free < 0) free = 0;
325     if ((*it)->maxSize().width() - (*it)->minSize().width() <= 0)
326       adjustable.erase(it);
327     it = next;
328   }
329   // some widgets may have max widths that restrict them, find the 'true'
330   // amount of free space after these widgets are not included
331   if (!adjustable.empty()) {
332     do {
333       each = free / adjustable.size();
334       for (it = adjustable.begin(), end = adjustable.end(); it != end;) {
335         std::list<Widget*>::iterator next = it; ++next;
336         int m = (*it)->maxSize().width() - (*it)->minSize().width();
337         if (m > 0 && m < each) {
338           free -= m;
339           if (free < 0) free = 0;
340           adjustable.erase(it);
341           break; // if one is found to be fixed, then the free space needs to
342                  // change, and the rest need to be reexamined
343         }
344         it = next;
345       }
346     } while (it != end && !adjustable.empty());
347   }
348
349   // place/size the widgets
350   if (!adjustable.empty())
351     each = free / adjustable.size();
352   else
353     each = 0;
354   for (it = visible.begin(), end = visible.end(); it != end; ++it) {
355     int w;
356     // is the widget adjustable?
357     std::list<Widget*>::const_iterator
358       found = std::find(adjustable.begin(), adjustable.end(), *it);
359     if (found != adjustable.end()) {
360       // adjustable
361       w = (*it)->minSize().width() + each;
362     } else {
363       // fixed
364       w = (*it)->minSize().width();
365     }
366     // align it vertically
367     int yy = y;
368     int hh = std::max(std::min(h, (*it)->_max_size.height()),
369                       (*it)->_min_size.height());
370     if (hh < h) {
371       switch(_alignment) {
372       case RenderStyle::RightBottomJustify:
373         yy += h - hh;
374         break;
375       case RenderStyle::CenterJustify:
376         yy += (h - hh) / 2;
377         break;
378       case RenderStyle::LeftTopJustify:
379         break;
380       }
381     }
382     (*it)->internal_moveresize(x, yy, w, hh);
383     (*it)->render();
384     (*it)->layout();
385     x += w + _bevel;
386   }
387 }
388
389 void Widget::layoutVert()
390 {
391   std::list<Widget*>::iterator it, end;
392
393   // work with just the visible children
394   std::list<Widget*> visible = _children;
395   for (it = visible.begin(), end = visible.end(); it != end;) {
396     std::list<Widget*>::iterator next = it; ++next;
397     if (!(*it)->visible())
398       visible.erase(it);
399     it = next;
400   }
401
402   if (visible.empty()) return;
403
404   int x, y, w, h; // working area
405   x = y = _bevel;
406   w = _area.width() - _borderwidth * 2 - _bevel * 2;
407   h = _area.height() - _borderwidth * 2 - _bevel * 2;
408   if (w < 0 || h < 0) return; // not worth laying anything out!
409
410   int free = h - (visible.size() - 1) * _bevel;
411   if (free < 0) free = 0;
412   int each;
413
414   std::list<Widget*> adjustable = visible;
415
416   // find the 'free' space, and how many children will be using it
417   for (it = adjustable.begin(), end = adjustable.end(); it != end;) {
418     std::list<Widget*>::iterator next = it; ++next;
419     free -= (*it)->minSize().height();
420     if (free < 0) free = 0;
421     if ((*it)->maxSize().height() - (*it)->minSize().height() <= 0)
422       adjustable.erase(it);
423     it = next;
424   }
425   // some widgets may have max heights that restrict them, find the 'true'
426   // amount of free space after these widgets are not included
427   if (!adjustable.empty()) {
428     do {
429       each = free / adjustable.size();
430       for (it = adjustable.begin(), end = adjustable.end(); it != end;) {
431         std::list<Widget*>::iterator next = it; ++next;
432         int m = (*it)->maxSize().height() - (*it)->minSize().height();
433         if (m > 0 && m < each) {
434           free -= m;
435           if (free < 0) free = 0;
436           adjustable.erase(it);
437           break; // if one is found to be fixed, then the free space needs to
438                  // change, and the rest need to be reexamined
439         }
440         it = next;
441       }
442     } while (it != end && !adjustable.empty());
443   }
444
445   // place/size the widgets
446   if (!adjustable.empty())
447   each = free / adjustable.size();
448   else
449     each = 0;
450   for (it = visible.begin(), end = visible.end(); it != end; ++it) {
451     int h;
452     // is the widget adjustable?
453     std::list<Widget*>::const_iterator
454       found = std::find(adjustable.begin(), adjustable.end(), *it);
455     if (found != adjustable.end()) {
456       // adjustable
457       h = (*it)->minSize().height() + each;
458     } else {
459       // fixed
460       h = (*it)->minSize().height();
461     }
462     // align it horizontally
463     int xx = x;
464     int ww = std::max(std::min(w, (*it)->_max_size.width()),
465                       (*it)->_min_size.width());
466     if (ww < w) {
467       switch(_alignment) {
468       case RenderStyle::RightBottomJustify:
469         xx += w - ww;
470         break;
471       case RenderStyle::CenterJustify:
472         xx += (w - ww) / 2;
473         break;
474       case RenderStyle::LeftTopJustify:
475         break;
476       }
477     }
478     (*it)->internal_moveresize(xx, y, ww, h);
479     (*it)->render();
480     (*it)->layout();
481     y += h + _bevel; 
482   }
483 }
484
485 void Widget::render()
486 {
487   if (!_dirty) return;
488   if (!_texture) {
489     // set a solid color as the default background
490     XSetWindowBackground(**display, _window,
491                          RenderStyle::style(_screen)->
492                          titlebarUnfocusBackground()->color().pixel());
493     return;
494   }
495   if (_borderwidth * 2 > _area.width() ||
496       _borderwidth * 2 > _area.height())
497     return; // no surface to draw on
498   
499   Surface *s = new Surface(_screen, Size(_area.width() - _borderwidth * 2,
500                                          _area.height() - _borderwidth * 2));
501   display->renderControl(_screen)->drawBackground(*s, *_texture);
502
503   renderForeground(*s); // for inherited types to render onto the _surface
504
505   XSetWindowBackgroundPixmap(**display, _window, s->pixmap());
506   XClearWindow(**display, _window);
507
508   // delete the old surface *after* its pixmap isn't in use anymore
509   if (_surface) delete _surface;
510
511   _surface = s;
512
513   _dirty = false;
514 }
515
516 void Widget::renderChildren()
517 {
518   std::list<Widget*>::iterator it, end = _children.end();
519   for (it = _children.begin(); it != end; ++it)
520     (*it)->render();
521 }
522
523 void Widget::styleChanged(const RenderStyle &)
524 {
525   refresh();
526 }
527
528 void Widget::exposeHandler(const XExposeEvent &e)
529 {
530   EventHandler::exposeHandler(e);
531   XClearArea(**display, _window, e.x, e.y, e.width, e.height, false);
532 }
533
534 void Widget::configureHandler(const XConfigureEvent &e)
535 {
536   if (_ignore_config) {
537     _ignore_config--;
538   } else {
539     // only interested in these for top level windows
540     if (_parent) return;
541     
542     XEvent ev;
543     ev.xconfigure.width = e.width;
544     ev.xconfigure.height = e.height;
545     while (XCheckTypedWindowEvent(**display, window(), ConfigureNotify, &ev));
546
547     if (!(ev.xconfigure.width == area().width() &&
548           ev.xconfigure.height == area().height())) {
549       _area = Rect(_area.position(), Size(e.width, e.height));
550       update();
551     }
552   }
553 }
554
555 }