]> icculus.org git repositories - dana/openbox.git/blob - src/Workspace.cc
sync with blackbox-cvs
[dana/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 <assert.h>
42
43 #include <functional>
44 #include <string>
45
46 using std::string;
47
48 #include "i18n.hh"
49 #include "blackbox.hh"
50 #include "Clientmenu.hh"
51 #include "Netizen.hh"
52 #include "Screen.hh"
53 #include "Toolbar.hh"
54 #include "Util.hh"
55 #include "Window.hh"
56 #include "Workspace.hh"
57 #include "Windowmenu.hh"
58
59
60 Workspace::Workspace(BScreen *scrn, unsigned int i) {
61   screen = scrn;
62
63   cascade_x = cascade_y = 32;
64
65   id = i;
66
67   clientmenu = new Clientmenu(this);
68
69   lastfocus = (BlackboxWindow *) 0;
70
71   setName(screen->getNameOfWorkspace(id));
72 }
73
74
75 void Workspace::addWindow(BlackboxWindow *w, bool place) {
76   assert(w != 0);
77
78   if (place) placeWindow(w);
79
80   w->setWorkspace(id);
81   w->setWindowNumber(windowList.size());
82
83   stackingList.push_front(w);
84   windowList.push_back(w);
85
86   clientmenu->insert(w->getTitle());
87   clientmenu->update();
88
89   screen->updateNetizenWindowAdd(w->getClientWindow(), id);
90
91   raiseWindow(w);
92 }
93
94
95 unsigned int Workspace::removeWindow(BlackboxWindow *w) {
96   assert(w != 0);
97
98   stackingList.remove(w);
99
100   // pass focus to the next appropriate window
101   if ((w->isFocused() || w == lastfocus) &&
102       ! screen->getBlackbox()->doShutdown()) {
103     BlackboxWindow *newfocus = 0;
104     if (w->isTransient())
105       newfocus = w->getTransientFor();
106     if (! newfocus && ! stackingList.empty())
107       newfocus = stackingList.front();
108
109     assert(newfocus != w);  // this would be very wrong.
110
111     if (id == screen->getCurrentWorkspaceID()) {
112       /*
113         if the window is on the visible workspace, then try focus it, and fall
114         back to the default focus target if the window won't focus.
115       */
116       if (! newfocus || ! newfocus->setInputFocus())
117         screen->getBlackbox()->setFocusedWindow(0);
118     } else if (lastfocus == w) {
119       /*
120         If this workspace is not the current one do not assume that
121         w == lastfocus. If a sticky window is removed on a workspace other
122         than where it originated, it will fire the removeWindow on a
123         non-visible workspace.
124       */
125       
126       /*
127         If the window isn't on the visible workspace, don't focus the new one,
128         just mark it to be focused when the workspace comes into view.
129       */
130       setLastFocusedWindow(newfocus);
131     }
132   }
133
134   windowList.remove(w);
135   clientmenu->remove(w->getWindowNumber());
136   clientmenu->update();
137
138   screen->updateNetizenWindowDel(w->getClientWindow());
139
140   BlackboxWindowList::iterator it = windowList.begin();
141   const BlackboxWindowList::iterator end = windowList.end();
142   unsigned int i = 0;
143   for (; it != end; ++it, ++i)
144     (*it)->setWindowNumber(i);
145
146   if (i == 0)
147     cascade_x = cascade_y = 32;
148
149   return i;
150 }
151
152
153 void Workspace::showAll(void) {
154   std::for_each(stackingList.begin(), stackingList.end(),
155                 std::mem_fun(&BlackboxWindow::show));
156 }
157
158
159 void Workspace::hideAll(void) {
160   // withdraw in reverse order to minimize the number of Expose events
161
162   BlackboxWindowList lst(stackingList.rbegin(), stackingList.rend());
163
164   BlackboxWindowList::iterator it = lst.begin();
165   const BlackboxWindowList::iterator end = lst.end();
166   for (; it != end; ++it) {
167     BlackboxWindow *bw = *it;
168     if (! bw->isStuck())
169       bw->withdraw();
170   }
171 }
172
173
174 void Workspace::removeAll(void) {
175   while (! windowList.empty())
176     windowList.front()->iconify();
177 }
178
179
180 /*
181  * returns the number of transients for win, plus the number of transients
182  * associated with each transient of win
183  */
184 static int countTransients(const BlackboxWindow * const win) {
185   int ret = win->getTransients().size();
186   if (ret > 0) {
187     BlackboxWindowList::const_iterator it, end = win->getTransients().end();
188     for (it = win->getTransients().begin(); it != end; ++it) {
189       ret += countTransients(*it);
190     }
191   }
192   return ret;
193 }
194
195
196 /*
197  * puts the transients of win into the stack. windows are stacked above
198  * the window before it in the stackvector being iterated, meaning
199  * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
200  * stack[1], etc...
201  */
202 void Workspace::raiseTransients(const BlackboxWindow * const win,
203                                 StackVector::iterator &stack) {
204   if (win->getTransients().size() == 0) return; // nothing to do
205
206   // put win's transients in the stack
207   BlackboxWindowList::const_iterator it, end = win->getTransients().end();
208   for (it = win->getTransients().begin(); it != end; ++it) {
209     *stack++ = (*it)->getFrameWindow();
210     screen->updateNetizenWindowRaise((*it)->getClientWindow());
211
212     if (! (*it)->isIconic()) {
213       Workspace *wkspc = screen->getWorkspace((*it)->getWorkspaceNumber());
214       wkspc->stackingList.remove((*it));
215       wkspc->stackingList.push_front((*it));
216     }
217   }
218
219   // put transients of win's transients in the stack
220   for (it = win->getTransients().begin(); it != end; ++it) {
221     raiseTransients(*it, stack);
222   }
223 }
224
225
226 void Workspace::lowerTransients(const BlackboxWindow * const win,
227                                 StackVector::iterator &stack) {
228   if (win->getTransients().size() == 0) return; // nothing to do
229
230   // put transients of win's transients in the stack
231   BlackboxWindowList::const_reverse_iterator it,
232     end = win->getTransients().rend();
233   for (it = win->getTransients().rbegin(); it != end; ++it) {
234     lowerTransients(*it, stack);
235   }
236
237   // put win's transients in the stack
238   for (it = win->getTransients().rbegin(); it != end; ++it) {
239     *stack++ = (*it)->getFrameWindow();
240     screen->updateNetizenWindowLower((*it)->getClientWindow());
241
242     if (! (*it)->isIconic()) {
243       Workspace *wkspc = screen->getWorkspace((*it)->getWorkspaceNumber());
244       wkspc->stackingList.remove((*it));
245       wkspc->stackingList.push_back((*it));
246     }
247   }
248 }
249
250
251 void Workspace::raiseWindow(BlackboxWindow *w) {
252   BlackboxWindow *win = w;
253
254   // walk up the transient_for's to the window that is not a transient
255   while (win->isTransient()) {
256     if (! win->getTransientFor()) break;
257     win = win->getTransientFor();
258   }
259
260   // get the total window count (win and all transients)
261   unsigned int i = 1 + countTransients(win);
262
263   // stack the window with all transients above
264   StackVector stack_vector(i);
265   StackVector::iterator stack = stack_vector.begin();
266
267   *(stack++) = win->getFrameWindow();
268   screen->updateNetizenWindowRaise(win->getClientWindow());
269   if (! win->isIconic()) {
270     Workspace *wkspc = screen->getWorkspace(win->getWorkspaceNumber());
271     wkspc->stackingList.remove(win);
272     wkspc->stackingList.push_front(win);
273   }
274
275   raiseTransients(win, stack);
276
277   screen->raiseWindows(&stack_vector[0], stack_vector.size());
278 }
279
280
281 void Workspace::lowerWindow(BlackboxWindow *w) {
282   BlackboxWindow *win = w;
283
284   // walk up the transient_for's to the window that is not a transient
285   while (win->isTransient()) {
286     if (! win->getTransientFor()) break;
287     win = win->getTransientFor();
288   }
289
290   // get the total window count (win and all transients)
291   unsigned int i = 1 + countTransients(win);
292
293   // stack the window with all transients above
294   StackVector stack_vector(i);
295   StackVector::iterator stack = stack_vector.begin();
296
297   lowerTransients(win, stack);
298
299   *(stack++) = win->getFrameWindow();
300   screen->updateNetizenWindowLower(win->getClientWindow());
301   if (! win->isIconic()) {
302     Workspace *wkspc = screen->getWorkspace(win->getWorkspaceNumber());
303     wkspc->stackingList.remove(win);
304     wkspc->stackingList.push_back(win);
305   }
306
307   XLowerWindow(screen->getBaseDisplay()->getXDisplay(), stack_vector.front());
308   XRestackWindows(screen->getBaseDisplay()->getXDisplay(),
309                   &stack_vector[0], stack_vector.size());
310 }
311
312
313 void Workspace::reconfigure(void) {
314   clientmenu->reconfigure();
315   std::for_each(windowList.begin(), windowList.end(),
316                 std::mem_fun(&BlackboxWindow::reconfigure));
317 }
318
319
320 void Workspace::updateFocusModel(void) {
321   std::for_each(windowList.begin(), windowList.end(),
322                 std::mem_fun(&BlackboxWindow::updateFocusModel));
323 }
324
325
326 BlackboxWindow *Workspace::getWindow(unsigned int index) {
327   if (index < windowList.size()) {
328     BlackboxWindowList::iterator it = windowList.begin();
329     for(; index > 0; --index, ++it); /* increment to index */
330     return *it;
331   }
332   return 0;
333 }
334
335
336 BlackboxWindow*
337 Workspace::getNextWindowInList(BlackboxWindow *w) {
338   BlackboxWindowList::iterator it = std::find(windowList.begin(),
339                                               windowList.end(),
340                                               w);
341   assert(it != windowList.end());   // window must be in list
342   ++it;                             // next window
343   if (it == windowList.end())
344     return windowList.front();      // if we walked off the end, wrap around
345
346   return *it;
347 }
348
349
350 BlackboxWindow* Workspace::getPrevWindowInList(BlackboxWindow *w) {
351   BlackboxWindowList::iterator it = std::find(windowList.begin(),
352                                               windowList.end(),
353                                               w);
354   assert(it != windowList.end()); // window must be in list
355   if (it == windowList.begin())
356     return windowList.back();     // if we walked of the front, wrap around
357
358   return *(--it);
359 }
360
361
362 BlackboxWindow* Workspace::getTopWindowOnStack(void) const {
363   return stackingList.front();
364 }
365
366
367 void Workspace::sendWindowList(Netizen &n) {
368   BlackboxWindowList::iterator it = windowList.begin(),
369     end = windowList.end();
370   for(; it != end; ++it)
371     n.sendWindowAdd((*it)->getClientWindow(), getID());
372 }
373
374
375 unsigned int Workspace::getCount(void) const {
376   return windowList.size();
377 }
378
379
380 bool Workspace::isCurrent(void) const {
381   return (id == screen->getCurrentWorkspaceID());
382 }
383
384
385 bool Workspace::isLastWindow(const BlackboxWindow* const w) const {
386   return (w == windowList.back());
387 }
388
389 void Workspace::setCurrent(void) {
390   screen->changeWorkspaceID(id);
391 }
392
393
394 void Workspace::setName(const string& new_name) {
395   if (! new_name.empty()) {
396     name = new_name;
397   } else {
398     string tmp =i18n(WorkspaceSet, WorkspaceDefaultNameFormat, "Workspace %d");
399     assert(tmp.length() < 32);
400     char default_name[32];
401     sprintf(default_name, tmp.c_str(), id + 1);
402     name = default_name;
403   }
404
405   clientmenu->setLabel(name);
406   clientmenu->update();
407   screen->saveWorkspaceNames();
408 }
409
410
411 /*
412  * Calculate free space available for window placement.
413  */
414 typedef std::vector<Rect> rectList;
415
416 static rectList calcSpace(const Rect &win, const rectList &spaces) {
417   Rect isect, extra;
418   rectList result;
419   rectList::const_iterator siter, end = spaces.end();
420   for (siter = spaces.begin(); siter != end; ++siter) {
421     const Rect &curr = *siter;
422
423     if(! win.intersects(curr)) {
424       result.push_back(curr);
425       continue;
426     }
427
428     /* Use an intersection of win and curr to determine the space around
429      * curr that we can use.
430      *
431      * NOTE: the spaces calculated can overlap.
432      */
433     isect = curr & win;
434
435     // left
436     extra.setCoords(curr.left(), curr.top(),
437                     isect.left() - 1, curr.bottom());
438     if (extra.valid()) result.push_back(extra);
439
440     // top
441     extra.setCoords(curr.left(), curr.top(),
442                     curr.right(), isect.top() - 1);
443     if (extra.valid()) result.push_back(extra);
444
445     // right
446     extra.setCoords(isect.right() + 1, curr.top(),
447                     curr.right(), curr.bottom());
448     if (extra.valid()) result.push_back(extra);
449
450     // bottom
451     extra.setCoords(curr.left(), isect.bottom() + 1,
452                     curr.right(), curr.bottom());
453     if (extra.valid()) result.push_back(extra);
454   }
455   return result;
456 }
457
458
459 static bool rowRLBT(const Rect &first, const Rect &second) {
460   if (first.bottom() == second.bottom())
461     return first.right() > second.right();
462   return first.bottom() > second.bottom();
463 }
464
465 static bool rowRLTB(const Rect &first, const Rect &second) {
466   if (first.y() == second.y())
467     return first.right() > second.right();
468   return first.y() < second.y();
469 }
470
471 static bool rowLRBT(const Rect &first, const Rect &second) {
472   if (first.bottom() == second.bottom())
473     return first.x() < second.x();
474   return first.bottom() > second.bottom();
475 }
476
477 static bool rowLRTB(const Rect &first, const Rect &second) {
478   if (first.y() == second.y())
479     return first.x() < second.x();
480   return first.y() < second.y();
481 }
482
483 static bool colLRTB(const Rect &first, const Rect &second) {
484   if (first.x() == second.x())
485     return first.y() < second.y();
486   return first.x() < second.x();
487 }
488
489 static bool colLRBT(const Rect &first, const Rect &second) {
490   if (first.x() == second.x())
491     return first.bottom() > second.bottom();
492   return first.x() < second.x();
493 }
494
495 static bool colRLTB(const Rect &first, const Rect &second) {
496   if (first.right() == second.right())
497     return first.y() < second.y();
498   return first.right() > second.right();
499 }
500
501 static bool colRLBT(const Rect &first, const Rect &second) {
502   if (first.right() == second.right())
503     return first.bottom() > second.bottom();
504   return first.right() > second.right();
505 }
506
507
508 bool Workspace::smartPlacement(Rect& win, const Rect& availableArea) {
509   rectList spaces;
510   spaces.push_back(availableArea); //initially the entire screen is free
511
512   //Find Free Spaces
513   BlackboxWindowList::const_iterator wit = windowList.begin(),
514     end = windowList.end();
515   Rect tmp;
516   for (; wit != end; ++wit) {
517     const BlackboxWindow* const curr = *wit;
518
519     if (curr->isShaded()) continue;
520
521     tmp.setRect(curr->frameRect().x(), curr->frameRect().y(),
522                 curr->frameRect().width() + screen->getBorderWidth(),
523                 curr->frameRect().height() + screen->getBorderWidth());
524
525     spaces = calcSpace(tmp, spaces);
526   }
527
528   if (screen->getPlacementPolicy() == BScreen::RowSmartPlacement) {
529     if(screen->getRowPlacementDirection() == BScreen::LeftRight) {
530       if(screen->getColPlacementDirection() == BScreen::TopBottom)
531         std::sort(spaces.begin(), spaces.end(), rowLRTB);
532       else
533         std::sort(spaces.begin(), spaces.end(), rowLRBT);
534     } else {
535       if(screen->getColPlacementDirection() == BScreen::TopBottom)
536         std::sort(spaces.begin(), spaces.end(), rowRLTB);
537       else
538         std::sort(spaces.begin(), spaces.end(), rowRLBT);
539     }
540   } else {
541     if(screen->getColPlacementDirection() == BScreen::TopBottom) {
542       if(screen->getRowPlacementDirection() == BScreen::LeftRight)
543         std::sort(spaces.begin(), spaces.end(), colLRTB);
544       else
545         std::sort(spaces.begin(), spaces.end(), colRLTB);
546     } else {
547       if(screen->getRowPlacementDirection() == BScreen::LeftRight)
548         std::sort(spaces.begin(), spaces.end(), colLRBT);
549       else
550         std::sort(spaces.begin(), spaces.end(), colRLBT);
551     }
552   }
553
554   rectList::const_iterator sit = spaces.begin(), spaces_end = spaces.end();
555   for(; sit != spaces_end; ++sit) {
556     if (sit->width() >= win.width() && sit->height() >= win.height())
557       break;
558   }
559
560   if (sit == spaces_end)
561     return False;
562
563   //set new position based on the empty space found
564   const Rect& where = *sit;
565   win.setX(where.x());
566   win.setY(where.y());
567
568   // adjust the location() based on left/right and top/bottom placement
569   if (screen->getPlacementPolicy() == BScreen::RowSmartPlacement) {
570     if (screen->getRowPlacementDirection() == BScreen::RightLeft)
571       win.setX(where.right() - win.width());
572     if (screen->getColPlacementDirection() == BScreen::BottomTop)
573       win.setY(where.bottom() - win.height());
574   } else {
575     if (screen->getColPlacementDirection() == BScreen::BottomTop)
576       win.setY(win.y() + where.height() - win.height());
577     if (screen->getRowPlacementDirection() == BScreen::RightLeft)
578       win.setX(win.x() + where.width() - win.width());
579   }
580   return True;
581 }
582
583
584 bool Workspace::underMousePlacement(Rect &win, const Rect &availableArea) {
585   int x, y, rx, ry;
586   Window c, r;
587   unsigned int m;
588   XQueryPointer(screen->getBlackbox()->getXDisplay(), screen->getRootWindow(),
589                 &r, &c, &rx, &ry, &x, &y, &m);
590   x = rx - win.width() / 2;
591   y = ry - win.height() / 2;
592
593   if (x < availableArea.x())
594     x = availableArea.x();
595   if (y < availableArea.y())
596     y = availableArea.y();
597   if (x + win.width() > availableArea.x() + availableArea.width())
598     x = availableArea.x() + availableArea.width() - win.width();
599   if (y + win.height() > availableArea.y() + availableArea.height())
600     y = availableArea.y() + availableArea.height() - win.height();
601
602   win.setX(x);
603   win.setY(y);
604
605   return True;
606 }
607
608
609 bool Workspace::cascadePlacement(Rect &win, const Rect &availableArea) {
610   if ((cascade_x > static_cast<signed>(availableArea.width() / 2)) ||
611       (cascade_y > static_cast<signed>(availableArea.height() / 2)))
612     cascade_x = cascade_y = 32;
613
614   if (cascade_x == 32) {
615     cascade_x += availableArea.x();
616     cascade_y += availableArea.y();
617   }
618
619   win.setPos(cascade_x, cascade_y);
620
621   return True;
622 }
623
624
625 void Workspace::placeWindow(BlackboxWindow *win) {
626   Rect availableArea(screen->availableArea()),
627     new_win(availableArea.x(), availableArea.y(),
628             win->frameRect().width(), win->frameRect().height());
629   bool placed = False;
630
631   switch (screen->getPlacementPolicy()) {
632   case BScreen::RowSmartPlacement:
633   case BScreen::ColSmartPlacement:
634     placed = smartPlacement(new_win, availableArea);
635     break;
636   case BScreen::UnderMousePlacement:
637     placed = underMousePlacement(new_win, availableArea);
638   default:
639     break; // handled below
640   } // switch
641
642   if (placed == False) {
643     cascadePlacement(new_win, availableArea);
644     cascade_x += win->getTitleHeight() + (screen->getBorderWidth() * 2);
645     cascade_y += win->getTitleHeight() + (screen->getBorderWidth() * 2);
646   }
647
648   if (new_win.right() > availableArea.right())
649     new_win.setX(availableArea.left());
650   if (new_win.bottom() > availableArea.bottom())
651     new_win.setY(availableArea.top());
652   win->configure(new_win.x(), new_win.y(), new_win.width(), new_win.height());
653 }