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