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