diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..93fdee2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +MIT License + +Openbox Key Editor +Copyright (C) 2009-2011 nsf +Copyleft 2011-.... Obkey developpers, translators and maintainers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7e604ae --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ + +lang: + cd po; make + +installdeb: deb + sudo dpkg -i ./obkey.deb + # find . -mtime 0 -name '*.deb' -exec sudo dpkg -i {} + + +installpy: + python setup.py install + +deb: lang + python setup.py \ + --command-packages=stdeb.command sdist_dsc \ + --package obkey \ + --section x11 + cd deb_dist/obkey-*/; \ + dpkg-buildpackage -rfakeroot -uc -us + find . -mtime 0 -name '*.deb' -exec cp {} ./obkey.deb \; + +lint: + pylint --output-format=parseable --reports=y ./obkey_parts | tee pylint.log + +clean: + rm -rf deb_dist dist build obkey.egg-info diff --git a/README.md b/README.md index 92a985c..4514ae7 100644 --- a/README.md +++ b/README.md @@ -3,37 +3,75 @@ ObKey - Openbox Key Editor (PyGObject version) ![ObKey](wiki/screenshot_obkey.png) -# About me -After tried almost every window managers, -and having recently left my 'awesomewm' configuration behind a sharp #. -I use OpenBox on my low ressource machine, because it allows to change -windows with direction (north, east, south, west) which is really intuitive way to switch focus. +# Installation -Another easy to use capability in OpenBox, is the Emacs style multi-levels shorcut. +# With Git +```shell +git clone https://github.com/luffah/obkey.git -But, it is really boring to edit OpenBox XML rc file. -After searching in some forums, i found ObKey, which is usefull, but not perfectly usable. -So i forked the project. +# test it works (you can use it directly this way) +python obkey -# Bugs/Enhancement (Reasons of this fork) -- you can set keybings, save them, close, and re-open the tool and see that it has disappeared -- you cannot organize your keybinding collection with drag and drop +# MANAGE DEPENDENCIES +# AND INSTALL -# Changes between obkey 1.1 and obkey 1.2 -- sorted actions in edition pane -- direct preview of relations between actions and keybind +## With PIP and setup.py +sudo pip install gi gettext -TODO -> button to sort the keybind / drag and drop / alerting users on failure +sudo python setup.py install + +## With Debian installer +sudo apt install python-gi python-gettext +make installdeb +``` + +# Without Git + +## Debian + +Download the package here : [Obkey for debian](https://github.com/luffah/obkey/raw/master/obkey.deb) + +Below the last checksum. +```shell +md5sum obkey.deb | grep d4ab76711cbea8afcf88cb138e07a90d && echo OK + +sudo apt install python-gi python-gettext +sudo dpkg -i obkey.deb +``` + +# Usage +```shell +# Minimalist +obkey + +# Custom file +obkey rc.xml +# With foreign languages +LANGUAGE=fr obkey -# About +``` -This fork aims to continue the project since bugs and enhancement stills needed. +# Why ObKey ? +OpenBox is lightweight ! -Another wish for the future could be the integration : - - _either_ integration this tool for other window manager (e.g. xmonad) - - _or_ integration in a setting manager for OpenBox +OpenBox is great ! + +OpenBox is one of the most customizable Window Manager in the World ! + +OpenBox allows to precisely place windows and to easily switch without clicking a mouse. + +But, OpenBox configuration is written in XML.. + +Hey ! no need to navigate too much in XML, there's ObKey ! + + +# Changes between obkey 1.1 and obkey 1.2 +- sorted actions in edition pane +- direct preview of relations between actions and keybind + +TODO -> button to sort the keybind / drag and drop / alerting users on failure # About KeyBindings Key bindings in OpenBox official site : @@ -41,6 +79,6 @@ Key bindings in OpenBox official site : http://openbox.org/wiki/Help:Bindings If you want to edit shortcut directly in command line interface, -see the next project : +see the next project (not updated since 2013...): https://sourceforge.net/projects/obhotkey/ -https://sourceforge.net/projects/obhotkey/ +The more serious alternative to obkey is probably lxhotkey : https://github.com/lxde/lxhotkey diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..f980e76 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-slate diff --git a/locale/fr/LC_MESSAGES/obkey.mo b/locale/fr/LC_MESSAGES/obkey.mo deleted file mode 100644 index fa81736..0000000 Binary files a/locale/fr/LC_MESSAGES/obkey.mo and /dev/null differ diff --git a/misc/obkey.appdata.xml b/misc/obkey.appdata.xml new file mode 100644 index 0000000..db10147 --- /dev/null +++ b/misc/obkey.appdata.xml @@ -0,0 +1,43 @@ + + + + + + + org.obkey.obkey + CC0-1.0 + MIT + ObKey + ObKey + OpenBox keybinding editor + Éditeur de raccourcis claviers OpenBox + +

+ ObKey shows your OpenBox keybinds in a list, and details the actions in a side pane. . +
+ ObKey is complementary with ObConf. +

+

In short, you can easily customize these types of keybinds:

+
    +
  • applications you launch
  • +
  • window positions and size
  • +
  • workspace management
  • +
+

(Note : can be used with LXDE)

+
+ + + + Obkey keybind list + https://github.com/luffah/obkey/raw/master/wiki/screenshot_obkey.png + + + + http://github.com/luffah/obkey + Utilities + + + obkey + + +
diff --git a/misc/obkey.desktop b/misc/obkey.desktop index f59db24..3007307 100644 --- a/misc/obkey.desktop +++ b/misc/obkey.desktop @@ -1,9 +1,14 @@ [Desktop Entry] -Version=1.0 +Version=1.2 Type=Application Name=Openbox Key bindings +Name[fr]=Raccourcis clavier Openbox Name[zh_TW]=Openbox 組態管理器 Comment=Configure and personalize the Openbox key bindings manager +comment[fr]=Outil de personnalisation des raccoucis clavier pour le gestionnaire de fenêtres Openbox +Comment[bs]=Konfiguriši i prilagodi prečice na tastaturi za Openbox +Comment[hr]=Konfiguriši i prilagodi prečice na tastaturi za Openbox +Comment[rs]=Конфигуриши и прилагоди пречице на тастатури за Openbox Icon=obconf Exec=obkey %f Categories=Settings;DesktopSettings;GTK; diff --git a/obkey b/obkey index 16b19f9..4dff5f0 100755 --- a/obkey +++ b/obkey @@ -1,70 +1,147 @@ #!/usr/bin/python2 -#----------------------------------------------------------------------- -# Openbox Key Editor -# Copyright (C) 2009 nsf -# v1.1 - Code migrated from PyGTK to PyGObject github.com/stevenhoneyman/obkey -# v1.2pre1 - solve a minor bug on copy-paste bug github.com/luffah/obkey -# v1.2pre2 - 19.06.2016 - structured presentation of actions... github.com/luffah/obkey -# -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -#----------------------------------------------------------------------- - -import sys, os -from gi.repository import Gtk -from gi.repository import GObject -#~ import obkey_classes -import obkey_classes as obkey_classes -import xml.dom.minidom - -def die(widget, data=None): - Gtk.main_quit() - -# get rc file -path = os.getenv("HOME") + "/.config/openbox/rc.xml" -if len(sys.argv) == 2: - path = sys.argv[1] - -ob = obkey_classes.OpenboxConfig() -ob.load(path) - -win = Gtk.Window(Gtk.WindowType.TOPLEVEL) -win.set_default_size(800,480) -win.set_title(obkey_classes.config_title) -win.connect("destroy", die) - -tbl = obkey_classes.PropertyTable() -al = obkey_classes.ActionList(tbl) -ktbl = obkey_classes.KeyTable(al, ob) - -vbox = Gtk.VPaned() -vbox.pack1(tbl.widget, True, False) -vbox.pack2(al.widget, True, False) - -hbox = Gtk.HPaned() -hbox.pack1(ktbl.widget, True, False) -hbox.pack2(vbox, False, False) - -win.add(hbox) -win.show_all() -# get rid of stupid autocalculation -w, h = win.get_size() -hbox.set_position(w-250) -ktbl.view.grab_focus() -Gtk.main() +""" + Openbox Key Editor + Copyright (C) 2009 nsf + v1.1 - Code migrated from PyGTK to PyGObject + github.com/stevenhoneyman/obkey + v1.2pre - 19.06.2016 - structured presentation of actions... + v1.2 - 24.02.2018 - slightly refactored code - more dynamic + github.com/luffah/obkey + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +""" + +import sys +import os +import re +from obkey_parts import PropertyTable, KeyTable, ActionList, OpenboxConfig, Gtk + + +def get_rcfile(): + """return the rcfile from args or find it""" + ret = None + rcfileregex = re.compile(r'.*\.xml.*') + rcfiles = filter(rcfileregex.match, sys.argv) + if rcfiles: + ret = rcfiles[0] + else: + try: + import psutil + # get rc file from current running openbox + for pdesc in psutil.process_iter(): + proc = pdesc.as_dict(attrs=['pid', 'name', 'cmdline']) + if proc['name'] == 'openbox': + ob_params = proc['cmdline'] + param_name = '--config-file' + if param_name in ob_params: + ret = ob_params[ob_params.index(param_name) + 1] + except ImportError: + pass + return ret or (os.getenv("HOME") + "/.config/openbox/rc.xml") + + +def die(window, application_data=None): + """kill the application""" + if application_data: + pass + if window: + Gtk.main_quit() + + +def view(): + """Initialize obkey window""" + + obkeyconf = OpenboxConfig() + obkeyconf.load(get_rcfile()) + + window = Gtk.Window() + + window.set_title('obkey') + window.connect("destroy", die) + + width = window.get_preferred_width()[1] + height = window.get_preferred_height()[1] + + propertytable = PropertyTable() + actionlist = ActionList(propertytable) + keytable = KeyTable(actionlist, obkeyconf) + VBOX_GRID = False + # FIXME: none of the 2 solutions are clean + if VBOX_GRID: + # FIXME : got problems when changing the element in the keytabl + window.set_default_size(max(width,800), max(height,600)) + verticalbox = Gtk.VPaned() + verticalbox.pack1(propertytable.widget, True, True) + verticalbox.pack2(actionlist.widget, True, True) + + horizontalbox = Gtk.HPaned() + horizontalbox.pack1(keytable.widget, True, False) + horizontalbox.pack2(verticalbox, False, False) + window.add(horizontalbox) + else : + # FIXME : got problems in fullscreen mode + window.set_default_size(width, height) + grid = Gtk.Grid(column_homogeneous=True, column_spacing=5, row_spacing=200) + grid.attach(keytable.widget, 0, 0, 2, 4) + grid.attach(propertytable.widget, 2, 0, 1, 3) + grid.attach(actionlist.widget, 2, 2, 1, 2) + grid.set_hexpand(True) + grid.set_vexpand(True) + window.add(grid) + window.show_all() + keytable.view.grab_focus() + window.set_default_size(height, width) + Gtk.main() + + +OPT_DESC = { + 'rc.xml': ['OpenBox configuration file to edit'], + '--help|-h': ['Show this help'], +} + +def usage(): + """usage""" + opt = [] + for (optname, optdesc) in OPT_DESC.items(): + tmpopt = [optname] + if len(optdesc) > 1: # params + tmpopt.extend(optdesc[1].keys()) + opt.append(' '.join(tmpopt)) + print( + 'Usage : ' + sys.argv[0] + ' ' + + ' '.join( + [('[%s]' % a) for a in opt] + ) + ) + for (optname, optdesc) in OPT_DESC.items(): + print "{0:<14s} {1:s}".format(optname, optdesc[0]) + tmpopt = [optname] + if len(optdesc) > 1: # params + for (param, pdesc) in optdesc[1].items(): + print " {0:<14s} {1:s}".format(param, pdesc) + +if __name__ == '__main__': + help_re = re.compile('--help|-h') + help_matched = filter(help_re.match, sys.argv) + if help_matched: + usage() + else: + view() diff --git a/obkey.deb b/obkey.deb new file mode 100644 index 0000000..04a8bc6 Binary files /dev/null and b/obkey.deb differ diff --git a/obkey_classes.py b/obkey_classes.py deleted file mode 100644 index 877ea15..0000000 --- a/obkey_classes.py +++ /dev/null @@ -1,2048 +0,0 @@ -#!/usr/bin/python2 -#------------------------------------------------------------------------------ -# Openbox Key Editor -# Copyright (C) 2009 nsf -# v1.1 - Code migrated from PyGTK to PyGObject github.com/stevenhoneyman/obkey -# v1.2pre1 - solve a minor bug on copy-paste bug github.com/luffah/obkey -# v1.2pre2 - structured presentation of actions... github.com/luffah/obkey -# (v1.2 - aims to add drag and drop, classed actions, and alerting) -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -#------------------------------------------------------------------------------ - -import xml.dom.minidom -from StringIO import StringIO -from string import strip -from gi.repository import GObject -import copy -from gi.repository import Gtk -import sys -import os -import gettext -from xml.sax.saxutils import escape - -#===================================================================================== -# Config -#===================================================================================== - -# XXX: Sorry, for now this is it. If you know a better way to do this with setup.py: -# please mail me. - -config_prefix = '/usr' -config_icons = os.path.join(config_prefix, 'share/obkey/icons') -#~ config_locale_dir = os.path.join(config_prefix, 'share/locale') -config_locale_dir = os.path.join('./locale') - -gettext.install('obkey', config_locale_dir) # init gettext - -# localized title -config_title = _('obkey') - -#===================================================================================== -# Key utils -#===================================================================================== - -replace_table_openbox2gtk = { - "mod1" : "", - "mod2" : "", - "mod3" : "", - "mod4" : "", - "mod5" : "", - "control" : "", - "c" : "", - "alt" : "", - "a" : "", - "meta" : "", - "m" : "", - "super" : "", - "w" : "", - "shift" : "", - "s" : "", - "hyper" : "", - "h" : "" - -} - -replace_table_gtk2openbox = { - "Mod1" : "Mod1", - "Mod2" : "Mod2", - "Mod3" : "Mod3", - "Mod4" : "Mod4", - "Mod5" : "Mod5", - "Control" : "C", - "Primary" : "C", - "Alt" : "A", - "Meta" : "M", - "Super" : "W", - "Shift" : "S", - "Hyper" : "H" -} - -def key_openbox2gtk(obstr): - toks = obstr.split("-") - try: - toksgdk = [replace_table_openbox2gtk[mod.lower()] for mod in toks[:-1]] - except: - return (0, 0) - toksgdk.append(toks[-1]) - return Gtk.accelerator_parse("".join(toksgdk)) - -def key_gtk2openbox(key, mods): - result = "" - if mods: - s = Gtk.accelerator_name(0, mods) - svec = [replace_table_gtk2openbox[i] for i in s[1:-1].split('><')] - result = '-'.join(svec) - if key: - k = Gtk.accelerator_name(key, 0) - if result != "": - result += '-' - result += k - return result - -#===================================================================================== -# This is the uber cool switchers/conditions(sensors) system. -# Helps a lot with widgets sensitivity. -#===================================================================================== - -class SensCondition: - def __init__(self, initial_state): - self.switchers = [] - self.state = initial_state - - def register_switcher(self, sw): - self.switchers.append(sw) - - def set_state(self, state): - if self.state == state: - return - self.state = state - for sw in self.switchers: - sw.notify() - -class SensSwitcher: - def __init__(self, conditions): - self.conditions = conditions - self.widgets = [] - - for c in conditions: - c.register_switcher(self) - - def append(self, widget): - self.widgets.append(widget) - - def set_sensitive(self, state): - for w in self.widgets: - w.set_sensitive(state) - - def notify(self): - for c in self.conditions: - if not c.state: - self.set_sensitive(False) - return - self.set_sensitive(True) - -#===================================================================================== -# KeyTable -#===================================================================================== - -class KeyTable: - def __init__(self, actionlist, ob): - self.widget = Gtk.VBox() - self.ob = ob - self.actionlist = actionlist - actionlist.set_callback(self.actions_cb) - - self.icons = self.load_icons() - - self.model, self.cqk_model = self.create_models() - self.view, self.cqk_view = self.create_views(self.model, self.cqk_model) - - # copy & paste - self.copied = None - - # sensitivity switchers & conditions - self.cond_insert_child = SensCondition(False) - self.cond_paste_buffer = SensCondition(False) - self.cond_selection_available = SensCondition(False) - - self.sw_insert_child_and_paste = SensSwitcher([self.cond_insert_child, self.cond_paste_buffer]) - self.sw_insert_child = SensSwitcher([self.cond_insert_child]) - self.sw_paste_buffer = SensSwitcher([self.cond_paste_buffer]) - self.sw_selection_available = SensSwitcher([self.cond_selection_available]) - - # self.context_menu - self.context_menu = self.create_context_menu() - - for kb in self.ob.keyboard.keybinds: - self.apply_keybind(kb) - - self.apply_cqk_initial_value() - - # self.add_child_button - self.widget.pack_start(self.create_toolbar(), False, True, 0) - self.widget.pack_start(self.create_scroll(self.view), True, True, 0) - self.widget.pack_start(self.create_cqk_hbox(self.cqk_view), False, True, 0) - - if len(self.model): - self.view.get_selection().select_iter(self.model.get_iter_first()) - - self.sw_insert_child_and_paste.notify() - self.sw_insert_child.notify() - self.sw_paste_buffer.notify() - self.sw_selection_available.notify() - - def create_cqk_hbox(self, cqk_view): - cqk_hbox = Gtk.HBox() - cqk_label = Gtk.Label(label=_("chainQuitKey:")) - cqk_label.set_padding(5,5) - - cqk_frame = Gtk.Frame() - cqk_frame.add(cqk_view) - - cqk_hbox.pack_start(cqk_label, False, False, False) - cqk_hbox.pack_start(cqk_frame, True, True, 0) - return cqk_hbox - - def create_context_menu(self): - context_menu = Gtk.Menu() - self.context_items = {} - - item = Gtk.ImageMenuItem(Gtk.STOCK_CUT) - item.connect('activate', lambda menu: self.cut_selected()) - item.get_child().set_label(_("Cut")) - context_menu.append(item) - self.sw_selection_available.append(item) - - item = Gtk.ImageMenuItem(Gtk.STOCK_COPY) - item.connect('activate', lambda menu: self.copy_selected()) - item.get_child().set_label(_("Copy")) - context_menu.append(item) - self.sw_selection_available.append(item) - - item = Gtk.ImageMenuItem(Gtk.STOCK_PASTE) - item.connect('activate', lambda menu: self.insert_sibling(copy.deepcopy(self.copied))) - item.get_child().set_label(_("Paste")) - context_menu.append(item) - self.sw_paste_buffer.append(item) - - item = Gtk.ImageMenuItem(Gtk.STOCK_PASTE) - item.get_child().set_label(_("Paste as child")) - item.connect('activate', lambda menu: self.insert_child(copy.deepcopy(self.copied))) - context_menu.append(item) - self.sw_insert_child_and_paste.append(item) - - item = Gtk.ImageMenuItem(Gtk.STOCK_REMOVE) - item.connect('activate', lambda menu: self.del_selected()) - item.get_child().set_label(_("Remove")) - context_menu.append(item) - self.sw_selection_available.append(item) - - context_menu.show_all() - return context_menu - - def create_models(self): - model = Gtk.TreeStore(GObject.TYPE_UINT, # accel key - GObject.TYPE_INT, # accel mods - GObject.TYPE_STRING, # accel string (openbox) - GObject.TYPE_BOOLEAN, # chroot - GObject.TYPE_BOOLEAN, # show chroot - GObject.TYPE_PYOBJECT, # OBKeyBind - GObject.TYPE_STRING # keybind descriptor - ) - - cqk_model = Gtk.ListStore(GObject.TYPE_UINT, # accel key - GObject.TYPE_INT, # accel mods - GObject.TYPE_STRING) # accel string (openbox) - return (model, cqk_model) - - def create_scroll(self, view): - scroll = Gtk.ScrolledWindow() - scroll.add(view) - scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) - scroll.set_shadow_type(Gtk.ShadowType.IN) - return scroll - - def create_views(self, model, cqk_model): - # added accel_mode=1 (CELL_RENDERER_ACCEL_MODE_OTHER) for key "Tab" - r0 = Gtk.CellRendererAccel(accel_mode=1) - r0.props.editable = True - r0.connect('accel-edited', self.accel_edited) - - r1 = Gtk.CellRendererText() - r1.props.editable = True - r1.connect('edited', self.key_edited) - - r3 = Gtk.CellRendererText() - r3.props.editable = False - - r2 = Gtk.CellRendererToggle() - r2.connect('toggled', self.chroot_toggled) - - c0 = Gtk.TreeViewColumn(_("Key"), r0, accel_key=0, accel_mods=1) - c1 = Gtk.TreeViewColumn(_("Key (text)"), r1, text=2) - c2 = Gtk.TreeViewColumn(_("Chroot"), r2, active=3, visible=4) - c3 = Gtk.TreeViewColumn(_("Action"), r3, text=6) - - c0.set_expand(True) - - view = Gtk.TreeView(model) - view.append_column(c3) - view.append_column(c0) - view.append_column(c1) - view.append_column(c2) - view.get_selection().connect('changed', self.view_cursor_changed) - view.connect('button-press-event', self.view_button_clicked) - - # chainQuitKey table (wtf hack) - - r0 = Gtk.CellRendererAccel() - r0.props.editable = True - r0.connect('accel-edited', self.cqk_accel_edited) - - r1 = Gtk.CellRendererText() - r1.props.editable = True - r1.connect('edited', self.cqk_key_edited) - - c0 = Gtk.TreeViewColumn("Key", r0, accel_key=0, accel_mods=1) - c1 = Gtk.TreeViewColumn("Key (text)", r1, text=2) - - c0.set_expand(True) - - def cqk_view_focus_lost(view, event): - view.get_selection().unselect_all() - - cqk_view = Gtk.TreeView(cqk_model) - cqk_view.set_headers_visible(False) - cqk_view.append_column(c0) - cqk_view.append_column(c1) - cqk_view.connect('focus-out-event', cqk_view_focus_lost) - return (view, cqk_view) - - def create_toolbar(self): - toolbar = Gtk.Toolbar() - toolbar.set_style(Gtk.ToolbarStyle.ICONS) - toolbar.set_show_arrow(False) - - but = Gtk.ToolButton(Gtk.STOCK_SAVE) - but.set_tooltip_text(_("Save ") + self.ob.path + _(" file")) - but.connect('clicked', lambda but: self.ob.save()) - toolbar.insert(but, -1) - - toolbar.insert(Gtk.SeparatorToolItem(), -1) - - but = Gtk.ToolButton(Gtk.STOCK_DND_MULTIPLE) - but.set_tooltip_text(_("Duplicate sibling keybind")) - but.connect('clicked', lambda but: self.duplicate_selected()) - toolbar.insert(but, -1) - - but = Gtk.ToolButton(Gtk.STOCK_COPY) - but.set_tooltip_text(_("Copy")) - but.connect('clicked', lambda but: self.copy_selected()) - toolbar.insert(but, -1) - - but = Gtk.ToolButton() - but.set_icon_widget(self.icons['add_sibling']) - but.set_tooltip_text(_("Insert sibling keybind")) - but.connect('clicked', lambda but: self.insert_sibling(OBKeyBind())) - toolbar.insert(but, -1) - - but = Gtk.ToolButton(Gtk.STOCK_PASTE) - but.set_tooltip_text(_("Paste")) - but.connect('clicked', lambda but: self.insert_sibling(copy.deepcopy(self.copied))) - toolbar.insert(but, -1) - - - but = Gtk.ToolButton() - but.set_icon_widget(self.icons['add_child']) - but.set_tooltip_text(_("Insert child keybind")) - but.connect('clicked', lambda but: self.insert_child(OBKeyBind())) - toolbar.insert(but, -1) - - but = Gtk.ToolButton(Gtk.STOCK_PASTE) - but.set_tooltip_text(_("Paste as child")) - but.connect('clicked', lambda but: self.insert_child(copy.deepcopy(self.copied))) - toolbar.insert(but, -1) - - but = Gtk.ToolButton(Gtk.STOCK_REMOVE) - but.set_tooltip_text(_("Remove keybind")) - but.connect('clicked', lambda but: self.del_selected()) - toolbar.insert(but, -1) - self.sw_selection_available.append(but) - - sep = Gtk.SeparatorToolItem() - sep.set_draw(False) - sep.set_expand(True) - toolbar.insert(sep, -1) - - toolbar.insert(Gtk.SeparatorToolItem(), -1) - - but = Gtk.ToolButton(Gtk.STOCK_QUIT) - but.set_tooltip_text(_("Quit application")) - but.connect('clicked', lambda but: Gtk.main_quit()) - toolbar.insert(but, -1) - return toolbar - - def apply_cqk_initial_value(self): - cqk_accel_key, cqk_accel_mods = key_openbox2gtk(self.ob.keyboard.chainQuitKey) - if cqk_accel_mods == 0: - self.ob.keyboard.chainQuitKey = "" - self.cqk_model.append((cqk_accel_key, cqk_accel_mods, self.ob.keyboard.chainQuitKey)) - - def get_action_desc(self,kb): - # frst_action added in Version 1.2 - if len(kb.actions) > 0: - if kb.actions[0].name == "Execute": - frst_action = "[ " + kb.actions[0].options['command'] + " ]" - elif kb.actions[0].name == "SendToDesktop": - frst_action = _(kb.actions[0].name) + " " + str(kb.actions[0].options['desktop']) - elif kb.actions[0].name == "Desktop": - frst_action = _(kb.actions[0].name) + " " + str(kb.actions[0].options['desktop']) - else : - frst_action = _(kb.actions[0].name) - else : - frst_action = "." - return frst_action - - def apply_keybind(self, kb, parent=None): - accel_key, accel_mods = key_openbox2gtk(kb.key) - chroot = kb.chroot - show_chroot = len(kb.children) > 0 or not len(kb.actions) - - n = self.model.append(parent, - (accel_key, accel_mods, kb.key, chroot, show_chroot, kb, self.get_action_desc(kb))) - - for c in kb.children: - self.apply_keybind(c, n) - - def load_icons(self): - icons = {} - icons_path = 'icons' - if os.path.isdir(config_icons): - icons_path = config_icons - icons['add_sibling'] = Gtk.Image.new_from_file(os.path.join(icons_path, "add_sibling.png")) - icons['add_child'] = Gtk.Image.new_from_file(os.path.join(icons_path, "add_child.png")) - return icons - - #----------------------------------------------------------------------------- - # callbacks - - def view_button_clicked(self, view, event): - if event.button == 3: - x = int(event.x) - y = int(event.y) - time = event.time - pathinfo = view.get_path_at_pos(x, y) - if pathinfo: - path, col, cellx, celly = pathinfo - view.grab_focus() - view.set_cursor(path, col, 0) - self.context_menu.popup(None, None, None, None, event.button, time) - else: - view.grab_focus() - view.get_selection().unselect_all() - self.context_menu.popup(None, None, None, None, event.button, time) - return 1 - - def actions_cb(self): - (model, it) = self.view.get_selection().get_selected() - kb = model.get_value(it, 5) - if len(kb.actions) == 0: - model.set_value(it, 4, True) - self.cond_insert_child.set_state(True) - else: - model.set_value(it, 4, False) - self.cond_insert_child.set_state(False) - - def view_cursor_changed(self, selection): - (model, it) = selection.get_selected() - actions = None - if it: - kb = model.get_value(it, 5) - if len(kb.children) == 0 and not kb.chroot: - actions = kb.actions - self.cond_selection_available.set_state(True) - self.cond_insert_child.set_state(len(kb.actions) == 0) - else: - self.cond_insert_child.set_state(False) - self.cond_selection_available.set_state(False) - self.actionlist.set_actions(actions) - - def cqk_accel_edited(self, cell, path, accel_key, accel_mods, keycode): - self.cqk_model[path][0] = accel_key - self.cqk_model[path][1] = accel_mods - kstr = key_gtk2openbox(accel_key, accel_mods) - self.cqk_model[path][2] = kstr - self.ob.keyboard.chainQuitKey = kstr - self.view.grab_focus() - - def cqk_key_edited(self, cell, path, text): - self.cqk_model[path][0], self.cqk_model[path][1] = key_openbox2gtk(text) - self.cqk_model[path][2] = text - self.ob.keyboard.chainQuitKey = text - self.view.grab_focus() - - def accel_edited(self, cell, path, accel_key, accel_mods, keycode): - self.model[path][0] = accel_key - self.model[path][1] = accel_mods - kstr = key_gtk2openbox(accel_key, accel_mods) - self.model[path][2] = kstr - self.model[path][5].key = kstr - - def key_edited(self, cell, path, text): - self.model[path][0], self.model[path][1] = key_openbox2gtk(text) - self.model[path][2] = text - self.model[path][5].key = text - - def chroot_toggled(self, cell, path): - self.model[path][3] = not self.model[path][3] - kb = self.model[path][5] - kb.chroot = self.model[path][3] - if kb.chroot: - self.actionlist.set_actions(None) - elif not kb.children: - self.actionlist.set_actions(kb.actions) - - #----------------------------------------------------------------------------- - def cut_selected(self): - self.copy_selected() - self.del_selected() - - def duplicate_selected(self): - self.copy_selected() - self.insert_sibling(copy.deepcopy(self.copied)) - - def copy_selected(self): - (model, it) = self.view.get_selection().get_selected() - if it: - sel = model.get_value(it, 5) - self.copied = copy.deepcopy(sel) - self.cond_paste_buffer.set_state(True) - - def _insert_keybind(self, keybind, parent=None, after=None): - keybind.parent = parent - if parent: - kbs = parent.children - else: - kbs = self.ob.keyboard.keybinds - - if after: - kbs.insert(kbs.index(after)+1, keybind) - else: - kbs.append(keybind) - - def insert_sibling(self, keybind): - (model, it) = self.view.get_selection().get_selected() - - accel_key, accel_mods = key_openbox2gtk(keybind.key) - show_chroot = len(keybind.children) > 0 or not len(keybind.actions) - - if it: - parent_it = model.iter_parent(it) - parent = None - if parent_it: - parent = model.get_value(parent_it, 5) - after = model.get_value(it, 5) - - self._insert_keybind(keybind, parent, after) - newit = self.model.insert_after(parent_it, it, - (accel_key, accel_mods, keybind.key, keybind.chroot, show_chroot, keybind, self.get_action_desc(keybind))) - else: - self._insert_keybind(keybind) - newit = self.model.append(None, - (accel_key, accel_mods, keybind.key, keybind.chroot, show_chroot, keybind, self.get_action_desc(keybind))) - - if newit: - for c in keybind.children: - self.apply_keybind(c, newit) - self.view.get_selection().select_iter(newit) - - def insert_child(self, keybind): - (model, it) = self.view.get_selection().get_selected() - parent = model.get_value(it, 5) - self._insert_keybind(keybind, parent) - - accel_key, accel_mods = key_openbox2gtk(keybind.key) - show_chroot = len(keybind.children) > 0 or not len(keybind.actions) - - newit = self.model.append(it, - (accel_key, accel_mods, keybind.key, keybind.chroot, show_chroot, keybind)) - - # it means that we have inserted first child here, change status - if len(parent.children) == 1: - self.actionlist.set_actions(None) - - def del_selected(self): - (model, it) = self.view.get_selection().get_selected() - if it: - kb = model.get_value(it, 5) - kbs = self.ob.keyboard.keybinds - if kb.parent: - kbs = kb.parent.children - kbs.remove(kb) - isok = self.model.remove(it) - if isok: - self.view.get_selection().select_iter(it) - - -#===================================================================================== -# PropertyTable -#===================================================================================== - -class PropertyTable: - def __init__(self): - self.widget = Gtk.ScrolledWindow() - self.table = Gtk.Table(1,2) - self.table.set_row_spacings(5) - self.widget.add_with_viewport(self.table) - self.widget.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) - - def add_row(self, label_text, table): - label = Gtk.Label(label=_(label_text)) - label.set_alignment(0, 0) - row = self.table.props.n_rows - self.table.attach(label, 0, 1, row, row+1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 0, 5, 0) - self.table.attach(table, 1, 2, row, row+1, Gtk.AttachOptions.FILL, 0, 5, 0) - - def clear(self): - cs = self.table.get_children() - cs.reverse() - for c in cs: - self.table.remove(c) - self.table.resize(1, 2) - - def set_action(self, action): - self.clear() - if not action: - return - for a in action.option_defs: - widget = a.generate_widget(action) - # IF can return a list - if isinstance(widget,list): - for row in widget: - self.add_row(row['name'] + ":", row['widget']) - else: - self.add_row(a.name + ":", widget) - self.table.queue_resize() - self.table.show_all() - -#===================================================================================== -# ActionList -#===================================================================================== - -class ActionList: - def __init__(self, proptable=None): - self.widget = Gtk.VBox() - self.actions = None - self.proptable = proptable - - # actions callback, called when action added or deleted - # for chroot possibility tracing - self.actions_cb = None - - # copy & paste buffer - self.copied = None - - # sensitivity switchers & conditions - self.cond_paste_buffer = SensCondition(False) - self.cond_selection_available = SensCondition(False) - self.cond_action_list_nonempty = SensCondition(False) - self.cond_can_move_up = SensCondition(False) - self.cond_can_move_down = SensCondition(False) - - self.sw_paste_buffer = SensSwitcher([self.cond_paste_buffer]) - self.sw_selection_available = SensSwitcher([self.cond_selection_available]) - self.sw_action_list_nonempty = SensSwitcher([self.cond_action_list_nonempty]) - self.sw_can_move_up = SensSwitcher([self.cond_can_move_up]) - self.sw_can_move_down = SensSwitcher([self.cond_can_move_down]) - - self.model = self.create_model() - self.view = self.create_view(self.model) - - self.context_menu = self.create_context_menu() - - self.widget.pack_start(self.create_scroll(self.view), True, True, 0) - self.widget.pack_start(self.create_toolbar(), False, True, 0) - - self.sw_paste_buffer.notify() - self.sw_selection_available.notify() - self.sw_action_list_nonempty.notify() - self.sw_can_move_up.notify() - self.sw_can_move_down.notify() - - def create_model(self): - return Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_PYOBJECT) - - def create_choices(self, ch): - choices = ch - action_list1 = {} - - #~ Version 1.1 - #~ for a in actions: - #~ action_list[_(a)] = a - #~ for a in sorted(action_list.keys()): - #~ choices.append(None,[a,action_list[a]]) - - # Version 1.2 - for a in actions_choices: - action_list1[_(a)] = a - for a in sorted(action_list1.keys()): - action_list2 = {} - content_a=actions_choices[action_list1[a]] - if ( type(content_a) is dict ): - iter0 = choices.append(None,[a,""]) - - for b in content_a: - action_list2[_(b)] = b - - for b in sorted(action_list2.keys()): - action_list3 = {} - content_b=content_a[action_list2[b]] - if ( type(content_b) is dict ): - iter1 = choices.append(iter0,[b,""] ) - - for c in content_b: - action_list3[_(c)] = c - for c in sorted(action_list3.keys()): - choices.append(iter1,[c,action_list3[c]]) - - else: - choices.append(iter0,[b,action_list2[b]] ) - else: - choices.append(None,[a,action_list1[a]] ) - - return choices - - def create_scroll(self, view): - scroll = Gtk.ScrolledWindow() - scroll.add(view) - scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) - scroll.set_shadow_type(Gtk.ShadowType.IN) - return scroll - - def create_view(self, model): - renderer = Gtk.CellRendererCombo() - def editingstarted(cell, widget, path): - widget.set_wrap_width(1) - - chs = Gtk.TreeStore(GObject.TYPE_STRING,GObject.TYPE_STRING) - renderer.props.model = self.create_choices(chs) - renderer.props.text_column = 0 - renderer.props.editable = True - renderer.props.has_entry = False - renderer.connect('changed', self.action_class_changed) - renderer.connect('editing-started', editingstarted) - - column = Gtk.TreeViewColumn(_("Actions"), renderer, text=0) - - view = Gtk.TreeView(model) - view.append_column(column) - view.get_selection().connect('changed', self.view_cursor_changed) - view.connect('button-press-event', self.view_button_clicked) - return view - - def create_context_menu(self): - context_menu = Gtk.Menu() - self.context_items = {} - - item = Gtk.ImageMenuItem(Gtk.STOCK_CUT) - item.connect('activate', lambda menu: self.cut_selected()) - item.get_child().set_label(_("Cut")) - context_menu.append(item) - self.sw_selection_available.append(item) - - item = Gtk.ImageMenuItem(Gtk.STOCK_COPY) - item.connect('activate', lambda menu: self.copy_selected()) - item.get_child().set_label(_("Copy")) - context_menu.append(item) - self.sw_selection_available.append(item) - - item = Gtk.ImageMenuItem(Gtk.STOCK_PASTE) - item.connect('activate', lambda menu: self.insert_action(self.copied)) - item.get_child().set_label(_("Paste")) - context_menu.append(item) - self.sw_paste_buffer.append(item) - - item = Gtk.ImageMenuItem(Gtk.STOCK_REMOVE) - item.connect('activate', lambda menu: self.del_selected()) - item.get_child().set_label(_("Remove")) - context_menu.append(item) - self.sw_selection_available.append(item) - - context_menu.show_all() - return context_menu - - def create_toolbar(self): - toolbar = Gtk.Toolbar() - toolbar.set_style(Gtk.ToolbarStyle.ICONS) - toolbar.set_icon_size(Gtk.IconSize.SMALL_TOOLBAR) - toolbar.set_show_arrow(False) - - but = Gtk.ToolButton(Gtk.STOCK_ADD) - but.set_tooltip_text(_("Insert action")) - but.connect('clicked', lambda but: self.insert_action(OBAction("Focus"))) - toolbar.insert(but, -1) - - but = Gtk.ToolButton(Gtk.STOCK_REMOVE) - but.set_tooltip_text(_("Remove action")) - but.connect('clicked', lambda but: self.del_selected()) - toolbar.insert(but, -1) - self.sw_selection_available.append(but) - - but = Gtk.ToolButton(Gtk.STOCK_GO_UP) - but.set_tooltip_text(_("Move action up")) - but.connect('clicked', lambda but: self.move_selected_up()) - toolbar.insert(but, -1) - self.sw_can_move_up.append(but) - - but = Gtk.ToolButton(Gtk.STOCK_GO_DOWN) - but.set_tooltip_text(_("Move action down")) - but.connect('clicked', lambda but: self.move_selected_down()) - toolbar.insert(but, -1) - self.sw_can_move_down.append(but) - - sep = Gtk.SeparatorToolItem() - sep.set_draw(False) - sep.set_expand(True) - toolbar.insert(sep, -1) - - but = Gtk.ToolButton(Gtk.STOCK_DELETE) - but.set_tooltip_text(_("Remove all actions")) - but.connect('clicked', lambda but: self.clear()) - toolbar.insert(but, -1) - self.sw_action_list_nonempty.append(but) - return toolbar - #----------------------------------------------------------------------------- - # callbacks - - def view_button_clicked(self, view, event): - if event.button == 3: - x = int(event.x) - y = int(event.y) - time = event.time - pathinfo = view.get_path_at_pos(x, y) - if pathinfo: - path, col, cellx, celly = pathinfo - view.grab_focus() - view.set_cursor(path, col, 0) - self.context_menu.popup(None, None, None, event.button, time, False) - else: - view.grab_focus() - view.get_selection().unselect_all() - self.context_menu.popup(None, None, None, event.button, time, False) - return 1 - - def action_class_changed(self, combo, path, it): - m = combo.props.model - ntype = m.get_value(it, 1) - self.model[path][0] = m.get_value(it, 0) - self.model[path][1].mutate(ntype) - if self.proptable: - self.proptable.set_action(self.model[path][1]) - - def view_cursor_changed(self, selection): - (model, it) = selection.get_selected() - act = None - if it: - act = model.get_value(it, 1) - if self.proptable: - self.proptable.set_action(act) - if act: - l = len(self.actions) - i = self.actions.index(act) - self.cond_can_move_up.set_state(i != 0) - self.cond_can_move_down.set_state(l > 1 and i+1 < l) - self.cond_selection_available.set_state(True) - else: - self.cond_can_move_up.set_state(False) - self.cond_can_move_down.set_state(False) - self.cond_selection_available.set_state(False) - - #----------------------------------------------------------------------------- - def cut_selected(self): - self.copy_selected() - self.del_selected() - - def duplicate_selected(self): - self.copy_selected() - self.insert_action(self.copied) - - def copy_selected(self): - if self.actions is None: - return - - (model, it) = self.view.get_selection().get_selected() - if it: - a = model.get_value(it, 1) - self.copied = copy.deepcopy(a) - self.cond_paste_buffer.set_state(True) - - def clear(self): - if self.actions is None or not len(self.actions): - return - - del self.actions[:] - self.model.clear() - - self.cond_action_list_nonempty.set_state(False) - if self.actions_cb: - self.actions_cb() - - def move_selected_up(self): - if self.actions is None: - return - - (model, it) = self.view.get_selection().get_selected() - if not it: - return - - i, = self.model.get_path(it) - l = len(self.model) - self.cond_can_move_up.set_state(i-1 != 0) - self.cond_can_move_down.set_state(l > 1 and i < l) - if i == 0: - return - - itprev = self.model.get_iter(i-1) - self.model.swap(it, itprev) - action = self.model.get_value(it, 1) - - i = self.actions.index(action) - tmp = self.actions[i-1] - self.actions[i-1] = action - self.actions[i] = tmp - - def move_selected_down(self): - if self.actions is None: - return - - (model, it) = self.view.get_selection().get_selected() - if not it: - return - - i, = self.model.get_path(it) - l = len(self.model) - self.cond_can_move_up.set_state(i+1 != 0) - self.cond_can_move_down.set_state(l > 1 and i+2 < l) - if i+1 >= l: - return - - itnext = self.model.iter_next(it) - self.model.swap(it, itnext) - action = self.model.get_value(it, 1) - - i = self.actions.index(action) - tmp = self.actions[i+1] - self.actions[i+1] = action - self.actions[i] = tmp - - def insert_action(self, action): - if self.actions is None: - return - - (model, it) = self.view.get_selection().get_selected() - if it: - self._insert_action(action, model.get_value(it, 1)) - newit = self.model.insert_after(it, (_(action.name), action)) - else: - self._insert_action(action) - newit = self.model.append((_(action.name), action)) - - if newit: - self.view.get_selection().select_iter(newit) - - self.cond_action_list_nonempty.set_state(len(self.model)) - if self.actions_cb: - self.actions_cb() - - def del_selected(self): - if self.actions is None: - return - - (model, it) = self.view.get_selection().get_selected() - if it: - self.actions.remove(model.get_value(it, 1)) - isok = self.model.remove(it) - if isok: - self.view.get_selection().select_iter(it) - - self.cond_action_list_nonempty.set_state(len(self.model)) - if self.actions_cb: - self.actions_cb() - - #----------------------------------------------------------------------------- - - def set_actions(self, actionlist): - self.actions = actionlist - self.model.clear() - self.widget.set_sensitive(self.actions is not None) - if not self.actions: - return - for a in self.actions: - self.model.append((_(a.name), a)) - - if len(self.model): - self.view.get_selection().select_iter(self.model.get_iter_first()) - self.cond_action_list_nonempty.set_state(len(self.model)) - - def _insert_action(self, action, after=None): - if after: - self.actions.insert(self.actions.index(after)+1, action) - else: - self.actions.append(action) - - def set_callback(self, cb): - self.actions_cb = cb - -#===================================================================================== -# MiniActionList -#===================================================================================== - -class MiniActionList(ActionList): - def __init__(self, proptable=None): - ActionList.__init__(self, proptable) - self.widget.set_size_request(-1, 120) - self.view.set_headers_visible(False) - - def create_choices(self,ch): - choices = ch - action_list = {} - for a in actions: - action_list[_(a)] = a - for a in sorted(action_list.keys()): - if len(actions[action_list[a]]) == 0: - choices.append((a,action_list[a])) - return choices - -#===================================================================================== -# XML Utilites -#===================================================================================== -# parse - -def xml_parse_attr(elt, name): - return elt.getAttribute(name) - -def xml_parse_attr_bool(elt, name): - attr = elt.getAttribute(name).lower() - if attr == "true" or attr == "yes" or attr == "on": - return True - return False - -def xml_parse_string(elt): - if elt.hasChildNodes(): - return elt.firstChild.nodeValue - else: - return "" - -def xml_parse_bool(elt): - val = elt.firstChild.nodeValue.lower() - if val == "true" or val == "yes" or val == "on": - return True - return False - -def xml_find_nodes(elt, name): - children = [] - for n in elt.childNodes: - if n.nodeName == name: - children.append(n) - return children - -def xml_find_node(elt, name): - nodes = xml_find_nodes(elt, name) - if len(nodes) == 1: - return nodes[0] - else: - return None - -def fixed_writexml(self, writer, indent="", addindent="", newl=""): - # indent = current indentation - # addindent = indentation to add to higher levels - # newl = newline string - writer.write(indent+"<" + self.tagName) - - attrs = self._get_attributes() - a_names = attrs.keys() - a_names.sort() - - for a_name in a_names: - writer.write(" %s=\"" % a_name) - xml.dom.minidom._write_data(writer, attrs[a_name].value) - writer.write("\"") - if self.childNodes: - if len(self.childNodes) == 1 \ - and self.childNodes[0].nodeType == xml.dom.minidom.Node.TEXT_NODE: - writer.write(">") - self.childNodes[0].writexml(writer, "", "", "") - writer.write("%s" % (self.tagName, newl)) - return - writer.write(">%s" % newl) - for node in self.childNodes: - fixed_writexml(node, writer,indent+addindent,addindent,newl) - writer.write("%s%s" % (indent,self.tagName,newl)) - else: - writer.write("/>%s"%(newl)) - -def fixed_toprettyxml(self, indent="", addindent="\t", newl="\n"): - # indent = current indentation - # addindent = indentation to add to higher levels - # newl = newline string - writer = StringIO() - - fixed_writexml(self, writer, indent, addindent, newl) - return writer.getvalue() - -#===================================================================================== -# Openbox Glue -#===================================================================================== - -# Option Classes (for OBAction) -# 1. Parse function for OBAction to parse the data. -# 2. Getter(s) and Setter(s) for OBAction to operate on the data (registered by -# the parse function). -# 3. Widget generator for property editor to represent the data. -# Examples of such classes: string, int, filename, list of actions, -# list (choose one variant of many), string-int with custom validator(?) - -# Actions -# An array of Options: + -# These actions are being applied to OBAction instances. - -#===================================================================================== -# Option Class: String -#===================================================================================== - -class OCString(object): - __slots__ = ('name', 'default', 'alts') - - def __init__(self, name, default, alts=[]): - self.name = name - self.default = default - self.alts = alts - - def apply_default(self, action): - action.options[self.name] = self.default - - def parse(self, action, dom): - node = xml_find_node(dom, self.name) - if not node: - for a in self.alts: - node = xml_find_node(dom, a) - if node: - break - if node: - action.options[self.name] = xml_parse_string(node) - else: - action.options[self.name] = self.default - - def deparse(self, action): - val = action.options[self.name] - if val == self.default: - return None - val = escape(val) - return xml.dom.minidom.parseString("<"+str(self.name)+">"+str(val)+"").documentElement - - def generate_widget(self, action): - def changed(entry, action): - text = entry.get_text() - action.options[self.name] = text - - entry = Gtk.Entry() - entry.set_text(action.options[self.name]) - entry.connect('changed', changed, action) - return entry - -#===================================================================================== -# Option Class: Combo -#===================================================================================== - -class OCCombo(object): - __slots__ = ('name', 'default', 'choices') - - def __init__(self, name, default, choices): - self.name = name - self.default = default - self.choices = choices - - def apply_default(self, action): - action.options[self.name] = self.default - - def parse(self, action, dom): - node = xml_find_node(dom, self.name) - if node: - action.options[self.name] = xml_parse_string(node) - else: - action.options[self.name] = self.default - - def deparse(self, action): - val = action.options[self.name] - if val == self.default: - return None - return xml.dom.minidom.parseString("<"+str(self.name)+">"+str(val)+"").documentElement - - def generate_widget(self, action): - def changed(combo, action): - text = combo.get_active() - action.options[self.name] = self.choices[text] - - model = Gtk.ListStore(GObject.TYPE_STRING) - for c in self.choices: - model.append((_(c),)) - combo = Gtk.ComboBox() - combo.set_active(self.choices.index(action.options[self.name])) - combo.set_model(model) - cell = Gtk.CellRendererText() - combo.pack_start(cell, True) - combo.add_attribute(cell, 'text', 0) - combo.connect('changed', changed, action) - return combo - -#===================================================================================== -# Option Class: Number -#===================================================================================== - -class OCNumber(object): - __slots__ = ('name', 'default', 'min', 'max', 'explicit_defaults') - - def __init__(self, name, default, mmin, mmax, explicit_defaults=False): - self.name = name - self.default = default - self.min = mmin - self.max = mmax - self.explicit_defaults = explicit_defaults - - def apply_default(self, action): - action.options[self.name] = self.default - - def parse(self, action, dom): - node = xml_find_node(dom, self.name) - if node: - action.options[self.name] = int(float(xml_parse_string(node))) - else: - action.options[self.name] = self.default - - def deparse(self, action): - val = action.options[self.name] - if not self.explicit_defaults and (val == self.default): - return None - return xml.dom.minidom.parseString("<"+str(self.name)+">"+str(val)+"").documentElement - - def generate_widget(self, action): - def changed(num, action): - n = num.get_value_as_int() - action.options[self.name] = n - - num = Gtk.SpinButton() - num.set_increments(1, 5) - num.set_range(self.min, self.max) - num.set_value(action.options[self.name]) - num.connect('value-changed', changed, action) - return num - -#===================================================================================== -# Option Class: OCIf -# -# NO UI config yet -# -# Reason: keep manually defined IF key bindings -#===================================================================================== - -class OCIf(object): - __slots__ = ('name', 'default', 'props', 'then', 'els') - - def __init__(self, name, default): - self.name = name - self.default = default - - self.props = [] - self.then = [] - self.els = [] - - def apply_default(self, action): - action.options[self.name] = self.default - - def parse(self, action, dom): - node = xml_find_node(dom, self.name) - if dom.hasChildNodes(): - for child in dom.childNodes: - if child.nodeName == "then": - self.then = self._parseAction(dom,action,"then") - elif child.nodeName == "else": - self.els = self._parseAction(dom,action, "else") - else: - if not isinstance(child,xml.dom.minidom.Text): - self.props += [child] - - def _parseAction(self, dom,action,nodeName): - obAct = OCFinalActions() - obAct.name = nodeName - obAct.parse(action,dom); - return obAct - - def deparse(self, action): - frag = [] - - # props - for el in self.props: - frag.append(el) - - # conditions - #themEl = xml.dom.minidom.Element("then") - themEl = self.then.deparse(action) - themEl.tagName = "then" - - # else - elseEl = self.els.deparse(action) - elseEl.tagName = "else" - - frag.append(themEl) - frag.append(elseEl) - - ## print - #zz = xml.dom.minidom.Element("action") - #for el in frag: - # zz.appendChild(el) - #print zz.toxml() - - return frag - - def generate_widget(self, action): - label = Gtk.Label("IF Not fully supported yet") - opts = [] - for el in self.props: - opts.append({'name': "Cond.", "widget": Gtk.Label(el.toxml())}) - opts.append({'name': "then", "widget": self.then.generate_widget(action)}) - opts.append({'name':"else",'widget': self.els.generate_widget(action)}) - return opts - -#===================================================================================== -# Option Class: Boolean -#===================================================================================== - -class OCBoolean(object): - __slots__ = ('name', 'default') - - def __init__(self, name, default): - self.name = name - self.default = default - - def apply_default(self, action): - action.options[self.name] = self.default - - def parse(self, action, dom): - node = xml_find_node(dom, self.name) - if node: - action.options[self.name] = xml_parse_bool(node) - else: - action.options[self.name] = self.default - - def deparse(self, action): - if action.options[self.name] == self.default: - return None - if action.options[self.name]: - return xml.dom.minidom.parseString("<"+str(self.name)+">yes").documentElement - else: - return xml.dom.minidom.parseString("<"+str(self.name)+">no").documentElement - - def generate_widget(self, action): - def changed(checkbox, action): - active = checkbox.get_active() - action.options[self.name] = active - - check = Gtk.CheckButton() - check.set_active(action.options[self.name]) - check.connect('toggled', changed, action) - return check - -#===================================================================================== -# Option Class: StartupNotify -#===================================================================================== - -class OCStartupNotify(object): - def __init__(self): - self.name = "startupnotify" - - def apply_default(self, action): - action.options['startupnotify_enabled'] = False - action.options['startupnotify_wmclass'] = "" - action.options['startupnotify_name'] = "" - action.options['startupnotify_icon'] = "" - - def parse(self, action, dom): - self.apply_default(action) - - startupnotify = xml_find_node(dom, "startupnotify") - if not startupnotify: - return - - enabled = xml_find_node(startupnotify, "enabled") - if enabled: - action.options['startupnotify_enabled'] = xml_parse_bool(enabled) - wmclass = xml_find_node(startupnotify, "wmclass") - if wmclass: - action.options['startupnotify_wmclass'] = xml_parse_string(wmclass) - name = xml_find_node(startupnotify, "name") - if name: - action.options['startupnotify_name'] = xml_parse_string(name) - icon = xml_find_node(startupnotify, "icon") - if icon: - action.options['startupnotify_icon'] = xml_parse_string(icon) - - def deparse(self, action): - if not action.options['startupnotify_enabled']: - return None - root = xml.dom.minidom.parseString("yes").documentElement - if action.options['startupnotify_wmclass'] != "": - root.appendChild(xml.dom.minidom.parseString(""+action.options['startupnotify_wmclass']+"").documentElement) - if action.options['startupnotify_name'] != "": - root.appendChild(xml.dom.minidom.parseString(""+action.options['startupnotify_name']+"").documentElement) - if action.options['startupnotify_icon'] != "": - root.appendChild(xml.dom.minidom.parseString(""+action.options['startupnotify_icon']+"").documentElement) - return root - - def generate_widget(self, action): - def enabled_toggled(checkbox, action, sens_list): - active = checkbox.get_active() - action.options['startupnotify_enabled'] = active - for w in sens_list: - w.set_sensitive(active) - - def text_changed(textbox, action, var): - text = textbox.get_text() - action.options[var] = text - - - wmclass = Gtk.Entry() - wmclass.set_size_request(100,-1) - wmclass.set_text(action.options['startupnotify_wmclass']) - wmclass.connect('changed', text_changed, action, 'startupnotify_wmclass') - - name = Gtk.Entry() - name.set_size_request(100,-1) - name.set_text(action.options['startupnotify_name']) - name.connect('changed', text_changed, action, 'startupnotify_name') - - icon = Gtk.Entry() - icon.set_size_request(100,-1) - icon.set_text(action.options['startupnotify_icon']) - icon.connect('changed', text_changed, action, 'startupnotify_icon') - - sens_list = [wmclass, name, icon] - - enabled = Gtk.CheckButton() - enabled.set_active(action.options['startupnotify_enabled']) - enabled.connect('toggled', enabled_toggled, action, sens_list) - - def put_table(table, label_text, widget, row, addtosens=True): - label = Gtk.Label(label=_(label_text)) - label.set_padding(5,5) - label.set_alignment(0,0) - if addtosens: - sens_list.append(label) - table.attach(label, 0, 1, row, row+1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 0, 0, 0) - table.attach(widget, 1, 2, row, row+1, Gtk.AttachOptions.FILL, 0, 0, 0) - - table = Gtk.Table(1, 2) - put_table(table, "enabled:", enabled, 0, False) - put_table(table, "wmclass:", wmclass, 1) - put_table(table, "name:", name, 2) - put_table(table, "icon:", icon, 3) - - sens = enabled.get_active() - for w in sens_list: - w.set_sensitive(sens) - - frame = Gtk.Frame() - frame.add(table) - return frame - -#===================================================================================== -# Option Class: FinalActions -#===================================================================================== - -class OCFinalActions(object): - __slots__ = ('name') - - def __init__(self): - self.name = "finalactions" - - def apply_default(self, action): - a1 = OBAction() - a1.mutate("Focus") - a2 = OBAction() - a2.mutate("Raise") - a3 = OBAction() - a3.mutate("Unshade") - - action.options[self.name] = [a1, a2, a3] - - def parse(self, action, dom): - node = xml_find_node(dom, self.name) - action.options[self.name] = [] - if node: - for a in xml_find_nodes(node, "action"): - act = OBAction() - act.parse(a) - action.options[self.name].append(act) - else: - self.apply_default(action) - - def deparse(self, action): - a = action.options[self.name] - if len(a) == 3: - if a[0].name == "Focus" and a[1].name == "Raise" and a[2].name == "Unshade": - return None - if len(a) == 0: - return None - root = xml.dom.minidom.parseString("").documentElement - for act in a: - node = act.deparse() - root.appendChild(node) - return root - - def generate_widget(self, action): - w = MiniActionList() - w.set_actions(action.options[self.name]) - frame = Gtk.Frame() - frame.add(w.widget) - return frame - -#------------------------------------------------------------------------------------- - -#~ actions = { - #~ "Execute": [ - #~ OCString("command", "", ['execute']), - #~ OCString("prompt", ""), - #~ OCStartupNotify() - #~ ], - #~ "ShowMenu": [ - #~ OCString("menu", "") - #~ ], - #~ "NextWindow": [ - #~ OCCombo('dialog', 'list', ['list', 'icons', 'none']), - #~ OCBoolean("bar", True), - #~ OCBoolean("raise", False), - #~ OCBoolean("allDesktops", False), - #~ OCBoolean("panels", False), - #~ OCBoolean("desktop", False), - #~ OCBoolean("linear", False), - #~ OCFinalActions() - #~ ], - #~ "PreviousWindow": [ - #~ OCBoolean("dialog", True), - #~ OCBoolean("bar", True), - #~ OCBoolean("raise", False), - #~ OCBoolean("allDesktops", False), - #~ OCBoolean("panels", False), - #~ OCBoolean("desktop", False), - #~ OCBoolean("linear", False), - #~ OCFinalActions() - #~ ], - #~ "DirectionalFocusNorth": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - #~ "DirectionalFocusSouth": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - #~ "DirectionalFocusEast": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - #~ "DirectionalFocusWest": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - #~ "DirectionalFocusNorthEast": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - #~ "DirectionalFocusNorthWest": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - #~ "DirectionalFocusSouthEast": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - #~ "DirectionalFocusSouthWest": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - #~ "DirectionalTargetNorth": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - #~ "DirectionalTargetSouth": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - #~ "DirectionalTargetEast": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - #~ "DirectionalTargetWest": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - #~ "DirectionalTargetNorthEast": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - #~ "DirectionalTargetNorthWest": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - #~ "DirectionalTargetSouthEast": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - #~ "DirectionalTargetSouthWest": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - #~ "Desktop": [ OCNumber("desktop", 1, 1, 9999, True) ], - #~ "DesktopNext": [ OCBoolean("wrap", True) ], - #~ "DesktopPrevious": [ OCBoolean("wrap", True) ], - #~ "DesktopLeft": [ OCBoolean("wrap", True) ], - #~ "DesktopRight": [ OCBoolean("wrap", True) ], - #~ "DesktopUp": [ OCBoolean("wrap", True) ], - #~ "DesktopDown": [ OCBoolean("wrap", True) ], - #~ "GoToDesktop": [ OCString("to", ""), OCString("wrap", "") ], - #~ "DesktopLast": [], - #~ "AddDesktopLast": [], - #~ "RemoveDesktopLast": [], - #~ "AddDesktopCurrent": [], - #~ "RemoveDesktopCurrent": [], - #~ "ToggleShowDesktop": [], - #~ "ToggleDockAutohide": [], - #~ "Reconfigure": [], - #~ "Restart": [ OCString("command", "", ["execute"]) ], - #~ "Exit": [ OCBoolean("prompt", True) ], - #~ "SessionLogout": [ OCBoolean("prompt", True) ], - #~ "Debug": [ OCString("string", "") ], - #~ "If": [ OCIf("", "") ], -#~ - #~ "Focus": [], - #~ "Raise": [], - #~ "Lower": [], - #~ "RaiseLower": [], - #~ "Unfocus": [], - #~ "FocusToBottom": [], - #~ "Iconify": [], - #~ "Close": [], - #~ "ToggleShade": [], - #~ "Shade": [], - #~ "Unshade": [], - #~ "ToggleOmnipresent": [], - #~ "ToggleMaximizeFull": [], - #~ "MaximizeFull": [], - #~ "UnmaximizeFull": [], - #~ "ToggleMaximizeVert": [], - #~ "MaximizeVert": [], - #~ "UnmaximizeVert": [], - #~ "ToggleMaximizeHorz": [], - #~ "MaximizeHorz": [], - #~ "UnmaximizeHorz": [], - #~ "ToggleFullscreen": [], - #~ "ToggleDecorations": [], - #~ "Decorate": [], - #~ "Undecorate": [], - #~ "SendToDesktop": [ OCNumber("desktop", 1, 1, 9999, True), OCBoolean("follow", True) ], - #~ "SendToDesktopNext": [ OCBoolean("wrap", True), OCBoolean("follow", True) ], - #~ "SendToDesktopPrevious": [ OCBoolean("wrap", True), OCBoolean("follow", True) ], - #~ "SendToDesktopLeft": [ OCBoolean("wrap", True), OCBoolean("follow", True) ], - #~ "SendToDesktopRight": [ OCBoolean("wrap", True), OCBoolean("follow", True) ], - #~ "SendToDesktopUp": [ OCBoolean("wrap", True), OCBoolean("follow", True) ], - #~ "SendToDesktopDown": [ OCBoolean("wrap", True), OCBoolean("follow", True) ], - #~ "Move": [], - #~ "Resize": [ - #~ OCCombo("edge", "none", ['none', "top", "left", "right", "bottom", "topleft", "topright", "bottomleft", "bottomright"]) - #~ ], - #~ "MoveToCenter": [], - #~ "MoveResizeTo": [ - #~ OCString("x", "current"), - #~ OCString("y", "current"), - #~ OCString("width", "current"), - #~ OCString("height", "current"), - #~ OCString("monitor", "current") - #~ ], - #~ "MoveRelative": [ - #~ OCNumber("x", 0, -9999, 9999), - #~ OCNumber("y", 0, -9999, 9999) - #~ ], - #~ "ResizeRelative": [ - #~ OCNumber("left", 0, -9999, 9999), - #~ OCNumber("right", 0, -9999, 9999), - #~ OCNumber("top", 0, -9999, 9999), - #~ OCNumber("bottom", 0, -9999, 9999) - #~ ], - #~ "MoveToEdgeNorth": [], - #~ "MoveToEdgeSouth": [], - #~ "MoveToEdgeWest": [], - #~ "MoveToEdgeEast": [], - #~ "GrowToEdgeNorth": [], - #~ "GrowToEdgeSouth": [], - #~ "GrowToEdgeWest": [], - #~ "GrowToEdgeEast": [], - #~ "ShadeLower": [], - #~ "UnshadeRaise": [], - #~ "ToggleAlwaysOnTop": [], - #~ "ToggleAlwaysOnBottom": [], - #~ "SendToTopLayer": [], - #~ "SendToBottomLayer": [], - #~ "SendToNormalLayer": [], -#~ - #~ "BreakChroot": [] -#~ } - - -actions_window_nav = { - "NextWindow": [OCCombo('dialog', 'list', ['list', 'icons', 'none']),OCBoolean("bar", True),OCBoolean("raise", False),OCBoolean("allDesktops", False),OCBoolean("panels", False),OCBoolean("desktop", False),OCBoolean("linear", False),OCFinalActions()], - "PreviousWindow": [OCBoolean("dialog", True),OCBoolean("bar", True),OCBoolean("raise", False),OCBoolean("allDesktops", False),OCBoolean("panels", False),OCBoolean("desktop", False),OCBoolean("linear", False),OCFinalActions()], - "DirectionalFocusNorth": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - "DirectionalFocusSouth": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - "DirectionalFocusEast": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - "DirectionalFocusWest": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - "DirectionalFocusNorthEast": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - "DirectionalFocusNorthWest": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - "DirectionalFocusSouthEast": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - "DirectionalFocusSouthWest": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - "DirectionalTargetNorth": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - "DirectionalTargetSouth": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - "DirectionalTargetEast": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - "DirectionalTargetWest": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - "DirectionalTargetNorthEast": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - "DirectionalTargetNorthWest": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - "DirectionalTargetSouthEast": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], - "DirectionalTargetSouthWest": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ] -} -actions_desktop_nav_mov = { - "Desktop": [ OCNumber("desktop", 1, 1, 9999, True) ], - "DesktopNext": [ OCBoolean("wrap", True) ], - "DesktopPrevious": [ OCBoolean("wrap", True) ], - "DesktopLeft": [ OCBoolean("wrap", True) ], - "DesktopRight": [ OCBoolean("wrap", True) ], - "DesktopUp": [ OCBoolean("wrap", True) ], - "DesktopDown": [ OCBoolean("wrap", True) ], - "GoToDesktop": [ OCString("to", ""), OCString("wrap", "") ], - "DesktopLast": [] -} -actions_desktop_nav_del = { - "RemoveDesktopLast": [], - "RemoveDesktopCurrent": [] -} -actions_desktop_nav_add = { - "AddDesktopLast": [], - "AddDesktopCurrent": [] -} -actions_wm = { - "ShowMenu": [OCString("menu", "")], - "ToggleDockAutohide": [], - "Reconfigure": [], - "Restart": [ OCString("command", "", ["execute"]) ], - "Exit": [ OCBoolean("prompt", True) ], - "SessionLogout": [ OCBoolean("prompt", True) ], - "Debug": [ OCString("string", "") ], - "ToggleShowDesktop": [] -} -actions_window_focus = { - "Focus": [], - "Unfocus": [], - "FocusToBottom": [], - "RaiseLower": [], - "Raise": [], - "Lower": [], - "ShadeLower": [], - "UnshadeRaise": [], - "ToggleAlwaysOnTop": [], - "ToggleAlwaysOnBottom": [], - "SendToTopLayer": [], - "SendToBottomLayer": [], - "SendToNormalLayer": [] -} -actions_window_set = { - "Iconify": [], - "Close": [], - "ToggleShade": [], - "Shade": [], - "Unshade": [], - "ToggleOmnipresent": [], - "ToggleMaximizeFull": [], - "MaximizeFull": [], - "UnmaximizeFull": [], - "ToggleMaximizeVert": [], - "MaximizeVert": [], - "UnmaximizeVert": [], - "ToggleMaximizeHorz": [], - "MaximizeHorz": [], - "UnmaximizeHorz": [], - "ToggleFullscreen": [], - "ToggleDecorations": [], - "Decorate": [], - "Undecorate": [] -} - -actions_window_send = { - "SendToDesktop": [ OCNumber("desktop", 1, 1, 9999, True), OCBoolean("follow", True) ], - "SendToDesktopNext": [ OCBoolean("wrap", True), OCBoolean("follow", True) ], - "SendToDesktopPrevious": [ OCBoolean("wrap", True), OCBoolean("follow", True) ], - "SendToDesktopLeft": [ OCBoolean("wrap", True), OCBoolean("follow", True) ], - "SendToDesktopRight": [ OCBoolean("wrap", True), OCBoolean("follow", True) ], - "SendToDesktopUp": [ OCBoolean("wrap", True), OCBoolean("follow", True) ], - "SendToDesktopDown": [ OCBoolean("wrap", True), OCBoolean("follow", True) ] -} -actions_window_move = { - "Move": [], - "MoveToCenter": [], - "MoveResizeTo": [ - OCString("x", "current"), - OCString("y", "current"), - OCString("width", "current"), - OCString("height", "current"), - OCString("monitor", "current") - ], - "MoveRelative": [ - OCNumber("x", 0, -9999, 9999), - OCNumber("y", 0, -9999, 9999) - ], - "MoveToEdgeNorth": [], - "MoveToEdgeSouth": [], - "MoveToEdgeWest": [], - "MoveToEdgeEast": [] -} -actions_window_resize = { - "Resize": [ - OCCombo("edge", "none", ['none', "top", "left", "right", "bottom", "topleft", "topright", "bottomleft", "bottomright"]) - ], - "ResizeRelative": [ - OCNumber("left", 0, -9999, 9999), - OCNumber("right", 0, -9999, 9999), - OCNumber("top", 0, -9999, 9999), - OCNumber("bottom", 0, -9999, 9999) - ], - "GrowToEdgeNorth": [], - "GrowToEdgeSouth": [], - "GrowToEdgeWest": [], - "GrowToEdgeEast": [] -} - -actions_choices = { - "Execute": [ - OCString("command", "", ['execute']), - OCString("prompt", ""), - OCStartupNotify() - ], -# IF Not fully supported yet -# "If": [ OCIf("", "") ], - "BreakChroot": [] -} - -actions={} -for k in actions_choices: - actions[k]=actions_choices[k] -for k in actions_window_nav: - actions[k]=actions_window_nav[k] -for k in actions_window_focus: - actions[k]=actions_window_focus[k] -for k in actions_window_move: - actions[k]=actions_window_move[k] -for k in actions_window_resize: - actions[k]=actions_window_resize[k] -for k in actions_window_send: - actions[k]=actions_window_send[k] -for k in actions_desktop_nav_add: - actions[k]=actions_desktop_nav_add[k] -for k in actions_desktop_nav_del: - actions[k]=actions_desktop_nav_del[k] -for k in actions_desktop_nav_mov: - actions[k]=actions_desktop_nav_mov[k] -for k in actions_window_set: - actions[k]=actions_window_set[k] -for k in actions_wm: - actions[k]=actions_wm[k] - -actions_choices["Window Navigation"]=actions_window_nav; -actions_choices["Window Focus"]=actions_window_focus; -actions_choices["Window Move"]=actions_window_move; -actions_choices["Window Resize"]=actions_window_resize; -actions_choices["Window Desktop Change"]=actions_window_send; -actions_choices["Desktop Navigation"]={ - "Add desktop" : actions_desktop_nav_add, - "Remove desktop" : actions_desktop_nav_del, - "Move to desktop" :actions_desktop_nav_mov - }; -actions_choices["Window Properties"]=actions_window_set; -actions_choices["Window/Session Management"]=actions_wm; - - -#===================================================================================== -# Config parsing and interaction -#===================================================================================== - -class OBAction: - def __init__(self, name=None): - self.options = {} - self.option_defs = [] - self.name = name - if name: - self.mutate(name) - - def parse(self, dom): - # call parseChild if childNodes exist - if dom.hasChildNodes(): - for child in dom.childNodes: - self.parseChild(child) - - # parse 'name' attribute, get options hash and parse - self.name = xml_parse_attr(dom, "name") - - try: - self.option_defs = actions[self.name] - except KeyError: - pass - - for od in self.option_defs: - od.parse(self, dom) - - # calls itself until no childNodes are found and strip() values of last node - def parseChild(self, dom): - try: - if dom.hasChildNodes(): - for child in dom.childNodes: - try: - child.nodeValue = child.nodeValue.strip() - except AttributeError: - pass - self.parseChild(child) - except AttributeError: - pass - else: - try: - dom.nodeValue = dom.nodeValue.strip() - except AttributeError: - pass - - def deparse(self): - root = xml.dom.minidom.parseString('').documentElement - for od in self.option_defs: - od_node = od.deparse(self) - if isinstance(od_node,list): - for el in od_node: - root.appendChild(el) - elif od_node: - root.appendChild(od_node) - return root - - def mutate(self, newtype): - if hasattr(self, "option_defs") and actions[newtype] == self.option_defs: - self.options = {} - self.name = newtype - return - - self.options = {} - self.name = newtype - self.option_defs = actions[self.name] - - for od in self.option_defs: - od.apply_default(self) - - def __deepcopy__(self, memo): - # we need deepcopy here, because option_defs are never copied - result = self.__class__() - result.option_defs = self.option_defs - result.options = copy.deepcopy(self.options, memo) - result.name = copy.deepcopy(self.name, memo) - return result -#------------------------------------------------------------------------------------- - -class OBKeyBind: - def __init__(self, parent=None): - self.children = [] - self.actions = [] - self.key = "a" - self.chroot = False - self.parent = parent - - def parse(self, dom): - self.key = xml_parse_attr(dom, "key") - self.chroot = xml_parse_attr_bool(dom, "chroot") - - kbinds = xml_find_nodes(dom, "keybind") - if len(kbinds): - for k in kbinds: - kb = OBKeyBind(self) - kb.parse(k) - self.children.append(kb) - else: - for a in xml_find_nodes(dom, "action"): - newa = OBAction() - newa.parse(a) - self.actions.append(newa) - - def deparse(self): - if self.chroot: - root = xml.dom.minidom.parseString('').documentElement - else: - root = xml.dom.minidom.parseString('').documentElement - - if len(self.children): - for k in self.children: - root.appendChild(k.deparse()) - else: - for a in self.actions: - root.appendChild(a.deparse()) - return root - - def insert_empty_action(self, after=None): - newact = OBAction() - newact.mutate("Execute") - - if after: - self.actions.insert(self.actions.index(after)+1, newact) - else: - self.actions.append(newact) - return newact - - def move_up(self, action): - i = self.actions.index(action) - tmp = self.actions[i-1] - self.actions[i-1] = action - self.actions[i] = tmp - - def move_down(self, action): - i = self.actions.index(action) - tmp = self.actions[i+1] - self.actions[i+1] = action - self.actions[i] = tmp - -#------------------------------------------------------------------------------------- - -class OBKeyboard: - def __init__(self, dom): - self.chainQuitKey = None - self.keybinds = [] - - cqk = xml_find_node(dom, "chainQuitKey") - if cqk: - self.chainQuitKey = xml_parse_string(cqk) - - for keybind_node in xml_find_nodes(dom, "keybind"): - kb = OBKeyBind() - kb.parse(keybind_node) - self.keybinds.append(kb) - - def deparse(self): - root = xml.dom.minidom.parseString('').documentElement - chainQuitKey_node = xml.dom.minidom.parseString(''+str(self.chainQuitKey)+'').documentElement - root.appendChild(chainQuitKey_node) - - for k in self.keybinds: - root.appendChild(k.deparse()) - - return root - -#------------------------------------------------------------------------------------- - -class OpenboxConfig: - def __init__(self): - self.dom = None - self.keyboard = None - self.path = None - - def load(self, path): - self.path = path - - # load config DOM - self.dom = xml.dom.minidom.parse(path) - - # try load keyboard DOM - keyboard = xml_find_node(self.dom.documentElement, "keyboard") - if keyboard: - self.keyboard = OBKeyboard(keyboard) - - def save(self): - if self.path is None: - return - - # it's all hack, waste of resources etc, but does pretty good result - keyboard = xml_find_node(self.dom.documentElement, "keyboard") - newdom = xml_find_node(xml.dom.minidom.parseString(fixed_toprettyxml(self.keyboard.deparse()," "," ")),"keyboard") - self.dom.documentElement.replaceChild(newdom, keyboard) - f = file(self.path, "w") - if f: - xmlform = self.dom.documentElement - f.write(xmlform.toxml("utf8")) - f.close() - self.reconfigure_openbox() - - def reconfigure_openbox(self): - os.system("openbox --reconfigure") diff --git a/obkey_parts/ActionList.py b/obkey_parts/ActionList.py new file mode 100644 index 0000000..2684f97 --- /dev/null +++ b/obkey_parts/ActionList.py @@ -0,0 +1,1525 @@ +""" + This file is a part of Openbox Key Editor + Copyright (C) 2009 nsf + v1.1 - Code migrated from PyGTK to PyGObject + github.com/stevenhoneyman/obkey + v1.2pre - 19.06.2016 - structured presentation of actions... + v1.2 - 24.02.2018 - slightly refactored code - more dynamic + github.com/luffah/obkey + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +""" +import copy +from obkey_parts.XmlUtils import ( + xml_get_str, xml_parse_bool, xml_find_node, xml_find_nodes, Element, + parseString, escape, minidom +) +from obkey_parts.Gui import ( + SensCondition, SensSwitcher, TYPE_STRING, TYPE_PYOBJECT, + NEVER, AUTOMATIC, EXPAND, FILL, Gtk +) +from obkey_parts.Resources import _ + +class OBAction(object): + """OBAction""" + + def __init__(self, name=None): + """__init__ + + :param name: + """ + self.options = {} + self.option_defs = [] + self.name = name + if name: + self.mutate(name) + + def parse(self, dom): + """parse + + :param dom: + """ + # call parse_child if childNodes exist + if dom.hasChildNodes(): + for child in dom.childNodes: + self.parse_child(child) + + # parse 'name' attribute, get options hash and parse + self.name = dom.getAttribute("name") + + try: + self.option_defs = ACTIONS[self.name] + except KeyError: + pass + + for optdef in self.option_defs: + optdef.parse(self, dom) + + # calls itself until no childNodes are found + # and strip() values of last node + def parse_child(self, dom): + """parse_child + + :param dom: + """ + try: + if dom.hasChildNodes(): + for child in dom.childNodes: + try: + child.nodeValue = child.nodeValue.strip() + except AttributeError: + pass + self.parse_child(child) + except AttributeError: + pass + else: + try: + dom.nodeValue = dom.nodeValue.strip() + except AttributeError: + pass + + def deparse(self): + """deparse""" + root = Element('action') + root.setAttribute('name', str(self.name)) + for optdef in self.option_defs: + od_node = optdef.deparse(self) + if isinstance(od_node, list): + for opt in od_node: + root.appendChild(opt) + elif od_node: + root.appendChild(od_node) + return root + + def mutate(self, newtype): + """mutate + + :param newtype: + """ + if ( + hasattr(self, "option_defs") and + ACTIONS[newtype] == self.option_defs): + self.options = {} + self.name = newtype + return + + self.options = {} + self.name = newtype + self.option_defs = ACTIONS[self.name] + + for optdef in self.option_defs: + optdef.apply_default(self) + + def __deepcopy__(self, memo): + """__deepcopy__ + + :param memo: + """ + # we need deepcopy here, because option_defs are never copied + result = self.__class__() + result.option_defs = self.option_defs + result.options = copy.deepcopy(self.options, memo) + result.name = copy.deepcopy(self.name, memo) + return result + + +# ========================================================= +# ActionList +# ========================================================= +class ActionList(object): + """ActionList""" + + def __init__(self, proptable=None): + # self.widget = Gtk.VBox() + self.widget = Gtk.ScrolledWindow() + self.actions = None + self.proptable = proptable + self._actions_choices = ACTIONS_CHOICES + + # actions callback, called when action added or deleted + # for chroot possibility tracing + self.actions_cb = None + + # copy & paste buffer + self.copied = None + + # sensitivity switchers & conditions + self.cond_paste_buffer = SensCondition(False) + self.cond_selection_available = SensCondition(False) + self.cond_action_list_nonempty = SensCondition(False) + self.cond_can_move_up = SensCondition(False) + self.cond_can_move_down = SensCondition(False) + + self.sw_paste_buffer = SensSwitcher([self.cond_paste_buffer]) + self.sw_selection_available = SensSwitcher( + [self.cond_selection_available]) + self.sw_action_list_nonempty = SensSwitcher( + [self.cond_action_list_nonempty]) + self.sw_can_move_up = SensSwitcher( + [self.cond_can_move_up]) + self.sw_can_move_down = SensSwitcher( + [self.cond_can_move_down]) + + self.model = Gtk.ListStore(TYPE_STRING, TYPE_PYOBJECT) + self.view = self.create_view(self.model) + + self.context_menu = self.create_context_menu() + + self.vbox = Gtk.VBox() + self.vbox.pack_start( + self.create_scroll(self.view), + True, True, 0) + self.vbox.pack_start( + self.create_toolbar(), + False, True, 0) + self.widget.add_with_viewport(self.vbox) + + self.sw_paste_buffer.notify() + self.sw_selection_available.notify() + self.sw_action_list_nonempty.notify() + self.sw_can_move_up.notify() + self.sw_can_move_down.notify() + + def create_choices(self, ch): + """create_choices + + :param ch: + """ + ret = ch + actions_a = {} + + for act in self._actions_choices: + actions_a[_(act)] = act + for act in sorted(actions_a.keys()): + actions_b = {} + content_a = self._actions_choices[actions_a[act]] + if (type(content_a) is dict): + iter0 = ret.append(None, [act, ""]) + + for b in content_a: + actions_b[_(b)] = b + + for b in sorted(actions_b.keys()): + actions_c = {} + content_b = content_a[actions_b[b]] + if (type(content_b) is dict): + iter1 = ret.append( + iter0, [b, ""]) + + for c in content_b: + actions_c[_(c)] = c + for c in sorted(actions_c.keys()): + ret.append(iter1, [c, actions_c[c]]) + + else: + ret.append(iter0, [b, actions_b[b]]) + else: + ret.append(None, [act, actions_a[act]]) + + return ret + + def create_scroll(self, view): + """create_scroll + + :param view: + """ + scroll = Gtk.ScrolledWindow() + scroll.add(view) + scroll.set_policy(NEVER, AUTOMATIC) + scroll.set_shadow_type(Gtk.ShadowType.IN) + return scroll + + def create_view(self, model): + """create_view + + :param model: + """ + renderer = Gtk.CellRendererCombo() + + def editingstarted(cell, widget, path): + widget.set_wrap_width(1) + + chs = Gtk.TreeStore(TYPE_STRING, TYPE_STRING) + renderer.props.model = self.create_choices(chs) + renderer.props.text_column = 0 + renderer.props.editable = True + renderer.props.has_entry = False + renderer.connect('changed', self.action_class_changed) + renderer.connect('editing-started', editingstarted) + + column = Gtk.TreeViewColumn(_("Actions"), renderer, text=0) + + view = Gtk.TreeView(model) + view.append_column(column) + view.get_selection().connect('changed', self.view_cursor_changed) + view.connect('button-press-event', self.view_button_clicked) + return view + + def proptable_changed(self): + """proptable_changed""" + if self.actions_cb: + self.actions_cb() + + def create_context_menu(self): + """create_context_menu""" + context_menu = Gtk.Menu() + self.context_items = {} + + item = Gtk.ImageMenuItem(Gtk.STOCK_CUT) + item.connect('activate', lambda menu: self.cut_selected()) + item.get_child().set_label(_("Cut")) + context_menu.append(item) + self.sw_selection_available.append(item) + + item = Gtk.ImageMenuItem(Gtk.STOCK_COPY) + item.connect('activate', lambda menu: self.copy_selected()) + item.get_child().set_label(_("Copy")) + context_menu.append(item) + self.sw_selection_available.append(item) + + item = Gtk.ImageMenuItem(Gtk.STOCK_PASTE) + item.connect('activate', lambda menu: self.insert_action(self.copied)) + item.get_child().set_label(_("Paste")) + context_menu.append(item) + self.sw_paste_buffer.append(item) + + item = Gtk.ImageMenuItem(Gtk.STOCK_REMOVE) + item.connect('activate', lambda menu: self.del_selected()) + item.get_child().set_label(_("Remove")) + context_menu.append(item) + self.sw_selection_available.append(item) + + context_menu.show_all() + return context_menu + + def create_toolbar(self): + """create_toolbar""" + toolbar = Gtk.Toolbar() + toolbar.set_style(Gtk.ToolbarStyle.ICONS) + toolbar.set_icon_size(Gtk.IconSize.SMALL_TOOLBAR) + toolbar.set_show_arrow(False) + + but = Gtk.ToolButton(Gtk.STOCK_ADD) + but.set_tooltip_text(_("Insert action")) + but.connect('clicked', + lambda b: self.insert_action( + OBAction("Execute"))) + toolbar.insert(but, -1) + + but = Gtk.ToolButton(Gtk.STOCK_REMOVE) + but.set_tooltip_text(_("Remove action")) + but.connect('clicked', + lambda b: self.del_selected()) + toolbar.insert(but, -1) + self.sw_selection_available.append(but) + + but = Gtk.ToolButton(Gtk.STOCK_GO_UP) + but.set_tooltip_text(_("Move action up")) + but.connect('clicked', + lambda b: self.move_selected_up()) + toolbar.insert(but, -1) + self.sw_can_move_up.append(but) + + but = Gtk.ToolButton(Gtk.STOCK_GO_DOWN) + but.set_tooltip_text(_("Move action down")) + but.connect('clicked', + lambda b: self.move_selected_down()) + toolbar.insert(but, -1) + self.sw_can_move_down.append(but) + + sep = Gtk.SeparatorToolItem() + sep.set_draw(False) + sep.set_expand(True) + toolbar.insert(sep, -1) + + but = Gtk.ToolButton(Gtk.STOCK_DELETE) + but.set_tooltip_text(_("Remove all actions")) + but.connect('clicked', lambda b: self.clear()) + toolbar.insert(but, -1) + self.sw_action_list_nonempty.append(but) + return toolbar + # ----------------------------------------------------- + # callbacks + + def view_button_clicked(self, view, event): + """view_button_clicked + + :param view: + :param event: + """ + if event.button == 3: + x = int(event.x) + y = int(event.y) + time = event.time + pathinfo = view.get_path_at_pos(x, y) + if pathinfo: + path, col, cellx, celly = pathinfo + view.grab_focus() + view.set_cursor(path, col, 0) + self.context_menu.popup( + None, None, None, + event.button, time, False) + else: + view.grab_focus() + view.get_selection().unselect_all() + self.context_menu.popup( + None, None, None, + event.button, time, False) + return 1 + + def action_class_changed(self, combo, path, item): + """action_class_changed + + :param combo: the selected combo + :param path: idx in the table + :param item: the selected combo item + """ + model = combo.props.model + ntype = model.get_value(item, 1) + self.model[path][0] = model.get_value(item, 0) + self.model[path][1].mutate(ntype) + if self.proptable: + self.proptable.set_action(self.model[path][1]) + if self.actions_cb: + self.actions_cb() + + def view_cursor_changed(self, selection): + """view_cursor_changed + + :param selection: + """ + (model, item) = selection.get_selected() + act = None + if item: + act = model.get_value(item, 1) + if self.proptable: + self.proptable.set_action(act, self.proptable_changed) + # self.widget.add_with_viewport(self.proptable) + if act: + nb_actions = len(self.actions) + idx_act = self.actions.index(act) + self.cond_can_move_up.set_state(idx_act != 0) + self.cond_can_move_down.set_state(nb_actions > 1 and idx_act + 1 < nb_actions) + self.cond_selection_available.set_state(True) + else: + self.cond_can_move_up.set_state(False) + self.cond_can_move_down.set_state(False) + self.cond_selection_available.set_state(False) + + # ----------------------------------------------------- + def cut_selected(self): + """cut_selected""" + self.copy_selected() + self.del_selected() + + def duplicate_selected(self): + """duplicate_selected""" + self.copy_selected() + self.insert_action(self.copied) + + def copy_selected(self): + """copy_selected""" + if self.actions is None: + return + + (model, item) = self.view.get_selection().get_selected() + if item: + act = model.get_value(item, 1) + self.copied = copy.deepcopy(act) + self.cond_paste_buffer.set_state(True) + + def clear(self): + """clear""" + if self.actions is None or not len(self.actions): + return + + del self.actions[:] + self.model.clear() + + self.cond_action_list_nonempty.set_state(False) + if self.actions_cb: + self.actions_cb() + + def move_selected_up(self): + """move_selected_up""" + if self.actions is None: + return + + (_, item) = self.view.get_selection().get_selected() + if not item: + return + + idx_act, = self.model.get_path(item) + nb_act = len(self.model) + self.cond_can_move_up.set_state(idx_act - 1 != 0) + self.cond_can_move_down.set_state(nb_act > 1 and idx_act < nb_act) + if idx_act == 0: + return + + itprev = self.model.get_iter(idx_act - 1) + self.model.swap(item, itprev) + action = self.model.get_value(item, 1) + + idx_act = self.actions.index(action) + tmp = self.actions[idx_act - 1] + self.actions[idx_act - 1] = action + self.actions[idx_act] = tmp + + def move_selected_down(self): + """move_selected_down""" + if self.actions is None: + return + + (_, item) = self.view.get_selection().get_selected() + if not item: + return + + i, = self.model.get_path(item) + nb_act = len(self.model) + self.cond_can_move_up.set_state(i + 1 != 0) + self.cond_can_move_down.set_state(nb_act > 1 and i + 2 < nb_act) + if i + 1 >= nb_act: + return + + itnext = self.model.iter_next(item) + self.model.swap(item, itnext) + action = self.model.get_value(item, 1) + + i = self.actions.index(action) + tmp = self.actions[i + 1] + self.actions[i + 1] = action + self.actions[i] = tmp + + def insert_action(self, action): + """insert_action + + :param action: + """ + if self.actions is None: + return + + (model, item) = self.view.get_selection().get_selected() + if item: + self._insert_action(action, model.get_value(item, 1)) + newit = self.model.insert_after(item, (_(action.name), action)) + else: + self._insert_action(action) + newit = self.model.append((_(action.name), action)) + + if newit: + self.view.get_selection().select_iter(newit) + + self.cond_action_list_nonempty.set_state(len(self.model)) + if self.actions_cb: + self.actions_cb() + + def del_selected(self): + """del_selected""" + if self.actions is None: + return + + (model, item) = self.view.get_selection().get_selected() + if item: + self.actions.remove(model.get_value(item, 1)) + isok = self.model.remove(item) + if isok: + self.view.get_selection().select_iter(item) + + self.cond_action_list_nonempty.set_state(len(self.model)) + if self.actions_cb: + self.actions_cb() + + # ----------------------------------------------------- + + def set_actions(self, actionlist): + """set_actions + + :param actionlist: + """ + self.actions = actionlist + self.model.clear() + self.widget.set_sensitive(self.actions is not None) + if not self.actions: + return + for act in self.actions: + self.model.append((_(act.name), act)) + + if len(self.model): + self.view.get_selection().select_iter(self.model.get_iter_first()) + self.cond_action_list_nonempty.set_state(len(self.model)) + + def _insert_action(self, action, after=None): + """_insert_action + + :param action: + :param after: + """ + if after: + self.actions.insert(self.actions.index(after) + 1, action) + else: + self.actions.append(action) + + def set_callback(self, callback): + """set_callback + + :param callback: + """ + self.actions_cb = callback + + +# ========================================================= +# EndActionList = limited choice of Action list +# ========================================================= +class EndActionList(ActionList): + """EndActionList""" + + def __init__(self, proptable=None): + ActionList.__init__(self, proptable) + self.widget.set_size_request(-1, 120) + self.view.set_headers_visible(False) + self._actions_choices = MINI_ACTIONS_CHOICES + + +# ========================================================= +# Openbox Glue +# ========================================================= + +# Option Classes (for OBAction) +# 1. Parse function for OBAction to parse the data. +# 2. Getter(s) and Setter(s) for OBAction to operate on the data (registered by +# the parse function). +# 3. Widget generator for property editor to represent the data. +# Examples of such classes: string, int, filename, list of actions, +# list (choose one variant of many), string-int with custom validator(?) + +# Actions +# An array of Options: + +# These actions are being applied to OBAction instances. + + +# ========================================================= +# Option Class: String +# ========================================================= +class OCString(object): + """OCString""" + __slots__ = ('name', 'default', 'alts') + + def __init__(self, name, default, alts=[]): + self.name = name + self.default = default + self.alts = alts + + def apply_default(self, action): + """apply_default + + :param action: + """ + action.options[self.name] = self.default + + def parse(self, action, dom): + """parse + + :param action: + :param dom: + """ + node = xml_find_node(dom, self.name) + if not node: + for act in self.alts: + node = xml_find_node(dom, act) + if node: + break + if node: + action.options[self.name] = xml_get_str(node) + else: + action.options[self.name] = self.default + + def deparse(self, action): + """deparse + + :param action: + """ + val = action.options[self.name] + if val == self.default: + return None + return parseString( + "<" + str(self.name) + ">" + + str(escape(val)) + + "" + ).documentElement + + def generate_widget(self, action, callback=None): + """generate_widget + + :param action: + :param callback: + """ + def changed(entry, action): + text = entry.get_text() + action.options[self.name] = text + if callback: + callback() + + entry = Gtk.Entry() + entry.set_text(action.options[self.name]) + entry.connect('changed', changed, action) + return entry + + +# ========================================================= +# Option Class: Combo +# ========================================================= +class OCCombo(object): + __slots__ = ('name', 'default', 'choices') + + def __init__(self, name, default, choices): + self.name = name + self.default = default + self.choices = choices + + def apply_default(self, action): + """apply_default + + :param action: + """ + action.options[self.name] = self.default + + def parse(self, action, dom): + """parse + + :param action: + :param dom: + """ + node = xml_find_node(dom, self.name) + if node: + action.options[self.name] = xml_get_str(node) + else: + action.options[self.name] = self.default + + def deparse(self, action): + """deparse + + :param action: + """ + val = action.options[self.name] + if val == self.default: + return None + return parseString( + "<" + str(self.name) + ">" + + str(val) + + "" + ).documentElement + + def generate_widget(self, action, callback=None): + """generate_widget + + :param action: + :param callback: + """ + def changed(combo, action): + text = combo.get_active() + action.options[self.name] = self.choices[text] + + model = Gtk.ListStore(TYPE_STRING) + for choice in self.choices: + model.append((_(choice),)) + combo = Gtk.ComboBox() + combo.set_active(self.choices.index(action.options[self.name])) + combo.set_model(model) + cell = Gtk.CellRendererText() + combo.pack_start(cell, True) + combo.add_attribute(cell, 'text', 0) + combo.connect('changed', changed, action) + return combo + + +# ========================================================= +# Option Class: Number +# ========================================================= +class OCNumber(object): + """OCNumber""" + __slots__ = ('name', 'default', 'min', 'max', 'explicit_defaults') + + def __init__(self, name, default, mmin, mmax, explicit_defaults=False): + """__init__ + + :param name: + :param default: + :param mmin: + :param mmax: + :param explicit_defaults: + """ + self.name = name + self.default = default + self.min = mmin + self.max = mmax + self.explicit_defaults = explicit_defaults + + def apply_default(self, action): + """apply_default + + :param action: + """ + action.options[self.name] = self.default + + def parse(self, action, dom): + """parse + + :param action: + :param dom: + """ + node = xml_find_node(dom, self.name) + if node: + action.options[self.name] = int(float(xml_get_str(node))) + else: + action.options[self.name] = self.default + + def deparse(self, action): + """deparse + + :param action: + """ + val = action.options[self.name] + if not self.explicit_defaults and (val == self.default): + return None + return parseString( + "<" + str(self.name) + ">" + + str(val) + + "" + ).documentElement + + def generate_widget(self, action, callback=None): + """generate_widget + + :param action: + :param callback: + """ + def changed(num, action): + action.options[self.name] = num.get_value_as_int() + + num = Gtk.SpinButton() + num.set_increments(1, 5) + num.set_range(self.min, self.max) + num.set_value(action.options[self.name]) + num.connect('value-changed', changed, action) + return num + + +# ========================================================= +# Option Class: OCIf +# +# NO UI config yet +# +# Reason: keep manually defined IF key bindings +# ========================================================= +class OCIf(object): + __slots__ = ('name', 'default', 'props', 'then', 'els') + + def __init__(self, name, default): + """__init__ + + :param name: + :param default: + """ + self.name = name + self.default = default + + self.props = [] + self.then = [] + self.els = [] + + def apply_default(self, action): + """apply_default + + :param action: + """ + action.options[self.name] = self.default + + def parse(self, action, dom): + """parse + + :param action: + :param dom: + """ + node = xml_find_node(dom, self.name) +# if dom.hasChildNodes(): + if node.hasChildNodes(): + for child in node.childNodes: + if child.nodeName == "then": + self.then = self._parseAction( + node, action, "then") + elif child.nodeName == "else": + self.els = self._parseAction( + node, action, "else") + else: + if not isinstance(child, minidom.Text): + self.props += [child] + + def _parseAction(self, dom, action, nodeName): + obAct = OCFinalActions() + obAct.name = nodeName + obAct.parse(action, dom) + return obAct + + def deparse(self, action): + """deparse + + :param action: + """ + frag = [] + + # props + for el in self.props: + frag.append(el) + + # conditions + # themEl = minidom.Element("then") + themEl = self.then.deparse(action) + themEl.tagName = "then" + + # else + elseEl = self.els.deparse(action) + elseEl.tagName = "else" + + frag.append(themEl) + frag.append(elseEl) + + # print + # zz = Element("action") + # for el in frag: + # zz.appendChild(el) + # print zz.toxml() + + return frag + + def generate_widget(self, action, callback=None): + """generate_widget + + :param action: + :param callback: + """ + # label = + Gtk.Label("IF Not fully supported yet") + opts = [] + for el in self.props: + opts.append({ + 'name': "Cond.", + "widget": Gtk.Label(el.toxml()) + }) + opts.append({ + 'name': "then", + "widget": self.then.generate_widget(action) + }) + opts.append({ + 'name': "else", + 'widget': self.els.generate_widget(action) + }) + return opts + + +# ========================================================= +# Option Class: Boolean +# ========================================================= +class OCBoolean(object): + __slots__ = ('name', 'default') + + def __init__(self, name, default): + """__init__ + + :param name: + :param default: + """ + self.name = name + self.default = default + + def apply_default(self, action): + """apply_default + + :param action: + """ + action.options[self.name] = self.default + + def parse(self, action, dom): + """parse + + :param action: + :param dom: + """ + node = xml_find_node(dom, self.name) + if node: + action.options[self.name] = xml_parse_bool(node) + else: + action.options[self.name] = self.default + + def deparse(self, action): + """deparse + + :param action: + """ + if action.options[self.name] == self.default: + return None + if action.options[self.name]: + return parseString( + "<" + str(self.name) + + ">yes" + ).documentElement + else: + return parseString( + "<" + str(self.name) + + ">no" + ).documentElement + + def generate_widget(self, action, callback=None): + """generate_widget + + :param action: + :param callback: + """ + def changed(checkbox, action): + active = checkbox.get_active() + action.options[self.name] = active + + check = Gtk.CheckButton() + check.set_active(action.options[self.name]) + check.connect('toggled', changed, action) + return check + + +# ========================================================= +# Option Class: StartupNotify +# ========================================================= +class OCStartupNotify(object): + + def __init__(self): + """__init__""" + self.name = "startupnotify" + + def apply_default(self, action): + """apply_default + + :param action: + """ + action.options['startupnotify_enabled'] = False + action.options['startupnotify_wmclass'] = "" + action.options['startupnotify_name'] = "" + action.options['startupnotify_icon'] = "" + + def parse(self, action, dom): + """parse + + :param action: + :param dom: + """ + self.apply_default(action) + + startupnotify = xml_find_node(dom, "startupnotify") + if not startupnotify: + return + + enabled = xml_find_node(startupnotify, "enabled") + if enabled: + action.options['startupnotify_enabled'] = xml_parse_bool(enabled) + wmclass = xml_find_node(startupnotify, "wmclass") + if wmclass: + action.options['startupnotify_wmclass'] = xml_get_str(wmclass) + name = xml_find_node(startupnotify, "name") + if name: + action.options['startupnotify_name'] = xml_get_str(name) + icon = xml_find_node(startupnotify, "icon") + if icon: + action.options['startupnotify_icon'] = xml_get_str(icon) + + def deparse(self, action): + """deparse + + :param action: + """ + if not action.options['startupnotify_enabled']: + return None + root = parseString( + "yes" + ).documentElement + if action.options['startupnotify_wmclass'] != "": + root.appendChild(parseString( + "" + + action.options['startupnotify_wmclass'] + + "" + ).documentElement) + if action.options['startupnotify_name'] != "": + root.appendChild(parseString( + "" + + action.options['startupnotify_name'] + + "" + ).documentElement) + if action.options['startupnotify_icon'] != "": + root.appendChild(parseString( + "" + + action.options['startupnotify_icon'] + + "" + ).documentElement) + return root + + def generate_widget(self, action, callback=None): + """generate_widget + + :param action: + :param callback: + """ + def enabled_toggled(checkbox, action, sens_list): + active = checkbox.get_active() + action.options['startupnotify_enabled'] = active + for w in sens_list: + w.set_sensitive(active) + + def text_changed(textbox, action, var): + text = textbox.get_text() + action.options[var] = text + + wmclass = Gtk.Entry() + wmclass.set_size_request(100, -1) + wmclass.set_text( + action.options['startupnotify_wmclass']) + wmclass.connect( + 'changed', text_changed, action, + 'startupnotify_wmclass') + + name = Gtk.Entry() + name.set_size_request(100, -1) + name.set_text(action.options['startupnotify_name']) + name.connect( + 'changed', text_changed, action, + 'startupnotify_name') + + icon = Gtk.Entry() + icon.set_size_request(100, -1) + icon.set_text(action.options['startupnotify_icon']) + icon.connect( + 'changed', text_changed, action, + 'startupnotify_icon') + + sens_list = [wmclass, name, icon] + + enabled = Gtk.CheckButton() + enabled.set_active( + action.options['startupnotify_enabled']) + enabled.connect( + 'toggled', enabled_toggled, action, + sens_list) + + def put_table(table, label_text, widget, row, addtosens=True): + label = Gtk.Label(label=_(label_text)) + label.set_padding(5, 5) + label.set_alignment(0, 0) + if addtosens: + sens_list.append(label) + table.attach(label, 0, 1, row, row + 1, EXPAND | FILL, 0, 0, 0) + table.attach(widget, 1, 2, row, row + 1, FILL, 0, 0, 0) + + table = Gtk.Table(1, 2) + put_table(table, "enabled:", enabled, 0, False) + put_table(table, "wmclass:", wmclass, 1) + put_table(table, "name:", name, 2) + put_table(table, "icon:", icon, 3) + + sens = enabled.get_active() + for w in sens_list: + w.set_sensitive(sens) + + frame = Gtk.Frame() + frame.add(table) + return frame + + +# ========================================================= +# Option Class: FinalActions +# ========================================================= +class OCFinalActions(object): + __slots__ = ('name') + + def __init__(self): + """__init__""" + self.name = "finalactions" + + def apply_default(self, action): + """apply_default + + :param action: + """ + a1 = OBAction() + a1.mutate("Focus") + a2 = OBAction() + a2.mutate("Raise") + a3 = OBAction() + a3.mutate("Unshade") + + action.options[self.name] = [a1, a2, a3] + + def parse(self, action, dom): + """parse + + :param action: + :param dom: + """ + node = xml_find_node(dom, self.name) + action.options[self.name] = [] + if node: + for node_act in xml_find_nodes(node, "action"): + act = OBAction() + act.parse(node_act) + action.options[self.name].append(act) + else: + self.apply_default(action) + + def deparse(self, action): + """deparse + + :param action: + """ + act_opts = action.options[self.name] + if len(act_opts) == 3: + if ( + act_opts[0].name == "Focus" and + act_opts[1].name == "Raise" and + act_opts[2].name == "Unshade" + ): + return None + if len(act_opts) == 0: + return None + root = parseString( + "").documentElement + for act in act_opts: + node = act.deparse() + root.appendChild(node) + return root + + def generate_widget(self, action, callback=None): + """generate_widget + + :param action: + :param callback: + """ + w = EndActionList() + w.set_actions(action.options[self.name]) + frame = Gtk.Frame() + frame.add(w.widget) + return frame + + +# --------------------------------------------------------- +ACTIONS_WINDOW_NAV = { + "NextWindow": [ + OCCombo('dialog', 'list', ['list', 'icons', 'none']), + OCBoolean("bar", True), + OCBoolean("raise", False), + OCBoolean("allDesktops", False), + OCBoolean("panels", False), + OCBoolean("desktop", False), + OCBoolean("linear", False), + OCFinalActions() + ], + "PreviousWindow": [ + OCBoolean("dialog", True), + OCBoolean("bar", True), + OCBoolean("raise", False), + OCBoolean("allDesktops", False), + OCBoolean("panels", False), + OCBoolean("desktop", False), + OCBoolean("linear", False), + OCFinalActions() + ], + "DirectionalFocusNorth": [ + OCBoolean("dialog", True), + OCBoolean("bar", True), + OCBoolean("raise", False), + OCFinalActions() + ], + "DirectionalFocusSouth": [ + OCBoolean("dialog", True), + OCBoolean("bar", True), + OCBoolean("raise", False), + OCFinalActions() + ], + "DirectionalFocusEast": [ + OCBoolean("dialog", True), + OCBoolean("bar", True), + OCBoolean("raise", False), + OCFinalActions() + ], + "DirectionalFocusWest": [ + OCBoolean("dialog", True), + OCBoolean("bar", True), + OCBoolean("raise", False), + OCFinalActions() + ], + "DirectionalFocusNorthEast": [ + OCBoolean("dialog", True), + OCBoolean("bar", True), + OCBoolean("raise", False), + OCFinalActions() + ], + "DirectionalFocusNorthWest": [ + OCBoolean("dialog", True), + OCBoolean("bar", True), + OCBoolean("raise", False), + OCFinalActions() + ], + "DirectionalFocusSouthEast": [ + OCBoolean("dialog", True), + OCBoolean("bar", True), + OCBoolean("raise", False), + OCFinalActions() + ], + "DirectionalFocusSouthWest": [ + OCBoolean("dialog", True), + OCBoolean("bar", True), + OCBoolean("raise", False), + OCFinalActions() + ], + "DirectionalTargetNorth": [ + OCBoolean("dialog", True), + OCBoolean("bar", True), + OCBoolean("raise", False), + OCFinalActions() + ], + "DirectionalTargetSouth": [ + OCBoolean("dialog", True), + OCBoolean("bar", True), + OCBoolean("raise", False), + OCFinalActions() + ], + "DirectionalTargetEast": [ + OCBoolean("dialog", True), + OCBoolean("bar", True), + OCBoolean("raise", False), + OCFinalActions() + ], + "DirectionalTargetWest": [ + OCBoolean("dialog", True), + OCBoolean("bar", True), + OCBoolean("raise", False), + OCFinalActions() + ], + "DirectionalTargetNorthEast": [ + OCBoolean("dialog", True), + OCBoolean("bar", True), + OCBoolean("raise", False), + OCFinalActions() + ], + "DirectionalTargetNorthWest": [ + OCBoolean("dialog", True), + OCBoolean("bar", True), + OCBoolean("raise", False), + OCFinalActions() + ], + "DirectionalTargetSouthEast": [ + OCBoolean("dialog", True), + OCBoolean("bar", True), + OCBoolean("raise", False), + OCFinalActions() + ], + "DirectionalTargetSouthWest": [ + OCBoolean("dialog", True), + OCBoolean("bar", True), + OCBoolean("raise", False), + OCFinalActions() + ] +} +ACTIONS_DESKTOP_NAV_MOV = { + "Desktop": [ OCNumber("desktop", 1, 1, 9999, True) ], + "DesktopNext": [ OCBoolean("wrap", True) ], + "DesktopPrevious": [ OCBoolean("wrap", True) ], + "DesktopLeft": [ OCBoolean("wrap", True) ], + "DesktopRight": [ OCBoolean("wrap", True) ], + "DesktopUp": [ OCBoolean("wrap", True) ], + "DesktopDown": [ OCBoolean("wrap", True) ], + "GoToDesktop": [ OCString("to", ""), OCString("wrap", "") ], + "DesktopLast": [] +} +ACTIONS_DESKTOP_NAV_DEL = { + "RemoveDesktopLast": [], + "RemoveDesktopCurrent": [] +} +ACTIONS_DESKTOP_NAV_ADD = { + "AddDesktopLast": [], + "AddDesktopCurrent": [] +} +ACTIONS_WM = { + "ShowMenu": [OCString("menu", "")], + "ToggleDockAutohide": [], + "Reconfigure": [], + "Restart": [ + OCString("command", "", ["execute"]) + ], + "Exit": [ + OCBoolean("prompt", True) + ], + "SessionLogout": [ + OCBoolean("prompt", True) + ], + "Debug": [ + OCString("string", "") + ], + "ToggleShowDesktop": [] +} +ACTIONS_WINDOW_FOCUS = { + "Focus": [], + "Unfocus": [], + "FocusToBottom": [], + "RaiseLower": [], + "Raise": [], + "Lower": [], + "ShadeLower": [], + "UnshadeRaise": [], + "ToggleAlwaysOnTop": [], + "ToggleAlwaysOnBottom": [], + "SendToTopLayer": [], + "SendToBottomLayer": [], + "SendToNormalLayer": [] +} +ACTIONS_WINDOW_SET = { + "Iconify": [], + "Close": [], + "ToggleShade": [], + "Shade": [], + "Unshade": [], + "ToggleOmnipresent": [], + "ToggleMaximizeFull": [], + "MaximizeFull": [], + "UnmaximizeFull": [], + "ToggleMaximizeVert": [], + "MaximizeVert": [], + "UnmaximizeVert": [], + "ToggleMaximizeHorz": [], + "MaximizeHorz": [], + "UnmaximizeHorz": [], + "ToggleFullscreen": [], + "ToggleDecorations": [], + "Decorate": [], + "Undecorate": [] +} + +ACTIONS_WINDOW_SEND = { + "SendToDesktop": [ + OCNumber("desktop", 1, 1, 9999, True), + OCBoolean("follow", True) + ], + "SendToDesktopNext": [ + OCBoolean("wrap", True), + OCBoolean("follow", True) + ], + "SendToDesktopPrevious": [ + OCBoolean("wrap", True), + OCBoolean("follow", True) + ], + "SendToDesktopLeft": [ + OCBoolean("wrap", True), + OCBoolean("follow", True) + ], + "SendToDesktopRight": [ + OCBoolean("wrap", True), + OCBoolean("follow", True) + ], + "SendToDesktopUp": [ + OCBoolean("wrap", True), + OCBoolean("follow", True) + ], + "SendToDesktopDown": [ + OCBoolean("wrap", True), + OCBoolean("follow", True) + ] +} +ACTIONS_WINDOW_MOVE = { + "Move": [], + "MoveToCenter": [], + "MoveResizeTo": [ + OCString("x", "current"), + OCString("y", "current"), + OCString("width", "current"), + OCString("height", "current"), + OCString("monitor", "current") + ], + "MoveRelative": [ + OCNumber("x", 0, -9999, 9999), + OCNumber("y", 0, -9999, 9999) + ], + "MoveToEdgeNorth": [], + "MoveToEdgeSouth": [], + "MoveToEdgeWest": [], + "MoveToEdgeEast": [] +} +ACTIONS_WINDOW_RESIZE = { + "Resize": [ + OCCombo( + "edge", "none", [ + 'none', "top", "left", "right", "bottom", + "topleft", "topright", "bottomleft", + "bottomright" + ]) + ], + "ResizeRelative": [ + OCNumber("left", 0, -9999, 9999), + OCNumber("right", 0, -9999, 9999), + OCNumber("top", 0, -9999, 9999), + OCNumber("bottom", 0, -9999, 9999) + ], + "GrowToEdgeNorth": [], + "GrowToEdgeSouth": [], + "GrowToEdgeWest": [], + "GrowToEdgeEast": [] +} + +ACTIONS_CHOICES = { + "Execute": [ + OCString("command", "", ['execute']), + OCString("prompt", ""), + OCStartupNotify() + ], + # IF Not fully supported yet + # "If": [ OCIf("", "") ], + "BreakChroot": [] +} + +ACTIONS = {} +ACTIONS.update(ACTIONS_CHOICES) +ACTIONS.update(ACTIONS_WINDOW_NAV) +ACTIONS.update(ACTIONS_WINDOW_FOCUS) +ACTIONS.update(ACTIONS_WINDOW_MOVE) +ACTIONS.update(ACTIONS_WINDOW_RESIZE) +ACTIONS.update(ACTIONS_WINDOW_SEND) +ACTIONS.update(ACTIONS_DESKTOP_NAV_ADD) +ACTIONS.update(ACTIONS_DESKTOP_NAV_DEL) +ACTIONS.update(ACTIONS_DESKTOP_NAV_MOV) +ACTIONS.update(ACTIONS_WINDOW_SET) +ACTIONS.update(ACTIONS_WM) + +ACTIONS_CHOICES["Window Focus"] = ACTIONS_WINDOW_FOCUS +ACTIONS_CHOICES["Window Move"] = ACTIONS_WINDOW_MOVE +ACTIONS_CHOICES["Window Resize"] = ACTIONS_WINDOW_RESIZE +ACTIONS_CHOICES["Window Desktop Change"] = ACTIONS_WINDOW_SEND +ACTIONS_CHOICES["Desktop Navigation"] = { + "Add desktop": ACTIONS_DESKTOP_NAV_ADD, + "Remove desktop": ACTIONS_DESKTOP_NAV_DEL, + "Move to desktop": ACTIONS_DESKTOP_NAV_MOV +} +ACTIONS_CHOICES["Window Properties"] = ACTIONS_WINDOW_SET +ACTIONS_CHOICES["Window/Session Management"] = ACTIONS_WM + +MINI_ACTIONS_CHOICES = ACTIONS_CHOICES.copy() +ACTIONS_CHOICES["Window Navigation"] = ACTIONS_WINDOW_NAV diff --git a/obkey_parts/Gui.py b/obkey_parts/Gui.py new file mode 100644 index 0000000..3cd719c --- /dev/null +++ b/obkey_parts/Gui.py @@ -0,0 +1,117 @@ +""" + This file is a part of Openbox Key Editor + Copyright (C) 2009 nsf + v1.1 - Code migrated from PyGTK to PyGObject + github.com/stevenhoneyman/obkey + v1.2pre - 19.06.2016 - structured presentation of actions... + v1.2 - 24.02.2018 - slightly refactored code - more dynamic + github.com/luffah/obkey + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +""" +try: + import gi + gi.require_versions({'Gtk': '3.0', 'GLib': '2.0', 'Gio': '2.0'}) + from gi.repository import Gtk + from gi.repository.GObject import (TYPE_UINT, TYPE_INT, + TYPE_BOOLEAN, + TYPE_PYOBJECT, + TYPE_STRING) + from gi.repository.Gtk import AttachOptions, PolicyType + (NEVER, AUTOMATIC, FILL, EXPAND) = ( + PolicyType.NEVER, + PolicyType.AUTOMATIC, + AttachOptions.FILL, + AttachOptions.EXPAND) +except ImportError: + print "Gtk 3.0 is required to run obkey." + exit + +# ========================================================= +# This is the uber cool switchers/conditions(sensors) system. +# Helps a lot with widgets sensitivity. +# ========================================================= + + +class SensCondition: + + def __init__(self, initial_state): + """__init__ + + :param initial_state: + """ + self.switchers = [] + self.state = initial_state + + def register_switcher(self, sw): + """register_switcher + + :param sw: + """ + self.switchers.append(sw) + + def set_state(self, state): + """set_state + + :param state: + """ + if self.state == state: + return + self.state = state + for sw in self.switchers: + sw.notify() + + +class SensSwitcher: + + def __init__(self, conditions): + """__init__ + + :param conditions: + """ + self.conditions = conditions + self.widgets = [] + + for c in conditions: + c.register_switcher(self) + + def append(self, widget): + """append + + :param widget: + """ + self.widgets.append(widget) + + def set_sensitive(self, state): + """set_sensitive + + :param state: + """ + for w in self.widgets: + w.set_sensitive(state) + + def notify(self): + """notify""" + for c in self.conditions: + if not c.state: + self.set_sensitive(False) + return + self.set_sensitive(True) diff --git a/obkey_parts/KeyTable.py b/obkey_parts/KeyTable.py new file mode 100644 index 0000000..e25bc3c --- /dev/null +++ b/obkey_parts/KeyTable.py @@ -0,0 +1,648 @@ +""" + This file is a part of Openbox Key Editor + Copyright (C) 2009 nsf + v1.1 - Code migrated from PyGTK to PyGObject + github.com/stevenhoneyman/obkey + v1.2pre - 19.06.2016 - structured presentation of actions... + v1.2 - 24.02.2018 - slightly refactored code - more dynamic + github.com/luffah/obkey + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +""" + +import copy +from obkey_parts.Gui import AUTOMATIC +from obkey_parts.Gui import ( + Gtk, SensCondition, SensSwitcher, + TYPE_UINT, TYPE_INT, TYPE_STRING, TYPE_BOOLEAN, TYPE_PYOBJECT +) +from obkey_parts.Resources import res, _ +from obkey_parts.KeyUtils import key_openbox2gtk, key_gtk2openbox +from obkey_parts.OBKeyboard import OBKeyBind, OBKeyboard + +# ======================================================== = +# KeyTable +# ========================================================= + + +class KeyTable: + """KeyTable""" + + def __init__(self, actionlist, obconfig): + """__init__ + + :param actionlist: + :param ob: + """ + self.widget = Gtk.VBox() + self.ob = obconfig + if obconfig.keyboard_node: + self.keyboard = OBKeyboard(obconfig.keyboard_node) + self.actionlist = actionlist + actionlist.set_callback(self.actions_cb) + + self.icons = self.load_icons() + + self.model, self.cqk_model = self._create_models() + self.view, self.cqk_view = self._create_views( + self.model, self.cqk_model) + + # copy & paste + self.copied = None + + # sensitivity switchers & conditions + self.cond_insert_child = SensCondition(False) + self.cond_paste_buffer = SensCondition(False) + self.cond_selection_available = SensCondition(False) + + self.sw_insert_child_and_paste = SensSwitcher( + [self.cond_insert_child, + self.cond_paste_buffer]) + self.sw_insert_child = SensSwitcher( + [self.cond_insert_child]) + self.sw_paste_buffer = SensSwitcher( + [self.cond_paste_buffer]) + self.sw_selection_available = SensSwitcher( + [self.cond_selection_available]) + + self.context_menu = self._create_context_menu() + + for kb in self.keyboard.keybinds: + self.apply_keybind(kb) + + self.apply_cqk_initial_value() + + # self.add_child_button + self.widget.pack_start(self._create_toolbar(), + False, True, 0) + self.widget.pack_start(self._create_scroll(self.view), + True, True, 0) + self.widget.pack_start(self._create_cqk_hbox(self.cqk_view), + False, True, 0) + + if len(self.model): + self.view.get_selection().select_iter(self.model.get_iter_first()) + + self.sw_insert_child_and_paste.notify() + self.sw_insert_child.notify() + self.sw_paste_buffer.notify() + self.sw_selection_available.notify() + + def _create_cqk_hbox(self, cqk_view): + """_create_cqk_hbox + + :param cqk_view: + """ + cqk_hbox = Gtk.HBox() + cqk_label = Gtk.Label(label=_("chainQuitKey:")) + cqk_label.set_padding(5, 5) + + cqk_frame = Gtk.Frame() + cqk_frame.add(cqk_view) + + cqk_hbox.pack_start(cqk_label, False, False, False) + cqk_hbox.pack_start(cqk_frame, True, True, 0) + return cqk_hbox + + def _create_context_menu(self): + """_create_context_menu""" + context_menu = Gtk.Menu() + self.context_items = {} + + item = Gtk.ImageMenuItem(Gtk.STOCK_CUT) + item.connect('activate', lambda menu: self.cut_selected()) + item.get_child().set_label(_("Cut")) + context_menu.append(item) + self.sw_selection_available.append(item) + + item = Gtk.ImageMenuItem(Gtk.STOCK_COPY) + item.connect('activate', lambda menu: self.copy_selected()) + item.get_child().set_label(_("Copy")) + context_menu.append(item) + self.sw_selection_available.append(item) + + item = Gtk.ImageMenuItem(Gtk.STOCK_PASTE) + item.connect('activate', + lambda menu: self.insert_sibling( + copy.deepcopy(self.copied))) + item.get_child().set_label(_("Paste")) + context_menu.append(item) + self.sw_paste_buffer.append(item) + + item = Gtk.ImageMenuItem(Gtk.STOCK_PASTE) + item.get_child().set_label(_("Paste as child")) + item.connect('activate', + lambda menu: self.insert_child( + copy.deepcopy(self.copied))) + context_menu.append(item) + self.sw_insert_child_and_paste.append(item) + + item = Gtk.ImageMenuItem(Gtk.STOCK_REMOVE) + item.connect('activate', + lambda menu: self.del_selected()) + item.get_child().set_label(_("Remove")) + context_menu.append(item) + self.sw_selection_available.append(item) + + context_menu.show_all() + return context_menu + + def _create_models(self): + """_create_models""" + model = Gtk.TreeStore( + TYPE_UINT, # accel key + TYPE_INT, # accel mods + TYPE_STRING, # accel string (openbox) + TYPE_BOOLEAN, # chroot + TYPE_BOOLEAN, # show chroot + TYPE_PYOBJECT, # OBKeyBind + TYPE_STRING # keybind descriptor + ) + + cqk_model = Gtk.ListStore( + TYPE_UINT, # accel key + TYPE_INT, # accel mods + TYPE_STRING) # accel string (openbox) + return (model, cqk_model) + + def _create_scroll(self, view): + """_create_scroll + + :param view: + """ + scroll = Gtk.ScrolledWindow() + scroll.add(view) + scroll.set_policy(AUTOMATIC, AUTOMATIC) + scroll.set_shadow_type(Gtk.ShadowType.IN) + return scroll + + def _create_views(self, model, cqk_model): + """_create_views + + :param model: + :param cqk_model: + """ + # added accel_mode=1 (CELL_RENDERER_ACCEL_MODE_OTHER) for key "Tab" + r0 = Gtk.CellRendererAccel(accel_mode=1) + r0.props.editable = True + r0.connect('accel-edited', self.accel_edited) + + r1 = Gtk.CellRendererText() + r1.props.editable = True + r1.connect('edited', self.key_edited) + + r3 = Gtk.CellRendererText() + r3.props.editable = False + + r2 = Gtk.CellRendererToggle() + r2.connect('toggled', self.chroot_toggled) + + c0 = Gtk.TreeViewColumn(_("Key"), r0, accel_key=0, accel_mods=1) + c1 = Gtk.TreeViewColumn(_("Key (text)"), r1, text=2) + c2 = Gtk.TreeViewColumn(_("Chroot"), r2, active=3, visible=4) + c3 = Gtk.TreeViewColumn(_("Action"), r3, text=6) + + c0.set_resizable(True) + c1.set_resizable(True) + c2.set_resizable(True) + c3.set_resizable(True) + c0.set_fixed_width(200) + c1.set_fixed_width(200) + c2.set_fixed_width(100) + c3.set_fixed_width(200) + # the action column is the most important one, + # so make it get the extra available space + c3.set_expand(True) + + view = Gtk.TreeView(model) + view.append_column(c3) + view.append_column(c0) + view.append_column(c1) + view.append_column(c2) + view.get_selection().connect('changed', self.view_cursor_changed) + view.connect('button-press-event', self.view_button_clicked) + + # chainQuitKey table (wtf hack) + + r0 = Gtk.CellRendererAccel() + r0.props.editable = True + r0.connect('accel-edited', self.cqk_accel_edited) + + r1 = Gtk.CellRendererText() + r1.props.editable = True + r1.connect('edited', self.cqk_key_edited) + + c0 = Gtk.TreeViewColumn("Key", r0, accel_key=0, accel_mods=1) + c1 = Gtk.TreeViewColumn("Key (text)", r1, text=2) + + def cqk_view_focus_lost(view, event): + view.get_selection().unselect_all() + + cqk_view = Gtk.TreeView(cqk_model) + cqk_view.set_headers_visible(False) + cqk_view.append_column(c0) + cqk_view.append_column(c1) + cqk_view.connect('focus-out-event', cqk_view_focus_lost) + return (view, cqk_view) + + def _create_toolbar(self): + """_create_toolbar""" + toolbar = Gtk.Toolbar() + toolbar.set_style(Gtk.ToolbarStyle.ICONS) + toolbar.set_show_arrow(False) + + but = Gtk.ToolButton(Gtk.STOCK_SAVE) + but.set_tooltip_text(_("Save ") + self.ob.path + _(" file")) + but.connect('clicked', lambda b: self.ob.save(self.keyboard.deparse())) + toolbar.insert(but, -1) + + toolbar.insert(Gtk.SeparatorToolItem(), -1) + + but = Gtk.ToolButton(Gtk.STOCK_DND_MULTIPLE) + but.set_tooltip_text(_("Duplicate sibling keybind")) + but.connect('clicked', + lambda b: self.duplicate_selected()) + toolbar.insert(but, -1) + + but = Gtk.ToolButton(Gtk.STOCK_COPY) + but.set_tooltip_text(_("Copy")) + but.connect('clicked', + lambda b: self.copy_selected()) + toolbar.insert(but, -1) + + toolbar.insert(Gtk.SeparatorToolItem(), -1) + + but = Gtk.ToolButton() + but.set_icon_widget(self.icons['add_sibling']) + but.set_tooltip_text(_("Insert sibling keybind")) + but.connect('clicked', + lambda b: self.insert_sibling( + OBKeyBind())) + toolbar.insert(but, -1) + + but = Gtk.ToolButton(Gtk.STOCK_PASTE) + but.set_tooltip_text(_("Paste")) + but.connect('clicked', + lambda b: self.insert_sibling( + copy.deepcopy(self.copied))) + toolbar.insert(but, -1) + + toolbar.insert(Gtk.SeparatorToolItem(), -1) + + but = Gtk.ToolButton() + but.set_icon_widget(self.icons['add_child']) + but.set_tooltip_text(_("Insert child keybind")) + but.connect('clicked', + lambda b: self.insert_child( + OBKeyBind())) + toolbar.insert(but, -1) + + but = Gtk.ToolButton(Gtk.STOCK_PASTE) + but.set_tooltip_text(_("Paste as child")) + but.connect('clicked', + lambda b: self.insert_child( + copy.deepcopy(self.copied))) + toolbar.insert(but, -1) + + toolbar.insert(Gtk.SeparatorToolItem(), -1) + + but = Gtk.ToolButton(Gtk.STOCK_REMOVE) + but.set_tooltip_text(_("Remove keybind")) + but.connect('clicked', + lambda b: self.del_selected()) + toolbar.insert(but, -1) + self.sw_selection_available.append(but) + + sep = Gtk.SeparatorToolItem() + sep.set_draw(False) + sep.set_expand(True) + toolbar.insert(sep, -1) + + toolbar.insert(Gtk.SeparatorToolItem(), -1) + + but = Gtk.ToolButton(Gtk.STOCK_QUIT) + but.set_tooltip_text(_("Quit application")) + but.connect('clicked', + lambda b: Gtk.main_quit()) + toolbar.insert(but, -1) + return toolbar + + def apply_cqk_initial_value(self): + """apply_cqk_initial_value""" + cqk_accel_key, cqk_accel_mods = key_openbox2gtk( + self.keyboard.chain_quit_key) + if cqk_accel_mods == 0: + self.keyboard.chain_quit_key = "" + self.cqk_model.append(( + cqk_accel_key, cqk_accel_mods, + self.keyboard.chain_quit_key)) + + def get_action_desc(self, kb): + """get_action_desc + + :param kb: + """ + if len(kb.actions) > 0: + if kb.actions[0].name == "Execute": + frst_action = "[ "\ + + kb.actions[0].options['command'] + " ]" + elif kb.actions[0].name == "SendToDesktop": + frst_action = _(kb.actions[0].name)\ + + " " + str(kb.actions[0].options['desktop']) + elif kb.actions[0].name == "Desktop": + frst_action = _(kb.actions[0].name)\ + + " " + str(kb.actions[0].options['desktop']) + else: + frst_action = _(kb.actions[0].name) + else: + frst_action = "." + return frst_action + + def apply_keybind(self, kb, parent=None): + """apply_keybind + + :param kb: + :param parent: + """ + accel_key, accel_mods = key_openbox2gtk(kb.key) + chroot = kb.chroot + show_chroot = len(kb.children) > 0 or not len(kb.actions) + + n = self.model.append(parent, ( + accel_key, accel_mods, kb.key, + chroot, show_chroot, kb, + self.get_action_desc(kb))) + + for c in kb.children: + self.apply_keybind(c, n) + + def load_icons(self): + """load_icons""" + icons = {} + icons['add_sibling'] = Gtk.Image.new_from_file( + res.getIcon("add_sibling.png")) + icons['add_child'] = Gtk.Image.new_from_file( + res.getIcon("add_child.png")) + return icons + + # ---------------------------------------------------------------------------- + # callbacks + + def view_button_clicked(self, view, event): + """view_button_clicked + + :param view: + :param event: + """ + if event.button == 3: + x = int(event.x) + y = int(event.y) + time = event.time + pathinfo = view.get_path_at_pos(x, y) + if pathinfo: + path, col, cellx, celly = pathinfo + view.grab_focus() + view.set_cursor(path, col, 0) + self.context_menu.popup( + None, None, None, None, + event.button, time) + else: + view.grab_focus() + view.get_selection().unselect_all() + self.context_menu.popup( + None, None, None, None, + event.button, time) + return 1 + + def actions_cb(self, val=None): + """actions_cb + + :param val: + """ + (model, it) = self.view.get_selection().get_selected() + kb = model.get_value(it, 5) + + if len(kb.actions) == 0: + model.set_value(it, 4, True) + self.cond_insert_child.set_state(True) + else: + model.set_value(it, 6, self.get_action_desc(kb)) + model.set_value(it, 4, False) + self.cond_insert_child.set_state(False) + + def view_cursor_changed(self, selection): + """view_cursor_changed + + :param selection: + """ + (model, it) = selection.get_selected() + actions = None + if it: + kb = model.get_value(it, 5) + if len(kb.children) == 0 and not kb.chroot: + actions = kb.actions + self.cond_selection_available.set_state(True) + self.cond_insert_child.set_state(len(kb.actions) == 0) + else: + self.cond_insert_child.set_state(False) + self.cond_selection_available.set_state(False) + self.actionlist.set_actions(actions) + + def cqk_accel_edited(self, cell, path, accel_key, accel_mods, keycode): + """cqk_accel_edited + + :param cell: + :param path: + :param accel_key: + :param accel_mods: + :param keycode: + """ + self.cqk_model[path][0] = accel_key + self.cqk_model[path][1] = accel_mods + kstr = key_gtk2openbox(accel_key, accel_mods) + self.cqk_model[path][2] = kstr + self.keyboard.chain_quit_key = kstr + self.view.grab_focus() + + def cqk_key_edited(self, cell, path, text): + """cqk_key_edited + + :param cell: + :param path: + :param text: + """ + self.cqk_model[path][0], self.cqk_model[path][1] \ + = key_openbox2gtk(text) + self.cqk_model[path][2] = text + self.keyboard.chain_quit_key = text + self.view.grab_focus() + + def accel_edited(self, cell, path, accel_key, accel_mods, keycode): + """accel_edited + + :param cell: + :param path: + :param accel_key: + :param accel_mods: + :param keycode: + """ + self.model[path][0] = accel_key + self.model[path][1] = accel_mods + kstr = key_gtk2openbox(accel_key, accel_mods) + self.model[path][2] = kstr + self.model[path][5].key = kstr + + def key_edited(self, cell, path, text): + """key_edited + + :param cell: + :param path: + :param text: + """ + self.model[path][0], self.model[path][1] = key_openbox2gtk(text) + self.model[path][2] = text + self.model[path][5].key = text + + def chroot_toggled(self, cell, path): + """chroot_toggled + + :param cell: + :param path: + """ + self.model[path][3] = not self.model[path][3] + kb = self.model[path][5] + kb.chroot = self.model[path][3] + if kb.chroot: + self.actionlist.set_actions(None) + elif not kb.children: + self.actionlist.set_actions(kb.actions) + + # ------------------------------------------------------------------------- + def cut_selected(self): + """cut_selected""" + self.copy_selected() + self.del_selected() + + def duplicate_selected(self): + """duplicate_selected""" + self.copy_selected() + self.insert_sibling(copy.deepcopy(self.copied)) + + def copy_selected(self): + """copy_selected""" + (model, it) = self.view.get_selection().get_selected() + if it: + sel = model.get_value(it, 5) + self.copied = copy.deepcopy(sel) + self.cond_paste_buffer.set_state(True) + + def _insert_keybind(self, keybind, parent=None, after=None): + """_insert_keybind + + :param keybind: + :param parent: + :param after: + """ + keybind.parent = parent + if parent: + kbs = parent.children + else: + kbs = self.keyboard.keybinds + + if after: + kbs.insert(kbs.index(after) + 1, keybind) + else: + kbs.append(keybind) + + def insert_sibling(self, keybind): + """insert_sibling + + :param keybind: + """ + (model, it) = self.view.get_selection().get_selected() + + accel_key, accel_mods = key_openbox2gtk(keybind.key) + show_chroot = len(keybind.children) > 0 or not len(keybind.actions) + + if it: + parent_it = model.iter_parent(it) + parent = None + if parent_it: + parent = model.get_value(parent_it, 5) + after = model.get_value(it, 5) + + self._insert_keybind(keybind, parent, after) + newit = self.model.insert_after( + parent_it, it, ( + accel_key, accel_mods, keybind.key, + keybind.chroot, show_chroot, + keybind, + self.get_action_desc(keybind))) + else: + self._insert_keybind(keybind) + newit = self.model.append(None, ( + accel_key, accel_mods, keybind.key, + keybind.chroot, show_chroot, + keybind, + self.get_action_desc(keybind))) + + if newit: + for c in keybind.children: + self.apply_keybind(c, newit) + self.view.get_selection().select_iter(newit) + + def insert_child(self, keybind): + """insert_child + + :param keybind: + """ + (model, it) = self.view.get_selection().get_selected() + parent = model.get_value(it, 5) + self._insert_keybind(keybind, parent) + + accel_key, accel_mods = key_openbox2gtk(keybind.key) + show_chroot = len(keybind.children) > 0 or not len(keybind.actions) +# newit = + self.model.append(it, ( + accel_key, accel_mods, keybind.key, + keybind.chroot, show_chroot, keybind, + self.get_action_desc(keybind))) + +# if newit: +# for c in keybind.children: +# self.apply_keybind(c, newit) +# self.view.get_selection().select_iter(newit) + # it means that we have inserted first child here, change status + if len(parent.children) == 1: + self.actionlist.set_actions(None) + + def del_selected(self): + """del_selected""" + (model, it) = self.view.get_selection().get_selected() + if it: + kb = model.get_value(it, 5) + kbs = self.keyboard.keybinds + if kb.parent: + kbs = kb.parent.children + kbs.remove(kb) + isok = self.model.remove(it) + if isok: + self.view.get_selection().select_iter(it) diff --git a/obkey_parts/KeyUtils.py b/obkey_parts/KeyUtils.py new file mode 100644 index 0000000..76e6ed2 --- /dev/null +++ b/obkey_parts/KeyUtils.py @@ -0,0 +1,92 @@ +""" + This file is a part of Openbox Key Editor + Copyright (C) 2009 nsf + v1.1 - Code migrated from PyGTK to PyGObject + github.com/stevenhoneyman/obkey + v1.2pre - 19.06.2016 - structured presentation of actions... + v1.2 - 24.02.2018 - slightly refactored code - more dynamic + github.com/luffah/obkey + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +""" +from obkey_parts.Gui import Gtk +# ========================================================= +# Key utils +# ========================================================= + +REPLACE_TABLE_OPENBOX2GTK = { + "mod1": "", "mod2": "", + "mod3": "", "mod4": "", "mod5": "", + "control": "", "c": "", + "alt": "", "a": "", + "meta": "", "m": "", + "super": "", "w": "", + "shift": "", "s": "", + "hyper": "", "h": "" +} + +REPLACE_TABLE_GTK2OPENBOX = { + "Mod1": "Mod1", "Mod2": "Mod2", + "Mod3": "Mod3", "Mod4": "Mod4", "Mod5": "Mod5", + "Control": "C", "Primary": "C", + "Alt": "A", + "Meta": "M", + "Super": "W", + "Shift": "S", + "Hyper": "H" +} + + +def key_openbox2gtk(obstr): + """key_openbox2gtk + + :param obstr: + """ + if obstr is not None: + toks = obstr.split("-") + try: + toksgdk = [REPLACE_TABLE_OPENBOX2GTK[mod.lower()] for mod in toks[:-1]] + except BufferError: + return (0, 0) + toksgdk.append(toks[-1]) + return Gtk.accelerator_parse("".join(toksgdk)) + + +def key_gtk2openbox(key, mods): + """key_gtk2openbox + + :param key: + :param mods: + """ + result = "" + if mods: + modtable = Gtk.accelerator_name(0, mods) + modtable = [ + REPLACE_TABLE_GTK2OPENBOX[i] + for i in modtable[1:-1].split('><') + ] + result = '-'.join(modtable) + if key: + keychar = Gtk.accelerator_name(key, 0) + if result != "": + result += '-' + result += keychar + return result diff --git a/obkey_parts/OBKeyboard.py b/obkey_parts/OBKeyboard.py new file mode 100644 index 0000000..001f5a3 --- /dev/null +++ b/obkey_parts/OBKeyboard.py @@ -0,0 +1,165 @@ +""" + This file is a part of Openbox Key Editor + Copyright (C) 2009 nsf + v1.1 - Code migrated from PyGTK to PyGObject + github.com/stevenhoneyman/obkey + v1.2pre - 19.06.2016 - structured presentation of actions... + v1.2 - 24.02.2018 - slightly refactored code - more dynamic + github.com/luffah/obkey + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ---- + OBKeyBind class definition + OBKeyboard is a collection of OBKeyBind + chainQuitKey +""" +from obkey_parts.ActionList import OBAction +from obkey_parts.XmlUtils import ( + xml_find_nodes, xml_find_node, xml_get_str, parseString, Element +) + + +class OBKeyBind(object): + + """OBKeyBind""" + + def __init__(self, parent=None): + """__init__ + + :param parent: + """ + self.children = [] + self.actions = [] + self.key = "a" + self.chroot = False + self.parent = parent + + def parse(self, dom): + """parse + + :param dom: + """ + self.key = dom.getAttribute("key") + self.chroot = dom.getAttribute("chroot") in ['true', 'yes', 'on'] + + kbinds = xml_find_nodes(dom, "keybind") + if len(kbinds): + for k in kbinds: + keybind = OBKeyBind(self) + keybind.parse(k) + self.children.append(keybind) + else: + for action in xml_find_nodes(dom, "action"): + newaction = OBAction() + newaction.parse(action) + self.actions.append(newaction) + + def deparse(self): + """deparse""" + root = Element('keybind') + root.setAttribute('key', str(self.key)) + if self.chroot: + root.setAttribute('chroot', "yes") + # root = parseString( + # ' < keybind key = "' + + # str(self.key) + + # '" chroot="yes"/ > ').documentElement + # else: + # root = parseString( + # '').documentElement + + if len(self.children): + for keybind in self.children: + root.appendChild(keybind.deparse()) + else: + for action in self.actions: + root.appendChild(action.deparse()) + return root + + # def insert_empty_action(self, after=None): + """insert_empty_action + + :param after: + """ + # newact = OBAction() + # newact.mutate("Execute") + + # if after: + # self.actions.insert(self.actions.index(after) + 1, newact) + # else: + # self.actions.append(newact) + # return newact + + def move_up(self, action): + """move_up + + :param action: + """ + i = self.actions.index(action) + tmp = self.actions[i - 1] + self.actions[i - 1] = action + self.actions[i] = tmp + + def move_down(self, action): + """move_down + + :param action: + """ + i = self.actions.index(action) + tmp = self.actions[i + 1] + self.actions[i + 1] = action + self.actions[i] = tmp + + +class OBKeyboard(object): + """OBKeyboard""" + + def __init__(self, dom): + """__init__ + + :param dom: + """ + self.chain_quit_key = None + self.keybinds = [] + + cqk = xml_find_node(dom, "chainQuitKey") + if cqk: + self.chain_quit_key = xml_get_str(cqk) + + for keybind_node in xml_find_nodes(dom, "keybind"): + keybind = OBKeyBind() + keybind.parse(keybind_node) + self.keybinds.append(keybind) + + def deparse(self): + """deparse""" + + root = parseString('').documentElement + chain_quit_key_node = parseString('' + + str(self.chain_quit_key) + + '').documentElement + root.appendChild(chain_quit_key_node) + + for k in self.keybinds: + root.appendChild(k.deparse()) + + return root diff --git a/obkey_parts/OpenboxConfig.py b/obkey_parts/OpenboxConfig.py new file mode 100644 index 0000000..db9a2b0 --- /dev/null +++ b/obkey_parts/OpenboxConfig.py @@ -0,0 +1,87 @@ +""" + This file is a part of Openbox Key Editor + Copyright (C) 2009 nsf + v1.1 - Code migrated from PyGTK to PyGObject + github.com/stevenhoneyman/obkey + v1.2pre - 19.06.2016 - structured presentation of actions... + v1.2 - 24.02.2018 - slightly refactored code - more dynamic + github.com/luffah/obkey + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +""" +from StringIO import StringIO +from os import system +from obkey_parts.XmlUtils import ( + minidom, xml_find_node, fixed_writexml, parseString +) + + +class OpenboxConfig: + + """OpenboxConfig""" + + def __init__(self): + """__init__""" + self.dom = None + self.keyboard = None + self.path = None + + def load(self, path): + """load + + :param path: + """ + self.path = path + + # load config DOM + self.dom = minidom.parse(path) + + # try load keyboard DOM + self.keyboard_node = xml_find_node( + self.dom.documentElement, + "keyboard" + ) + + def save(self, keyboard_node): + """save + + :param keyboard_node: + """ + if self.path is None: + return + + # it's all hack, waste of resources etc, but does pretty good result + writer = StringIO() + fixed_writexml(keyboard_node, writer, " ", " ", "\n") + + newdom = xml_find_node(parseString(writer.getvalue()), "keyboard") + keyboard = xml_find_node(self.dom.documentElement, "keyboard") + self.dom.documentElement.replaceChild(newdom, keyboard) + f = file(self.path, "w") + if f: + xmlform = self.dom.documentElement + f.write(xmlform.toxml("utf8")) + f.close() + self.reconfigure_openbox() + + def reconfigure_openbox(self): + """reconfigure_openbox""" + system("openbox --reconfigure") diff --git a/obkey_parts/PropertyTable.py b/obkey_parts/PropertyTable.py new file mode 100644 index 0000000..47a7799 --- /dev/null +++ b/obkey_parts/PropertyTable.py @@ -0,0 +1,103 @@ +""" + This file is a part of Openbox Key Editor + Copyright (C) 2009 nsf + v1.1 - Code migrated from PyGTK to PyGObject + github.com/stevenhoneyman/obkey + v1.2pre - 19.06.2016 - structured presentation of actions... + v1.2 - 24.02.2018 - slightly refactored code - more dynamic + github.com/luffah/obkey + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +""" + +from obkey_parts.Resources import _ +from obkey_parts.Gui import Gtk, NEVER, AUTOMATIC, EXPAND, FILL + +# ========================================================= +# PropertyTable +# ========================================================= + + +class PropertyTable: + + def __init__(self): + """__init__""" + self.widget = Gtk.ScrolledWindow() + self.table = Gtk.Table(1, 2) + self.table.set_row_spacings(5) + self.widget.add_with_viewport(self.table) + self.widget.set_policy(NEVER, AUTOMATIC) + + def add_row(self, label_text, table): + """add_row + + :param label_text: + :param table: + """ + label = Gtk.Label(label=_(label_text)) + label.set_alignment(0, 0) + row = self.table.props.n_rows + self.table.attach( + label, 0, 1, row, row + 1, + EXPAND | FILL, + 0, 5, 0) + self.table.attach(table, 1, 2, row, row + 1, FILL, 0, 5, 0) + + def clear(self): + """clear""" + cs = self.table.get_children() + cs.reverse() + for c in cs: + self.table.remove(c) + self.table.resize(1, 2) + + def set_action(self, action, cb=None): + """set_action + + :param action: + :param cb: + """ + self.clear() + # label = Gtk.Label(label=_('Properties')) + # label.set_alignment(0, 0) + # row = self.table.props.n_rows + # self.table.attach( + # label, 0, 1, row, row + 1, + # EXPAND | FILL, + # 0, 5, 0) + if not action: + return + for a in action.option_defs: + widget = a.generate_widget(action, cb) + # IF can return a list + if isinstance(widget, list): + for row in widget: + self.add_row(row['name'] + ":", row['widget']) + else: + self.add_row(a.name + ":", widget) + self.table.queue_resize() + self.table.show_all() + + +# event = gtk.gdk.Event(gtk.gdk.FOCUS_CHANGE) +# event.window = entry.get_window() # the gtk.gdk.Window of the widget +# event.send_event = True # this means you sent the event explicitly +# event.in_ = False # False for focus out, True for focus in diff --git a/obkey_parts/Resources.py b/obkey_parts/Resources.py new file mode 100644 index 0000000..4133b61 --- /dev/null +++ b/obkey_parts/Resources.py @@ -0,0 +1,76 @@ +#!/usr/bin/python2 +""" + This file is a part of Openbox Key Editor + Copyright (C) 2009 nsf + v1.1 - Code migrated from PyGTK to PyGObject + github.com/stevenhoneyman/obkey + v1.2pre - 19.06.2016 - structured presentation of actions... + v1.2 - 24.02.2018 - slightly refactored code - more dynamic + github.com/luffah/obkey + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +""" +import __builtin__ +from os.path import join as path_join, dirname, isdir +import sys + +# XXX: Sorry, for now this is it. +# If you know a better way to do this with setup.py: +# please mail me. + + +class Resources(object): + + """Resources of the application""" + + def __init__(self, argv): + """__init__ + + :param argv: + """ + if isdir('./resources/icons') and isdir('./resources/locale'): + self.icons = './resources/icons' + self.locale_dir = './resources/locale' + else: + config_prefix = dirname(dirname(argv[0])) + self.icons = path_join(config_prefix, 'share/obkey/icons') + self.locale_dir = path_join(config_prefix, 'share/locale') + try: + from gettext import install as gettext_init + gettext_init('obkey', self.locale_dir) + except ImportError: + print "Gettext is missing" + + def _(a): + return a + + def getIcon(self, fname): + """getIcon + + :param fname: + """ + return path_join(self.icons, fname) + + +res = Resources(sys.argv) + +# trick for syntax checkers +_ = __builtin__.__dict__['_'] diff --git a/obkey_parts/XmlUtils.py b/obkey_parts/XmlUtils.py new file mode 100644 index 0000000..5e5b3d6 --- /dev/null +++ b/obkey_parts/XmlUtils.py @@ -0,0 +1,106 @@ +""" + This file is a part of Openbox Key Editor + Copyright (C) 2009 nsf + v1.1 - Code migrated from PyGTK to PyGObject + github.com/stevenhoneyman/obkey + v1.2pre - 19.06.2016 - structured presentation of actions... + v1.2 - 24.02.2018 - slightly refactored code - more dynamic + github.com/luffah/obkey + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +""" +import xml.dom.minidom as minidom +from xml.dom.minidom import parseString, Element +from xml.sax.saxutils import escape + + +def xml_get_str(elt): + """xml_get_str + + :param elt:XML element + """ + return elt.firstChild.nodeValue if elt.hasChildNodes() else "" + + +def xml_parse_bool(elt): + """xml_parse_bool + + :param elt:XML element + """ + val = elt.firstChild.nodeValue.lower() + if val == "true" or val == "yes" or val == "on": + return True + return False + + +def xml_find_nodes(elt, name): + """xml_find_nodes + + :param elt:XML parent element + :param name:tag name + """ + return [node for node in elt.childNodes if node.nodeName == name] + + +def xml_find_node(elt, name): + """xml_find_node + + :param elt:XML parent element + :param name:tag name + """ + nodes = xml_find_nodes(elt, name) + return nodes[0] if len(nodes) == 1 else None + + +def fixed_writexml(self, writer, indent="", addindent="", newl=""): + """fixed_writexml + + :param writer:XML node + :param indent:current indentation + :param addindent:indentation to add to higher levels + :param newl:newline string + """ + writer.write(indent + "<" + self.tagName) + + attrs = self._get_attributes() + a_names = attrs.keys() +# a_names.sort() + + for a_name in a_names: + writer.write(" %s=\"" % a_name) + minidom._write_data(writer, attrs[a_name].value) + writer.write("\"") + if self.childNodes: + if len(self.childNodes) == 1 \ + and self.childNodes[0].nodeType \ + == minidom.Node.TEXT_NODE: + writer.write(">") + self.childNodes[0].writexml(writer, "", "", "") + writer.write("%s" % (self.tagName, newl)) + return + writer.write(">%s" % newl) + for node in self.childNodes: + fixed_writexml( + node, writer, + indent + addindent, addindent, newl) + writer.write("%s%s" % (indent, self.tagName, newl)) + else: + writer.write("/>%s" % (newl)) diff --git a/obkey_parts/__init__.py b/obkey_parts/__init__.py new file mode 100644 index 0000000..f52dbcc --- /dev/null +++ b/obkey_parts/__init__.py @@ -0,0 +1,40 @@ +#!/usr/bin/python2 +""" + This file is a part of Openbox Key Editor + Copyright (C) 2009 nsf + v1.1 - Code migrated from PyGTK to PyGObject + github.com/stevenhoneyman/obkey + v1.2pre1 - solve a minor bug on copy-paste bug + v1.2pre2 - 19.06.2016 - structured presentation of actions... + github.com/luffah/obkey + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +""" + +from obkey_parts.OpenboxConfig import OpenboxConfig +from obkey_parts.ActionList import ActionList +from obkey_parts.KeyTable import KeyTable +from obkey_parts.PropertyTable import PropertyTable +from obkey_parts.Gui import Gtk +# internal needs +from obkey_parts.OBKeyboard import OBKeyboard +from obkey_parts.Resources import _ +from obkey_parts.__version__ import __version__, __description__, __long_description__ diff --git a/obkey_parts/__version__.py b/obkey_parts/__version__.py new file mode 100644 index 0000000..b9fb33d --- /dev/null +++ b/obkey_parts/__version__.py @@ -0,0 +1,28 @@ + +""" obkey package informations +""" +MAJOR = 1 +MINOR = 2 +PATCH = 2 + +__version__ = "{0}.{1}.{2}".format(MAJOR, MINOR, PATCH) + +__description__ = 'Openbox Key Editor' +__long_description__ = """ +A keybinding editor for OpenBox, it includes launchers and window management keys. + +It allows to: + * can check almost all keybinds in one second. + * add new keybinds, the default key associated will be 'a' and no action will be associated; + * add new child keybinds; + * setup existing keybinds : + * add/remove/sort/setup actions in the actions list; + * change the keybind by clicking on the item in the list; + * duplicate existing keybinds; + * remove a keybind. + +The current drawbacks : + * XML inculsion is not managed. If you want to edit many files, then you shall open them with `obkey .xml`; + * `if` conditionnal tag is not supported (but did you knew it exists). + +""" diff --git a/po/Makefile b/po/Makefile index 33b4798..bb673f8 100644 --- a/po/Makefile +++ b/po/Makefile @@ -1,8 +1,8 @@ LANGS := $(patsubst obkey.%.po,%,$(wildcard *.po)) -TARGETS := $(patsubst %,../locale/%/LC_MESSAGES/obkey.mo,$(LANGS)) +TARGETS := $(patsubst %,../resources/locale/%/LC_MESSAGES/obkey.mo,$(LANGS)) all: $(TARGETS) -../locale/%/LC_MESSAGES/obkey.mo: obkey.%.po +../resources/locale/%/LC_MESSAGES/obkey.mo: obkey.%.po mkdir -p $(dir $@) msgfmt -o $@ $< diff --git a/po/obkey.bs.po b/po/obkey.bs.po new file mode 100644 index 0000000..7bdb603 --- /dev/null +++ b/po/obkey.bs.po @@ -0,0 +1,115 @@ +# Bosnian translation for obkey +# Copyright (C) 2009-2016 nsf +# This file is distributed under the same license as the obkey package. +# Dino Duratović , 2016. +# +msgid "" +msgstr "" +"Project-Id-Version: obkey 1.2pre\n" +"Report-Msgid-Bugs-To: https://github.com/stevenhoneyman/obkey\n" +"POT-Creation-Date: 2016-11-03 17:45+0100\n" +"PO-Revision-Date: 2016-11-03 18:10+0100\n" +"Language: bs\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Last-Translator: Dino Duratović \n" +"Language-Team: \n" +"X-Generator: Poedit 1.8.11\n" + +#: obkey_classes.py:55 +msgid "obkey" +msgstr "obkey" + +#: obkey_classes.py:213 +msgid "chainQuitKey:" +msgstr "Dugme za prekid lanca:" + +#: obkey_classes.py:229 obkey_classes.py:766 +msgid "Cut" +msgstr "Izreži" + +#: obkey_classes.py:235 obkey_classes.py:356 obkey_classes.py:772 +msgid "Copy" +msgstr "Kopiraj" + +#: obkey_classes.py:241 obkey_classes.py:367 obkey_classes.py:778 +msgid "Paste" +msgstr "Zalijepi" + +#: obkey_classes.py:246 obkey_classes.py:379 +msgid "Paste as child" +msgstr "Zalijepi kao podkomandu" + +#: obkey_classes.py:253 obkey_classes.py:784 +msgid "Remove" +msgstr "Ukloni" + +#: obkey_classes.py:298 +msgid "Key" +msgstr "Ključ" + +#: obkey_classes.py:299 +msgid "Key (text)" +msgstr "Ključ (tekst)" + +#: obkey_classes.py:300 +msgid "Chroot" +msgstr "Chroot" + +#: obkey_classes.py:301 +msgid "Action" +msgstr "Akcija" + +#: obkey_classes.py:344 +msgid "Save " +msgstr "Snimi" + +#: obkey_classes.py:344 +msgid " file" +msgstr " fajl" + +#: obkey_classes.py:351 +msgid "Duplicate sibling keybind" +msgstr "Dupliciraj srodnu priječicu" + +#: obkey_classes.py:362 +msgid "Insert sibling keybind" +msgstr "Ubaci srodnu priječicu" + +#: obkey_classes.py:374 +msgid "Insert child keybind" +msgstr "Ubaci podkomanda priječicu" + +#: obkey_classes.py:384 +msgid "Remove keybind" +msgstr "Ukloni priječicu" + +#: obkey_classes.py:397 +msgid "Quit application" +msgstr "Ugasi aplikaciju" + +#: obkey_classes.py:752 +msgid "Actions" +msgstr "Akcije" + +#: obkey_classes.py:798 +msgid "Insert action" +msgstr "Ubaci akciju" + +#: obkey_classes.py:803 +msgid "Remove action" +msgstr "Ukloni akciju" + +#: obkey_classes.py:809 +msgid "Move action up" +msgstr "Pomjeri akciju gore" + +#: obkey_classes.py:815 +msgid "Move action down" +msgstr "Pomjeri akciju dole" + +#: obkey_classes.py:826 +msgid "Remove all actions" +msgstr "Ukloni sve akcije" diff --git a/po/obkey.fr.po b/po/obkey.fr.po index c0c40d6..506e739 100644 --- a/po/obkey.fr.po +++ b/po/obkey.fr.po @@ -14,20 +14,20 @@ msgstr "" msgid "obkey" msgstr "Raccourcis clavier OpenBox" -msgid "Cu_t" -msgstr "Coupe_r" +msgid "Cut" +msgstr "Couper" -msgid "_Copy" -msgstr "_Copier" +msgid "Copy" +msgstr "Copier" -msgid "_Paste" -msgstr "_Coller" +msgid "Paste" +msgstr "Coller" -msgid "P_aste as child" -msgstr "_Coller en tant que raccourcis de niveau n + 1 " +msgid "Paste as child" +msgstr "Coller en tant que raccourcis de niveau n + 1 " -msgid "_Remove" -msgstr "_Supprimer" +msgid "Remove" +msgstr "Supprimer" msgid "Save " msgstr "Enregistrer " @@ -35,6 +35,9 @@ msgstr "Enregistrer " msgid " file" msgstr " fichier" +msgid "Duplicate sibling keybind" +msgstr "Dupliquer la combinaison de touches sélectionnée" + msgid "Insert sibling keybind" msgstr "Insérer une combinaison de touches" diff --git a/po/obkey.hr.po b/po/obkey.hr.po new file mode 100644 index 0000000..0acfbcc --- /dev/null +++ b/po/obkey.hr.po @@ -0,0 +1,115 @@ +# Croatian translation for obkey +# Copyright (C) 2009-2016 nsf +# This file is distributed under the same license as the obkey package. +# Dino Duratović , 2016. +# +msgid "" +msgstr "" +"Project-Id-Version: obkey 1.2pre\n" +"Report-Msgid-Bugs-To: https://github.com/stevenhoneyman/obkey\n" +"POT-Creation-Date: 2016-11-03 17:45+0100\n" +"PO-Revision-Date: 2016-11-03 18:08+0100\n" +"Language: hr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Last-Translator: Dino Duratović \n" +"Language-Team: \n" +"X-Generator: Poedit 1.8.11\n" + +#: obkey_classes.py:55 +msgid "obkey" +msgstr "obkey" + +#: obkey_classes.py:213 +msgid "chainQuitKey:" +msgstr "Dugme za prekid lanca:" + +#: obkey_classes.py:229 obkey_classes.py:766 +msgid "Cut" +msgstr "Izreži" + +#: obkey_classes.py:235 obkey_classes.py:356 obkey_classes.py:772 +msgid "Copy" +msgstr "Kopiraj" + +#: obkey_classes.py:241 obkey_classes.py:367 obkey_classes.py:778 +msgid "Paste" +msgstr "Zalijepi" + +#: obkey_classes.py:246 obkey_classes.py:379 +msgid "Paste as child" +msgstr "Zalijepi kao podkomandu" + +#: obkey_classes.py:253 obkey_classes.py:784 +msgid "Remove" +msgstr "Ukloni" + +#: obkey_classes.py:298 +msgid "Key" +msgstr "Ključ" + +#: obkey_classes.py:299 +msgid "Key (text)" +msgstr "Ključ (tekst)" + +#: obkey_classes.py:300 +msgid "Chroot" +msgstr "Chroot" + +#: obkey_classes.py:301 +msgid "Action" +msgstr "Akcija" + +#: obkey_classes.py:344 +msgid "Save " +msgstr "Snimi" + +#: obkey_classes.py:344 +msgid " file" +msgstr " fajl" + +#: obkey_classes.py:351 +msgid "Duplicate sibling keybind" +msgstr "Dupliciraj srodnu priječicu" + +#: obkey_classes.py:362 +msgid "Insert sibling keybind" +msgstr "Ubaci srodnu priječicu" + +#: obkey_classes.py:374 +msgid "Insert child keybind" +msgstr "Ubaci podkomanda priječicu" + +#: obkey_classes.py:384 +msgid "Remove keybind" +msgstr "Ukloni priječicu" + +#: obkey_classes.py:397 +msgid "Quit application" +msgstr "Ugasi aplikaciju" + +#: obkey_classes.py:752 +msgid "Actions" +msgstr "Akcije" + +#: obkey_classes.py:798 +msgid "Insert action" +msgstr "Ubaci akciju" + +#: obkey_classes.py:803 +msgid "Remove action" +msgstr "Ukloni akciju" + +#: obkey_classes.py:809 +msgid "Move action up" +msgstr "Pomjeri akciju gore" + +#: obkey_classes.py:815 +msgid "Move action down" +msgstr "Pomjeri akciju dole" + +#: obkey_classes.py:826 +msgid "Remove all actions" +msgstr "Ukloni sve akcije" diff --git a/po/obkey.sr.po b/po/obkey.sr.po new file mode 100644 index 0000000..830d6c1 --- /dev/null +++ b/po/obkey.sr.po @@ -0,0 +1,115 @@ +# Serbian translation for obkey +# Copyright (C) 2009-2016 nsf +# This file is distributed under the same license as the obkey package. +# Dino Duratović , 2016. +# +msgid "" +msgstr "" +"Project-Id-Version: obkey 1.2pre\n" +"Report-Msgid-Bugs-To: https://github.com/stevenhoneyman/obkey\n" +"POT-Creation-Date: 2016-11-03 17:45+0100\n" +"PO-Revision-Date: 2016-11-03 18:05+0100\n" +"Language: sr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Last-Translator: Dino Duratović \n" +"Language-Team: \n" +"X-Generator: Poedit 1.8.11\n" + +#: obkey_classes.py:55 +msgid "obkey" +msgstr "obkey" + +#: obkey_classes.py:213 +msgid "chainQuitKey:" +msgstr "Дугме за прекид ланца:" + +#: obkey_classes.py:229 obkey_classes.py:766 +msgid "Cut" +msgstr "Изрежи" + +#: obkey_classes.py:235 obkey_classes.py:356 obkey_classes.py:772 +msgid "Copy" +msgstr "Копирај" + +#: obkey_classes.py:241 obkey_classes.py:367 obkey_classes.py:778 +msgid "Paste" +msgstr "Залепи" + +#: obkey_classes.py:246 obkey_classes.py:379 +msgid "Paste as child" +msgstr "Залепи као подкоманду" + +#: obkey_classes.py:253 obkey_classes.py:784 +msgid "Remove" +msgstr "Уклони" + +#: obkey_classes.py:298 +msgid "Key" +msgstr "Кључ" + +#: obkey_classes.py:299 +msgid "Key (text)" +msgstr "Кључ (текст)" + +#: obkey_classes.py:300 +msgid "Chroot" +msgstr "Chroot" + +#: obkey_classes.py:301 +msgid "Action" +msgstr "Акција" + +#: obkey_classes.py:344 +msgid "Save " +msgstr "Сними" + +#: obkey_classes.py:344 +msgid " file" +msgstr " фајл" + +#: obkey_classes.py:351 +msgid "Duplicate sibling keybind" +msgstr "Дуплицирај сродну пречицу" + +#: obkey_classes.py:362 +msgid "Insert sibling keybind" +msgstr "Убаци сродну пречицу" + +#: obkey_classes.py:374 +msgid "Insert child keybind" +msgstr "Убаци подкоманда пречицу" + +#: obkey_classes.py:384 +msgid "Remove keybind" +msgstr "Уклону пречицу" + +#: obkey_classes.py:397 +msgid "Quit application" +msgstr "Угаси апликацију" + +#: obkey_classes.py:752 +msgid "Actions" +msgstr "Акције" + +#: obkey_classes.py:798 +msgid "Insert action" +msgstr "Убаци акцију" + +#: obkey_classes.py:803 +msgid "Remove action" +msgstr "Уклони акцију" + +#: obkey_classes.py:809 +msgid "Move action up" +msgstr "Помери акцију горе" + +#: obkey_classes.py:815 +msgid "Move action down" +msgstr "Помери акцију доле" + +#: obkey_classes.py:826 +msgid "Remove all actions" +msgstr "Уклони све акције" diff --git a/po/template.pot b/po/template.pot new file mode 100644 index 0000000..7b6399e --- /dev/null +++ b/po/template.pot @@ -0,0 +1,114 @@ +# LANGUAGE translation for obkey +# Copyright (C) 2009-2016 nsf +# This file is distributed under the same license as the obkey package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: obkey 1.2pre\n" +"Report-Msgid-Bugs-To: https://github.com/stevenhoneyman/obkey\n" +"POT-Creation-Date: 2016-11-03 17:45+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: obkey_classes.py:55 +msgid "obkey" +msgstr "" + +#: obkey_classes.py:213 +msgid "chainQuitKey:" +msgstr "" + +#: obkey_classes.py:229 obkey_classes.py:766 +msgid "Cut" +msgstr "" + +#: obkey_classes.py:235 obkey_classes.py:356 obkey_classes.py:772 +msgid "Copy" +msgstr "" + +#: obkey_classes.py:241 obkey_classes.py:367 obkey_classes.py:778 +msgid "Paste" +msgstr "" + +#: obkey_classes.py:246 obkey_classes.py:379 +msgid "Paste as child" +msgstr "" + +#: obkey_classes.py:253 obkey_classes.py:784 +msgid "Remove" +msgstr "" + +#: obkey_classes.py:298 +msgid "Key" +msgstr "" + +#: obkey_classes.py:299 +msgid "Key (text)" +msgstr "" + +#: obkey_classes.py:300 +msgid "Chroot" +msgstr "" + +#: obkey_classes.py:301 +msgid "Action" +msgstr "" + +#: obkey_classes.py:344 +msgid "Save " +msgstr "" + +#: obkey_classes.py:344 +msgid " file" +msgstr "" + +#: obkey_classes.py:351 +msgid "Duplicate sibling keybind" +msgstr "" + +#: obkey_classes.py:362 +msgid "Insert sibling keybind" +msgstr "" + +#: obkey_classes.py:374 +msgid "Insert child keybind" +msgstr "" + +#: obkey_classes.py:384 +msgid "Remove keybind" +msgstr "" + +#: obkey_classes.py:397 +msgid "Quit application" +msgstr "" + +#: obkey_classes.py:752 +msgid "Actions" +msgstr "" + +#: obkey_classes.py:798 +msgid "Insert action" +msgstr "" + +#: obkey_classes.py:803 +msgid "Remove action" +msgstr "" + +#: obkey_classes.py:809 +msgid "Move action up" +msgstr "" + +#: obkey_classes.py:815 +msgid "Move action down" +msgstr "" + +#: obkey_classes.py:826 +msgid "Remove all actions" +msgstr "" diff --git a/icons/add_child.png b/resources/icons/add_child.png similarity index 100% rename from icons/add_child.png rename to resources/icons/add_child.png diff --git a/icons/add_sibling.png b/resources/icons/add_sibling.png similarity index 100% rename from icons/add_sibling.png rename to resources/icons/add_sibling.png diff --git a/resources/locale/bs/LC_MESSAGES/obkey.mo b/resources/locale/bs/LC_MESSAGES/obkey.mo new file mode 100644 index 0000000..1e5c0ce Binary files /dev/null and b/resources/locale/bs/LC_MESSAGES/obkey.mo differ diff --git a/resources/locale/fr/LC_MESSAGES/obkey.mo b/resources/locale/fr/LC_MESSAGES/obkey.mo new file mode 100644 index 0000000..5951476 Binary files /dev/null and b/resources/locale/fr/LC_MESSAGES/obkey.mo differ diff --git a/resources/locale/hr/LC_MESSAGES/obkey.mo b/resources/locale/hr/LC_MESSAGES/obkey.mo new file mode 100644 index 0000000..430528a Binary files /dev/null and b/resources/locale/hr/LC_MESSAGES/obkey.mo differ diff --git a/resources/locale/sr/LC_MESSAGES/obkey.mo b/resources/locale/sr/LC_MESSAGES/obkey.mo new file mode 100644 index 0000000..902c6e0 Binary files /dev/null and b/resources/locale/sr/LC_MESSAGES/obkey.mo differ diff --git a/locale/uk/LC_MESSAGES/obkey.mo b/resources/locale/uk/LC_MESSAGES/obkey.mo similarity index 100% rename from locale/uk/LC_MESSAGES/obkey.mo rename to resources/locale/uk/LC_MESSAGES/obkey.mo diff --git a/setup.py b/setup.py index caf23a1..ee49970 100644 --- a/setup.py +++ b/setup.py @@ -1,20 +1,88 @@ -from distutils.core import setup +#!/usr/bin/env python +""" + Setup script + Usage : python setup.py build +""" +from os.path import abspath, join, dirname +import io from glob import glob -import os +from distutils.core import setup +from obkey_parts import __version__, __description__, __long_description__ +# Tests pass when applications are stored in /usr/ + +NAME = 'obkey' +DESCRIPTION = __description__ +URL = 'https://github.com/luffah/obkey' +LONG_DESCRIPTION = __long_description__ +AUTHOR = 'luffah' +AUTHOR_EMAIL = 'luffah@runbox.com' +SCRIPTS = ['obkey'] +# PY_MODULES=[a.replace('/','.').replace('.py','') for a in glob('obkey_parts/*.py')], +PACKAGES = ['obkey_parts'] +PYTHON_REQUIRES = '>=2.7.0' +VERSION = __version__ +LICENCES = 'MIT' +KEYWORDS = 'openbox keybindings keys shortcuts' + +RES_ICONS = ('resources/icons', 'share/obkey/icons') +RES_LOCALES = ('resources/locale', 'share/locale') +RES_DESKTOP = ('misc', 'share/applications') +RES_APPDATA = ('misc', 'share/appdata') + +LANGS = [a[len(RES_LOCALES[0] + '/'):] for a in glob(RES_LOCALES[0] + '/*')] -libdir = 'share/obkey/icons' -localedir = 'share/locale' +INSTALL_REQUIRES = ['gi', 'gettext'] -langs = [a[len("locale/"):] for a in glob('locale/*')] -locales = [(os.path.join(localedir, l, 'LC_MESSAGES'), - [os.path.join('locale', l, 'LC_MESSAGES', 'obkey.mo')]) for l in langs] +DATA_FILES = [ + (RES_ICONS[1], + [RES_ICONS[0] + '/add_child.png', RES_ICONS[0] + '/add_sibling.png']), + (RES_DESKTOP[1], + [RES_DESKTOP[0] + '/obkey.desktop'],), + (RES_APPDATA[1], + [RES_APPDATA[0] + '/obkey.appdata.xml'],) +] + [ + (join(RES_LOCALES[1], l, 'LC_MESSAGES'), + [join(RES_LOCALES[0], l, 'LC_MESSAGES', 'obkey.mo')]) for l in LANGS +] -setup(name='obkey', - version='1.2pre', - description='Openbox Key Editor', - author='nsf', - author_email='no.smile.face@gmail.com', - scripts=['obkey'], - py_modules=['obkey_classes'], - data_files=[(libdir, ['icons/add_child.png', 'icons/add_sibling.png'])] + locales - ) +setup( + name=NAME, + version=VERSION, + description=DESCRIPTION, + url=URL, + long_description=LONG_DESCRIPTION, + author=AUTHOR, + author_email=AUTHOR_EMAIL, + scripts=SCRIPTS, + install_requires=INSTALL_REQUIRES, + # py_modules=PY_MDULES, + packages=PACKAGES, + # packages=find_packages(), + data_files=DATA_FILES, + license=LICENCES, + keywords=KEYWORDS, + platform='Linux', + project_urls={ + 'Bug Reports': 'https://github.com/luffah/obkey/issues', + 'Source': 'https://github.com/luffah/obkey/', + }, + # For a list of valid classifiers, see + # https://pypi.python.org/pypi?%3Aaction=list_classifiers + classifiers=[ + 'Development Status :: 4 - Beta', + # 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Topic :: Desktop Environment :: Window Managers', + 'Topic :: Utilities', + 'License :: OSI Approved :: MIT License', + 'Environment :: X11 Applications :: GTK', + 'Operating System :: POSIX :: Linux', + # 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + ] + ) diff --git a/stdeb.cfg b/stdeb.cfg new file mode 100644 index 0000000..fc595b4 --- /dev/null +++ b/stdeb.cfg @@ -0,0 +1,3 @@ +[DEFAULT] +Depends: + python-gi