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