merge the C branch into HEAD
[dana/openbox.git] / python / stackedcycle.py
1 import ob, config, hooks, focus
2 from input import Pointer, Keyboard
3
4 config.add('stackedcycle',
5            'activate_while_cycling',
6            'Activate While Cycling',
7            "If this is True then windows will be activated as they are" + \
8            "highlighted in the cycling list (except iconified windows).",
9            'boolean',
10            True)
11
12 config.add('stackedcycle',
13            'raise_window',
14            'Raise After Cycling',
15            "If this is True, the selected window will be raised as well as " +\
16            "focused.",
17            'boolean',
18            True)
19
20 config.add('stackedcycle',
21            'include_all_desktops',
22            'Include Windows From All Desktops',
23            "If this is True then windows from all desktops will be included" +\
24            " in the stacking list.",
25            'boolean',
26            False)
27
28 config.add('stackedcycle',
29            'include_icons',
30            'Include Icons',
31            "If this is True then windows which are iconified on the current" +\
32            " desktop will be included in the stacking list.",
33            'boolean',
34            False)
35
36 config.add('stackedcycle',
37            'include_icons_all_desktops',
38            'Include Icons From All Desktops',
39            "If this is True then windows which are iconified from all " +\
40            "desktops will be included in the stacking list (if Include Icons"+\
41            " is also True).",
42            'boolean',
43            True)
44
45 config.add('stackedcycle',
46            'include_omnipresent',
47            'Include Omnipresent Windows',
48            "If this is True then windows which are on all-desktops at once " +\
49            "will be included in the stacking list.",
50            'boolean',
51            True)
52
53 def next(keydata, client): _cycle(keydata, client, True)
54 def previous(keydata, client): _cycle(keydata, client, False)
55
56 def _shouldAdd(client):
57     """Determines if a client should be added to the cycling list."""
58     curdesk = ob.Openbox.desktop()
59     desk = client.desktop()
60
61     if not (client.normal() and client.canFocus()): return False
62     if config.get('focus', 'avoid_skip_taskbar') and client.skipTaskbar():
63         return False
64
65     if client.iconic():
66         if config.get('stackedcycle', 'include_icons'):
67             if config.get('stackedcycle', 'include_icons_all_desktops'):
68                 return True
69             if desk == curdesk: return True
70         return False
71     if config.get('stackedcycle', 'include_omnipresent') and \
72        desk == 0xffffffff: return True
73     if config.get('stackedcycle', 'include_all_desktops'): return True
74     if desk == curdesk: return True
75
76     return False
77
78 def _populateItems():
79     global _items
80     # get the list of clients, keeping iconic windows at the bottom
81     _items = []
82     iconic_clients = []
83     for c in focus._clients:
84         if _shouldAdd(c):
85             if c.iconic(): iconic_clients.append(c)
86             else: _items.append(c)
87     _items.extend(iconic_clients)
88
89 def _populate():
90     global _pos, _items
91     try:
92         current = _items[_pos]
93     except IndexError: 
94         current = None
95     oldpos = _pos
96     _pos = -1
97
98     _populateItems()
99
100     i = 0
101     for item in _items:
102         # current item might have shifted after a populateItems() 
103         # call, so we need to do this test.
104         if current == item:
105             _pos = i
106         i += 1
107
108         # The item we were on might be gone entirely
109         if _pos < 0:
110             # try stay at the same spot in the menu
111             if oldpos >= len(_items):
112                 _pos = len(_items) - 1
113             else:
114                 _pos = oldpos
115
116
117 def _activate(final):
118     """
119     Activates (focuses and, if the user requested it, raises a window).
120     If final is True, then this is the very last window we're activating
121     and the user has finished cycling.
122     """
123     print "_activate"
124     try:
125         client = _items[_pos]
126     except IndexError: return # empty list
127     print client
128
129     # move the to client's desktop if required
130     if not (client.iconic() or client.desktop() == 0xffffffff or \
131             client.desktop() == ob.Openbox.desktop()):
132         ob.Openbox.setDesktop(client.desktop())
133         
134     if final or not client.iconic():
135         if final: r = config.get('stackedcycle', 'raise_window')
136         else: r = False
137         client.focus(True)
138         if final and client.shaded(): client.setShaded(False)
139         print "final", final, "raising", r
140         if r: client.raiseWindow()
141         if not final:
142             focus._skip += 1
143
144 def _cycle(keydata, client, forward):
145     global _cycling, _state, _pos, _inititem, _items
146
147     if not _cycling:
148         _items = [] # so it doesnt try start partway through the list
149         _populate()
150
151         if not _items: return # don't bother doing anything
152
153         Keyboard.grab(_grabfunc)
154         # the pointer grab causes pointer events during the keyboard grab
155         # to go away, which means we don't get enter notifies when the
156         # popup disappears, screwing up the focus
157         Pointer.grabPointer(True)
158
159         _cycling = True
160         _state = keydata.state
161         _pos = 0
162         _inititem = _items[_pos]
163
164     if forward:
165         _pos += 1
166     else:
167         _pos -= 1
168     # wrap around
169     if _pos < 0: _pos = len(_items) - 1
170     elif _pos >= len(_items): _pos = 0
171     if config.get('stackedcycle', 'activate_while_cycling'):
172         _activate(False) # activate, but dont deiconify/unshade/raise
173
174 def _grabfunc(keydata, client):
175     global _cycling
176     
177     done = False
178     notreverting = True
179     # have all the modifiers this started with been released?
180     if not _state & keydata.state:
181         done = True
182     elif keydata.press:
183         # has Escape been pressed?
184         if keydata.keychain == "Escape":
185             done = True
186             notreverting = False
187             # revert
188             try:
189                 _pos = _items.index(_inititem)
190             except:
191                 _pos = -1
192         # has Enter been pressed?
193         elif keydata.keychain == "Return":
194             done = True
195
196     if done:
197         # activate, and deiconify/unshade/raise
198         _activate(notreverting)
199         _cycling = False
200         Keyboard.ungrab()
201         Pointer.grabPointer(False)
202
203
204 _cycling  = False
205 _pos = 0
206 _inititem = None
207 _items = []
208 _state = 0
209
210 def _newwin(data):
211     if _cycling: _populate()
212 def _closewin(data):
213     if _cycling: _populate()
214
215 hooks.managed.append(_newwin)
216 hooks.closed.append(_closewin)