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