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)
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:
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
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.
25 # include "../config.h"
26 #endif // HAVE_CONFIG_H
30 #include <X11/Xatom.h>
34 #endif // HAVE_STDIO_H
38 #endif // HAVE_STRING_H
49 #include "blackbox.hh"
50 #include "Clientmenu.hh"
56 #include "Workspace.hh"
57 #include "Windowmenu.hh"
60 Workspace::Workspace(BScreen
*scrn
, unsigned int i
) {
63 cascade_x
= cascade_y
= 32;
67 clientmenu
= new Clientmenu(this);
69 lastfocus
= (BlackboxWindow
*) 0;
71 setName(screen
->getNameOfWorkspace(id
));
75 void Workspace::addWindow(BlackboxWindow
*w
, bool place
) {
78 if (place
) placeWindow(w
);
81 w
->setWindowNumber(windowList
.size());
83 stackingList
.push_front(w
);
84 windowList
.push_back(w
);
86 clientmenu
->insert(w
->getTitle());
89 screen
->updateNetizenWindowAdd(w
->getClientWindow(), id
);
95 unsigned int Workspace::removeWindow(BlackboxWindow
*w
) {
98 stackingList
.remove(w
);
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);
111 lastfocus
= (BlackboxWindow
*) 0;
113 windowList
.remove(w
);
114 clientmenu
->remove(w
->getWindowNumber());
115 clientmenu
->update();
117 screen
->updateNetizenWindowDel(w
->getClientWindow());
119 BlackboxWindowList::iterator it
= windowList
.begin();
120 const BlackboxWindowList::iterator end
= windowList
.end();
122 for (; it
!= end
; ++it
, ++i
)
123 (*it
)->setWindowNumber(i
);
126 cascade_x
= cascade_y
= 32;
132 void Workspace::showAll(void) {
133 std::for_each(stackingList
.begin(), stackingList
.end(),
134 std::mem_fun(&BlackboxWindow::show
));
138 void Workspace::hideAll(void) {
139 // withdraw in reverse order to minimize the number of Expose events
141 BlackboxWindowList
lst(stackingList
.rbegin(), stackingList
.rend());
143 BlackboxWindowList::iterator it
= lst
.begin();
144 const BlackboxWindowList::iterator end
= lst
.end();
145 for (; it
!= end
; ++it
) {
146 BlackboxWindow
*bw
= *it
;
153 void Workspace::removeAll(void) {
154 while (! windowList
.empty())
155 windowList
.front()->iconify();
160 * returns the number of transients for win, plus the number of transients
161 * associated with each transient of win
163 static int countTransients(const BlackboxWindow
* const win
) {
164 int ret
= win
->getTransients().size();
166 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
167 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
168 ret
+= countTransients(*it
);
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
181 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
182 StackVector::iterator
&stack
) {
183 if (win
->getTransients().size() == 0) return; // nothing to do
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());
191 if (! (*it
)->isIconic()) {
192 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
193 wkspc
->stackingList
.remove((*it
));
194 wkspc
->stackingList
.push_front((*it
));
198 // put transients of win's transients in the stack
199 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
200 raiseTransients(*it
, stack
);
205 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
206 StackVector::iterator
&stack
) {
207 if (win
->getTransients().size() == 0) return; // nothing to do
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
);
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());
221 if (! (*it
)->isIconic()) {
222 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
223 wkspc
->stackingList
.remove((*it
));
224 wkspc
->stackingList
.push_back((*it
));
230 void Workspace::raiseWindow(BlackboxWindow
*w
) {
231 BlackboxWindow
*win
= w
;
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();
239 // get the total window count (win and all transients)
240 unsigned int i
= 1 + countTransients(win
);
242 // stack the window with all transients above
243 StackVector
stack_vector(i
);
244 StackVector::iterator stack
= stack_vector
.begin();
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
);
254 raiseTransients(win
, stack
);
256 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
260 void Workspace::lowerWindow(BlackboxWindow
*w
) {
261 BlackboxWindow
*win
= w
;
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();
269 // get the total window count (win and all transients)
270 unsigned int i
= 1 + countTransients(win
);
272 // stack the window with all transients above
273 StackVector
stack_vector(i
);
274 StackVector::iterator stack
= stack_vector
.begin();
276 lowerTransients(win
, stack
);
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
);
286 XLowerWindow(screen
->getBaseDisplay()->getXDisplay(), stack_vector
.front());
287 XRestackWindows(screen
->getBaseDisplay()->getXDisplay(),
288 &stack_vector
[0], stack_vector
.size());
292 void Workspace::reconfigure(void) {
293 clientmenu
->reconfigure();
294 std::for_each(windowList
.begin(), windowList
.end(),
295 std::mem_fun(&BlackboxWindow::reconfigure
));
299 void Workspace::updateFocusModel(void) {
300 std::for_each(windowList
.begin(), windowList
.end(),
301 std::mem_fun(&BlackboxWindow::updateFocusModel
));
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 */
316 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
317 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
320 assert(it
!= windowList
.end()); // window must be in list
322 if (it
== windowList
.end())
323 return windowList
.front(); // if we walked off the end, wrap around
329 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
330 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
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
341 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
342 return stackingList
.front();
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());
354 unsigned int Workspace::getCount(void) const {
355 return windowList
.size();
359 bool Workspace::isCurrent(void) const {
360 return (id
== screen
->getCurrentWorkspaceID());
364 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
365 return (w
== windowList
.back());
368 void Workspace::setCurrent(void) {
369 screen
->changeWorkspaceID(id
);
373 void Workspace::setName(const string
& new_name
) {
374 if (! new_name
.empty()) {
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);
384 clientmenu
->setLabel(name
);
385 clientmenu
->update();
386 screen
->saveWorkspaceNames();
391 * Calculate free space available for window placement.
393 typedef std::vector
<Rect
> rectList
;
395 static rectList
calcSpace(const Rect
&win
, const rectList
&spaces
) {
398 rectList::const_iterator siter
, end
= spaces
.end();
399 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
400 const Rect
&curr
= *siter
;
402 if(! win
.intersects(curr
)) {
403 result
.push_back(curr
);
407 /* Use an intersection of win and curr to determine the space around
408 * curr that we can use.
410 * NOTE: the spaces calculated can overlap.
415 extra
.setCoords(curr
.left(), curr
.top(),
416 isect
.left() - 1, curr
.bottom());
417 if (extra
.valid()) result
.push_back(extra
);
420 extra
.setCoords(curr
.left(), curr
.top(),
421 curr
.right(), isect
.top() - 1);
422 if (extra
.valid()) result
.push_back(extra
);
425 extra
.setCoords(isect
.right() + 1, curr
.top(),
426 curr
.right(), curr
.bottom());
427 if (extra
.valid()) result
.push_back(extra
);
430 extra
.setCoords(curr
.left(), isect
.bottom() + 1,
431 curr
.right(), curr
.bottom());
432 if (extra
.valid()) result
.push_back(extra
);
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();
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();
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();
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();
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();
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();
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();
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();
487 bool Workspace::smartPlacement(Rect
& win
, const Rect
& availableArea
) {
489 spaces
.push_back(availableArea
); //initially the entire screen is free
492 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
493 end
= windowList
.end();
495 for (; wit
!= end
; ++wit
) {
496 const BlackboxWindow
* const curr
= *wit
;
498 if (curr
->isShaded()) continue;
500 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
501 curr
->frameRect().width() + screen
->getBorderWidth(),
502 curr
->frameRect().height() + screen
->getBorderWidth());
504 spaces
= calcSpace(tmp
, spaces
);
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
);
512 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
514 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
515 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
517 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
520 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
521 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
522 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
524 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
526 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
527 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
529 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
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())
539 if (sit
== spaces_end
)
542 //set new position based on the empty space found
543 const Rect
& where
= *sit
;
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());
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());
563 bool Workspace::underMousePlacement(Rect
&win
, const Rect
&availableArea
) {
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;
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();
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;
593 if (cascade_x
== 32) {
594 cascade_x
+= availableArea
.x();
595 cascade_y
+= availableArea
.y();
598 win
.setPos(cascade_x
, cascade_y
);
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());
610 switch (screen
->getPlacementPolicy()) {
611 case BScreen::RowSmartPlacement
:
612 case BScreen::ColSmartPlacement
:
613 placed
= smartPlacement(new_win
, availableArea
);
615 case BScreen::UnderMousePlacement
:
616 placed
= underMousePlacement(new_win
, availableArea
);
618 break; // handled below
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);
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());