From 671230f83e05bc0f60abd42cac0735657ff90ed6 Mon Sep 17 00:00:00 2001 From: Dana Jansens Date: Sun, 16 Feb 2003 23:14:30 +0000 Subject: [PATCH] add the new cycle module with super snazzy new Cycle classes. yay KatanaLynx! --- scripts/Makefile.am | 2 +- scripts/cycle.py | 486 ++++++++++++++++++++++++++++++++++++++++ scripts/defaults.py | 20 +- scripts/focuscycle.py | 72 ------ scripts/stackedcycle.py | 256 --------------------- 5 files changed, 499 insertions(+), 337 deletions(-) create mode 100644 scripts/cycle.py delete mode 100644 scripts/focuscycle.py delete mode 100644 scripts/stackedcycle.py diff --git a/scripts/Makefile.am b/scripts/Makefile.am index f547b0c9..01c1aa92 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -2,7 +2,7 @@ scriptdir = $(libdir)/openbox/python MAINTAINERCLEANFILES = Makefile.in script_PYTHON = config.py defaults.py focus.py callbacks.py \ focusmodel.py windowplacement.py behavior.py motion.py \ - historyplacement.py stackedcycle.py focuscycle.py + historyplacement.py cycle.py distclean-local: $(RM) *\~ .\#* diff --git a/scripts/cycle.py b/scripts/cycle.py new file mode 100644 index 00000000..6165afab --- /dev/null +++ b/scripts/cycle.py @@ -0,0 +1,486 @@ +import ob, otk +class _Cycle: + """ + This is a basic cycling class for anything, from xOr's stackedcycle.py, + that pops up a cycling menu when there's more than one thing to be cycled + to. + An example of inheriting from and modifying this class is _CycleWindows, + which allows users to cycle around windows. + + This class could conceivably be used to cycle through anything -- desktops, + windows of a specific class, XMMS playlists, etc. + """ + + """This specifies a rough limit of characters for the cycling list titles. + Titles which are larger will be chopped with an elipsis in their + center.""" + TITLE_SIZE_LIMIT = 80 + + """If this is non-zero then windows will be activated as they are + highlighted in the cycling list (except iconified windows).""" + ACTIVATE_WHILE_CYCLING = 0 + + """If this is true, we start cycling with the next (or previous) thing + selected.""" + START_WITH_NEXT = 1 + + """If this is true, a popup window will be displayed with the options + while cycling.""" + SHOW_POPUP = 1 + + def __init__(self): + """Initialize an instance of this class. Subclasses should + do any necessary event binding in their constructor as well. + """ + self.cycling = 0 # internal var used for going through the menu + self.items = [] # items to cycle through + + self.widget = None # the otk menu widget + self.menuwidgets = [] # labels in the otk menu widget TODO: RENAME + + def createPopup(self): + """Creates the cycling popup menu. + """ + self.widget = otk.Widget(self.screen.number(), ob.openbox, + otk.Widget.Vertical, 0, 1) + + def destroyPopup(self): + """Destroys (or rather, cleans up after) the cycling popup menu. + """ + self.menuwidgets = [] + self.widget = 0 + + def populateItems(self): + """Populate self.items with the appropriate items that can currently + be cycled through. self.items may be cleared out before this + method is called. + """ + pass + + def menuLabel(self, item): + """Return a string indicating the menu label for the given item. + Don't worry about title truncation. + """ + pass + + def itemEqual(self, item1, item2): + """Compare two items, return 1 if they're "equal" for purposes of + cycling, and 0 otherwise. + """ + # suggestion: define __eq__ on item classes so that this works + # in the general case. :) + return item1 == item2 + + def populateLists(self): + """Populates self.items and self.menuwidgets, and then shows and + positions the cycling popup. You probably shouldn't mess with + this function; instead, see populateItems and menuLabel. + """ + self.widget.hide() + + try: + current = self.items[self.menupos] + except IndexError: + current = None + oldpos = self.menupos + self.menupos = -1 + + self.items = [] + self.populateItems() + + # make the widgets + i = 0 + self.menuwidgets = [] + for i in range(len(self.items)): + c = self.items[i] + + w = otk.Label(self.widget) + # current item might have shifted after a populateItems() + # call, so we need to do this test. + if current and self.itemEqual(c, current): + self.menupos = i + w.setHilighted(1) + self.menuwidgets.append(w) + + t = self.menuLabel(c) + # TODO: maybe subclasses will want to truncate in different ways? + if len(t) > self.TITLE_SIZE_LIMIT: # limit the length of titles + t = t[:self.TITLE_SIZE_LIMIT / 2 - 2] + "..." + \ + t[0 - self.TITLE_SIZE_LIMIT / 2 - 2:] + w.setText(t) + + # The item we were on might be gone entirely + if self.menupos < 0: + # try stay at the same spot in the menu + if oldpos >= len(self.items): + self.menupos = len(self.items) - 1 + else: + self.menupos = oldpos + + # find the size for the popup + width = 0 + height = 0 + for w in self.menuwidgets: + size = w.minSize() + if size.width() > width: width = size.width() + height += size.height() + + # show or hide the list and its child widgets + if len(self.items) > 1: + size = self.screeninfo.size() + self.widget.moveresize(otk.Rect((size.width() - width) / 2, + (size.height() - height) / 2, + width, height)) + if self.SHOW_POPUP: self.widget.show(1) + + def activateTarget(self, final): + """Activates (focuses and, if the user requested it, raises a window). + If final is true, then this is the very last window we're activating + and the user has finished cycling. + """ + pass + + def setDataInfo(self, data): + """Retrieve and/or calculate information when we start cycling, + preferably caching it. Data is what's given to callback functions. + """ + self.screen = ob.openbox.screen(data.screen) + self.screeninfo = otk.display.screenInfo(data.screen) + + def chooseStartPos(self): + """Set self.menupos to a number between 0 and len(self.items) - 1. + By default the initial menupos is 0, but this can be used to change + it to some other position.""" + pass + + def cycle(self, data, forward): + """Does the actual job of cycling through windows. data is a callback + parameter, while forward is a boolean indicating whether the + cycling goes forwards (true) or backwards (false). + """ + + initial = 0 + + if not self.cycling: + ob.kgrab(data.screen, self.grabfunc) + # the pointer grab causes pointer events during the keyboard grab + # to go away, which means we don't get enter notifies when the + # popup disappears, screwing up the focus + ob.mgrab(data.screen) + + self.cycling = 1 + self.state = data.state + self.menupos = 0 + + self.setDataInfo(data) + + self.createPopup() + self.items = [] # so it doesnt try start partway through the list + self.populateLists() + + self.chooseStartPos() + self.initpos = self.menupos + + initial = 1 + + if not self.items: return # don't bother doing anything + + self.menuwidgets[self.menupos].setHighlighted(0) + + if initial and not self.START_WITH_NEXT: + pass + else: + if forward: + self.menupos += 1 + else: + self.menupos -= 1 + # wrap around + if self.menupos < 0: self.menupos = len(self.items) - 1 + elif self.menupos >= len(self.items): self.menupos = 0 + self.menuwidgets[self.menupos].setHighlighted(1) + if self.ACTIVATE_WHILE_CYCLING: + self.activateTarget(0) # activate, but dont deiconify/unshade/raise + + def grabfunc(self, data): + """A callback method that grabs away all keystrokes so that navigating + the cycling menu is possible.""" + done = 0 + notreverting = 1 + # have all the modifiers this started with been released? + if not self.state & data.state: + done = 1 + elif data.action == ob.KeyAction.Press: + # has Escape been pressed? + if data.key == "Escape": + done = 1 + notreverting = 0 + # revert + self.menupos = self.initpos + # has Enter been pressed? + elif data.key == "Return": + done = 1 + + if done: + # activate, and deiconify/unshade/raise + self.activateTarget(notreverting) + self.destroyPopup() + self.cycling = 0 + ob.kungrab() + ob.mungrab() + + def next(self, data): + """Focus the next window.""" + if not data.state: + raise RuntimeError("next must be bound to a key" + + "combination with at least one modifier") + self.cycle(data, 1) + + def previous(self, data): + """Focus the previous window.""" + if not data.state: + raise RuntimeError("previous must be bound to a key" + + "combination with at least one modifier") + self.cycle(data, 0) + +#---------------------- Window Cycling -------------------- +import focus +class _CycleWindows(_Cycle): + """ + This is a basic cycling class for Windows. + + An example of inheriting from and modifying this class is _ClassCycleWindows, + which allows users to cycle around windows of a certain application + name/class only. + + This class has an underscored name because I use the singleton pattern + (so CycleWindows is an actual instance of this class). This doesn't have + to be followed, but if it isn't followed then the user will have to create + their own instances of your class and use that (not always a bad thing). + + An example of using the CycleWindows singleton: + + from cycle import CycleWindows + CycleWindows.INCLUDE_ICONS = 0 # I don't like cycling to icons + ob.kbind(["A-Tab"], ob.KeyContext.All, CycleWindows.next) + ob.kbind(["A-S-Tab"], ob.KeyContext.All, CycleWindows.previous) + """ + + """If this is non-zero then windows from all desktops will be included in + the stacking list.""" + INCLUDE_ALL_DESKTOPS = 0 + + """If this is non-zero then windows which are iconified on the current + desktop will be included in the stacking list.""" + INCLUDE_ICONS = 1 + + """If this is non-zero then windows which are iconified from all desktops + will be included in the stacking list.""" + INCLUDE_ICONS_ALL_DESKTOPS = 1 + + """If this is non-zero then windows which are on all-desktops at once will + be included.""" + INCLUDE_OMNIPRESENT = 1 + + """A better default for window cycling than generic cycling.""" + ACTIVATE_WHILE_CYCLING = 1 + + """When cycling focus, raise the window chosen as well as focusing it.""" + RAISE_WINDOW = 1 + + def __init__(self): + _Cycle.__init__(self) + + def newwindow(data): + if self.cycling: self.populateLists() + def closewindow(data): + if self.cycling: self.populateLists() + + ob.ebind(ob.EventAction.NewWindow, newwindow) + ob.ebind(ob.EventAction.CloseWindow, closewindow) + + def shouldAdd(self, client): + """Determines if a client should be added to the cycling list.""" + curdesk = self.screen.desktop() + desk = client.desktop() + + if not client.normal(): return 0 + if not (client.canFocus() or client.focusNotify()): return 0 + if focus.AVOID_SKIP_TASKBAR and client.skipTaskbar(): return 0 + + if client.iconic(): + if self.INCLUDE_ICONS: + if self.INCLUDE_ICONS_ALL_DESKTOPS: return 1 + if desk == curdesk: return 1 + return 0 + if self.INCLUDE_OMNIPRESENT and desk == 0xffffffff: return 1 + if self.INCLUDE_ALL_DESKTOPS: return 1 + if desk == curdesk: return 1 + + return 0 + + def populateItems(self): + # get the list of clients, keeping iconic windows at the bottom + iconic_clients = [] + for c in focus._clients: + if self.shouldAdd(c): + if c.iconic(): iconic_clients.append(c) + else: self.items.append(c) + self.items.extend(iconic_clients) + + def menuLabel(self, client): + if client.iconic(): t = '[' + client.iconTitle() + ']' + else: t = client.title() + + if self.INCLUDE_ALL_DESKTOPS: + d = client.desktop() + if d == 0xffffffff: d = self.screen.desktop() + t = self.screen.desktopName(d) + " - " + t + + return t + + def itemEqual(self, client1, client2): + return client1.window() == client2.window() + + def activateTarget(self, final): + """Activates (focuses and, if the user requested it, raises a window). + If final is true, then this is the very last window we're activating + and the user has finished cycling.""" + try: + client = self.items[self.menupos] + except IndexError: return # empty list + + # move the to client's desktop if required + if not (client.iconic() or client.desktop() == 0xffffffff or \ + client.desktop() == self.screen.desktop()): + root = self.screeninfo.rootWindow() + ob.send_client_msg(root, otk.atoms.net_current_desktop, + root, client.desktop()) + + # send a net_active_window message for the target + if final or not client.iconic(): + if final: r = self.RAISE_WINDOW + else: r = 0 + ob.send_client_msg(self.screeninfo.rootWindow(), + otk.atoms.openbox_active_window, + client.window(), final, r) + if not final: + focus._skip += 1 + +# The singleton. +CycleWindows = _CycleWindows() + +#---------------------- Window Cycling -------------------- +import focus +class _CycleWindowsLinear(_CycleWindows): + """ + This class is an example of how to inherit from and make use of the + _CycleWindows class. This class also uses the singleton pattern. + + An example of using the CycleWindowsLinear singleton: + + from cycle import CycleWindowsLinear + CycleWindows.ALL_DESKTOPS = 1 # I want all my windows in the list + ob.kbind(["A-Tab"], ob.KeyContext.All, CycleWindowsLinear.next) + ob.kbind(["A-S-Tab"], ob.KeyContext.All, CycleWindowsLinear.previous) + """ + + """When cycling focus, raise the window chosen as well as focusing it.""" + RAISE_WINDOW = 0 + + """If this is true, a popup window will be displayed with the options + while cycling.""" + SHOW_POPUP = 0 + + def __init__(self): + _CycleWindows.__init__(self) + + def shouldAdd(self, client): + """Determines if a client should be added to the cycling list.""" + curdesk = self.screen.desktop() + desk = client.desktop() + + if not client.normal(): return 0 + if not (client.canFocus() or client.focusNotify()): return 0 + if focus.AVOID_SKIP_TASKBAR and client.skipTaskbar(): return 0 + + if client.iconic(): return 0 + if self.INCLUDE_OMNIPRESENT and desk == 0xffffffff: return 1 + if self.INCLUDE_ALL_DESKTOPS: return 1 + if desk == curdesk: return 1 + + return 0 + + def populateItems(self): + # get the list of clients, keeping iconic windows at the bottom + iconic_clients = [] + for i in range(self.screen.clientCount()): + c = self.screen.client(i) + if self.shouldAdd(c): + self.items.append(c) + + def chooseStartPos(self): + if focus._clients: + t = focus._clients[0] + for i,c in zip(range(len(self.items)), self.items): + if self.itemEqual(c, t): + self.menupos = i + break + + def menuLabel(self, client): + t = client.title() + + if self.INCLUDE_ALL_DESKTOPS: + d = client.desktop() + if d == 0xffffffff: d = self.screen.desktop() + t = self.screen.desktopName(d) + " - " + t + + return t + +# The singleton. +CycleWindowsLinear = _CycleWindowsLinear() + +#----------------------- Desktop Cycling ------------------ +class _CycleDesktops(_Cycle): + """ + Example of usage: + + from cycle import CycleDesktops + ob.kbind(["W-d"], ob.KeyContext.All, CycleDesktops.next) + ob.kbind(["W-S-d"], ob.KeyContext.All, CycleDesktops.previous) + """ + class Desktop: + def __init__(self, name, index): + self.name = name + self.index = index + def __eq__(self, other): + return other.index == self.index + + START_WITH_NEXT = 0 + + def __init__(self): + _Cycle.__init__(self) + + def populateItems(self): + for i in range(self.screen.numDesktops()): + self.items.append( + _CycleDesktops.Desktop(self.screen.desktopName(i), i)) + + def menuLabel(self, desktop): + return desktop.name + + def chooseStartPos(self): + self.menupos = self.screen.desktop() + + def activateTarget(self, final): + # TODO: refactor this bit + try: + desktop = self.items[self.menupos] + except IndexError: return + + root = self.screeninfo.rootWindow() + ob.send_client_msg(root, otk.atoms.net_current_desktop, + root, desktop.index) + +CycleDesktops = _CycleDesktops() + +print "Loaded cycle.py" diff --git a/scripts/defaults.py b/scripts/defaults.py index 7b4145c0..459c0d61 100644 --- a/scripts/defaults.py +++ b/scripts/defaults.py @@ -38,16 +38,20 @@ ob.kbind(["A-F4"], ob.KeyContext.All, callbacks.close) ob.kbind(["W-d"], ob.KeyContext.All, callbacks.toggle_show_desktop) # focus bindings -import stackedcycle # functions for doing stacked 'kde-style' cycling -ob.kbind(["A-Tab"], ob.KeyContext.All, stackedcycle.next) -ob.kbind(["A-S-Tab"], ob.KeyContext.All, stackedcycle.previous) -# if you want linear cycling instead of stacked cycling, comment out the focus +from cycle import CycleWindows +ob.kbind(["A-Tab"], ob.KeyContext.All, CycleWindows.next) +ob.kbind(["A-S-Tab"], ob.KeyContext.All, CycleWindows.previous) + +# if you want linear cycling instead of stacked cycling, comment out the two # bindings above, and use these instead. -#import focuscycle # functions for doing linear cycling -#focuscycle.RAISE_WINDOW = 0 # don't raise windows when they're activated -#ob.kbind(["A-Tab"], ob.KeyContext.All, focuscycle.next) -#ob.kbind(["A-S-Tab"], ob.KeyContext.All, focuscycle.previous) +#from cycle import CycleWindowsLinear +#ob.kbind(["A-Tab"], ob.KeyContext.All, CycleWindows.next) +#ob.kbind(["A-S-Tab"], ob.KeyContext.All, CycleWindows.previous) + +from cycle import CycleDesktops +ob.kbind(["C-Tab"], ob.KeyContext.All, CycleDesktops.next) +ob.kbind(["C-S-Tab"], ob.KeyContext.All, CycleDesktops.previous) # desktop changing bindings ob.kbind(["C-1"], ob.KeyContext.All, lambda(d): callbacks.change_desktop(d, 0)) diff --git a/scripts/focuscycle.py b/scripts/focuscycle.py deleted file mode 100644 index 37d5643f..00000000 --- a/scripts/focuscycle.py +++ /dev/null @@ -1,72 +0,0 @@ -########################################################################### -### Functions for cycling focus (in a 'linear' order) between windows. ### -########################################################################### - -########################################################################### -### Options that affect the behavior of the focuscycle module. ### -########################################################################### -RAISE_WINDOW = 1 -"""When cycling focus, raise the window chosen as well as focusing it. This - does not affect fallback focusing behavior.""" -# See focus.AVOID_SKIP_TASKBAR -########################################################################### - -def next(data, num=1): - """Focus the next window.""" - _cycle(data, num, 1) - -def previous(data, num=1): - """Focus the previous window.""" - _cycle(data, num, 0) - -########################################################################### -########################################################################### - -########################################################################### -### Internal stuff, should not be accessed outside the module. ### -########################################################################### - -import ob -import focus - -def _cycle(data, num, forward): - screen = ob.openbox.screen(data.screen) - count = screen.clientCount() - - if not count: return # no clients - - target = 0 - if data.client: - client_win = data.client.window() - found = 0 - r = range(count) - if not forward: - r.reverse() - for i in r: - if found: - target = i - found = 2 - break - elif screen.client(i).window() == client_win: - found = 1 - if found == 1: # wraparound - if forward: target = 0 - else: target = count - 1 - - t = target - desktop = screen.desktop() - while 1: - client = screen.client(t) - if client and focus._focusable(client, desktop) and client.focus(): - if RAISE_WINDOW: - screen.raiseWindow(client) - return - if forward: - t += num - if t >= count: t -= count - else: - t -= num - if t < 0: t += count - if t == target: return # nothing to focus - -print "Loaded focuscycle.py" diff --git a/scripts/stackedcycle.py b/scripts/stackedcycle.py deleted file mode 100644 index 76658ae1..00000000 --- a/scripts/stackedcycle.py +++ /dev/null @@ -1,256 +0,0 @@ -########################################################################### -### Functions for cycling focus (in a 'stacked' order) between windows. ### -########################################################################### - -########################################################################### -### Options that affect the behavior of the stackedcycle module. ### -########################################################################### -INCLUDE_ALL_DESKTOPS = 0 -"""If this is non-zero then windows from all desktops will be included in - the stacking list.""" -INCLUDE_ICONS = 1 -"""If this is non-zero then windows which are iconified on the current desktop - will be included in the stacking list.""" -INCLUDE_ICONS_ALL_DESKTOPS = 1 -"""If this is non-zero then windows which are iconified from all desktops - will be included in the stacking list.""" -INCLUDE_OMNIPRESENT = 1 -"""If this is non-zero then windows which are on all-desktops at once will - be included.""" -TITLE_SIZE_LIMIT = 80 -"""This specifies a rough limit of characters for the cycling list titles. - Titles which are larger will be chopped with an elipsis in their - center.""" -ACTIVATE_WHILE_CYCLING = 1 -"""If this is non-zero then windows will be activated as they are - highlighted in the cycling list (except iconified windows).""" -# See focus.AVOID_SKIP_TASKBAR -# See focuscycle.RAISE_WINDOW -########################################################################### - -def next(data): - """Focus the next window.""" - if not data.state: - raise RuntimeError("stackedcycle.next must be bound to a key" + - "combination with at least one modifier") - _o.cycle(data, 1) - -def previous(data): - """Focus the previous window.""" - if not data.state: - raise RuntimeError("stackedcycle.previous must be bound to a key" + - "combination with at least one modifier") - _o.cycle(data, 0) - -########################################################################### -########################################################################### - -########################################################################### -### Internal stuff, should not be accessed outside the module. ### -########################################################################### - -import otk -import ob -import focus -import focuscycle - -class _cycledata: - def __init__(self): - self.cycling = 0 - - def createpopup(self): - self.widget = otk.Widget(self.screen.number(), ob.openbox, - otk.Widget.Vertical, 0, 1) - - def destroypopup(self): - self.menuwidgets = [] - self.widget = 0 - - def shouldadd(self, client): - """Determines if a client should be added to the list.""" - curdesk = self.screen.desktop() - desk = client.desktop() - - if not client.normal(): return 0 - if not (client.canFocus() or client.focusNotify()): return 0 - if focus.AVOID_SKIP_TASKBAR and client.skipTaskbar(): return 0 - - if client.iconic(): - if INCLUDE_ICONS: - if INCLUDE_ICONS_ALL_DESKTOPS: return 1 - if desk == curdesk: return 1 - return 0 - if INCLUDE_OMNIPRESENT and desk == 0xffffffff: return 1 - if INCLUDE_ALL_DESKTOPS: return 1 - if desk == curdesk: return 1 - - return 0 - - def populatelist(self): - """Populates self.clients and self.menuwidgets, and then shows and - positions the cycling popup.""" - - self.widget.hide() - - try: - current = self.clients[self.menupos] - except IndexError: current = 0 - oldpos = self.menupos - self.menupos = -1 - - # get the list of clients, keeping iconic windows at the bottom - self.clients = [] - iconic_clients = [] - for c in focus._clients: - if c.iconic(): iconic_clients.append(c) - else: self.clients.append(c) - self.clients.extend(iconic_clients) - - # make the widgets - i = 0 - self.menuwidgets = [] - while i < len(self.clients): - c = self.clients[i] - if not self.shouldadd(c): - # make the clients and menuwidgets lists match - self.clients.pop(i) - continue - - w = otk.Label(self.widget) - if current and c.window() == current.window(): - self.menupos = i - w.setHighlighted(1) - self.menuwidgets.append(w) - - if c.iconic(): t = c.iconTitle() - else: t = c.title() - - if INCLUDE_ALL_DESKTOPS: - d = c.desktop() - if d == 0xffffffff: d = self.screen.desktop() - t = self.screen.desktopName(d) + " - " + t - - if len(t) > TITLE_SIZE_LIMIT: # limit the length of titles - t = t[:TITLE_SIZE_LIMIT / 2 - 2] + "..." + \ - t[0 - TITLE_SIZE_LIMIT / 2 - 2:] - w.setText(t) - - i += 1 - - # the window we were on may be gone - if self.menupos < 0: - # try stay at the same spot in the menu - if oldpos >= len(self.clients): - self.menupos = len(self.clients) - 1 - else: - self.menupos = oldpos - - # find the size for the popup - width = 0 - height = 0 - for w in self.menuwidgets: - size = w.minSize() - if size.width() > width: width = size.width() - height += size.height() - - # show or hide the list and its child widgets - if len(self.clients) > 1: - size = self.screeninfo.size() - self.widget.moveresize(otk.Rect((size.width() - width) / 2, - (size.height() - height) / 2, - width, height)) - self.widget.show(1) - - def activatetarget(self, final): - try: - client = self.clients[self.menupos] - except IndexError: return # empty list makes for this - - # move the to client's desktop if required - if not (client.iconic() or client.desktop() == 0xffffffff or \ - client.desktop() == self.screen.desktop()): - root = self.screeninfo.rootWindow() - ob.send_client_msg(root, otk.atoms.net_current_desktop, - root, client.desktop()) - - # send a net_active_window message for the target - if final or not client.iconic(): - if final: r = focuscycle.RAISE_WINDOW - else: r = 0 - ob.send_client_msg(self.screeninfo.rootWindow(), - otk.atoms.openbox_active_window, - client.window(), final, r) - if not final: - focus._skip += 1 - - def cycle(self, data, forward): - if not self.cycling: - ob.kgrab(data.screen, _grabfunc) - # the pointer grab causes pointer events during the keyboard grab - # to go away, which means we don't get enter notifies when the - # popup disappears, screwing up the focus - ob.mgrab(data.screen) - - self.cycling = 1 - self.state = data.state - self.screen = ob.openbox.screen(data.screen) - self.screeninfo = otk.display.screenInfo(data.screen) - self.menupos = 0 - self.createpopup() - self.clients = [] # so it doesnt try start partway through the list - self.populatelist() - - if not len(self.clients): return # don't both doing anything - - self.menuwidgets[self.menupos].setHighlighted(0) - if forward: - self.menupos += 1 - else: - self.menupos -= 1 - # wrap around - if self.menupos < 0: self.menupos = len(self.clients) - 1 - elif self.menupos >= len(self.clients): self.menupos = 0 - self.menuwidgets[self.menupos].setHighlighted(1) - if ACTIVATE_WHILE_CYCLING: - self.activatetarget(0) # activate, but dont deiconify/unshade/raise - - def grabfunc(self, data): - done = 0 - notreverting = 1 - # have all the modifiers this started with been released? - if not self.state & data.state: - done = 1 - elif data.action == ob.KeyAction.Press: - # has Escape been pressed? - if data.key == "Escape": - done = 1 - notreverting = 0 - # revert - self.menupos = 0 - # has Enter been pressed? - elif data.key == "Return": - done = 1 - - if done: - # activate, and deiconify/unshade/raise - self.activatetarget(notreverting) - self.destroypopup() - self.cycling = 0 - ob.kungrab() - ob.mungrab() - -def _newwindow(data): - if _o.cycling: _o.populatelist() - -def _closewindow(data): - if _o.cycling: _o.populatelist() - -def _grabfunc(data): - _o.grabfunc(data) - -ob.ebind(ob.EventAction.NewWindow, _newwindow) -ob.ebind(ob.EventAction.CloseWindow, _closewindow) - -_o = _cycledata() - -print "Loaded stackedcycle.py" -- 2.45.2