]> Dogcows Code - chaz/openbox/blob - src/Workspace.cc
merge from netwm-merge2 to netwm-merge3. Basically, all of netwm that we intend to...
[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
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 screen->lowerDesktops();
311 }
312
313
314 void Workspace::reconfigure(void) {
315 clientmenu->reconfigure();
316 std::for_each(windowList.begin(), windowList.end(),
317 std::mem_fun(&BlackboxWindow::reconfigure));
318 }
319
320
321 void Workspace::updateFocusModel(void) {
322 std::for_each(windowList.begin(), windowList.end(),
323 std::mem_fun(&BlackboxWindow::updateFocusModel));
324 }
325
326
327 BlackboxWindow *Workspace::getWindow(unsigned int index) {
328 if (index < windowList.size()) {
329 BlackboxWindowList::iterator it = windowList.begin();
330 for(; index > 0; --index, ++it); /* increment to index */
331 return *it;
332 }
333 return 0;
334 }
335
336
337 BlackboxWindow*
338 Workspace::getNextWindowInList(BlackboxWindow *w) {
339 BlackboxWindowList::iterator it = std::find(windowList.begin(),
340 windowList.end(),
341 w);
342 assert(it != windowList.end()); // window must be in list
343 ++it; // next window
344 if (it == windowList.end())
345 return windowList.front(); // if we walked off the end, wrap around
346
347 return *it;
348 }
349
350
351 BlackboxWindow* Workspace::getPrevWindowInList(BlackboxWindow *w) {
352 BlackboxWindowList::iterator it = std::find(windowList.begin(),
353 windowList.end(),
354 w);
355 assert(it != windowList.end()); // window must be in list
356 if (it == windowList.begin())
357 return windowList.back(); // if we walked of the front, wrap around
358
359 return *(--it);
360 }
361
362
363 BlackboxWindow* Workspace::getTopWindowOnStack(void) const {
364 return stackingList.front();
365 }
366
367
368 void Workspace::sendWindowList(Netizen &n) {
369 BlackboxWindowList::iterator it = windowList.begin(),
370 end = windowList.end();
371 for(; it != end; ++it)
372 n.sendWindowAdd((*it)->getClientWindow(), getID());
373 }
374
375
376 unsigned int Workspace::getCount(void) const {
377 return windowList.size();
378 }
379
380
381 void Workspace::appendStackOrder(BlackboxWindowList &stack_order) const {
382 BlackboxWindowList::const_iterator it = stackingList.begin();
383 const BlackboxWindowList::const_iterator end = stackingList.end();
384 for (; it != end; ++it)
385 stack_order.push_back(*it);
386 }
387
388
389 bool Workspace::isCurrent(void) const {
390 return (id == screen->getCurrentWorkspaceID());
391 }
392
393
394 bool Workspace::isLastWindow(const BlackboxWindow* const w) const {
395 return (w == windowList.back());
396 }
397
398 void Workspace::setCurrent(void) {
399 screen->changeWorkspaceID(id);
400 }
401
402
403 void Workspace::setName(const string& new_name) {
404 if (! new_name.empty()) {
405 name = new_name;
406 } else {
407 string tmp =i18n(WorkspaceSet, WorkspaceDefaultNameFormat, "Workspace %d");
408 assert(tmp.length() < 32);
409 char default_name[32];
410 sprintf(default_name, tmp.c_str(), id + 1);
411 name = default_name;
412 }
413
414 clientmenu->setLabel(name);
415 clientmenu->update();
416 screen->saveWorkspaceNames();
417 }
418
419
420 /*
421 * Calculate free space available for window placement.
422 */
423 typedef std::vector<Rect> rectList;
424
425 static rectList calcSpace(const Rect &win, const rectList &spaces) {
426 Rect isect, extra;
427 rectList result;
428 rectList::const_iterator siter, end = spaces.end();
429 for (siter = spaces.begin(); siter != end; ++siter) {
430 const Rect &curr = *siter;
431
432 if(! win.intersects(curr)) {
433 result.push_back(curr);
434 continue;
435 }
436
437 /* Use an intersection of win and curr to determine the space around
438 * curr that we can use.
439 *
440 * NOTE: the spaces calculated can overlap.
441 */
442 isect = curr & win;
443
444 // left
445 extra.setCoords(curr.left(), curr.top(),
446 isect.left() - 1, curr.bottom());
447 if (extra.valid()) result.push_back(extra);
448
449 // top
450 extra.setCoords(curr.left(), curr.top(),
451 curr.right(), isect.top() - 1);
452 if (extra.valid()) result.push_back(extra);
453
454 // right
455 extra.setCoords(isect.right() + 1, curr.top(),
456 curr.right(), curr.bottom());
457 if (extra.valid()) result.push_back(extra);
458
459 // bottom
460 extra.setCoords(curr.left(), isect.bottom() + 1,
461 curr.right(), curr.bottom());
462 if (extra.valid()) result.push_back(extra);
463 }
464 return result;
465 }
466
467
468 static bool rowRLBT(const Rect &first, const Rect &second) {
469 if (first.bottom() == second.bottom())
470 return first.right() > second.right();
471 return first.bottom() > second.bottom();
472 }
473
474 static bool rowRLTB(const Rect &first, const Rect &second) {
475 if (first.y() == second.y())
476 return first.right() > second.right();
477 return first.y() < second.y();
478 }
479
480 static bool rowLRBT(const Rect &first, const Rect &second) {
481 if (first.bottom() == second.bottom())
482 return first.x() < second.x();
483 return first.bottom() > second.bottom();
484 }
485
486 static bool rowLRTB(const Rect &first, const Rect &second) {
487 if (first.y() == second.y())
488 return first.x() < second.x();
489 return first.y() < second.y();
490 }
491
492 static bool colLRTB(const Rect &first, const Rect &second) {
493 if (first.x() == second.x())
494 return first.y() < second.y();
495 return first.x() < second.x();
496 }
497
498 static bool colLRBT(const Rect &first, const Rect &second) {
499 if (first.x() == second.x())
500 return first.bottom() > second.bottom();
501 return first.x() < second.x();
502 }
503
504 static bool colRLTB(const Rect &first, const Rect &second) {
505 if (first.right() == second.right())
506 return first.y() < second.y();
507 return first.right() > second.right();
508 }
509
510 static bool colRLBT(const Rect &first, const Rect &second) {
511 if (first.right() == second.right())
512 return first.bottom() > second.bottom();
513 return first.right() > second.right();
514 }
515
516
517 bool Workspace::smartPlacement(Rect& win, const Rect& availableArea) {
518 rectList spaces;
519 spaces.push_back(availableArea); //initially the entire screen is free
520
521 //Find Free Spaces
522 BlackboxWindowList::const_iterator wit = windowList.begin(),
523 end = windowList.end();
524 Rect tmp;
525 for (; wit != end; ++wit) {
526 const BlackboxWindow* const curr = *wit;
527
528 if (curr->isShaded()) continue;
529
530 tmp.setRect(curr->frameRect().x(), curr->frameRect().y(),
531 curr->frameRect().width() + screen->getBorderWidth(),
532 curr->frameRect().height() + screen->getBorderWidth());
533
534 spaces = calcSpace(tmp, spaces);
535 }
536
537 if (screen->getPlacementPolicy() == BScreen::RowSmartPlacement) {
538 if(screen->getRowPlacementDirection() == BScreen::LeftRight) {
539 if(screen->getColPlacementDirection() == BScreen::TopBottom)
540 std::sort(spaces.begin(), spaces.end(), rowLRTB);
541 else
542 std::sort(spaces.begin(), spaces.end(), rowLRBT);
543 } else {
544 if(screen->getColPlacementDirection() == BScreen::TopBottom)
545 std::sort(spaces.begin(), spaces.end(), rowRLTB);
546 else
547 std::sort(spaces.begin(), spaces.end(), rowRLBT);
548 }
549 } else {
550 if(screen->getColPlacementDirection() == BScreen::TopBottom) {
551 if(screen->getRowPlacementDirection() == BScreen::LeftRight)
552 std::sort(spaces.begin(), spaces.end(), colLRTB);
553 else
554 std::sort(spaces.begin(), spaces.end(), colRLTB);
555 } else {
556 if(screen->getRowPlacementDirection() == BScreen::LeftRight)
557 std::sort(spaces.begin(), spaces.end(), colLRBT);
558 else
559 std::sort(spaces.begin(), spaces.end(), colRLBT);
560 }
561 }
562
563 rectList::const_iterator sit = spaces.begin(), spaces_end = spaces.end();
564 for(; sit != spaces_end; ++sit) {
565 if (sit->width() >= win.width() && sit->height() >= win.height())
566 break;
567 }
568
569 if (sit == spaces_end)
570 return False;
571
572 //set new position based on the empty space found
573 const Rect& where = *sit;
574 win.setX(where.x());
575 win.setY(where.y());
576
577 // adjust the location() based on left/right and top/bottom placement
578 if (screen->getPlacementPolicy() == BScreen::RowSmartPlacement) {
579 if (screen->getRowPlacementDirection() == BScreen::RightLeft)
580 win.setX(where.right() - win.width());
581 if (screen->getColPlacementDirection() == BScreen::BottomTop)
582 win.setY(where.bottom() - win.height());
583 } else {
584 if (screen->getColPlacementDirection() == BScreen::BottomTop)
585 win.setY(win.y() + where.height() - win.height());
586 if (screen->getRowPlacementDirection() == BScreen::RightLeft)
587 win.setX(win.x() + where.width() - win.width());
588 }
589 return True;
590 }
591
592
593 bool Workspace::underMousePlacement(Rect &win, const Rect &availableArea) {
594 int x, y, rx, ry;
595 Window c, r;
596 unsigned int m;
597 XQueryPointer(screen->getBlackbox()->getXDisplay(), screen->getRootWindow(),
598 &r, &c, &rx, &ry, &x, &y, &m);
599 x = rx - win.width() / 2;
600 y = ry - win.height() / 2;
601
602 if (x < availableArea.x())
603 x = availableArea.x();
604 if (y < availableArea.y())
605 y = availableArea.y();
606 if (x + win.width() > availableArea.x() + availableArea.width())
607 x = availableArea.x() + availableArea.width() - win.width();
608 if (y + win.height() > availableArea.y() + availableArea.height())
609 y = availableArea.y() + availableArea.height() - win.height();
610
611 win.setX(x);
612 win.setY(y);
613
614 return True;
615 }
616
617
618 bool Workspace::cascadePlacement(Rect &win, const Rect &availableArea) {
619 if ((cascade_x > static_cast<signed>(availableArea.width() / 2)) ||
620 (cascade_y > static_cast<signed>(availableArea.height() / 2)))
621 cascade_x = cascade_y = 32;
622
623 if (cascade_x == 32) {
624 cascade_x += availableArea.x();
625 cascade_y += availableArea.y();
626 }
627
628 win.setPos(cascade_x, cascade_y);
629
630 return True;
631 }
632
633
634 void Workspace::placeWindow(BlackboxWindow *win) {
635 Rect availableArea(screen->availableArea()),
636 new_win(availableArea.x(), availableArea.y(),
637 win->frameRect().width(), win->frameRect().height());
638 bool placed = False;
639
640 switch (screen->getPlacementPolicy()) {
641 case BScreen::RowSmartPlacement:
642 case BScreen::ColSmartPlacement:
643 placed = smartPlacement(new_win, availableArea);
644 break;
645 case BScreen::UnderMousePlacement:
646 placed = underMousePlacement(new_win, availableArea);
647 default:
648 break; // handled below
649 } // switch
650
651 if (placed == False) {
652 cascadePlacement(new_win, availableArea);
653 cascade_x += win->getTitleHeight() + (screen->getBorderWidth() * 2);
654 cascade_y += win->getTitleHeight() + (screen->getBorderWidth() * 2);
655 }
656
657 if (new_win.right() > availableArea.right())
658 new_win.setX(availableArea.left());
659 if (new_win.bottom() > availableArea.bottom())
660 new_win.setY(availableArea.top());
661 win->configure(new_win.x(), new_win.y(), new_win.width(), new_win.height());
662 }
This page took 0.068989 seconds and 5 git commands to generate.