all new stacked cycling code. so much sexy.
[mikachu/openbox.git] / scripts / stackedcycle.py
1 ###########################################################################
2 ### Functions for cycling focus (in a 'stacked' order) between windows. ###
3 ###########################################################################
4
5 ###########################################################################
6 ###    Options that affect the behavior of the stackedcycle module.     ###
7 ###              Also see the options in the focus module.              ###
8 ###########################################################################
9 include_all_desktops = 0
10 include_icons = 1
11 include_omnipresent = 1
12 title_size_limit = 80
13 activate_while_cycling = 1
14 ###########################################################################
15
16 def next(data):
17     """Focus the next window."""
18     _o.cycle(data, 1)
19     
20 def previous(data):
21     """Focus the previous window."""
22     _o.cycle(data, 0)
23
24 ###########################################################################
25 ###########################################################################
26
27 ###########################################################################
28 ###      Internal stuff, should not be accessed outside the module.     ###
29 ###########################################################################
30
31 import otk
32 import ob
33 import focus
34
35 class cycledata:
36     def __init__(self):
37         self.cycling = 0
38
39     def createpopup(self):
40         self.style = self.screen.style()
41         self.widget = otk.Widget(ob.openbox, self.style, otk.Widget.Vertical,
42                                  0, self.style.bevelWidth(), 1)
43         self.widget.setTexture(self.style.titlebarFocusBackground())
44
45     def destroypopup(self):
46         self.menuwidgets = []
47         self.widget = 0
48
49     def shouldadd(self, client):
50         """Determines if a client should be added to the list."""
51         curdesk = self.screen.desktop()
52         desk = client.desktop()
53
54         if not client.normal(): return 0
55         if not (client.canFocus() or client.focusNotify()): return 0
56         if focus.avoid_skip_taskbar and client.skipTaskbar(): return 0
57
58         if include_icons and client.iconic(): return 1
59         if include_omnipresent and desk == 0xffffffff: return 1
60         if include_all_desktops: return 1
61         if desk == curdesk: return 1
62
63         return 0
64
65     def populatelist(self):
66         """Populates self.clients and self.menuwidgets, and then shows and
67            positions the cycling popup."""
68
69         self.widget.hide()
70
71         try:
72             current = self.clients[self.menupos]
73         except IndexError: current = 0
74         oldpos = self.menupos
75         self.menupos = -1
76
77         # get the list of clients
78         self.clients = []
79         print focus._clients
80         for i in focus._clients:
81             c = ob.openbox.findClient(i)
82             if c: self.clients.append(c)
83
84         font = self.style.labelFont()
85         longest = 0
86         height = font.height()
87             
88         # make the widgets
89         i = 0
90         self.menuwidgets = []
91         while i < len(self.clients):
92             c = self.clients[i]
93             if not self.shouldadd(c):
94                 # make the clients and menuwidgets lists match
95                 self.clients.pop(i) 
96                 continue
97             
98             w = otk.FocusLabel(self.widget)
99             if current and c.window() == current.window():
100                 print "found " + str(i)
101                 self.menupos = i
102                 w.focus()
103             else:
104                 w.unfocus()
105             self.menuwidgets.append(w)
106
107             if c.iconic(): t = c.iconTitle()
108             else: t = c.title()
109             if len(t) > title_size_limit: # limit the length of titles
110                 t = t[:title_size_limit / 2 - 2] + "..." + \
111                     t[0 - title_size_limit / 2 - 2:]
112             length = font.measureString(t)
113             if length > longest: longest = length
114             w.setText(t)
115
116             i += 1
117
118         # the window we were on may be gone
119         if self.menupos < 0:
120             # try stay at the same spot in the menu
121             if oldpos >= len(self.clients):
122                 self.menupos = len(self.clients) - 1
123             else:
124                 self.menupos = oldpos
125
126         # fit to the largest item in the menu
127         for w in self.menuwidgets:
128             w.fitSize(longest, height)
129
130         # show or hide the list and its child widgets
131         if len(self.clients) > 1:
132             area = self.screeninfo.rect()
133             self.widget.update()
134             self.widget.move(area.x() + (area.width() -
135                                          self.widget.width()) / 2,
136                              area.y() + (area.height() -
137                                          self.widget.height()) / 2)
138             self.widget.show(1)
139
140     def activatetarget(self, final):
141         try:
142             client = self.clients[self.menupos]
143         except IndexError: return # empty list makes for this
144
145         # move the to client's desktop if required
146         if not (client.iconic() or client.desktop() == 0xffffffff or \
147                 client.desktop() == self.screen.desktop()):
148             root = self.screeninfo.rootWindow()
149             ob.send_client_msg(root, otk.Property_atoms().net_current_desktop,
150                                root, client.desktop())
151         
152         # send a net_active_window message for the target
153         if final or not client.iconic():
154             if final: r = focus.raise_window
155             else: r = 0
156             ob.send_client_msg(self.screeninfo.rootWindow(),
157                                otk.Property_atoms().openbox_active_window,
158                                client.window(), final, r)
159
160     def cycle(self, data, forward):
161         if not self.cycling:
162             self.cycling = 1
163             focus._disable = 1
164             self.state = data.state
165             self.screen = ob.openbox.screen(data.screen)
166             self.screeninfo = otk.display.screenInfo(data.screen)
167             self.menupos = 0
168             self.createpopup()
169             self.clients = [] # so it doesnt try start partway through the list
170             self.populatelist()
171         
172             ob.kgrab(self.screen.number(), _grabfunc)
173             # the pointer grab causes pointer events during the keyboard grab
174             # to go away, which means we don't get enter notifies when the
175             # popup disappears, screwing up the focus
176             ob.mgrab(self.screen.number())
177
178         self.menuwidgets[self.menupos].unfocus()
179         if forward:
180             self.menupos += 1
181         else:
182             self.menupos -= 1
183         # wrap around
184         if self.menupos < 0: self.menupos = len(self.clients) - 1
185         elif self.menupos >= len(self.clients): self.menupos = 0
186         self.menuwidgets[self.menupos].focus()
187         if activate_while_cycling:
188             self.activatetarget(0) # activate, but dont deiconify/unshade/raise
189
190     def grabfunc(self, data):
191         if data.action == ob.KeyAction.Release:
192             # have all the modifiers this started with been released?
193             if not self.state & data.state:
194                 self.cycling = 0
195                 focus._disable = 0
196                 self.activatetarget(1) # activate, and deiconify/unshade/raise
197                 self.destroypopup()
198                 ob.kungrab()
199                 ob.mungrab()
200
201
202 def _newwindow(data):
203     if _o.cycling: _o.populatelist()
204         
205 def _closewindow(data):
206     if _o.cycling: _o.populatelist()
207         
208 def _grabfunc(data):
209     _o.grabfunc(data)
210
211 ob.ebind(ob.EventAction.NewWindow, _newwindow)
212 ob.ebind(ob.EventAction.CloseWindow, _closewindow)
213
214 _o = cycledata()