]> icculus.org git repositories - mikachu/openbox.git/blob - src/Workspace.cc
reindent the help/usage message
[mikachu/openbox.git] / src / Workspace.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2 // Workspace.cc for Blackbox - an X11 Window manager
3 // Copyright (c) 2001 - 2002 Sean 'Shaleh' Perry <shaleh@debian.org>
4 // Copyright (c) 1997 - 2000 Brad Hughes (bhughes@tcac.net)
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining a
7 // copy of this software and associated documentation files (the "Software"),
8 // to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 // and/or sell copies of the Software, and to permit persons to whom the
11 // Software is furnished to do so, subject to the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 // DEALINGS IN THE SOFTWARE.
23
24 #ifdef    HAVE_CONFIG_H
25 #  include "../config.h"
26 #endif // HAVE_CONFIG_H
27
28 extern "C" {
29 #include <X11/Xlib.h>
30 #include <X11/Xatom.h>
31
32 #ifdef    HAVE_STDIO_H
33 #  include <stdio.h>
34 #endif // HAVE_STDIO_H
35
36 #ifdef HAVE_STRING_H
37 #  include <string.h>
38 #endif // HAVE_STRING_H
39 }
40
41 #include <functional>
42 #include <string>
43
44 using std::string;
45
46 #include "i18n.hh"
47 #include "blackbox.hh"
48 #include "Clientmenu.hh"
49 #include "Netizen.hh"
50 #include "Screen.hh"
51 #include "Toolbar.hh"
52 #include "Util.hh"
53 #include "Window.hh"
54 #include "Workspace.hh"
55 #include "Windowmenu.hh"
56
57
58 Workspace::Workspace(BScreen *scrn, unsigned int i) {
59   screen = scrn;
60
61   cascade_x = cascade_y = 32;
62
63   id = i;
64
65   clientmenu = new Clientmenu(this);
66
67   lastfocus = (BlackboxWindow *) 0;
68
69   setName(screen->getNameOfWorkspace(id));
70 }
71
72
73 void Workspace::addWindow(BlackboxWindow *w, bool place) {
74   assert(w != 0);
75
76   if (place) placeWindow(w);
77
78   w->setWorkspace(id);
79   w->setWindowNumber(windowList.size());
80
81   stackingList.push_front(w);
82   windowList.push_back(w);
83
84   clientmenu->insert(w->getTitle());
85   clientmenu->update();
86
87   screen->updateNetizenWindowAdd(w->getClientWindow(), id);
88
89   raiseWindow(w);
90 }
91
92
93 unsigned int Workspace::removeWindow(BlackboxWindow *w) {
94   assert(w != 0);
95
96   stackingList.remove(w);
97
98   if (w->isFocused() && ! screen->getBlackbox()->doShutdown()) {
99     BlackboxWindow *newfocus = 0;
100     if (w->isTransient())
101       newfocus = w->getTransientFor();
102     if (! newfocus && ! stackingList.empty())
103       newfocus = stackingList.front();
104     if (! newfocus || ! newfocus->setInputFocus())
105       screen->getBlackbox()->setFocusedWindow(0);
106   }
107
108   if (lastfocus == w)
109     lastfocus = (BlackboxWindow *) 0;
110
111   windowList.remove(w);
112   clientmenu->remove(w->getWindowNumber());
113   clientmenu->update();
114
115   screen->updateNetizenWindowDel(w->getClientWindow());
116
117   BlackboxWindowList::iterator it = windowList.begin();
118   const BlackboxWindowList::iterator end = windowList.end();
119   unsigned int i = 0;
120   for (; it != end; ++it, ++i)
121     (*it)->setWindowNumber(i);
122
123   if (i == 0)
124     cascade_x = cascade_y = 32;
125
126   return i;
127 }
128
129
130 void Workspace::showAll(void) {
131   std::for_each(stackingList.begin(), stackingList.end(),
132                 std::mem_fun(&BlackboxWindow::show));
133 }
134
135
136 void Workspace::hideAll(void) {
137   // withdraw in reverse order to minimize the number of Expose events
138
139   BlackboxWindowList lst(stackingList.rbegin(), stackingList.rend());
140
141   BlackboxWindowList::iterator it = lst.begin();
142   const BlackboxWindowList::iterator end = lst.end();
143   for (; it != end; ++it) {
144     BlackboxWindow *bw = *it;
145     if (! bw->isStuck())
146       bw->withdraw();
147   }
148 }
149
150
151 void Workspace::removeAll(void) {
152   while (! windowList.empty())
153     windowList.front()->iconify();
154 }
155
156
157 /*
158  * returns the number of transients for win, plus the number of transients
159  * associated with each transient of win
160  */
161 static int countTransients(const BlackboxWindow * const win) {
162   int ret = win->getTransients().size();
163   if (ret > 0) {
164     BlackboxWindowList::const_iterator it, end = win->getTransients().end();
165     for (it = win->getTransients().begin(); it != end; ++it) {
166       ret += countTransients(*it);
167     }
168   }
169   return ret;
170 }
171
172
173 /*
174  * puts the transients of win into the stack. windows are stacked above
175  * the window before it in the stackvector being iterated, meaning
176  * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
177  * stack[1], etc...
178  */
179 void Workspace::raiseTransients(const BlackboxWindow * const win,
180                                 StackVector::iterator &stack) {
181   if (win->getTransients().size() == 0) return; // nothing to do
182
183   // put win's transients in the stack
184   BlackboxWindowList::const_iterator it, end = win->getTransients().end();
185   for (it = win->getTransients().begin(); it != end; ++it) {
186     *stack++ = (*it)->getFrameWindow();
187     screen->updateNetizenWindowRaise((*it)->getClientWindow());
188
189     if (! (*it)->isIconic()) {
190       Workspace *wkspc = screen->getWorkspace((*it)->getWorkspaceNumber());
191       wkspc->stackingList.remove((*it));
192       wkspc->stackingList.push_front((*it));
193     }
194   }
195
196   // put transients of win's transients in the stack
197   for (it = win->getTransients().begin(); it != end; ++it) {
198     raiseTransients(*it, stack);
199   }
200 }
201
202
203 void Workspace::lowerTransients(const BlackboxWindow * const win,
204                                 StackVector::iterator &stack) {
205   if (win->getTransients().size() == 0) return; // nothing to do
206
207   // put transients of win's transients in the stack
208   BlackboxWindowList::const_reverse_iterator it,
209     end = win->getTransients().rend();
210   for (it = win->getTransients().rbegin(); it != end; ++it) {
211     lowerTransients(*it, stack);
212   }
213
214   // put win's transients in the stack
215   for (it = win->getTransients().rbegin(); it != end; ++it) {
216     *stack++ = (*it)->getFrameWindow();
217     screen->updateNetizenWindowLower((*it)->getClientWindow());
218
219     if (! (*it)->isIconic()) {
220       Workspace *wkspc = screen->getWorkspace((*it)->getWorkspaceNumber());
221       wkspc->stackingList.remove((*it));
222       wkspc->stackingList.push_back((*it));
223     }
224   }
225
226 }
227
228
229 void Workspace::raiseWindow(BlackboxWindow *w) {
230   BlackboxWindow *win = w;
231
232   // walk up the transient_for's to the window that is not a transient
233   while (win->isTransient()) {
234     if (! win->getTransientFor()) break;
235     win = win->getTransientFor();
236   }
237
238   // get the total window count (win and all transients)
239   unsigned int i = 1 + countTransients(win);
240
241   // stack the window with all transients above
242   StackVector stack_vector(i);
243   StackVector::iterator stack = stack_vector.begin();
244
245   *(stack++) = win->getFrameWindow();
246   screen->updateNetizenWindowRaise(win->getClientWindow());
247   if (! win->isIconic()) {
248     Workspace *wkspc = screen->getWorkspace(win->getWorkspaceNumber());
249     wkspc->stackingList.remove(win);
250     wkspc->stackingList.push_front(win);
251   }
252
253   raiseTransients(win, stack);
254
255   screen->raiseWindows(&stack_vector[0], stack_vector.size());
256 }
257
258
259 void Workspace::lowerWindow(BlackboxWindow *w) {
260   BlackboxWindow *win = w;
261
262   // walk up the transient_for's to the window that is not a transient
263   while (win->isTransient()) {
264     if (! win->getTransientFor()) break;
265     win = win->getTransientFor();
266   }
267
268   // get the total window count (win and all transients)
269   unsigned int i = 1 + countTransients(win);
270
271   // stack the window with all transients above
272   StackVector stack_vector(i);
273   StackVector::iterator stack = stack_vector.begin();
274
275   lowerTransients(win, stack);
276
277   *(stack++) = win->getFrameWindow();
278   screen->updateNetizenWindowLower(win->getClientWindow());
279   if (! win->isIconic()) {
280     Workspace *wkspc = screen->getWorkspace(win->getWorkspaceNumber());
281     wkspc->stackingList.remove(win);
282     wkspc->stackingList.push_back(win);
283   }
284
285   XLowerWindow(screen->getBaseDisplay()->getXDisplay(), stack_vector.front());
286   XRestackWindows(screen->getBaseDisplay()->getXDisplay(),
287                   &stack_vector[0], stack_vector.size());
288 }
289
290
291 void Workspace::reconfigure(void) {
292   clientmenu->reconfigure();
293   std::for_each(windowList.begin(), windowList.end(),
294                 std::mem_fun(&BlackboxWindow::reconfigure));
295 }
296
297
298 void Workspace::updateFocusModel(void) {
299   std::for_each(windowList.begin(), windowList.end(),
300                 std::mem_fun(&BlackboxWindow::updateFocusModel));
301 }
302
303
304 BlackboxWindow *Workspace::getWindow(unsigned int index) {
305   if (index < windowList.size()) {
306     BlackboxWindowList::iterator it = windowList.begin();
307     for(; index > 0; --index, ++it); /* increment to index */
308     return *it;
309   }
310   return 0;
311 }
312
313
314 BlackboxWindow*
315 Workspace::getNextWindowInList(BlackboxWindow *w) {
316   BlackboxWindowList::iterator it = std::find(windowList.begin(),
317                                               windowList.end(),
318                                               w);
319   assert(it != windowList.end());   // window must be in list
320   ++it;                             // next window
321   if (it == windowList.end())
322     return windowList.front();      // if we walked off the end, wrap around
323
324   return *it;
325 }
326
327
328 BlackboxWindow* Workspace::getPrevWindowInList(BlackboxWindow *w) {
329   BlackboxWindowList::iterator it = std::find(windowList.begin(),
330                                               windowList.end(),
331                                               w);
332   assert(it != windowList.end()); // window must be in list
333   if (it == windowList.begin())
334     return windowList.back();     // if we walked of the front, wrap around
335
336   return *(--it);
337 }
338
339
340 BlackboxWindow* Workspace::getTopWindowOnStack(void) const {
341   return stackingList.front();
342 }
343
344
345 void Workspace::sendWindowList(Netizen &n) {
346   BlackboxWindowList::iterator it = windowList.begin(),
347     end = windowList.end();
348   for(; it != end; ++it)
349     n.sendWindowAdd((*it)->getClientWindow(), getID());
350 }
351
352
353 unsigned int Workspace::getCount(void) const {
354   return windowList.size();
355 }
356
357
358 bool Workspace::isCurrent(void) const {
359   return (id == screen->getCurrentWorkspaceID());
360 }
361
362
363 bool Workspace::isLastWindow(const BlackboxWindow* const w) const {
364   return (w == windowList.back());
365 }
366
367 void Workspace::setCurrent(void) {
368   screen->changeWorkspaceID(id);
369 }
370
371
372 void Workspace::setName(const string& new_name) {
373   if (! new_name.empty()) {
374     name = new_name;
375   } else {
376     string tmp =i18n(WorkspaceSet, WorkspaceDefaultNameFormat, "Workspace %d");
377     assert(tmp.length() < 32);
378     char default_name[32];
379     sprintf(default_name, tmp.c_str(), id + 1);
380     name = default_name;
381   }
382
383   clientmenu->setLabel(name);
384   clientmenu->update();
385 }
386
387
388 /*
389  * Calculate free space available for window placement.
390  */
391 typedef std::vector<Rect> rectList;
392
393 static rectList calcSpace(const Rect &win, const rectList &spaces) {
394   Rect isect, extra;
395   rectList result;
396   rectList::const_iterator siter, end = spaces.end();
397   for (siter = spaces.begin(); siter != end; ++siter) {
398     const Rect &curr = *siter;
399
400     if(! win.intersects(curr)) {
401       result.push_back(curr);
402       continue;
403     }
404
405     /* Use an intersection of win and curr to determine the space around
406      * curr that we can use.
407      *
408      * NOTE: the spaces calculated can overlap.
409      */
410     isect = curr & win;
411
412     // left
413     extra.setCoords(curr.left(), curr.top(),
414                     isect.left() - 1, curr.bottom());
415     if (extra.valid()) result.push_back(extra);
416
417     // top
418     extra.setCoords(curr.left(), curr.top(),
419                     curr.right(), isect.top() - 1);
420     if (extra.valid()) result.push_back(extra);
421
422     // right
423     extra.setCoords(isect.right() + 1, curr.top(),
424                     curr.right(), curr.bottom());
425     if (extra.valid()) result.push_back(extra);
426
427     // bottom
428     extra.setCoords(curr.left(), isect.bottom() + 1,
429                     curr.right(), curr.bottom());
430     if (extra.valid()) result.push_back(extra);
431   }
432   return result;
433 }
434
435
436 static bool rowRLBT(const Rect &first, const Rect &second) {
437   if (first.bottom() == second.bottom())
438     return first.right() > second.right();
439   return first.bottom() > second.bottom();
440 }
441
442 static bool rowRLTB(const Rect &first, const Rect &second) {
443   if (first.y() == second.y())
444     return first.right() > second.right();
445   return first.y() < second.y();
446 }
447
448 static bool rowLRBT(const Rect &first, const Rect &second) {
449   if (first.bottom() == second.bottom())
450     return first.x() < second.x();
451   return first.bottom() > second.bottom();
452 }
453
454 static bool rowLRTB(const Rect &first, const Rect &second) {
455   if (first.y() == second.y())
456     return first.x() < second.x();
457   return first.y() < second.y();
458 }
459
460 static bool colLRTB(const Rect &first, const Rect &second) {
461   if (first.x() == second.x())
462     return first.y() < second.y();
463   return first.x() < second.x();
464 }
465
466 static bool colLRBT(const Rect &first, const Rect &second) {
467   if (first.x() == second.x())
468     return first.bottom() > second.bottom();
469   return first.x() < second.x();
470 }
471
472 static bool colRLTB(const Rect &first, const Rect &second) {
473   if (first.right() == second.right())
474     return first.y() < second.y();
475   return first.right() > second.right();
476 }
477
478 static bool colRLBT(const Rect &first, const Rect &second) {
479   if (first.right() == second.right())
480     return first.bottom() > second.bottom();
481   return first.right() > second.right();
482 }
483
484
485 bool Workspace::smartPlacement(Rect& win, const Rect& availableArea) {
486   rectList spaces;
487   spaces.push_back(availableArea); //initially the entire screen is free
488
489   //Find Free Spaces
490   BlackboxWindowList::iterator wit = windowList.begin(),
491                                end = windowList.end();
492   Rect tmp;
493   for (; wit != end; ++wit) {
494     const BlackboxWindow* const curr = *wit;
495     tmp.setRect(curr->frameRect().x(), curr->frameRect().y(),
496                 curr->frameRect().width() + screen->getBorderWidth(),
497                 curr->frameRect().height() + screen->getBorderWidth());
498
499     spaces = calcSpace(tmp, spaces);
500   }
501
502   if (screen->getPlacementPolicy() == BScreen::RowSmartPlacement) {
503     if(screen->getRowPlacementDirection() == BScreen::LeftRight) {
504       if(screen->getColPlacementDirection() == BScreen::TopBottom)
505         sort(spaces.begin(), spaces.end(), rowLRTB);
506       else
507         sort(spaces.begin(), spaces.end(), rowLRBT);
508     } else {
509       if(screen->getColPlacementDirection() == BScreen::TopBottom)
510         sort(spaces.begin(), spaces.end(), rowRLTB);
511       else
512         sort(spaces.begin(), spaces.end(), rowRLBT);
513     }
514   } else {
515     if(screen->getColPlacementDirection() == BScreen::TopBottom) {
516       if(screen->getRowPlacementDirection() == BScreen::LeftRight)
517         sort(spaces.begin(), spaces.end(), colLRTB);
518       else
519         sort(spaces.begin(), spaces.end(), colRLTB);
520     } else {
521       if(screen->getRowPlacementDirection() == BScreen::LeftRight)
522         sort(spaces.begin(), spaces.end(), colLRBT);
523       else
524         sort(spaces.begin(), spaces.end(), colRLBT);
525     }
526   }
527
528   rectList::const_iterator sit = spaces.begin(), spaces_end = spaces.end();
529   for(; sit != spaces_end; ++sit) {
530     if (sit->width() >= win.width() && sit->height() >= win.height())
531       break;
532   }
533
534   if (sit == spaces_end)
535     return False;
536
537   //set new position based on the empty space found
538   const Rect& where = *sit;
539   win.setX(where.x());
540   win.setY(where.y());
541
542   // adjust the location() based on left/right and top/bottom placement
543   if (screen->getPlacementPolicy() == BScreen::RowSmartPlacement) {
544     if (screen->getRowPlacementDirection() == BScreen::RightLeft)
545       win.setX(where.right() - win.width());
546     if (screen->getColPlacementDirection() == BScreen::BottomTop)
547       win.setY(where.bottom() - win.height());
548   } else {
549     if (screen->getColPlacementDirection() == BScreen::BottomTop)
550       win.setY(win.y() + where.height() - win.height());
551     if (screen->getRowPlacementDirection() == BScreen::RightLeft)
552       win.setX(win.x() + where.width() - win.width());
553   }
554   return True;
555 }
556
557
558 bool Workspace::cascadePlacement(Rect &win, const Rect &availableArea) {
559   if ((cascade_x > static_cast<signed>(availableArea.width() / 2)) ||
560       (cascade_y > static_cast<signed>(availableArea.height() / 2)))
561     cascade_x = cascade_y = 32;
562
563   if (cascade_x == 32) {
564     cascade_x += availableArea.x();
565     cascade_y += availableArea.y();
566   }
567
568   win.setPos(cascade_x, cascade_y);
569
570   return True;
571 }
572
573
574 void Workspace::placeWindow(BlackboxWindow *win) {
575   Rect availableArea(screen->availableArea()),
576     new_win(availableArea.x(), availableArea.y(),
577             win->frameRect().width(), win->frameRect().height());
578   bool placed = False;
579
580   switch (screen->getPlacementPolicy()) {
581   case BScreen::RowSmartPlacement:
582   case BScreen::ColSmartPlacement:
583     placed = smartPlacement(new_win, availableArea);
584     break;
585   default:
586     break; // handled below
587   } // switch
588
589   if (placed == False) {
590     cascadePlacement(new_win, availableArea);
591     cascade_x += win->getTitleHeight() + (screen->getBorderWidth() * 2);
592     cascade_y += win->getTitleHeight() + (screen->getBorderWidth() * 2);
593   }
594
595   if (new_win.right() > availableArea.right())
596     new_win.setX(availableArea.left());
597   if (new_win.bottom() > availableArea.bottom())
598     new_win.setY(availableArea.top());
599   win->configure(new_win.x(), new_win.y(), new_win.width(), new_win.height());
600 }