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