]> icculus.org git repositories - dana/openbox.git/blob - src/frame.cc
signed ints instead of unsigned ints again. less pain. pain bad.
[dana/openbox.git] / src / frame.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2
3 #ifdef HAVE_CONFIG_H
4 # include "../config.h"
5 #endif
6
7 extern "C" {
8 #ifdef    SHAPE
9 #include <X11/extensions/shape.h>
10 #endif // SHAPE
11 }
12
13 #include "frame.hh"
14 #include "client.hh"
15 #include "openbox.hh"
16 #include "otk/display.hh"
17
18 #include <string>
19 #include <cassert>
20
21 namespace ob {
22
23 const long Frame::event_mask;
24
25 Window createWindow(const otk::ScreenInfo *info, Window parent, 
26                     unsigned long mask, XSetWindowAttributes *attrib)
27 {
28   return XCreateWindow(**otk::display, parent, 0, 0, 1, 1, 0,
29                        info->depth(), InputOutput, info->visual(),
30                        mask, attrib);
31                        
32 }
33
34 Frame::Frame(Client *client)
35   : _client(client),
36     _visible(false),
37     _plate(0),
38     _title(0),
39     _label(0),
40     _handle(0),
41     _lgrip(0),
42     _rgrip(0),
43     _buttons(0),
44     _numbuttons(0),
45     _titleorder(0),
46     _frame_sur(0),
47     _title_sur(0),
48     _label_sur(0),
49     _handle_sur(0),
50     _grip_sur(0),
51     _buttons_sur(0)
52 {
53   assert(client);
54
55   XSetWindowAttributes attrib;
56   unsigned long mask;
57   const otk::ScreenInfo *info = otk::display->screenInfo(client->screen());
58
59   // create all of the decor windows (except title bar buttons)
60   mask = CWOverrideRedirect | CWEventMask;
61   attrib.event_mask = Frame::event_mask;
62   attrib.override_redirect = true;
63   _frame = createWindow(info, info->rootWindow(), mask, &attrib);
64
65   mask = 0;
66   _plate = createWindow(info, _frame, mask, &attrib);
67   mask = CWEventMask;
68   attrib.event_mask = (ButtonPressMask | ButtonReleaseMask | ButtonMotionMask |
69                        ExposureMask);
70   _title = createWindow(info, _frame, mask, &attrib);
71   _label = createWindow(info, _title, mask, &attrib);
72   _handle = createWindow(info, _frame, mask, &attrib);
73   mask |= CWCursor;
74   attrib.cursor = openbox->cursors().ll_angle;
75   _lgrip = createWindow(info, _handle, mask, &attrib);
76   attrib.cursor = openbox->cursors().lr_angle;
77   _rgrip = createWindow(info, _handle, mask, &attrib);
78
79   // the other stuff is shown based on decor settings
80   XMapWindow(**otk::display, _plate);
81   XMapWindow(**otk::display, _lgrip);
82   XMapWindow(**otk::display, _rgrip);
83   XMapWindow(**otk::display, _label);
84
85   applyStyle(*otk::RenderStyle::style(_client->screen()));
86
87   // XXX load buttons
88   _numbuttons = 0;
89   _buttons = new Window[0];
90   _buttons_sur = new otk::Surface*[0];
91   _titleorder = new int[1];
92   _titleorder[0] = -1;
93
94   // register all of the windows with the event dispatcher
95   Window *w = allWindows();
96   for (unsigned int i = 0; w[i]; ++i)
97     openbox->registerHandler(w[i], this);
98   delete [] w;
99 }
100
101 Frame::~Frame()
102 {
103   // unregister all of the windows with the event dispatcher
104   Window *w = allWindows();
105   for (unsigned int i = 0; w[i]; ++i)
106     openbox->clearHandler(w[i]);
107   delete [] w;
108
109   for (int i = 0; i < _numbuttons; ++i) {
110     XDestroyWindow(**otk::display, _buttons[i]);
111     delete _buttons_sur[i];
112   }
113   XDestroyWindow(**otk::display, _rgrip);
114   XDestroyWindow(**otk::display, _lgrip);
115   XDestroyWindow(**otk::display, _handle);
116   XDestroyWindow(**otk::display, _label);
117   XDestroyWindow(**otk::display, _title);
118   XDestroyWindow(**otk::display, _frame);
119
120   if (_frame_sur) delete _frame_sur;
121   if (_title_sur) delete _title_sur;
122   if (_label_sur) delete _label_sur;
123   if (_handle_sur) delete _handle_sur;
124   if (_grip_sur) delete _grip_sur;
125
126   delete [] _buttons;
127   delete [] _titleorder;
128   delete [] _buttons_sur;
129 }
130
131 void Frame::show()
132 {
133   if (!_visible) {
134     _visible = true;
135     XMapWindow(**otk::display, _frame);
136   }
137 }
138
139 void Frame::hide()
140 {
141   if (_visible) {
142     _visible = false;
143     XUnmapWindow(**otk::display, _frame);
144   }
145 }
146
147 MouseContext::MC Frame::mouseContext(Window win) const
148 {
149   if (win == _frame)  return MouseContext::Frame;
150   if (win == _title ||
151       win == _label)  return MouseContext::Titlebar;
152   if (win == _handle) return MouseContext::Handle;
153   if (win == _plate)  return MouseContext::Window;
154   if (win == _lgrip ||
155       win == _rgrip)  return MouseContext::Grip;
156   return (MouseContext::MC) -1;
157 }
158
159 Window *Frame::allWindows() const
160 {
161   Window *w = new Window[7 + _numbuttons + 1];
162   unsigned int i = 0;
163   w[i++] = _frame;
164   w[i++] = _plate;
165   w[i++] = _title;
166   w[i++] = _label;
167   w[i++] = _handle;
168   w[i++] = _lgrip;
169   w[i++] = _rgrip;
170   for (int j = 0; j < _numbuttons; ++j)
171     w[j + i++] = _buttons[j];
172   w[i] = 0;
173   return w;
174 }
175
176 void Frame::applyStyle(const otk::RenderStyle &style)
177 {
178   // set static border colors
179   XSetWindowBorder(**otk::display, _frame, style.frameBorderColor()->pixel());
180   XSetWindowBorder(**otk::display, _title, style.frameBorderColor()->pixel());
181   XSetWindowBorder(**otk::display, _handle, style.frameBorderColor()->pixel());
182   XSetWindowBorder(**otk::display, _lgrip, style.frameBorderColor()->pixel());
183   XSetWindowBorder(**otk::display, _rgrip, style.frameBorderColor()->pixel());
184
185   // size all the fixed-size elements
186   geom.font_height = style.labelFont()->height();
187   if (geom.font_height < 1) geom.font_height = 1;
188   geom.button_size = geom.font_height - 2;
189   if (geom.button_size < 1) geom.button_size = 1;
190   geom.handle_height = style.handleWidth();
191   if (geom.handle_height < 1) geom.handle_height = 1;
192   geom.bevel = style.bevelWidth();
193   
194   XResizeWindow(**otk::display, _lgrip, geom.grip_width(), geom.handle_height);
195   XResizeWindow(**otk::display, _rgrip, geom.grip_width(), geom.handle_height);
196   
197   for (int i = 0; i < _numbuttons; ++i)
198     XResizeWindow(**otk::display, _buttons[i],
199                   geom.button_size, geom.button_size);
200 }
201
202 void Frame::styleChanged(const otk::RenderStyle &style)
203 {
204   applyStyle(style);
205   
206   // size/position everything
207   adjustSize();
208   adjustPosition();
209 }
210
211 void Frame::adjustFocus()
212 {
213   // XXX optimizations later...
214   adjustSize();
215 }
216
217 void Frame::adjustTitle()
218 {
219   // XXX optimizations later...
220   adjustSize();
221 }
222
223 static void render(int screen, const otk::Size &size, Window win,
224                    otk::Surface **surface,
225                    const otk::RenderTexture &texture)
226 {
227   otk::Surface *s = new otk::Surface(screen, size);
228   otk::display->renderControl(screen)->drawBackground(*s, texture);
229   XSetWindowBackgroundPixmap(**otk::display, win, s->pixmap());
230   XClearWindow(**otk::display, win);
231   if (*surface) delete *surface;
232   *surface = s;
233 }
234
235 void Frame::adjustSize()
236 {
237   Client::DecorationFlags decorations = _client->decorations();
238   const otk::RenderStyle *style = otk::RenderStyle::style(_client->screen());
239
240   if (decorations & Client::Decor_Border) {
241     geom.bwidth = style->frameBorderWidth();
242     geom.cbwidth = style->clientBorderWidth();
243   } else {
244     geom.bwidth = geom.cbwidth = 0;
245   }
246   _innersize.left = _innersize.top = _innersize.bottom = _innersize.right =
247     geom.cbwidth;
248   geom.width = _client->area().width() + geom.cbwidth * 2;
249   assert(geom.width > 0);
250
251   // set border widths
252   XSetWindowBorderWidth(**otk::display, _plate, geom.cbwidth);
253   XSetWindowBorderWidth(**otk::display, _frame, geom.bwidth);
254   XSetWindowBorderWidth(**otk::display, _title, geom.bwidth);
255   XSetWindowBorderWidth(**otk::display, _handle, geom.bwidth);
256   XSetWindowBorderWidth(**otk::display, _lgrip, geom.bwidth);
257   XSetWindowBorderWidth(**otk::display, _rgrip, geom.bwidth);
258   
259   // position/size and map/unmap all the windows
260
261   if (decorations & Client::Decor_Titlebar) {
262     XMoveResizeWindow(**otk::display, _title, -geom.bwidth, -geom.bwidth,
263                       geom.width, geom.title_height());
264     _innersize.top += geom.title_height() + geom.bwidth;
265     XMapWindow(**otk::display, _title);
266
267     // layout the title bar elements
268     layoutTitle();
269   } else
270     XUnmapWindow(**otk::display, _title);
271
272   if (decorations & Client::Decor_Handle) {
273     geom.handle_y = _innersize.top + _client->area().height() + geom.cbwidth;
274     XMoveResizeWindow(**otk::display, _handle, -geom.bwidth, geom.handle_y,
275                       geom.width, geom.handle_height);
276     XMoveWindow(**otk::display, _lgrip, -geom.bwidth, -geom.bwidth);
277     XMoveWindow(**otk::display, _rgrip,
278                 -geom.bwidth + geom.width - geom.grip_width(),
279                 -geom.bwidth);
280     _innersize.bottom += geom.handle_height + geom.bwidth;
281     XMapWindow(**otk::display, _handle);
282   } else
283     XUnmapWindow(**otk::display, _handle);
284   
285   XResizeWindow(**otk::display, _frame, geom.width,
286                 (_client->shaded() ? geom.title_height() :
287                  _innersize.top + _innersize.bottom +
288                  _client->area().height()));
289
290   // do this in two steps because clients whose gravity is set to
291   // 'Static' don't end up getting moved at all with an XMoveResizeWindow
292   XMoveWindow(**otk::display, _plate, _innersize.left - geom.cbwidth,
293               _innersize.top - geom.cbwidth);
294   XResizeWindow(**otk::display, _plate, _client->area().width(),
295                 _client->area().height());
296
297   _size.left   = _innersize.left + geom.bwidth;
298   _size.right  = _innersize.right + geom.bwidth;
299   _size.top    = _innersize.top + geom.bwidth;
300   _size.bottom = _innersize.bottom + geom.bwidth;
301
302   _area = otk::Rect(_area.position(), otk::Size(_client->area().width() +
303                                                 _size.left + _size.right,
304                                                 _client->area().height() +
305                                                 _size.top + _size.bottom));
306
307   // render all the elements
308   int screen = _client->screen();
309   bool focus = _client->focused();
310   if (decorations & Client::Decor_Titlebar) {
311     render(screen, otk::Size(geom.width, geom.title_height()), _title,
312            &_title_sur, *(focus ? style->titlebarFocusBackground() :
313                           style->titlebarUnfocusBackground()));
314     
315     renderLabel();
316   }
317
318   if (decorations & Client::Decor_Handle) {
319     render(screen, otk::Size(geom.width, geom.handle_height), _handle,
320            &_handle_sur, *(focus ? style->handleFocusBackground() :
321                            style->handleUnfocusBackground()));
322     render(screen, otk::Size(geom.grip_width(), geom.handle_height), _lgrip,
323            &_grip_sur, *(focus ? style->gripFocusBackground() :
324                          style->gripUnfocusBackground()));
325     XSetWindowBackgroundPixmap(**otk::display, _rgrip, _grip_sur->pixmap());
326     XClearWindow(**otk::display, _rgrip);
327   }
328
329   XSetWindowBorder(**otk::display, _plate,
330                    focus ? style->clientBorderFocusColor()->pixel() :
331                    style->clientBorderUnfocusColor()->pixel());
332   
333   adjustShape();
334 }
335
336 void Frame::renderLabel()
337 {
338   const otk::RenderStyle *style = otk::RenderStyle::style(_client->screen());
339   const otk::RenderControl *control =
340     otk::display->renderControl(_client->screen());
341   const otk::Font *font = style->labelFont();
342
343   otk::Surface *s = new otk::Surface(_client->screen(),
344                                      otk::Size(geom.label_width,
345                                                geom.label_height()));
346   control->drawBackground(*s, *(_client->focused() ?
347                                 style->labelFocusBackground() :
348                                 style->labelUnfocusBackground()));
349
350   otk::ustring t = _client->title(); // the actual text to draw
351   int x = geom.bevel;                // x coord for the text
352
353   if (x * 2 > geom.label_width) return; // no room at all
354
355   // find a string that will fit inside the area for text
356   otk::ustring::size_type text_len = t.size();
357   int length;
358   int maxsize = geom.label_width - geom.bevel * 2;
359       
360   do {
361     t.resize(text_len);
362     length = font->measureString(t);  // this returns an unsigned, so check < 0
363     if (length < 0) length = maxsize; // if the string's that long just adjust
364   } while (length > maxsize && text_len-- > 0);
365
366   if (text_len <= 0) return; // won't fit anything
367
368   // justify the text
369   switch (style->labelTextJustify()) {
370   case otk::RenderStyle::RightBottomJustify:
371     x += maxsize - length;
372     break;
373   case otk::RenderStyle::CenterJustify:
374     x += (maxsize - length) / 2;
375     break;
376   case otk::RenderStyle::LeftTopJustify:
377     break;
378   }
379  
380   control->drawString(*s, *font, x, 0,
381                       *(_client->focused() ? style->textFocusColor() :
382                         style->textUnfocusColor()), t);
383
384   XSetWindowBackgroundPixmap(**otk::display, _label, s->pixmap());
385   XClearWindow(**otk::display, _label);
386   if (_label_sur) delete _label_sur;
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 }