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 // 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();
109 assert(newfocus
!= w
); // this would be very wrong.
111 if (id
== screen
->getCurrentWorkspaceID()) {
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.
116 if (! newfocus
|| ! newfocus
->setInputFocus())
117 screen
->getBlackbox()->setFocusedWindow(0);
118 } else if (lastfocus
== w
) {
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.
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.
130 setLastFocusedWindow(newfocus
);
134 windowList
.remove(w
);
135 clientmenu
->remove(w
->getWindowNumber());
136 clientmenu
->update();
138 screen
->updateNetizenWindowDel(w
->getClientWindow());
140 BlackboxWindowList::iterator it
= windowList
.begin();
141 const BlackboxWindowList::iterator end
= windowList
.end();
143 for (; it
!= end
; ++it
, ++i
)
144 (*it
)->setWindowNumber(i
);
147 cascade_x
= cascade_y
= 32;
153 void Workspace::showAll(void) {
154 std::for_each(stackingList
.begin(), stackingList
.end(),
155 std::mem_fun(&BlackboxWindow::show
));
159 void Workspace::hideAll(void) {
160 // withdraw in reverse order to minimize the number of Expose events
162 BlackboxWindowList
lst(stackingList
.rbegin(), stackingList
.rend());
164 BlackboxWindowList::iterator it
= lst
.begin();
165 const BlackboxWindowList::iterator end
= lst
.end();
166 for (; it
!= end
; ++it
) {
167 BlackboxWindow
*bw
= *it
;
174 void Workspace::removeAll(void) {
175 while (! windowList
.empty())
176 windowList
.front()->iconify();
181 * returns the number of transients for win, plus the number of transients
182 * associated with each transient of win
184 static int countTransients(const BlackboxWindow
* const win
) {
185 int ret
= win
->getTransients().size();
187 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
188 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
189 ret
+= countTransients(*it
);
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
202 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
203 StackVector::iterator
&stack
) {
204 if (win
->getTransients().size() == 0) return; // nothing to do
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());
212 if (! (*it
)->isIconic()) {
213 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
214 wkspc
->stackingList
.remove((*it
));
215 wkspc
->stackingList
.push_front((*it
));
219 // put transients of win's transients in the stack
220 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
221 raiseTransients(*it
, stack
);
226 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
227 StackVector::iterator
&stack
) {
228 if (win
->getTransients().size() == 0) return; // nothing to do
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
);
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());
242 if (! (*it
)->isIconic()) {
243 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
244 wkspc
->stackingList
.remove((*it
));
245 wkspc
->stackingList
.push_back((*it
));
251 void Workspace::raiseWindow(BlackboxWindow
*w
) {
252 BlackboxWindow
*win
= w
;
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();
260 // get the total window count (win and all transients)
261 unsigned int i
= 1 + countTransients(win
);
263 // stack the window with all transients above
264 StackVector
stack_vector(i
);
265 StackVector::iterator stack
= stack_vector
.begin();
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
);
275 raiseTransients(win
, stack
);
277 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
281 void Workspace::lowerWindow(BlackboxWindow
*w
) {
282 BlackboxWindow
*win
= w
;
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();
290 // get the total window count (win and all transients)
291 unsigned int i
= 1 + countTransients(win
);
293 // stack the window with all transients above
294 StackVector
stack_vector(i
);
295 StackVector::iterator stack
= stack_vector
.begin();
297 lowerTransients(win
, stack
);
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
);
307 XLowerWindow(screen
->getBaseDisplay()->getXDisplay(), stack_vector
.front());
308 XRestackWindows(screen
->getBaseDisplay()->getXDisplay(),
309 &stack_vector
[0], stack_vector
.size());
313 void Workspace::reconfigure(void) {
314 clientmenu
->reconfigure();
315 std::for_each(windowList
.begin(), windowList
.end(),
316 std::mem_fun(&BlackboxWindow::reconfigure
));
320 void Workspace::updateFocusModel(void) {
321 std::for_each(windowList
.begin(), windowList
.end(),
322 std::mem_fun(&BlackboxWindow::updateFocusModel
));
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 */
337 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
338 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
341 assert(it
!= windowList
.end()); // window must be in list
343 if (it
== windowList
.end())
344 return windowList
.front(); // if we walked off the end, wrap around
350 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
351 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
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
362 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
363 return stackingList
.front();
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());
375 unsigned int Workspace::getCount(void) const {
376 return windowList
.size();
380 bool Workspace::isCurrent(void) const {
381 return (id
== screen
->getCurrentWorkspaceID());
385 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
386 return (w
== windowList
.back());
389 void Workspace::setCurrent(void) {
390 screen
->changeWorkspaceID(id
);
394 void Workspace::setName(const string
& new_name
) {
395 if (! new_name
.empty()) {
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);
405 clientmenu
->setLabel(name
);
406 clientmenu
->update();
407 screen
->saveWorkspaceNames();
412 * Calculate free space available for window placement.
414 typedef std::vector
<Rect
> rectList
;
416 static rectList
calcSpace(const Rect
&win
, const rectList
&spaces
) {
419 rectList::const_iterator siter
, end
= spaces
.end();
420 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
421 const Rect
&curr
= *siter
;
423 if(! win
.intersects(curr
)) {
424 result
.push_back(curr
);
428 /* Use an intersection of win and curr to determine the space around
429 * curr that we can use.
431 * NOTE: the spaces calculated can overlap.
436 extra
.setCoords(curr
.left(), curr
.top(),
437 isect
.left() - 1, curr
.bottom());
438 if (extra
.valid()) result
.push_back(extra
);
441 extra
.setCoords(curr
.left(), curr
.top(),
442 curr
.right(), isect
.top() - 1);
443 if (extra
.valid()) result
.push_back(extra
);
446 extra
.setCoords(isect
.right() + 1, curr
.top(),
447 curr
.right(), curr
.bottom());
448 if (extra
.valid()) result
.push_back(extra
);
451 extra
.setCoords(curr
.left(), isect
.bottom() + 1,
452 curr
.right(), curr
.bottom());
453 if (extra
.valid()) result
.push_back(extra
);
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();
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();
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();
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();
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();
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();
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();
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();
508 bool Workspace::smartPlacement(Rect
& win
, const Rect
& availableArea
) {
510 spaces
.push_back(availableArea
); //initially the entire screen is free
513 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
514 end
= windowList
.end();
516 for (; wit
!= end
; ++wit
) {
517 const BlackboxWindow
* const curr
= *wit
;
519 if (curr
->isShaded()) continue;
521 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
522 curr
->frameRect().width() + screen
->getBorderWidth(),
523 curr
->frameRect().height() + screen
->getBorderWidth());
525 spaces
= calcSpace(tmp
, spaces
);
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
);
533 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
535 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
536 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
538 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
541 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
542 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
543 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
545 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
547 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
548 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
550 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
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())
560 if (sit
== spaces_end
)
563 //set new position based on the empty space found
564 const Rect
& where
= *sit
;
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());
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());
584 bool Workspace::underMousePlacement(Rect
&win
, const Rect
&availableArea
) {
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;
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();
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;
614 if (cascade_x
== 32) {
615 cascade_x
+= availableArea
.x();
616 cascade_y
+= availableArea
.y();
619 win
.setPos(cascade_x
, cascade_y
);
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());
631 switch (screen
->getPlacementPolicy()) {
632 case BScreen::RowSmartPlacement
:
633 case BScreen::ColSmartPlacement
:
634 placed
= smartPlacement(new_win
, availableArea
);
636 case BScreen::UnderMousePlacement
:
637 placed
= underMousePlacement(new_win
, availableArea
);
639 break; // handled below
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);
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());