]> icculus.org git repositories - dana/openbox.git/blob - src/frame.cc
free the surfaces' pixeldata after rendering it
[dana/openbox.git] / src / frame.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #include "config.h"
4
5 extern "C" {
6 #ifdef    SHAPE
7 #include <X11/extensions/shape.h>
8 #endif // SHAPE
9 }
10
11 #include "frame.hh"
12 #include "client.hh"
13 #include "openbox.hh"
14 #include "otk/display.hh"
15
16 #include <string>
17 #include <cassert>
18
19 namespace ob {
20
21 const long Frame::event_mask;
22
23 Window createWindow(const otk::ScreenInfo *info, Window parent, 
24                     unsigned long mask, XSetWindowAttributes *attrib)
25 {
26   return XCreateWindow(**otk::display, parent, 0, 0, 1, 1, 0,
27                        info->depth(), InputOutput, info->visual(),
28                        mask, attrib);
29                        
30 }
31
32 Frame::Frame(Client *client)
33   : _client(client),
34     _visible(false),
35     _plate(0),
36     _title(0),
37     _label(0),
38     _handle(0),
39     _lgrip(0),
40     _rgrip(0),
41     _buttons(0),
42     _numbuttons(0),
43     _titleorder(0),
44     _frame_sur(0),
45     _title_sur(0),
46     _label_sur(0),
47     _handle_sur(0),
48     _grip_sur(0),
49     _buttons_sur(0)
50 {
51   assert(client);
52
53   XSetWindowAttributes attrib;
54   unsigned long mask;
55   const otk::ScreenInfo *info = otk::display->screenInfo(client->screen());
56
57   // create all of the decor windows (except title bar buttons)
58   mask = CWOverrideRedirect | CWEventMask;
59   attrib.event_mask = Frame::event_mask;
60   attrib.override_redirect = true;
61   _frame = createWindow(info, info->rootWindow(), mask, &attrib);
62
63   mask = 0;
64   _plate = createWindow(info, _frame, mask, &attrib);
65   mask = CWEventMask;
66   attrib.event_mask = (ButtonPressMask | ButtonReleaseMask | ButtonMotionMask |
67                        ExposureMask);
68   _title = createWindow(info, _frame, mask, &attrib);
69   _label = createWindow(info, _title, mask, &attrib);
70   _handle = createWindow(info, _frame, mask, &attrib);
71   mask |= CWCursor;
72   attrib.cursor = openbox->cursors().ll_angle;
73   _lgrip = createWindow(info, _handle, mask, &attrib);
74   attrib.cursor = openbox->cursors().lr_angle;
75   _rgrip = createWindow(info, _handle, mask, &attrib);
76
77   // the other stuff is shown based on decor settings
78   XMapWindow(**otk::display, _plate);
79   XMapWindow(**otk::display, _lgrip);
80   XMapWindow(**otk::display, _rgrip);
81   XMapWindow(**otk::display, _label);
82
83   applyStyle(*otk::RenderStyle::style(_client->screen()));
84
85   // XXX load buttons
86   _numbuttons = 0;
87   _buttons = new Window[0];
88   _buttons_sur = new otk::Surface*[0];
89   _titleorder = new int[1];
90   _titleorder[0] = -1;
91
92   // register all of the windows with the event dispatcher
93   Window *w = allWindows();
94   for (unsigned int i = 0; w[i]; ++i)
95     openbox->registerHandler(w[i], this);
96   delete [] w;
97 }
98
99 Frame::~Frame()
100 {
101   // unregister all of the windows with the event dispatcher
102   Window *w = allWindows();
103   for (unsigned int i = 0; w[i]; ++i)
104     openbox->clearHandler(w[i]);
105   delete [] w;
106
107   for (int i = 0; i < _numbuttons; ++i) {
108     XDestroyWindow(**otk::display, _buttons[i]);
109     delete _buttons_sur[i];
110   }
111   XDestroyWindow(**otk::display, _rgrip);
112   XDestroyWindow(**otk::display, _lgrip);
113   XDestroyWindow(**otk::display, _handle);
114   XDestroyWindow(**otk::display, _label);
115   XDestroyWindow(**otk::display, _title);
116   XDestroyWindow(**otk::display, _frame);
117
118   if (_frame_sur) delete _frame_sur;
119   if (_title_sur) delete _title_sur;
120   if (_label_sur) delete _label_sur;
121   if (_handle_sur) delete _handle_sur;
122   if (_grip_sur) delete _grip_sur;
123
124   delete [] _buttons;
125   delete [] _titleorder;
126   delete [] _buttons_sur;
127 }
128
129 void Frame::show()
130 {
131   if (!_visible) {
132     _visible = true;
133     XMapWindow(**otk::display, _frame);
134   }
135 }
136
137 void Frame::hide()
138 {
139   if (_visible) {
140     _visible = false;
141     XUnmapWindow(**otk::display, _frame);
142   }
143 }
144
145 MouseContext::MC Frame::mouseContext(Window win) const
146 {
147   if (win == _frame)  return MouseContext::Frame;
148   if (win == _title ||
149       win == _label)  return MouseContext::Titlebar;
150   if (win == _handle) return MouseContext::Handle;
151   if (win == _plate)  return MouseContext::Window;
152   if (win == _lgrip ||
153       win == _rgrip)  return MouseContext::Grip;
154   return (MouseContext::MC) -1;
155 }
156
157 Window *Frame::allWindows() const
158 {
159   Window *w = new Window[7 + _numbuttons + 1];
160   unsigned int i = 0;
161   w[i++] = _frame;
162   w[i++] = _plate;
163   w[i++] = _title;
164   w[i++] = _label;
165   w[i++] = _handle;
166   w[i++] = _lgrip;
167   w[i++] = _rgrip;
168   for (int j = 0; j < _numbuttons; ++j)
169     w[j + i++] = _buttons[j];
170   w[i] = 0;
171   return w;
172 }
173
174 void Frame::applyStyle(const otk::RenderStyle &style)
175 {
176   // set static border colors
177   XSetWindowBorder(**otk::display, _frame, style.frameBorderColor()->pixel());
178   XSetWindowBorder(**otk::display, _title, style.frameBorderColor()->pixel());
179   XSetWindowBorder(**otk::display, _handle, style.frameBorderColor()->pixel());
180   XSetWindowBorder(**otk::display, _lgrip, style.frameBorderColor()->pixel());
181   XSetWindowBorder(**otk::display, _rgrip, style.frameBorderColor()->pixel());
182
183   // size all the fixed-size elements
184   geom.font_height = style.labelFont()->height();
185   if (geom.font_height < 1) geom.font_height = 1;
186   geom.button_size = geom.font_height - 2;
187   if (geom.button_size < 1) geom.button_size = 1;
188   geom.handle_height = style.handleWidth();
189   if (geom.handle_height < 1) geom.handle_height = 1;
190   geom.bevel = style.bevelWidth();
191   
192   XResizeWindow(**otk::display, _lgrip, geom.grip_width(), geom.handle_height);
193   XResizeWindow(**otk::display, _rgrip, geom.grip_width(), geom.handle_height);
194   
195   for (int i = 0; i < _numbuttons; ++i)
196     XResizeWindow(**otk::display, _buttons[i],
197                   geom.button_size, geom.button_size);
198 }
199
200 void Frame::styleChanged(const otk::RenderStyle &style)
201 {
202   applyStyle(style);
203   
204   // size/position everything
205   adjustSize();
206   adjustPosition();
207 }
208
209 void Frame::adjustFocus()
210 {
211   // XXX optimizations later...
212   adjustSize();
213 }
214
215 void Frame::adjustTitle()
216 {
217   // XXX optimizations later...
218   adjustSize();
219 }
220
221 static void render(int screen, const otk::Size &size, Window win,
222                    otk::Surface **surface,
223                    const otk::RenderTexture &texture)
224 {
225   otk::Surface *s = new otk::Surface(screen, size);
226   otk::display->renderControl(screen)->drawBackground(*s, texture);
227   XSetWindowBackgroundPixmap(**otk::display, win, s->pixmap());
228   XClearWindow(**otk::display, win);
229   if (*surface) delete *surface;
230   s->freePixelData();
231   *surface = s;
232 }
233
234 void Frame::adjustSize()
235 {
236   Client::DecorationFlags decorations = _client->decorations();
237   const otk::RenderStyle *style = otk::RenderStyle::style(_client->screen());
238
239   if (decorations & Client::Decor_Border) {
240     geom.bwidth = style->frameBorderWidth();
241     geom.cbwidth = style->clientBorderWidth();
242   } else {
243     geom.bwidth = geom.cbwidth = 0;
244   }
245   _innersize.left = _innersize.top = _innersize.bottom = _innersize.right =
246     geom.cbwidth;
247   geom.width = _client->area().width() + geom.cbwidth * 2;
248   assert(geom.width > 0);
249
250   // set border widths
251   XSetWindowBorderWidth(**otk::display, _plate, geom.cbwidth);
252   XSetWindowBorderWidth(**otk::display, _frame, geom.bwidth);
253   XSetWindowBorderWidth(**otk::display, _title, geom.bwidth);
254   XSetWindowBorderWidth(**otk::display, _handle, geom.bwidth);
255   XSetWindowBorderWidth(**otk::display, _lgrip, geom.bwidth);
256   XSetWindowBorderWidth(**otk::display, _rgrip, geom.bwidth);
257   
258   // position/size and map/unmap all the windows
259
260   if (decorations & Client::Decor_Titlebar) {
261     XMoveResizeWindow(**otk::display, _title, -geom.bwidth, -geom.bwidth,
262                       geom.width, geom.title_height());
263     _innersize.top += geom.title_height() + geom.bwidth;
264     XMapWindow(**otk::display, _title);
265
266     // layout the title bar elements
267     layoutTitle();
268   } else
269     XUnmapWindow(**otk::display, _title);
270
271   if (decorations & Client::Decor_Handle) {
272     geom.handle_y = _innersize.top + _client->area().height() + geom.cbwidth;
273     XMoveResizeWindow(**otk::display, _handle, -geom.bwidth, geom.handle_y,
274                       geom.width, geom.handle_height);
275     XMoveWindow(**otk::display, _lgrip, -geom.bwidth, -geom.bwidth);
276     XMoveWindow(**otk::display, _rgrip,
277                 -geom.bwidth + geom.width - geom.grip_width(),
278                 -geom.bwidth);
279     _innersize.bottom += geom.handle_height + geom.bwidth;
280     XMapWindow(**otk::display, _handle);
281   } else
282     XUnmapWindow(**otk::display, _handle);
283   
284   XResizeWindow(**otk::display, _frame, geom.width,
285                 (_client->shaded() ? geom.title_height() :
286                  _innersize.top + _innersize.bottom +
287                  _client->area().height()));
288
289   // do this in two steps because clients whose gravity is set to
290   // 'Static' don't end up getting moved at all with an XMoveResizeWindow
291   XMoveWindow(**otk::display, _plate, _innersize.left - geom.cbwidth,
292               _innersize.top - geom.cbwidth);
293   XResizeWindow(**otk::display, _plate, _client->area().width(),
294                 _client->area().height());
295
296   _size.left   = _innersize.left + geom.bwidth;
297   _size.right  = _innersize.right + geom.bwidth;
298   _size.top    = _innersize.top + geom.bwidth;
299   _size.bottom = _innersize.bottom + geom.bwidth;
300
301   _area = otk::Rect(_area.position(), otk::Size(_client->area().width() +
302                                                 _size.left + _size.right,
303                                                 _client->area().height() +
304                                                 _size.top + _size.bottom));
305
306   // render all the elements
307   int screen = _client->screen();
308   bool focus = _client->focused();
309   if (decorations & Client::Decor_Titlebar) {
310     render(screen, otk::Size(geom.width, geom.title_height()), _title,
311            &_title_sur, *(focus ? style->titlebarFocusBackground() :
312                           style->titlebarUnfocusBackground()));
313     
314     renderLabel();
315   }
316
317   if (decorations & Client::Decor_Handle) {
318     render(screen, otk::Size(geom.width, geom.handle_height), _handle,
319            &_handle_sur, *(focus ? style->handleFocusBackground() :
320                            style->handleUnfocusBackground()));
321     render(screen, otk::Size(geom.grip_width(), geom.handle_height), _lgrip,
322            &_grip_sur, *(focus ? style->gripFocusBackground() :
323                          style->gripUnfocusBackground()));
324     XSetWindowBackgroundPixmap(**otk::display, _rgrip, _grip_sur->pixmap());
325     XClearWindow(**otk::display, _rgrip);
326   }
327
328   XSetWindowBorder(**otk::display, _plate,
329                    focus ? style->clientBorderFocusColor()->pixel() :
330                    style->clientBorderUnfocusColor()->pixel());
331   
332   adjustShape();
333 }
334
335 void Frame::renderLabel()
336 {
337   const otk::RenderStyle *style = otk::RenderStyle::style(_client->screen());
338   const otk::RenderControl *control =
339     otk::display->renderControl(_client->screen());
340   const otk::Font *font = style->labelFont();
341
342   otk::Surface *s = new otk::Surface(_client->screen(),
343                                      otk::Size(geom.label_width,
344                                                geom.label_height()));
345   control->drawBackground(*s, *(_client->focused() ?
346                                 style->labelFocusBackground() :
347                                 style->labelUnfocusBackground()));
348
349   otk::ustring t = _client->title(); // the actual text to draw
350   int x = geom.bevel;                // x coord for the text
351
352   if (x * 2 > geom.label_width) return; // no room at all
353
354   // find a string that will fit inside the area for text
355   otk::ustring::size_type text_len = t.size();
356   int length;
357   int maxsize = geom.label_width - geom.bevel * 2;
358       
359   do {
360     t.resize(text_len);
361     length = font->measureString(t);  // this returns an unsigned, so check < 0
362     if (length < 0) length = maxsize; // if the string's that long just adjust
363   } while (length > maxsize && text_len-- > 0);
364
365   if (text_len <= 0) return; // won't fit anything
366
367   // justify the text
368   switch (style->labelTextJustify()) {
369   case otk::RenderStyle::RightBottomJustify:
370     x += maxsize - length;
371     break;
372   case otk::RenderStyle::CenterJustify:
373     x += (maxsize - length) / 2;
374     break;
375   case otk::RenderStyle::LeftTopJustify:
376     break;
377   }
378  
379   control->drawString(*s, *font, x, 0,
380                       *(_client->focused() ? style->textFocusColor() :
381                         style->textUnfocusColor()), t);
382
383   XSetWindowBackgroundPixmap(**otk::display, _label, s->pixmap());
384   XClearWindow(**otk::display, _label);
385   if (_label_sur) delete _label_sur;
386   s->freePixelData();
387   _label_sur = s;
388 }
389
390 void Frame::layoutTitle()
391 {
392   geom.label_width = geom.width - geom.bevel * 2;
393   if (geom.label_width < 1) geom.label_width = 1;
394   XMoveResizeWindow(**otk::display, _label, geom.bevel, geom.bevel,
395                     geom.label_width, geom.font_height);
396 }
397
398 void Frame::adjustPosition()
399 {
400   int x, y;
401   x = _client->area().x();
402   y = _client->area().y();
403   clientGravity(x, y);
404   XMoveWindow(**otk::display, _frame, x, y);
405   _area = otk::Rect(otk::Point(x, y), _area.size());
406 }
407
408
409 void Frame::adjustShape()
410 {
411 #ifdef SHAPE
412   Client::DecorationFlags decorations = _client->decorations();
413   
414   if (!_client->shaped()) {
415     // clear the shape on the frame window
416     XShapeCombineMask(**otk::display, _frame, ShapeBounding,
417                       _innersize.left,
418                       _innersize.top,
419                       None, ShapeSet);
420   } else {
421     // make the frame's shape match the clients
422     XShapeCombineShape(**otk::display, _frame, ShapeBounding,
423                        _innersize.left,
424                        _innersize.top,
425                        _client->window(), ShapeBounding, ShapeSet);
426
427     int num = 0;
428     XRectangle xrect[2];
429
430     if (decorations & Client::Decor_Titlebar) {
431       xrect[0].x = -geom.bevel;
432       xrect[0].y = -geom.bevel;
433       xrect[0].width = geom.width + geom.bwidth * 2;
434       xrect[0].height = geom.title_height() + geom.bwidth * 2;
435       ++num;
436     }
437
438     if (decorations & Client::Decor_Handle) {
439       xrect[1].x = -geom.bevel;
440       xrect[1].y = geom.handle_y;
441       xrect[1].width = geom.width + geom.bwidth * 2;
442       xrect[1].height = geom.handle_height + geom.bwidth * 2;
443       ++num;
444     }
445
446     XShapeCombineRectangles(**otk::display, _frame,
447                             ShapeBounding, 0, 0, xrect, num,
448                             ShapeUnion, Unsorted);
449   }
450 #endif // SHAPE
451 }
452
453
454 void Frame::adjustState()
455 {
456 // XXX  _button_alldesk.update();
457 // XXX  _button_max.update();
458 }
459
460
461 void Frame::grabClient()
462 {
463   // reparent the client to the frame
464   XReparentWindow(**otk::display, _client->window(), _plate, 0, 0);
465   /*
466     When reparenting the client window, it is usually not mapped yet, since
467     this occurs from a MapRequest. However, in the case where Openbox is
468     starting up, the window is already mapped, so we'll see unmap events for
469     it. There are 2 unmap events generated that we see, one with the 'event'
470     member set the root window, and one set to the client, but both get handled
471     and need to be ignored.
472   */
473   if (openbox->state() == Openbox::State_Starting)
474     _client->ignore_unmaps += 2;
475
476   // select the event mask on the client's parent (to receive config/map req's)
477   XSelectInput(**otk::display, _plate, SubstructureRedirectMask);
478
479   // map the client so it maps when the frame does
480   XMapWindow(**otk::display, _client->window());
481
482   adjustSize();
483   adjustPosition();
484 }
485
486
487 void Frame::releaseClient()
488 {
489   XEvent ev;
490
491   // check if the app has already reparented its window away
492   if (XCheckTypedWindowEvent(**otk::display, _client->window(),
493                              ReparentNotify, &ev)) {
494     XPutBackEvent(**otk::display, &ev);
495     // re-map the window since the unmanaging process unmaps it
496     XMapWindow(**otk::display, _client->window());  
497   } else {
498     // according to the ICCCM - if the client doesn't reparent itself, then we
499     // will reparent the window to root for them
500     XReparentWindow(**otk::display, _client->window(),
501                     otk::display->screenInfo(_client->screen())->rootWindow(),
502                     _client->area().x(), _client->area().y());
503   }
504 }
505
506
507 void Frame::clientGravity(int &x, int &y)
508 {
509   // horizontal
510   switch (_client->gravity()) {
511   default:
512   case NorthWestGravity:
513   case SouthWestGravity:
514   case WestGravity:
515     break;
516
517   case NorthGravity:
518   case SouthGravity:
519   case CenterGravity:
520     x -= (_size.left + _size.right) / 2;
521     break;
522
523   case NorthEastGravity:
524   case SouthEastGravity:
525   case EastGravity:
526     x -= _size.left + _size.right;
527     break;
528
529   case ForgetGravity:
530   case StaticGravity:
531     x -= _size.left;
532     break;
533   }
534
535   // vertical
536   switch (_client->gravity()) {
537   default:
538   case NorthWestGravity:
539   case NorthEastGravity:
540   case NorthGravity:
541     break;
542
543   case CenterGravity:
544   case EastGravity:
545   case WestGravity:
546     y -= (_size.top + _size.bottom) / 2;
547     break;
548
549   case SouthWestGravity:
550   case SouthEastGravity:
551   case SouthGravity:
552     y -= _size.top + _size.bottom;
553     break;
554
555   case ForgetGravity:
556   case StaticGravity:
557     y -= _size.top;
558     break;
559   }
560 }
561
562
563 void Frame::frameGravity(int &x, int &y)
564 {
565   // horizontal
566   switch (_client->gravity()) {
567   default:
568   case NorthWestGravity:
569   case WestGravity:
570   case SouthWestGravity:
571     break;
572   case NorthGravity:
573   case CenterGravity:
574   case SouthGravity:
575     x += (_size.left + _size.right) / 2;
576     break;
577   case NorthEastGravity:
578   case EastGravity:
579   case SouthEastGravity:
580     x += _size.left + _size.right;
581     break;
582   case StaticGravity:
583   case ForgetGravity:
584     x += _size.left;
585     break;
586   }
587
588   // vertical
589   switch (_client->gravity()) {
590   default:
591   case NorthWestGravity:
592   case WestGravity:
593   case SouthWestGravity:
594     break;
595   case NorthGravity:
596   case CenterGravity:
597   case SouthGravity:
598     y += (_size.top + _size.bottom) / 2;
599     break;
600   case NorthEastGravity:
601   case EastGravity:
602   case SouthEastGravity:
603     y += _size.top + _size.bottom;
604     break;
605   case StaticGravity:
606   case ForgetGravity:
607     y += _size.top;
608     break;
609   }
610 }
611
612
613 }