├── COPYING ├── NEWS ├── icons ├── add_child.png └── add_sibling.png ├── locale └── uk │ └── LC_MESSAGES │ └── obkey.mo ├── misc └── obkey.desktop ├── obkey ├── obkey_classes.py ├── po ├── Makefile ├── README └── obkey.uk.po └── setup.py /COPYING: -------------------------------------------------------------------------------- 1 | Openbox Key Editor 2 | Copyright (C) 2009-2011 nsf 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | 04 August 2011: 2 | 3 | * Release 1.0, development is halted. 4 | 5 | ----------------------------------------------------------------------------- 6 | 7 | 28 September 2009: 8 | 9 | * Bug fixes. 10 | * Pretty XML printing patch from Andrwe Lord Weber. 11 | 12 | ----------------------------------------------------------------------------- 13 | 14 | 16 February 2009: 15 | 16 | * Context menu for action list. 17 | * New sensitivity system for widgets (those are sensitive which make sense). 18 | * Better parse error handling. 19 | * Code was refactored a bit. 20 | * Distribution system. 21 | * And bug fixes. 22 | 23 | ----------------------------------------------------------------------------- 24 | 25 | 11 February 2009: 26 | 27 | * New custom icons: add_child and add_sibling. 28 | * Tooltips for all toolbar buttons. 29 | * "Remove all" button on action list toolbar. 30 | 31 | ----------------------------------------------------------------------------- 32 | 33 | 10 February 2009: 34 | 35 | * Context menu functionality (right mouse button click), available actions 36 | (only for keybinds): 37 | - cut 38 | - copy 39 | - paste 40 | - paste as child 41 | - delete 42 | 43 | -------------------------------------------------------------------------------- /icons/add_child.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsf/obkey/e7faba50163d491e840d9a772be83ef35dd592df/icons/add_child.png -------------------------------------------------------------------------------- /icons/add_sibling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsf/obkey/e7faba50163d491e840d9a772be83ef35dd592df/icons/add_sibling.png -------------------------------------------------------------------------------- /locale/uk/LC_MESSAGES/obkey.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nsf/obkey/e7faba50163d491e840d9a772be83ef35dd592df/locale/uk/LC_MESSAGES/obkey.mo -------------------------------------------------------------------------------- /misc/obkey.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Application 4 | Name=Openbox Key bindings 5 | Name[zh_TW]=Openbox 組態管理器 6 | Comment=Configure and personalize the Openbox key bindings manager 7 | Icon=obconf 8 | Exec=obkey %f 9 | Categories=Settings;DesktopSettings;GTK; 10 | MimeType=application/x-openbox-theme; 11 | StartupNotify=true 12 | Terminal=false 13 | -------------------------------------------------------------------------------- /obkey: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #----------------------------------------------------------------------- 3 | # Openbox Key Editor 4 | # Copyright (C) 2009 nsf 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | #----------------------------------------------------------------------- 24 | 25 | import sys, os 26 | import gtk 27 | import gobject 28 | import obkey_classes 29 | import xml.dom.minidom 30 | 31 | def die(widget, data=None): 32 | gtk.main_quit() 33 | 34 | # get rc file 35 | path = os.getenv("HOME") + "/.config/openbox/rc.xml" 36 | if len(sys.argv) == 2: 37 | path = sys.argv[1] 38 | 39 | ob = obkey_classes.OpenboxConfig() 40 | ob.load(path) 41 | 42 | win = gtk.Window(gtk.WINDOW_TOPLEVEL) 43 | win.set_default_size(640,480) 44 | win.set_title(obkey_classes.config_title) 45 | win.connect("destroy", die) 46 | 47 | tbl = obkey_classes.PropertyTable() 48 | al = obkey_classes.ActionList(tbl) 49 | ktbl = obkey_classes.KeyTable(al, ob) 50 | 51 | vbox = gtk.VPaned() 52 | vbox.pack1(tbl.widget, True, False) 53 | vbox.pack2(al.widget, True, False) 54 | 55 | hbox = gtk.HPaned() 56 | hbox.pack1(ktbl.widget, True, False) 57 | hbox.pack2(vbox, False, False) 58 | 59 | win.add(hbox) 60 | win.show_all() 61 | # get rid of stupid autocalculation 62 | w, h = win.get_size() 63 | hbox.set_position(w-250) 64 | ktbl.view.grab_focus() 65 | gtk.main() 66 | -------------------------------------------------------------------------------- /obkey_classes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | #------------------------------------------------------------------------------ 4 | # Openbox Key Editor 5 | # Copyright (C) 2009 nsf 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | #------------------------------------------------------------------------------ 25 | 26 | import xml.dom.minidom 27 | from StringIO import StringIO 28 | from string import strip 29 | import gobject 30 | import copy 31 | import gtk 32 | import sys 33 | import os 34 | import gettext 35 | 36 | reload(sys) 37 | sys.setdefaultencoding('utf-8') 38 | 39 | #===================================================================================== 40 | # Config 41 | #===================================================================================== 42 | 43 | # XXX: Sorry, for now this is it. If you know a better way to do this with setup.py: 44 | # please mail me. 45 | 46 | config_prefix = '/usr' 47 | config_icons = os.path.join(config_prefix, 'share/obkey/icons') 48 | config_locale_dir = os.path.join(config_prefix, 'share/locale') 49 | 50 | gettext.install('obkey', config_locale_dir) # init gettext 51 | 52 | # localized title 53 | config_title = _('obkey') 54 | 55 | #===================================================================================== 56 | # Key utils 57 | #===================================================================================== 58 | 59 | replace_table_openbox2gtk = { 60 | "mod1" : "", 61 | "mod2" : "", 62 | "mod3" : "", 63 | "mod4" : "", 64 | "mod5" : "", 65 | "control" : "", 66 | "c" : "", 67 | "alt" : "", 68 | "a" : "", 69 | "meta" : "", 70 | "m" : "", 71 | "super" : "", 72 | "w" : "", 73 | "shift" : "", 74 | "s" : "", 75 | "hyper" : "", 76 | "h" : "" 77 | } 78 | 79 | replace_table_gtk2openbox = { 80 | "Mod1" : "Mod1", 81 | "Mod2" : "Mod2", 82 | "Mod3" : "Mod3", 83 | "Mod4" : "Mod4", 84 | "Mod5" : "Mod5", 85 | "Control" : "C", 86 | "Alt" : "A", 87 | "Meta" : "M", 88 | "Super" : "W", 89 | "Shift" : "S", 90 | "Hyper" : "H" 91 | } 92 | 93 | def key_openbox2gtk(obstr): 94 | toks = obstr.split("-") 95 | try: 96 | toksgdk = [replace_table_openbox2gtk[mod.lower()] for mod in toks[:-1]] 97 | except: 98 | return (0, 0) 99 | toksgdk.append(toks[-1]) 100 | return gtk.accelerator_parse("".join(toksgdk)) 101 | 102 | def key_gtk2openbox(key, mods): 103 | result = "" 104 | if mods: 105 | s = gtk.accelerator_name(0, mods) 106 | svec = [replace_table_gtk2openbox[i] for i in s[1:-1].split('><')] 107 | result = '-'.join(svec) 108 | if key: 109 | k = gtk.accelerator_name(key, 0) 110 | if result != "": 111 | result += '-' 112 | result += k 113 | return result 114 | 115 | #===================================================================================== 116 | # This is the uber cool switchers/conditions(sensors) system. 117 | # Helps a lot with widgets sensitivity. 118 | #===================================================================================== 119 | 120 | class SensCondition: 121 | def __init__(self, initial_state): 122 | self.switchers = [] 123 | self.state = initial_state 124 | 125 | def register_switcher(self, sw): 126 | self.switchers.append(sw) 127 | 128 | def set_state(self, state): 129 | if self.state == state: 130 | return 131 | self.state = state 132 | for sw in self.switchers: 133 | sw.notify() 134 | 135 | class SensSwitcher: 136 | def __init__(self, conditions): 137 | self.conditions = conditions 138 | self.widgets = [] 139 | 140 | for c in conditions: 141 | c.register_switcher(self) 142 | 143 | def append(self, widget): 144 | self.widgets.append(widget) 145 | 146 | def set_sensitive(self, state): 147 | for w in self.widgets: 148 | w.set_sensitive(state) 149 | 150 | def notify(self): 151 | for c in self.conditions: 152 | if not c.state: 153 | self.set_sensitive(False) 154 | return 155 | self.set_sensitive(True) 156 | 157 | #===================================================================================== 158 | # KeyTable 159 | #===================================================================================== 160 | 161 | class KeyTable: 162 | def __init__(self, actionlist, ob): 163 | self.widget = gtk.VBox() 164 | self.ob = ob 165 | self.actionlist = actionlist 166 | actionlist.set_callback(self.actions_cb) 167 | 168 | self.icons = self.load_icons() 169 | 170 | self.model, self.cqk_model = self.create_models() 171 | self.view, self.cqk_view = self.create_views(self.model, self.cqk_model) 172 | 173 | # copy & paste 174 | self.copied = None 175 | 176 | # sensitivity switchers & conditions 177 | self.cond_insert_child = SensCondition(False) 178 | self.cond_paste_buffer = SensCondition(False) 179 | self.cond_selection_available = SensCondition(False) 180 | 181 | self.sw_insert_child_and_paste = SensSwitcher([self.cond_insert_child, self.cond_paste_buffer]) 182 | self.sw_insert_child = SensSwitcher([self.cond_insert_child]) 183 | self.sw_paste_buffer = SensSwitcher([self.cond_paste_buffer]) 184 | self.sw_selection_available = SensSwitcher([self.cond_selection_available]) 185 | 186 | # self.context_menu 187 | self.context_menu = self.create_context_menu() 188 | 189 | for kb in self.ob.keyboard.keybinds: 190 | self.apply_keybind(kb) 191 | 192 | self.apply_cqk_initial_value() 193 | 194 | # self.add_child_button 195 | self.widget.pack_start(self.create_toolbar(), False) 196 | self.widget.pack_start(self.create_scroll(self.view)) 197 | self.widget.pack_start(self.create_cqk_hbox(self.cqk_view), False) 198 | 199 | if len(self.model): 200 | self.view.get_selection().select_iter(self.model.get_iter_first()) 201 | 202 | self.sw_insert_child_and_paste.notify() 203 | self.sw_insert_child.notify() 204 | self.sw_paste_buffer.notify() 205 | self.sw_selection_available.notify() 206 | 207 | def create_cqk_hbox(self, cqk_view): 208 | cqk_hbox = gtk.HBox() 209 | cqk_label = gtk.Label(_("chainQuitKey:")) 210 | cqk_label.set_padding(5,5) 211 | 212 | cqk_frame = gtk.Frame() 213 | cqk_frame.add(cqk_view) 214 | 215 | cqk_hbox.pack_start(cqk_label, False) 216 | cqk_hbox.pack_start(cqk_frame) 217 | return cqk_hbox 218 | 219 | def create_context_menu(self): 220 | context_menu = gtk.Menu() 221 | self.context_items = {} 222 | 223 | item = gtk.ImageMenuItem(gtk.STOCK_CUT) 224 | item.connect('activate', lambda menu: self.cut_selected()) 225 | item.get_child().set_label(_("Cu_t")) 226 | context_menu.append(item) 227 | self.sw_selection_available.append(item) 228 | 229 | item = gtk.ImageMenuItem(gtk.STOCK_COPY) 230 | item.connect('activate', lambda menu: self.copy_selected()) 231 | item.get_child().set_label(_("_Copy")) 232 | context_menu.append(item) 233 | self.sw_selection_available.append(item) 234 | 235 | item = gtk.ImageMenuItem(gtk.STOCK_PASTE) 236 | item.connect('activate', lambda menu: self.insert_sibling(self.copied)) 237 | item.get_child().set_label(_("_Paste")) 238 | context_menu.append(item) 239 | self.sw_paste_buffer.append(item) 240 | 241 | item = gtk.ImageMenuItem(gtk.STOCK_PASTE) 242 | item.get_child().set_label(_("P_aste as child")) 243 | item.connect('activate', lambda menu: self.insert_child(self.copied)) 244 | context_menu.append(item) 245 | self.sw_insert_child_and_paste.append(item) 246 | 247 | item = gtk.ImageMenuItem(gtk.STOCK_REMOVE) 248 | item.connect('activate', lambda menu: self.del_selected()) 249 | item.get_child().set_label(_("_Remove")) 250 | context_menu.append(item) 251 | self.sw_selection_available.append(item) 252 | 253 | context_menu.show_all() 254 | return context_menu 255 | 256 | def create_models(self): 257 | model = gtk.TreeStore(gobject.TYPE_UINT, # accel key 258 | gobject.TYPE_INT, # accel mods 259 | gobject.TYPE_STRING, # accel string (openbox) 260 | gobject.TYPE_BOOLEAN, # chroot 261 | gobject.TYPE_BOOLEAN, # show chroot 262 | gobject.TYPE_PYOBJECT # OBKeyBind 263 | ) 264 | 265 | cqk_model = gtk.ListStore(gobject.TYPE_UINT, # accel key 266 | gobject.TYPE_INT, # accel mods 267 | gobject.TYPE_STRING) # accel string (openbox) 268 | return (model, cqk_model) 269 | 270 | def create_scroll(self, view): 271 | scroll = gtk.ScrolledWindow() 272 | scroll.add(view) 273 | scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) 274 | scroll.set_shadow_type(gtk.SHADOW_IN) 275 | return scroll 276 | 277 | def create_views(self, model, cqk_model): 278 | r0 = gtk.CellRendererAccel() 279 | r0.props.editable = True 280 | r0.connect('accel-edited', self.accel_edited) 281 | 282 | r1 = gtk.CellRendererText() 283 | r1.props.editable = True 284 | r1.connect('edited', self.key_edited) 285 | 286 | r2 = gtk.CellRendererToggle() 287 | r2.connect('toggled', self.chroot_toggled) 288 | 289 | c0 = gtk.TreeViewColumn(_("Key"), r0, accel_key=0, accel_mods=1) 290 | c1 = gtk.TreeViewColumn(_("Key (text)"), r1, text=2) 291 | c2 = gtk.TreeViewColumn(_("Chroot"), r2, active=3, visible=4) 292 | 293 | c0.set_expand(True) 294 | 295 | view = gtk.TreeView(model) 296 | view.append_column(c0) 297 | view.append_column(c1) 298 | view.append_column(c2) 299 | view.get_selection().connect('changed', self.view_cursor_changed) 300 | view.connect('button-press-event', self.view_button_clicked) 301 | 302 | # chainQuitKey table (wtf hack) 303 | 304 | r0 = gtk.CellRendererAccel() 305 | r0.props.editable = True 306 | r0.connect('accel-edited', self.cqk_accel_edited) 307 | 308 | r1 = gtk.CellRendererText() 309 | r1.props.editable = True 310 | r1.connect('edited', self.cqk_key_edited) 311 | 312 | c0 = gtk.TreeViewColumn("Key", r0, accel_key=0, accel_mods=1) 313 | c1 = gtk.TreeViewColumn("Key (text)", r1, text=2) 314 | 315 | c0.set_expand(True) 316 | 317 | def cqk_view_focus_lost(view, event): 318 | view.get_selection().unselect_all() 319 | 320 | cqk_view = gtk.TreeView(cqk_model) 321 | cqk_view.set_headers_visible(False) 322 | cqk_view.append_column(c0) 323 | cqk_view.append_column(c1) 324 | cqk_view.connect('focus-out-event', cqk_view_focus_lost) 325 | return (view, cqk_view) 326 | 327 | def create_toolbar(self): 328 | toolbar = gtk.Toolbar() 329 | toolbar.set_style(gtk.TOOLBAR_ICONS) 330 | toolbar.set_show_arrow(False) 331 | 332 | but = gtk.ToolButton(gtk.STOCK_SAVE) 333 | but.set_tooltip_text(_("Save ") + self.ob.path + _(" file")) 334 | but.connect('clicked', lambda but: self.ob.save()) 335 | toolbar.insert(but, -1) 336 | 337 | toolbar.insert(gtk.SeparatorToolItem(), -1) 338 | 339 | but = gtk.ToolButton(self.icons['add_sibling']) 340 | but.set_tooltip_text(_("Insert sibling keybind")) 341 | but.connect('clicked', lambda but: self.insert_sibling(OBKeyBind())) 342 | toolbar.insert(but, -1) 343 | 344 | but = gtk.ToolButton(self.icons['add_child']) 345 | but.set_tooltip_text(_("Insert child keybind")) 346 | but.connect('clicked', lambda but: self.insert_child(OBKeyBind())) 347 | toolbar.insert(but, -1) 348 | self.sw_insert_child.append(but) 349 | 350 | but = gtk.ToolButton(gtk.STOCK_REMOVE) 351 | but.set_tooltip_text(_("Remove keybind")) 352 | but.connect('clicked', lambda but: self.del_selected()) 353 | toolbar.insert(but, -1) 354 | self.sw_selection_available.append(but) 355 | 356 | sep = gtk.SeparatorToolItem() 357 | sep.set_draw(False) 358 | sep.set_expand(True) 359 | toolbar.insert(sep, -1) 360 | 361 | toolbar.insert(gtk.SeparatorToolItem(), -1) 362 | 363 | but = gtk.ToolButton(gtk.STOCK_QUIT) 364 | but.set_tooltip_text(_("Quit application")) 365 | but.connect('clicked', lambda but: gtk.main_quit()) 366 | toolbar.insert(but, -1) 367 | return toolbar 368 | 369 | def apply_cqk_initial_value(self): 370 | cqk_accel_key, cqk_accel_mods = key_openbox2gtk(self.ob.keyboard.chainQuitKey) 371 | if cqk_accel_mods == 0: 372 | self.ob.keyboard.chainQuitKey = "" 373 | self.cqk_model.append((cqk_accel_key, cqk_accel_mods, self.ob.keyboard.chainQuitKey)) 374 | 375 | def apply_keybind(self, kb, parent=None): 376 | accel_key, accel_mods = key_openbox2gtk(kb.key) 377 | chroot = kb.chroot 378 | show_chroot = len(kb.children) > 0 or not len(kb.actions) 379 | 380 | n = self.model.append(parent, 381 | (accel_key, accel_mods, kb.key, chroot, show_chroot, kb)) 382 | 383 | for c in kb.children: 384 | self.apply_keybind(c, n) 385 | 386 | def load_icons(self): 387 | icons = {} 388 | icons_path = 'icons' 389 | if os.path.isdir(config_icons): 390 | icons_path = config_icons 391 | icons['add_sibling'] = gtk.image_new_from_file(os.path.join(icons_path, "add_sibling.png")) 392 | icons['add_child'] = gtk.image_new_from_file(os.path.join(icons_path, "add_child.png")) 393 | return icons 394 | 395 | 396 | #----------------------------------------------------------------------------- 397 | # callbacks 398 | 399 | def view_button_clicked(self, view, event): 400 | if event.button == 3: 401 | x = int(event.x) 402 | y = int(event.y) 403 | time = event.time 404 | pathinfo = view.get_path_at_pos(x, y) 405 | if pathinfo: 406 | path, col, cellx, celly = pathinfo 407 | view.grab_focus() 408 | view.set_cursor(path, col, 0) 409 | self.context_menu.popup(None, None, None, event.button, time) 410 | else: 411 | view.grab_focus() 412 | view.get_selection().unselect_all() 413 | self.context_menu.popup(None, None, None, event.button, time) 414 | return 1 415 | 416 | def actions_cb(self): 417 | (model, it) = self.view.get_selection().get_selected() 418 | kb = model.get_value(it, 5) 419 | if len(kb.actions) == 0: 420 | model.set_value(it, 4, True) 421 | self.cond_insert_child.set_state(True) 422 | else: 423 | model.set_value(it, 4, False) 424 | self.cond_insert_child.set_state(False) 425 | 426 | def view_cursor_changed(self, selection): 427 | (model, it) = selection.get_selected() 428 | actions = None 429 | if it: 430 | kb = model.get_value(it, 5) 431 | if len(kb.children) == 0 and not kb.chroot: 432 | actions = kb.actions 433 | self.cond_selection_available.set_state(True) 434 | self.cond_insert_child.set_state(len(kb.actions) == 0) 435 | else: 436 | self.cond_insert_child.set_state(False) 437 | self.cond_selection_available.set_state(False) 438 | self.actionlist.set_actions(actions) 439 | 440 | def cqk_accel_edited(self, cell, path, accel_key, accel_mods, keycode): 441 | self.cqk_model[path][0] = accel_key 442 | self.cqk_model[path][1] = accel_mods 443 | kstr = key_gtk2openbox(accel_key, accel_mods) 444 | self.cqk_model[path][2] = kstr 445 | self.ob.keyboard.chainQuitKey = kstr 446 | self.view.grab_focus() 447 | 448 | def cqk_key_edited(self, cell, path, text): 449 | self.cqk_model[path][0], self.cqk_model[path][1] = key_openbox2gtk(text) 450 | self.cqk_model[path][2] = text 451 | self.ob.keyboard.chainQuitKey = text 452 | self.view.grab_focus() 453 | 454 | def accel_edited(self, cell, path, accel_key, accel_mods, keycode): 455 | self.model[path][0] = accel_key 456 | self.model[path][1] = accel_mods 457 | kstr = key_gtk2openbox(accel_key, accel_mods) 458 | self.model[path][2] = kstr 459 | self.model[path][5].key = kstr 460 | 461 | def key_edited(self, cell, path, text): 462 | self.model[path][0], self.model[path][1] = key_openbox2gtk(text) 463 | self.model[path][2] = text 464 | self.model[path][5].key = text 465 | 466 | def chroot_toggled(self, cell, path): 467 | self.model[path][3] = not self.model[path][3] 468 | kb = self.model[path][5] 469 | kb.chroot = self.model[path][3] 470 | if kb.chroot: 471 | self.actionlist.set_actions(None) 472 | elif not kb.children: 473 | self.actionlist.set_actions(kb.actions) 474 | 475 | #----------------------------------------------------------------------------- 476 | def cut_selected(self): 477 | self.copy_selected() 478 | self.del_selected() 479 | 480 | def copy_selected(self): 481 | (model, it) = self.view.get_selection().get_selected() 482 | if it: 483 | sel = model.get_value(it, 5) 484 | self.copied = copy.deepcopy(sel) 485 | self.cond_paste_buffer.set_state(True) 486 | 487 | def _insert_keybind(self, keybind, parent=None, after=None): 488 | keybind.parent = parent 489 | if parent: 490 | kbs = parent.children 491 | else: 492 | kbs = self.ob.keyboard.keybinds 493 | 494 | if after: 495 | kbs.insert(kbs.index(after)+1, keybind) 496 | else: 497 | kbs.append(keybind) 498 | 499 | def insert_sibling(self, keybind): 500 | (model, it) = self.view.get_selection().get_selected() 501 | 502 | accel_key, accel_mods = key_openbox2gtk(keybind.key) 503 | show_chroot = len(keybind.children) > 0 or not len(keybind.actions) 504 | 505 | if it: 506 | parent_it = model.iter_parent(it) 507 | parent = None 508 | if parent_it: 509 | parent = model.get_value(parent_it, 5) 510 | after = model.get_value(it, 5) 511 | 512 | self._insert_keybind(keybind, parent, after) 513 | newit = self.model.insert_after(parent_it, it, 514 | (accel_key, accel_mods, keybind.key, keybind.chroot, show_chroot, keybind)) 515 | else: 516 | self._insert_keybind(keybind) 517 | newit = self.model.append(None, 518 | (accel_key, accel_mods, keybind.key, keybind.chroot, show_chroot, keybind)) 519 | 520 | if newit: 521 | for c in keybind.children: 522 | self.apply_keybind(c, newit) 523 | self.view.get_selection().select_iter(newit) 524 | 525 | def insert_child(self, keybind): 526 | (model, it) = self.view.get_selection().get_selected() 527 | parent = model.get_value(it, 5) 528 | self._insert_keybind(keybind, parent) 529 | 530 | accel_key, accel_mods = key_openbox2gtk(keybind.key) 531 | show_chroot = len(keybind.children) > 0 or not len(keybind.actions) 532 | 533 | newit = self.model.append(it, 534 | (accel_key, accel_mods, keybind.key, keybind.chroot, show_chroot, keybind)) 535 | 536 | # it means that we have inserted first child here, change status 537 | if len(parent.children) == 1: 538 | self.actionlist.set_actions(None) 539 | 540 | def del_selected(self): 541 | (model, it) = self.view.get_selection().get_selected() 542 | if it: 543 | kb = model.get_value(it, 5) 544 | kbs = self.ob.keyboard.keybinds 545 | if kb.parent: 546 | kbs = kb.parent.children 547 | kbs.remove(kb) 548 | isok = self.model.remove(it) 549 | if isok: 550 | self.view.get_selection().select_iter(it) 551 | 552 | 553 | #===================================================================================== 554 | # PropertyTable 555 | #===================================================================================== 556 | 557 | class PropertyTable: 558 | def __init__(self): 559 | self.widget = gtk.ScrolledWindow() 560 | self.table = gtk.Table(1,2) 561 | self.table.set_row_spacings(5) 562 | self.widget.add_with_viewport(self.table) 563 | self.widget.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) 564 | 565 | def add_row(self, label_text, table): 566 | label = gtk.Label(_(label_text)) 567 | label.set_alignment(0, 0) 568 | row = self.table.props.n_rows 569 | self.table.attach(label, 0, 1, row, row+1, gtk.EXPAND | gtk.FILL, 0, 5, 0) 570 | self.table.attach(table, 1, 2, row, row+1, gtk.FILL, 0, 5, 0) 571 | 572 | def clear(self): 573 | cs = self.table.get_children() 574 | cs.reverse() 575 | for c in cs: 576 | self.table.remove(c) 577 | self.table.resize(1, 2) 578 | 579 | def set_action(self, action): 580 | self.clear() 581 | if not action: 582 | return 583 | for a in action.option_defs: 584 | self.add_row(a.name + ":", a.generate_widget(action)) 585 | self.table.queue_resize() 586 | self.table.show_all() 587 | 588 | #===================================================================================== 589 | # ActionList 590 | #===================================================================================== 591 | 592 | class ActionList: 593 | def __init__(self, proptable=None): 594 | self.widget = gtk.VBox() 595 | self.actions = None 596 | self.proptable = proptable 597 | 598 | # actions callback, called when action added or deleted 599 | # for chroot possibility tracing 600 | self.actions_cb = None 601 | 602 | # copy & paste buffer 603 | self.copied = None 604 | 605 | # sensitivity switchers & conditions 606 | self.cond_paste_buffer = SensCondition(False) 607 | self.cond_selection_available = SensCondition(False) 608 | self.cond_action_list_nonempty = SensCondition(False) 609 | self.cond_can_move_up = SensCondition(False) 610 | self.cond_can_move_down = SensCondition(False) 611 | 612 | self.sw_paste_buffer = SensSwitcher([self.cond_paste_buffer]) 613 | self.sw_selection_available = SensSwitcher([self.cond_selection_available]) 614 | self.sw_action_list_nonempty = SensSwitcher([self.cond_action_list_nonempty]) 615 | self.sw_can_move_up = SensSwitcher([self.cond_can_move_up]) 616 | self.sw_can_move_down = SensSwitcher([self.cond_can_move_down]) 617 | 618 | self.model = self.create_model() 619 | self.view = self.create_view(self.model) 620 | 621 | self.context_menu = self.create_context_menu() 622 | 623 | self.widget.pack_start(self.create_scroll(self.view)) 624 | self.widget.pack_start(self.create_toolbar(), False) 625 | 626 | self.sw_paste_buffer.notify() 627 | self.sw_selection_available.notify() 628 | self.sw_action_list_nonempty.notify() 629 | self.sw_can_move_up.notify() 630 | self.sw_can_move_down.notify() 631 | 632 | def create_model(self): 633 | return gtk.ListStore(gobject.TYPE_STRING, # name of the action 634 | gobject.TYPE_PYOBJECT) # associated OBAction 635 | 636 | def create_choices(self): 637 | choices = gtk.ListStore(gobject.TYPE_STRING,gobject.TYPE_STRING) 638 | action_list = {} 639 | for a in actions: 640 | action_list[_(a)] = a 641 | for a in sorted(action_list.keys()): 642 | choices.append((a,action_list[a])) 643 | return choices 644 | 645 | def create_scroll(self, view): 646 | scroll = gtk.ScrolledWindow() 647 | scroll.add(view) 648 | scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) 649 | scroll.set_shadow_type(gtk.SHADOW_IN) 650 | return scroll 651 | 652 | def create_view(self, model): 653 | renderer = gtk.CellRendererCombo() 654 | 655 | def editingstarted(cell, widget, path): 656 | widget.set_wrap_width(4) 657 | 658 | renderer.props.model = self.create_choices() 659 | renderer.props.text_column = 0 660 | renderer.props.editable = True 661 | renderer.props.has_entry = False 662 | renderer.connect('changed', self.action_class_changed) 663 | renderer.connect('editing-started', editingstarted) 664 | 665 | column = gtk.TreeViewColumn(_("Actions"), renderer, text=0) 666 | 667 | view = gtk.TreeView(model) 668 | view.append_column(column) 669 | view.get_selection().connect('changed', self.view_cursor_changed) 670 | view.connect('button-press-event', self.view_button_clicked) 671 | return view 672 | 673 | def create_context_menu(self): 674 | context_menu = gtk.Menu() 675 | self.context_items = {} 676 | 677 | item = gtk.ImageMenuItem(gtk.STOCK_CUT) 678 | item.connect('activate', lambda menu: self.cut_selected()) 679 | item.get_child().set_label(_("Cu_t")) 680 | context_menu.append(item) 681 | self.sw_selection_available.append(item) 682 | 683 | item = gtk.ImageMenuItem(gtk.STOCK_COPY) 684 | item.connect('activate', lambda menu: self.copy_selected()) 685 | item.get_child().set_label(_("_Copy")) 686 | context_menu.append(item) 687 | self.sw_selection_available.append(item) 688 | 689 | item = gtk.ImageMenuItem(gtk.STOCK_PASTE) 690 | item.connect('activate', lambda menu: self.insert_action(self.copied)) 691 | item.get_child().set_label(_("_Paste")) 692 | context_menu.append(item) 693 | self.sw_paste_buffer.append(item) 694 | 695 | item = gtk.ImageMenuItem(gtk.STOCK_REMOVE) 696 | item.connect('activate', lambda menu: self.del_selected()) 697 | item.get_child().set_label(_("_Remove")) 698 | context_menu.append(item) 699 | self.sw_selection_available.append(item) 700 | 701 | context_menu.show_all() 702 | return context_menu 703 | 704 | def create_toolbar(self): 705 | toolbar = gtk.Toolbar() 706 | toolbar.set_style(gtk.TOOLBAR_ICONS) 707 | toolbar.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR) 708 | toolbar.set_show_arrow(False) 709 | 710 | but = gtk.ToolButton(gtk.STOCK_ADD) 711 | but.set_tooltip_text(_("Insert action")) 712 | but.connect('clicked', lambda but: self.insert_action(OBAction("Focus"))) 713 | toolbar.insert(but, -1) 714 | 715 | but = gtk.ToolButton(gtk.STOCK_REMOVE) 716 | but.set_tooltip_text(_("Remove action")) 717 | but.connect('clicked', lambda but: self.del_selected()) 718 | toolbar.insert(but, -1) 719 | self.sw_selection_available.append(but) 720 | 721 | but = gtk.ToolButton(gtk.STOCK_GO_UP) 722 | but.set_tooltip_text(_("Move action up")) 723 | but.connect('clicked', lambda but: self.move_selected_up()) 724 | toolbar.insert(but, -1) 725 | self.sw_can_move_up.append(but) 726 | 727 | but = gtk.ToolButton(gtk.STOCK_GO_DOWN) 728 | but.set_tooltip_text(_("Move action down")) 729 | but.connect('clicked', lambda but: self.move_selected_down()) 730 | toolbar.insert(but, -1) 731 | self.sw_can_move_down.append(but) 732 | 733 | sep = gtk.SeparatorToolItem() 734 | sep.set_draw(False) 735 | sep.set_expand(True) 736 | toolbar.insert(sep, -1) 737 | 738 | but = gtk.ToolButton(gtk.STOCK_DELETE) 739 | but.set_tooltip_text(_("Remove all actions")) 740 | but.connect('clicked', lambda but: self.clear()) 741 | toolbar.insert(but, -1) 742 | self.sw_action_list_nonempty.append(but) 743 | return toolbar 744 | #----------------------------------------------------------------------------- 745 | # callbacks 746 | 747 | def view_button_clicked(self, view, event): 748 | if event.button == 3: 749 | x = int(event.x) 750 | y = int(event.y) 751 | time = event.time 752 | pathinfo = view.get_path_at_pos(x, y) 753 | if pathinfo: 754 | path, col, cellx, celly = pathinfo 755 | view.grab_focus() 756 | view.set_cursor(path, col, 0) 757 | self.context_menu.popup(None, None, None, event.button, time) 758 | else: 759 | view.grab_focus() 760 | view.get_selection().unselect_all() 761 | self.context_menu.popup(None, None, None, event.button, time) 762 | return 1 763 | 764 | def action_class_changed(self, combo, path, it): 765 | m = combo.props.model 766 | ntype = m.get_value(it, 1) 767 | self.model[path][0] = m.get_value(it, 0) 768 | self.model[path][1].mutate(ntype) 769 | if self.proptable: 770 | self.proptable.set_action(self.model[path][1]) 771 | 772 | def view_cursor_changed(self, selection): 773 | (model, it) = selection.get_selected() 774 | act = None 775 | if it: 776 | act = model.get_value(it, 1) 777 | if self.proptable: 778 | self.proptable.set_action(act) 779 | if act: 780 | l = len(self.actions) 781 | i = self.actions.index(act) 782 | self.cond_can_move_up.set_state(i != 0) 783 | self.cond_can_move_down.set_state(l > 1 and i+1 < l) 784 | self.cond_selection_available.set_state(True) 785 | else: 786 | self.cond_can_move_up.set_state(False) 787 | self.cond_can_move_down.set_state(False) 788 | self.cond_selection_available.set_state(False) 789 | 790 | #----------------------------------------------------------------------------- 791 | def cut_selected(self): 792 | self.copy_selected() 793 | self.del_selected() 794 | 795 | def copy_selected(self): 796 | if self.actions is None: 797 | return 798 | 799 | (model, it) = self.view.get_selection().get_selected() 800 | if it: 801 | a = model.get_value(it, 1) 802 | self.copied = copy.deepcopy(a) 803 | self.cond_paste_buffer.set_state(True) 804 | 805 | def clear(self): 806 | if self.actions is None or not len(self.actions): 807 | return 808 | 809 | del self.actions[:] 810 | self.model.clear() 811 | 812 | self.cond_action_list_nonempty.set_state(False) 813 | if self.actions_cb: 814 | self.actions_cb() 815 | 816 | def move_selected_up(self): 817 | if self.actions is None: 818 | return 819 | 820 | (model, it) = self.view.get_selection().get_selected() 821 | if not it: 822 | return 823 | 824 | i, = self.model.get_path(it) 825 | l = len(self.model) 826 | self.cond_can_move_up.set_state(i-1 != 0) 827 | self.cond_can_move_down.set_state(l > 1 and i < l) 828 | if i == 0: 829 | return 830 | 831 | itprev = self.model.get_iter(i-1) 832 | self.model.swap(it, itprev) 833 | action = self.model.get_value(it, 1) 834 | 835 | i = self.actions.index(action) 836 | tmp = self.actions[i-1] 837 | self.actions[i-1] = action 838 | self.actions[i] = tmp 839 | 840 | def move_selected_down(self): 841 | if self.actions is None: 842 | return 843 | 844 | (model, it) = self.view.get_selection().get_selected() 845 | if not it: 846 | return 847 | 848 | i, = self.model.get_path(it) 849 | l = len(self.model) 850 | self.cond_can_move_up.set_state(i+1 != 0) 851 | self.cond_can_move_down.set_state(l > 1 and i+2 < l) 852 | if i+1 >= l: 853 | return 854 | 855 | itnext = self.model.iter_next(it) 856 | self.model.swap(it, itnext) 857 | action = self.model.get_value(it, 1) 858 | 859 | i = self.actions.index(action) 860 | tmp = self.actions[i+1] 861 | self.actions[i+1] = action 862 | self.actions[i] = tmp 863 | 864 | def insert_action(self, action): 865 | if self.actions is None: 866 | return 867 | 868 | (model, it) = self.view.get_selection().get_selected() 869 | if it: 870 | self._insert_action(action, model.get_value(it, 1)) 871 | newit = self.model.insert_after(it, (_(action.name), action)) 872 | else: 873 | self._insert_action(action) 874 | newit = self.model.append((_(action.name), action)) 875 | 876 | if newit: 877 | self.view.get_selection().select_iter(newit) 878 | 879 | self.cond_action_list_nonempty.set_state(len(self.model)) 880 | if self.actions_cb: 881 | self.actions_cb() 882 | 883 | def del_selected(self): 884 | if self.actions is None: 885 | return 886 | 887 | (model, it) = self.view.get_selection().get_selected() 888 | if it: 889 | self.actions.remove(model.get_value(it, 1)) 890 | isok = self.model.remove(it) 891 | if isok: 892 | self.view.get_selection().select_iter(it) 893 | 894 | self.cond_action_list_nonempty.set_state(len(self.model)) 895 | if self.actions_cb: 896 | self.actions_cb() 897 | 898 | #----------------------------------------------------------------------------- 899 | 900 | def set_actions(self, actionlist): 901 | self.actions = actionlist 902 | self.model.clear() 903 | self.widget.set_sensitive(self.actions is not None) 904 | if not self.actions: 905 | return 906 | for a in self.actions: 907 | self.model.append((_(a.name), a)) 908 | if len(self.model): 909 | self.view.get_selection().select_iter(self.model.get_iter_first()) 910 | self.cond_action_list_nonempty.set_state(len(self.model)) 911 | 912 | def _insert_action(self, action, after=None): 913 | if after: 914 | self.actions.insert(self.actions.index(after)+1, action) 915 | else: 916 | self.actions.append(action) 917 | 918 | def set_callback(self, cb): 919 | self.actions_cb = cb 920 | 921 | #===================================================================================== 922 | # MiniActionList 923 | #===================================================================================== 924 | 925 | class MiniActionList(ActionList): 926 | def __init__(self, proptable=None): 927 | ActionList.__init__(self, proptable) 928 | self.widget.set_size_request(-1, 120) 929 | self.view.set_headers_visible(False) 930 | 931 | def create_choices(self): 932 | choices = gtk.ListStore(gobject.TYPE_STRING,gobject.TYPE_STRING) 933 | action_list = {} 934 | for a in actions: 935 | action_list[_(a)] = a 936 | for a in sorted(action_list.keys()): 937 | if len(actions[action_list[a]]) == 0: 938 | choices.append((a,action_list[a])) 939 | return choices 940 | 941 | #===================================================================================== 942 | # XML Utilites 943 | #===================================================================================== 944 | # parse 945 | 946 | def xml_parse_attr(elt, name): 947 | return elt.getAttribute(name) 948 | 949 | def xml_parse_attr_bool(elt, name): 950 | attr = elt.getAttribute(name).lower() 951 | if attr == "true" or attr == "yes" or attr == "on": 952 | return True 953 | return False 954 | 955 | def xml_parse_string(elt): 956 | if elt.hasChildNodes(): 957 | return elt.firstChild.nodeValue 958 | else: 959 | return "" 960 | 961 | def xml_parse_bool(elt): 962 | val = elt.firstChild.nodeValue.lower() 963 | if val == "true" or val == "yes" or val == "on": 964 | return True 965 | return False 966 | 967 | def xml_find_nodes(elt, name): 968 | children = [] 969 | for n in elt.childNodes: 970 | if n.nodeName == name: 971 | children.append(n) 972 | return children 973 | 974 | def xml_find_node(elt, name): 975 | nodes = xml_find_nodes(elt, name) 976 | if len(nodes) == 1: 977 | return nodes[0] 978 | else: 979 | return None 980 | 981 | def fixed_writexml(self, writer, indent="", addindent="", newl=""): 982 | # indent = current indentation 983 | # addindent = indentation to add to higher levels 984 | # newl = newline string 985 | writer.write(indent+"<" + self.tagName) 986 | 987 | attrs = self._get_attributes() 988 | a_names = attrs.keys() 989 | a_names.sort() 990 | 991 | for a_name in a_names: 992 | writer.write(" %s=\"" % a_name) 993 | xml.dom.minidom._write_data(writer, attrs[a_name].value) 994 | writer.write("\"") 995 | if self.childNodes: 996 | if len(self.childNodes) == 1 \ 997 | and self.childNodes[0].nodeType == xml.dom.minidom.Node.TEXT_NODE: 998 | writer.write(">") 999 | self.childNodes[0].writexml(writer, "", "", "") 1000 | writer.write("%s" % (self.tagName, newl)) 1001 | return 1002 | writer.write(">%s" % newl) 1003 | for node in self.childNodes: 1004 | fixed_writexml(node, writer,indent+addindent,addindent,newl) 1005 | writer.write("%s%s" % (indent,self.tagName,newl)) 1006 | else: 1007 | writer.write("/>%s"%(newl)) 1008 | 1009 | def fixed_toprettyxml(self, indent="", addindent="\t", newl="\n"): 1010 | # indent = current indentation 1011 | # addindent = indentation to add to higher levels 1012 | # newl = newline string 1013 | writer = StringIO() 1014 | 1015 | fixed_writexml(self, writer, indent, addindent, newl) 1016 | return writer.getvalue() 1017 | 1018 | #===================================================================================== 1019 | # Openbox Glue 1020 | #===================================================================================== 1021 | 1022 | # Option Classes (for OBAction) 1023 | # 1. Parse function for OBAction to parse the data. 1024 | # 2. Getter(s) and Setter(s) for OBAction to operate on the data (registered by 1025 | # the parse function). 1026 | # 3. Widget generator for property editor to represent the data. 1027 | # Examples of such classes: string, int, filename, list of actions, 1028 | # list (choose one variant of many), string-int with custom validator(?) 1029 | 1030 | # Actions 1031 | # An array of Options: + 1032 | # These actions are being applied to OBAction instances. 1033 | 1034 | #===================================================================================== 1035 | # Option Class: String 1036 | #===================================================================================== 1037 | 1038 | class OCString(object): 1039 | __slots__ = ('name', 'default', 'alts') 1040 | 1041 | def __init__(self, name, default, alts=[]): 1042 | self.name = name 1043 | self.default = default 1044 | self.alts = alts 1045 | 1046 | def apply_default(self, action): 1047 | action.options[self.name] = self.default 1048 | 1049 | def parse(self, action, dom): 1050 | node = xml_find_node(dom, self.name) 1051 | if not node: 1052 | for a in self.alts: 1053 | node = xml_find_node(dom, a) 1054 | if node: 1055 | break 1056 | if node: 1057 | action.options[self.name] = xml_parse_string(node) 1058 | else: 1059 | action.options[self.name] = self.default 1060 | 1061 | def deparse(self, action): 1062 | val = action.options[self.name] 1063 | if val == self.default: 1064 | return None 1065 | return xml.dom.minidom.parseString("<"+str(self.name)+">"+str(val)+"").documentElement 1066 | 1067 | def generate_widget(self, action): 1068 | def changed(entry, action): 1069 | text = entry.get_text() 1070 | action.options[self.name] = text 1071 | 1072 | entry = gtk.Entry() 1073 | entry.set_text(action.options[self.name]) 1074 | entry.connect('changed', changed, action) 1075 | return entry 1076 | 1077 | #===================================================================================== 1078 | # Option Class: Combo 1079 | #===================================================================================== 1080 | 1081 | class OCCombo(object): 1082 | __slots__ = ('name', 'default', 'choices') 1083 | 1084 | def __init__(self, name, default, choices): 1085 | self.name = name 1086 | self.default = default 1087 | self.choices = choices 1088 | 1089 | def apply_default(self, action): 1090 | action.options[self.name] = self.default 1091 | 1092 | def parse(self, action, dom): 1093 | node = xml_find_node(dom, self.name) 1094 | if node: 1095 | action.options[self.name] = xml_parse_string(node) 1096 | else: 1097 | action.options[self.name] = self.default 1098 | 1099 | def deparse(self, action): 1100 | val = action.options[self.name] 1101 | if val == self.default: 1102 | return None 1103 | return xml.dom.minidom.parseString("<"+str(self.name)+">"+str(val)+"").documentElement 1104 | 1105 | def generate_widget(self, action): 1106 | def changed(combo, action): 1107 | text = combo.get_active() 1108 | action.options[self.name] = self.choices[text] 1109 | 1110 | model = gtk.ListStore(gobject.TYPE_STRING) 1111 | for c in self.choices: 1112 | model.append((_(c),)) 1113 | 1114 | combo = gtk.ComboBox() 1115 | combo.set_active(self.choices.index(action.options[self.name])) 1116 | combo.set_model(model) 1117 | cell = gtk.CellRendererText() 1118 | combo.pack_start(cell, True) 1119 | combo.add_attribute(cell, 'text', 0) 1120 | combo.connect('changed', changed, action) 1121 | return combo 1122 | 1123 | #===================================================================================== 1124 | # Option Class: Number 1125 | #===================================================================================== 1126 | 1127 | class OCNumber(object): 1128 | __slots__ = ('name', 'default', 'min', 'max', 'explicit_defaults') 1129 | 1130 | def __init__(self, name, default, mmin, mmax, explicit_defaults=False): 1131 | self.name = name 1132 | self.default = default 1133 | self.min = mmin 1134 | self.max = mmax 1135 | self.explicit_defaults = explicit_defaults 1136 | 1137 | def apply_default(self, action): 1138 | action.options[self.name] = self.default 1139 | 1140 | def parse(self, action, dom): 1141 | node = xml_find_node(dom, self.name) 1142 | if node: 1143 | action.options[self.name] = int(float(xml_parse_string(node))) 1144 | else: 1145 | action.options[self.name] = self.default 1146 | 1147 | def deparse(self, action): 1148 | val = action.options[self.name] 1149 | if not self.explicit_defaults and (val == self.default): 1150 | return None 1151 | return xml.dom.minidom.parseString("<"+str(self.name)+">"+str(val)+"").documentElement 1152 | 1153 | def generate_widget(self, action): 1154 | def changed(num, action): 1155 | n = num.get_value_as_int() 1156 | action.options[self.name] = n 1157 | 1158 | num = gtk.SpinButton() 1159 | num.set_increments(1, 5) 1160 | num.set_range(self.min, self.max) 1161 | num.set_value(action.options[self.name]) 1162 | num.connect('value-changed', changed, action) 1163 | return num 1164 | 1165 | #===================================================================================== 1166 | # Option Class: Boolean 1167 | #===================================================================================== 1168 | 1169 | class OCBoolean(object): 1170 | __slots__ = ('name', 'default') 1171 | 1172 | def __init__(self, name, default): 1173 | self.name = name 1174 | self.default = default 1175 | 1176 | def apply_default(self, action): 1177 | action.options[self.name] = self.default 1178 | 1179 | def parse(self, action, dom): 1180 | node = xml_find_node(dom, self.name) 1181 | if node: 1182 | action.options[self.name] = xml_parse_bool(node) 1183 | else: 1184 | action.options[self.name] = self.default 1185 | 1186 | def deparse(self, action): 1187 | if action.options[self.name] == self.default: 1188 | return None 1189 | if action.options[self.name]: 1190 | return xml.dom.minidom.parseString("<"+str(self.name)+">yes").documentElement 1191 | else: 1192 | return xml.dom.minidom.parseString("<"+str(self.name)+">no").documentElement 1193 | 1194 | def generate_widget(self, action): 1195 | def changed(checkbox, action): 1196 | active = checkbox.get_active() 1197 | action.options[self.name] = active 1198 | 1199 | check = gtk.CheckButton() 1200 | check.set_active(action.options[self.name]) 1201 | check.connect('toggled', changed, action) 1202 | return check 1203 | 1204 | #===================================================================================== 1205 | # Option Class: StartupNotify 1206 | #===================================================================================== 1207 | 1208 | class OCStartupNotify(object): 1209 | def __init__(self): 1210 | self.name = "startupnotify" 1211 | 1212 | def apply_default(self, action): 1213 | action.options['startupnotify_enabled'] = False 1214 | action.options['startupnotify_wmclass'] = "" 1215 | action.options['startupnotify_name'] = "" 1216 | action.options['startupnotify_icon'] = "" 1217 | 1218 | def parse(self, action, dom): 1219 | self.apply_default(action) 1220 | 1221 | startupnotify = xml_find_node(dom, "startupnotify") 1222 | if not startupnotify: 1223 | return 1224 | 1225 | enabled = xml_find_node(startupnotify, "enabled") 1226 | if enabled: 1227 | action.options['startupnotify_enabled'] = xml_parse_bool(enabled) 1228 | wmclass = xml_find_node(startupnotify, "wmclass") 1229 | if wmclass: 1230 | action.options['startupnotify_wmclass'] = xml_parse_string(wmclass) 1231 | name = xml_find_node(startupnotify, "name") 1232 | if name: 1233 | action.options['startupnotify_name'] = xml_parse_string(name) 1234 | icon = xml_find_node(startupnotify, "icon") 1235 | if icon: 1236 | action.options['startupnotify_icon'] = xml_parse_string(icon) 1237 | 1238 | def deparse(self, action): 1239 | if not action.options['startupnotify_enabled']: 1240 | return None 1241 | root = xml.dom.minidom.parseString("yes").documentElement 1242 | if action.options['startupnotify_wmclass'] != "": 1243 | root.appendChild(xml.dom.minidom.parseString(""+action.options['startupnotify_wmclass']+"").documentElement) 1244 | if action.options['startupnotify_name'] != "": 1245 | root.appendChild(xml.dom.minidom.parseString(""+action.options['startupnotify_name']+"").documentElement) 1246 | if action.options['startupnotify_icon'] != "": 1247 | root.appendChild(xml.dom.minidom.parseString(""+action.options['startupnotify_icon']+"").documentElement) 1248 | return root 1249 | 1250 | def generate_widget(self, action): 1251 | def enabled_toggled(checkbox, action, sens_list): 1252 | active = checkbox.get_active() 1253 | action.options['startupnotify_enabled'] = active 1254 | for w in sens_list: 1255 | w.set_sensitive(active) 1256 | 1257 | def text_changed(textbox, action, var): 1258 | text = textbox.get_text() 1259 | action.options[var] = text 1260 | 1261 | 1262 | wmclass = gtk.Entry() 1263 | wmclass.set_size_request(100,-1) 1264 | wmclass.set_text(action.options['startupnotify_wmclass']) 1265 | wmclass.connect('changed', text_changed, action, 'startupnotify_wmclass') 1266 | 1267 | name = gtk.Entry() 1268 | name.set_size_request(100,-1) 1269 | name.set_text(action.options['startupnotify_name']) 1270 | name.connect('changed', text_changed, action, 'startupnotify_name') 1271 | 1272 | icon = gtk.Entry() 1273 | icon.set_size_request(100,-1) 1274 | icon.set_text(action.options['startupnotify_icon']) 1275 | icon.connect('changed', text_changed, action, 'startupnotify_icon') 1276 | 1277 | sens_list = [wmclass, name, icon] 1278 | 1279 | enabled = gtk.CheckButton() 1280 | enabled.set_active(action.options['startupnotify_enabled']) 1281 | enabled.connect('toggled', enabled_toggled, action, sens_list) 1282 | 1283 | def put_table(table, label_text, widget, row, addtosens=True): 1284 | label = gtk.Label(_(label_text)) 1285 | label.set_padding(5,5) 1286 | label.set_alignment(0,0) 1287 | if addtosens: 1288 | sens_list.append(label) 1289 | table.attach(label, 0, 1, row, row+1, gtk.EXPAND | gtk.FILL, 0, 0, 0) 1290 | table.attach(widget, 1, 2, row, row+1, gtk.FILL, 0, 0, 0) 1291 | 1292 | table = gtk.Table(1, 2) 1293 | put_table(table, "enabled:", enabled, 0, False) 1294 | put_table(table, "wmclass:", wmclass, 1) 1295 | put_table(table, "name:", name, 2) 1296 | put_table(table, "icon:", icon, 3) 1297 | 1298 | sens = enabled.get_active() 1299 | for w in sens_list: 1300 | w.set_sensitive(sens) 1301 | 1302 | frame = gtk.Frame() 1303 | frame.add(table) 1304 | return frame 1305 | 1306 | #===================================================================================== 1307 | # Option Class: FinalActions 1308 | #===================================================================================== 1309 | 1310 | class OCFinalActions(object): 1311 | __slots__ = ('name') 1312 | 1313 | def __init__(self): 1314 | self.name = "finalactions" 1315 | 1316 | def apply_default(self, action): 1317 | a1 = OBAction() 1318 | a1.mutate("Focus") 1319 | a2 = OBAction() 1320 | a2.mutate("Raise") 1321 | a3 = OBAction() 1322 | a3.mutate("Unshade") 1323 | 1324 | action.options[self.name] = [a1, a2, a3] 1325 | 1326 | def parse(self, action, dom): 1327 | node = xml_find_node(dom, self.name) 1328 | action.options[self.name] = [] 1329 | if node: 1330 | for a in xml_find_nodes(node, "action"): 1331 | act = OBAction() 1332 | act.parse(a) 1333 | action.options[self.name].append(act) 1334 | else: 1335 | self.apply_default(action) 1336 | 1337 | def deparse(self, action): 1338 | a = action.options[self.name] 1339 | if len(a) == 3: 1340 | if a[0].name == "Focus" and a[1].name == "Raise" and a[2].name == "Unshade": 1341 | return None 1342 | if len(a) == 0: 1343 | return None 1344 | root = xml.dom.minidom.parseString("").documentElement 1345 | for act in a: 1346 | node = act.deparse() 1347 | root.appendChild(node) 1348 | return root 1349 | 1350 | def generate_widget(self, action): 1351 | w = MiniActionList() 1352 | w.set_actions(action.options[self.name]) 1353 | frame = gtk.Frame() 1354 | frame.add(w.widget) 1355 | return frame 1356 | 1357 | #------------------------------------------------------------------------------------- 1358 | 1359 | actions = { 1360 | "Execute": [ 1361 | OCString("command", "", ['execute']), 1362 | OCString("prompt", ""), 1363 | OCStartupNotify() 1364 | ], 1365 | "ShowMenu": [ 1366 | OCString("menu", "") 1367 | ], 1368 | "NextWindow": [ 1369 | OCCombo('dialog', 'list', ['list', 'icons', 'none']), 1370 | OCBoolean("bar", True), 1371 | OCBoolean("raise", False), 1372 | OCBoolean("allDesktops", False), 1373 | OCBoolean("panels", False), 1374 | OCBoolean("desktop", False), 1375 | OCBoolean("linear", False), 1376 | OCFinalActions() 1377 | ], 1378 | "PreviousWindow": [ 1379 | OCBoolean("dialog", True), 1380 | OCBoolean("bar", True), 1381 | OCBoolean("raise", False), 1382 | OCBoolean("allDesktops", False), 1383 | OCBoolean("panels", False), 1384 | OCBoolean("desktop", False), 1385 | OCBoolean("linear", False), 1386 | OCFinalActions() 1387 | ], 1388 | "DirectionalFocusNorth": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], 1389 | "DirectionalFocusSouth": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], 1390 | "DirectionalFocusEast": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], 1391 | "DirectionalFocusWest": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], 1392 | "DirectionalFocusNorthEast": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], 1393 | "DirectionalFocusNorthWest": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], 1394 | "DirectionalFocusSouthEast": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], 1395 | "DirectionalFocusSouthWest": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], 1396 | "DirectionalTargetNorth": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], 1397 | "DirectionalTargetSouth": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], 1398 | "DirectionalTargetEast": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], 1399 | "DirectionalTargetWest": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], 1400 | "DirectionalTargetNorthEast": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], 1401 | "DirectionalTargetNorthWest": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], 1402 | "DirectionalTargetSouthEast": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], 1403 | "DirectionalTargetSouthWest": [ OCBoolean("dialog", True), OCBoolean("bar", True), OCBoolean("raise", False), OCFinalActions() ], 1404 | "Desktop": [ OCNumber("desktop", 1, 1, 9999, True) ], 1405 | "DesktopNext": [ OCBoolean("wrap", True) ], 1406 | "DesktopPrevious": [ OCBoolean("wrap", True) ], 1407 | "DesktopLeft": [ OCBoolean("wrap", True) ], 1408 | "DesktopRight": [ OCBoolean("wrap", True) ], 1409 | "DesktopUp": [ OCBoolean("wrap", True) ], 1410 | "DesktopDown": [ OCBoolean("wrap", True) ], 1411 | "DesktopLast": [], 1412 | "AddDesktopLast": [], 1413 | "RemoveDesktopLast": [], 1414 | "AddDesktopCurrent": [], 1415 | "RemoveDesktopCurrent": [], 1416 | "ToggleShowDesktop": [], 1417 | "ToggleDockAutohide": [], 1418 | "Reconfigure": [], 1419 | "Restart": [ OCString("command", "", ["execute"]) ], 1420 | "Exit": [ OCBoolean("prompt", True) ], 1421 | "SessionLogout": [ OCBoolean("prompt", True) ], 1422 | "Debug": [ OCString("string", "") ], 1423 | 1424 | "Focus": [], 1425 | "Raise": [], 1426 | "Lower": [], 1427 | "RaiseLower": [], 1428 | "Unfocus": [], 1429 | "FocusToBottom": [], 1430 | "Iconify": [], 1431 | "Close": [], 1432 | "ToggleShade": [], 1433 | "Shade": [], 1434 | "Unshade": [], 1435 | "ToggleOmnipresent": [], 1436 | "ToggleMaximizeFull": [], 1437 | "MaximizeFull": [], 1438 | "UnmaximizeFull": [], 1439 | "ToggleMaximizeVert": [], 1440 | "MaximizeVert": [], 1441 | "UnmaximizeVert": [], 1442 | "ToggleMaximizeHorz": [], 1443 | "MaximizeHorz": [], 1444 | "UnmaximizeHorz": [], 1445 | "ToggleFullscreen": [], 1446 | "ToggleDecorations": [], 1447 | "Decorate": [], 1448 | "Undecorate": [], 1449 | "SendToDesktop": [ OCNumber("desktop", 1, 1, 9999, True), OCBoolean("follow", True) ], 1450 | "SendToDesktopNext": [ OCBoolean("wrap", True), OCBoolean("follow", True) ], 1451 | "SendToDesktopPrevious": [ OCBoolean("wrap", True), OCBoolean("follow", True) ], 1452 | "SendToDesktopLeft": [ OCBoolean("wrap", True), OCBoolean("follow", True) ], 1453 | "SendToDesktopRight": [ OCBoolean("wrap", True), OCBoolean("follow", True) ], 1454 | "SendToDesktopUp": [ OCBoolean("wrap", True), OCBoolean("follow", True) ], 1455 | "SendToDesktopDown": [ OCBoolean("wrap", True), OCBoolean("follow", True) ], 1456 | "Move": [], 1457 | "Resize": [ 1458 | OCCombo("edge", "none", ['none', "top", "left", "right", "bottom", "topleft", "topright", "bottomleft", "bottomright"]) 1459 | ], 1460 | "MoveToCenter": [], 1461 | "MoveResizeTo": [ 1462 | OCString("x", "current"), 1463 | OCString("y", "current"), 1464 | OCString("width", "current"), 1465 | OCString("height", "current"), 1466 | OCString("monitor", "current") 1467 | ], 1468 | "MoveRelative": [ 1469 | OCNumber("x", 0, -9999, 9999), 1470 | OCNumber("y", 0, -9999, 9999) 1471 | ], 1472 | "ResizeRelative": [ 1473 | OCNumber("left", 0, -9999, 9999), 1474 | OCNumber("right", 0, -9999, 9999), 1475 | OCNumber("top", 0, -9999, 9999), 1476 | OCNumber("bottom", 0, -9999, 9999) 1477 | ], 1478 | "MoveToEdgeNorth": [], 1479 | "MoveToEdgeSouth": [], 1480 | "MoveToEdgeWest": [], 1481 | "MoveToEdgeEast": [], 1482 | "GrowToEdgeNorth": [], 1483 | "GrowToEdgeSouth": [], 1484 | "GrowToEdgeWest": [], 1485 | "GrowToEdgeEast": [], 1486 | "ShadeLower": [], 1487 | "UnshadeRaise": [], 1488 | "ToggleAlwaysOnTop": [], 1489 | "ToggleAlwaysOnBottom": [], 1490 | "SendToTopLayer": [], 1491 | "SendToBottomLayer": [], 1492 | "SendToNormalLayer": [], 1493 | 1494 | "BreakChroot": [] 1495 | } 1496 | 1497 | #===================================================================================== 1498 | # Config parsing and interaction 1499 | #===================================================================================== 1500 | 1501 | class OBAction: 1502 | def __init__(self, name=None): 1503 | self.options = {} 1504 | self.option_defs = [] 1505 | self.name = name 1506 | if name: 1507 | self.mutate(name) 1508 | 1509 | def parse(self, dom): 1510 | # call parseChild if childNodes exist 1511 | if dom.hasChildNodes(): 1512 | for child in dom.childNodes: 1513 | self.parseChild(child) 1514 | 1515 | # parse 'name' attribute, get options hash and parse 1516 | self.name = xml_parse_attr(dom, "name") 1517 | 1518 | try: 1519 | self.option_defs = actions[self.name] 1520 | except KeyError: 1521 | pass 1522 | 1523 | for od in self.option_defs: 1524 | od.parse(self, dom) 1525 | 1526 | # calls itself until no childNodes are found and strip() values of last node 1527 | def parseChild(self, dom): 1528 | try: 1529 | if dom.hasChildNodes(): 1530 | for child in dom.childNodes: 1531 | try: 1532 | child.nodeValue = child.nodeValue.strip() 1533 | except AttributeError: 1534 | pass 1535 | self.parseChild(child) 1536 | except AttributeError: 1537 | pass 1538 | else: 1539 | try: 1540 | dom.nodeValue = dom.nodeValue.strip() 1541 | except AttributeError: 1542 | pass 1543 | 1544 | def deparse(self): 1545 | root = xml.dom.minidom.parseString('').documentElement 1546 | for od in self.option_defs: 1547 | od_node = od.deparse(self) 1548 | if od_node: 1549 | root.appendChild(od_node) 1550 | return root 1551 | 1552 | def mutate(self, newtype): 1553 | if hasattr(self, "option_defs") and actions[newtype] == self.option_defs: 1554 | self.options = {} 1555 | self.name = newtype 1556 | return 1557 | 1558 | self.options = {} 1559 | self.name = newtype 1560 | self.option_defs = actions[self.name] 1561 | 1562 | for od in self.option_defs: 1563 | od.apply_default(self) 1564 | 1565 | def __deepcopy__(self, memo): 1566 | # we need deepcopy here, because option_defs are never copied 1567 | result = self.__class__() 1568 | result.option_defs = self.option_defs 1569 | result.options = copy.deepcopy(self.options, memo) 1570 | result.name = copy.deepcopy(self.name, memo) 1571 | return result 1572 | #------------------------------------------------------------------------------------- 1573 | 1574 | class OBKeyBind: 1575 | def __init__(self, parent=None): 1576 | self.children = [] 1577 | self.actions = [] 1578 | self.key = "a" 1579 | self.chroot = False 1580 | self.parent = parent 1581 | 1582 | def parse(self, dom): 1583 | self.key = xml_parse_attr(dom, "key") 1584 | self.chroot = xml_parse_attr_bool(dom, "chroot") 1585 | 1586 | kbinds = xml_find_nodes(dom, "keybind") 1587 | if len(kbinds): 1588 | for k in kbinds: 1589 | kb = OBKeyBind(self) 1590 | kb.parse(k) 1591 | self.children.append(kb) 1592 | else: 1593 | for a in xml_find_nodes(dom, "action"): 1594 | newa = OBAction() 1595 | newa.parse(a) 1596 | self.actions.append(newa) 1597 | 1598 | def deparse(self): 1599 | if self.chroot: 1600 | root = xml.dom.minidom.parseString('').documentElement 1601 | else: 1602 | root = xml.dom.minidom.parseString('').documentElement 1603 | 1604 | if len(self.children): 1605 | for k in self.children: 1606 | root.appendChild(k.deparse()) 1607 | else: 1608 | for a in self.actions: 1609 | root.appendChild(a.deparse()) 1610 | return root 1611 | 1612 | def insert_empty_action(self, after=None): 1613 | newact = OBAction() 1614 | newact.mutate("Execute") 1615 | 1616 | if after: 1617 | self.actions.insert(self.actions.index(after)+1, newact) 1618 | else: 1619 | self.actions.append(newact) 1620 | return newact 1621 | 1622 | def move_up(self, action): 1623 | i = self.actions.index(action) 1624 | tmp = self.actions[i-1] 1625 | self.actions[i-1] = action 1626 | self.actions[i] = tmp 1627 | 1628 | def move_down(self, action): 1629 | i = self.actions.index(action) 1630 | tmp = self.actions[i+1] 1631 | self.actions[i+1] = action 1632 | self.actions[i] = tmp 1633 | 1634 | #------------------------------------------------------------------------------------- 1635 | 1636 | class OBKeyboard: 1637 | def __init__(self, dom): 1638 | self.chainQuitKey = None 1639 | self.keybinds = [] 1640 | 1641 | cqk = xml_find_node(dom, "chainQuitKey") 1642 | if cqk: 1643 | self.chainQuitKey = xml_parse_string(cqk) 1644 | 1645 | for keybind_node in xml_find_nodes(dom, "keybind"): 1646 | kb = OBKeyBind() 1647 | kb.parse(keybind_node) 1648 | self.keybinds.append(kb) 1649 | 1650 | def deparse(self): 1651 | root = xml.dom.minidom.parseString('').documentElement 1652 | chainQuitKey_node = xml.dom.minidom.parseString(''+str(self.chainQuitKey)+'').documentElement 1653 | root.appendChild(chainQuitKey_node) 1654 | 1655 | for k in self.keybinds: 1656 | root.appendChild(k.deparse()) 1657 | 1658 | return root 1659 | 1660 | #------------------------------------------------------------------------------------- 1661 | 1662 | class OpenboxConfig: 1663 | def __init__(self): 1664 | self.dom = None 1665 | self.keyboard = None 1666 | self.path = None 1667 | 1668 | def load(self, path): 1669 | self.path = path 1670 | 1671 | # load config DOM 1672 | self.dom = xml.dom.minidom.parse(path) 1673 | 1674 | # try load keyboard DOM 1675 | keyboard = xml_find_node(self.dom.documentElement, "keyboard") 1676 | if keyboard: 1677 | self.keyboard = OBKeyboard(keyboard) 1678 | 1679 | def save(self): 1680 | if self.path is None: 1681 | return 1682 | 1683 | # it's all hack, waste of resources etc, but does pretty good result 1684 | keyboard = xml_find_node(self.dom.documentElement, "keyboard") 1685 | newdom = xml_find_node(xml.dom.minidom.parseString(fixed_toprettyxml(self.keyboard.deparse()," "," ")),"keyboard") 1686 | self.dom.documentElement.replaceChild(newdom, keyboard) 1687 | f = file(self.path, "w") 1688 | if f: 1689 | xmlform = self.dom.documentElement 1690 | f.write(xmlform.toxml("utf8")) 1691 | f.close() 1692 | self.reconfigure_openbox() 1693 | 1694 | def reconfigure_openbox(self): 1695 | os.system("openbox --reconfigure") 1696 | -------------------------------------------------------------------------------- /po/Makefile: -------------------------------------------------------------------------------- 1 | LANGS := $(patsubst obkey.%.po,%,$(wildcard *.po)) 2 | TARGETS := $(patsubst %,../locale/%/LC_MESSAGES/obkey.mo,$(LANGS)) 3 | 4 | all: $(TARGETS) 5 | 6 | ../locale/%/LC_MESSAGES/obkey.mo: obkey.%.po 7 | mkdir -p $(dir $@) 8 | msgfmt -o $@ $< 9 | -------------------------------------------------------------------------------- /po/README: -------------------------------------------------------------------------------- 1 | I store all translation files in a binary form. 2 | 3 | So, please, if you update translation files, run 'make' here and use 4 | '../locale' dir changes as a part of a git commit. 5 | -------------------------------------------------------------------------------- /po/obkey.uk.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: ob-autostart\n" 4 | "POT-Creation-Date: \n" 5 | "PO-Revision-Date: \n" 6 | "Last-Translator: Oleksandr Zayats \n" 7 | "MIME-Version: 1.0\n" 8 | "Content-Type: text/plain; charset=utf-8\n" 9 | "Content-Transfer-Encoding: 8bit\n" 10 | "X-Poedit-Language: Ukrainian\n" 11 | "X-Poedit-Country: UKRAINE\n" 12 | "Language-Team: \n" 13 | 14 | msgid "obkey" 15 | msgstr "Редактор хоткеїв" 16 | 17 | msgid "Cu_t" 18 | msgstr "Вирі_зати" 19 | 20 | msgid "_Copy" 21 | msgstr "_Копіювати" 22 | 23 | msgid "_Paste" 24 | msgstr "_Вставити" 25 | 26 | msgid "P_aste as child" 27 | msgstr "_Вставити як дочірний" 28 | 29 | msgid "_Remove" 30 | msgstr "_Видалити" 31 | 32 | msgid "Save " 33 | msgstr "Зберегти " 34 | 35 | msgid " file" 36 | msgstr " файл" 37 | 38 | msgid "Insert sibling keybind" 39 | msgstr "Вставити пряму комбінацію" 40 | 41 | msgid "Insert child keybind" 42 | msgstr "Вставити дочірню комбінацію" 43 | 44 | msgid "Remove keybind" 45 | msgstr "Видалити комбінацію" 46 | 47 | msgid "Quit application" 48 | msgstr "Вийти з програми" 49 | 50 | msgid "Actions" 51 | msgstr "Дії" 52 | 53 | msgid "Insert action" 54 | msgstr "Вставити дію" 55 | 56 | msgid "Remove action" 57 | msgstr "Видалити дію" 58 | 59 | msgid "Move action up" 60 | msgstr "Перемістити догори" 61 | 62 | msgid "Move action down" 63 | msgstr "Перемістити донизу" 64 | 65 | msgid "Remove all actions" 66 | msgstr "Видалити усі дії" 67 | 68 | msgid "Key" 69 | msgstr "Комбінація" 70 | 71 | msgid "Key (text)" 72 | msgstr "Ключ" 73 | 74 | msgid "Chroot" 75 | msgstr "від root" 76 | 77 | msgid "chainQuitKey:" 78 | msgstr "chainQuit клав:" 79 | 80 | msgid "enabled:" 81 | msgstr "увімкнено:" 82 | 83 | msgid "wmclass:" 84 | msgstr "wm клас:" 85 | 86 | msgid "name:" 87 | msgstr "ім'я:" 88 | 89 | msgid "icon:" 90 | msgstr "іконка:" 91 | 92 | msgid "command:" 93 | msgstr "команда:" 94 | 95 | msgid "prompt:" 96 | msgstr "підтвердження:" 97 | 98 | msgid "menu:" 99 | msgstr "меню:" 100 | 101 | msgid "dialog:" 102 | msgstr "діалог:" 103 | 104 | msgid "bar:" 105 | msgstr "_bar:" 106 | 107 | msgid "raise:" 108 | msgstr "підняти:" 109 | 110 | msgid "allDesktops:" 111 | msgstr "усі стільниці:" 112 | 113 | msgid "panels:" 114 | msgstr "панелі:" 115 | 116 | msgid "desktop:" 117 | msgstr "стільниця:" 118 | 119 | msgid "linear:" 120 | msgstr "лінійно:" 121 | 122 | msgid "follow:" 123 | msgstr "_follow:" 124 | 125 | msgid "wrap:" 126 | msgstr "_wrap:" 127 | 128 | msgid "width:" 129 | msgstr "ширина:" 130 | 131 | msgid "height:" 132 | msgstr "высота:" 133 | 134 | msgid "monitor:" 135 | msgstr "монітор:" 136 | 137 | msgid "string:" 138 | msgstr "стрінг:" 139 | 140 | msgid "edge:" 141 | msgstr "прив'язка:" 142 | 143 | msgid "finalactions:" 144 | msgstr "фінальні дії:" 145 | 146 | msgid "startupnotify:" 147 | msgstr "стартове увідомлення:" 148 | 149 | msgid "none" 150 | msgstr "не вибрано" 151 | 152 | msgid "top" 153 | msgstr "вгорі" 154 | 155 | msgid "left" 156 | msgstr "зліва" 157 | 158 | msgid "right" 159 | msgstr "справа" 160 | 161 | msgid "bottom" 162 | msgstr "знизу" 163 | 164 | msgid "topleft" 165 | msgstr "вгорі та зліва" 166 | 167 | msgid "topright" 168 | msgstr "вгорі та справа" 169 | 170 | msgid "bottomleft" 171 | msgstr "знизу та зліва" 172 | 173 | msgid "bottomright" 174 | msgstr "знизу та справа" 175 | 176 | msgid "Execute" 177 | msgstr "Виконання команди" 178 | 179 | msgid "ShowMenu" 180 | msgstr "Відобразити меню" 181 | 182 | msgid "NextWindow" 183 | msgstr "NextWindow" 184 | 185 | msgid "PreviousWindow" 186 | msgstr "PreviousWindow" 187 | 188 | msgid "DirectionalFocusNorth" 189 | msgstr "DirectionalFocusNorth" 190 | 191 | msgid "DirectionalFocusSouth" 192 | msgstr "DirectionalFocusSouth" 193 | 194 | msgid "DirectionalFocusEast" 195 | msgstr "DirectionalFocusEast" 196 | 197 | msgid "DirectionalFocusWest" 198 | msgstr "DirectionalFocusWest" 199 | 200 | msgid "DirectionalFocusNorthEast" 201 | msgstr "DirectionalFocusNorthEast" 202 | 203 | msgid "DirectionalFocusNorthWest" 204 | msgstr "DirectionalFocusNorthWest" 205 | 206 | msgid "DirectionalFocusSouthEast" 207 | msgstr "DirectionalFocusSouthEast" 208 | 209 | msgid "DirectionalFocusSouthWest" 210 | msgstr "DirectionalFocusSouthWest" 211 | 212 | msgid "DirectionalTargetNorth" 213 | msgstr "DirectionalTargetNorth" 214 | 215 | msgid "DirectionalTargetSouth" 216 | msgstr "DirectionalTargetSouth" 217 | 218 | msgid "DirectionalTargetEast" 219 | msgstr "DirectionalTargetEast" 220 | 221 | msgid "DirectionalTargetWest" 222 | msgstr "DirectionalTargetWest" 223 | 224 | msgid "DirectionalTargetNorthEast" 225 | msgstr "DirectionalTargetNorthEast" 226 | 227 | msgid "DirectionalTargetNorthWest" 228 | msgstr "DirectionalTargetNorthWest" 229 | 230 | msgid "DirectionalTargetSouthEast" 231 | msgstr "DirectionalTargetSouthEast" 232 | 233 | msgid "DirectionalTargetSouthWest" 234 | msgstr "DirectionalTargetSouthWest" 235 | 236 | msgid "Desktop" 237 | msgstr "Desktop" 238 | 239 | msgid "DesktopNext" 240 | msgstr "DesktopNext" 241 | 242 | msgid "DesktopPrevious" 243 | msgstr "DesktopPrevious" 244 | 245 | msgid "DesktopLeft" 246 | msgstr "DesktopLeft" 247 | 248 | msgid "DesktopRight" 249 | msgstr "DesktopRight" 250 | 251 | msgid "DesktopUp" 252 | msgstr "DesktopUp" 253 | 254 | msgid "DesktopDown" 255 | msgstr "DesktopDown" 256 | 257 | msgid "DesktopLast" 258 | msgstr "DesktopLast" 259 | 260 | msgid "AddDesktopLast" 261 | msgstr "AddDesktopLast" 262 | 263 | msgid "RemoveDesktopLast" 264 | msgstr "RemoveDesktopLast" 265 | 266 | msgid "AddDesktopCurrent" 267 | msgstr "AddDesktopCurrent" 268 | 269 | msgid "RemoveDesktopCurrent" 270 | msgstr "RemoveDesktopCurrent" 271 | 272 | msgid "ToggleShowDesktop" 273 | msgstr "ToggleShowDesktop" 274 | 275 | msgid "ToggleDockAutohide" 276 | msgstr "ToggleDockAutohide" 277 | 278 | msgid "Reconfigure" 279 | msgstr "Reconfigure" 280 | 281 | msgid "Restart" 282 | msgstr "Restart" 283 | 284 | msgid "Exit" 285 | msgstr "Exit" 286 | 287 | msgid "SessionLogout" 288 | msgstr "SessionLogout" 289 | 290 | msgid "Debug" 291 | msgstr "Debug" 292 | 293 | msgid "Focus" 294 | msgstr "Focus" 295 | 296 | msgid "Raise" 297 | msgstr "Raise" 298 | 299 | msgid "Lower" 300 | msgstr "Lower" 301 | 302 | msgid "RaiseLower" 303 | msgstr "RaiseLower" 304 | 305 | msgid "Unfocus" 306 | msgstr "Unfocus" 307 | 308 | msgid "FocusToBottom" 309 | msgstr "FocusToBottom" 310 | 311 | msgid "Iconify" 312 | msgstr "Iconify" 313 | 314 | msgid "Close" 315 | msgstr "Close" 316 | 317 | msgid "ToggleShade" 318 | msgstr "ToggleShade" 319 | 320 | msgid "Shade" 321 | msgstr "Shade" 322 | 323 | msgid "Unshade" 324 | msgstr "Unshade" 325 | 326 | msgid "ToggleOmnipresent" 327 | msgstr "ToggleOmnipresent" 328 | 329 | msgid "ToggleMaximizeFull" 330 | msgstr "ToggleMaximizeFull" 331 | 332 | msgid "MaximizeFull" 333 | msgstr "MaximizeFull" 334 | 335 | msgid "UnmaximizeFull" 336 | msgstr "UnmaximizeFull" 337 | 338 | msgid "ToggleMaximizeVert" 339 | msgstr "ToggleMaximizeVert" 340 | 341 | msgid "MaximizeVert" 342 | msgstr "MaximizeVert" 343 | 344 | msgid "UnmaximizeVert" 345 | msgstr "UnmaximizeVert" 346 | 347 | msgid "ToggleMaximizeHorz" 348 | msgstr "ToggleMaximizeHorz" 349 | 350 | msgid "MaximizeHorz" 351 | msgstr "MaximizeHorz" 352 | 353 | msgid "UnmaximizeHorz" 354 | msgstr "UnmaximizeHorz" 355 | 356 | msgid "ToggleFullscreen" 357 | msgstr "ToggleFullscreen" 358 | 359 | msgid "ToggleDecorations" 360 | msgstr "ToggleDecorations" 361 | 362 | msgid "Decorate" 363 | msgstr "Decorate" 364 | 365 | msgid "Undecorate" 366 | msgstr "Undecorate" 367 | 368 | msgid "SendToDesktop" 369 | msgstr "SendToDesktop" 370 | 371 | msgid "SendToDesktopNext" 372 | msgstr "SendToDesktopNext" 373 | 374 | msgid "SendToDesktopPrevious" 375 | msgstr "SendToDesktopPrevious" 376 | 377 | msgid "SendToDesktopLeft" 378 | msgstr "SendToDesktopLeft" 379 | 380 | msgid "SendToDesktopRight" 381 | msgstr "SendToDesktopRight" 382 | 383 | msgid "SendToDesktopUp" 384 | msgstr "SendToDesktopUp" 385 | 386 | msgid "SendToDesktopDown" 387 | msgstr "SendToDesktopDown" 388 | 389 | msgid "Move" 390 | msgstr "Move" 391 | 392 | msgid "Resize" 393 | msgstr "Resize" 394 | 395 | msgid "MoveToCenter" 396 | msgstr "MoveToCenter" 397 | 398 | msgid "MoveResizeTo" 399 | msgstr "MoveResizeTo" 400 | 401 | msgid "MoveRelative" 402 | msgstr "MoveRelative" 403 | 404 | msgid "ResizeRelative" 405 | msgstr "ResizeRelative" 406 | 407 | msgid "MoveToEdgeNorth" 408 | msgstr "MoveToEdgeNorth" 409 | 410 | msgid "MoveToEdgeSouth" 411 | msgstr "MoveToEdgeSouth" 412 | 413 | msgid "MoveToEdgeWest" 414 | msgstr "MoveToEdgeWest" 415 | 416 | msgid "MoveToEdgeEast" 417 | msgstr "MoveToEdgeEast" 418 | 419 | msgid "GrowToEdgeNorth" 420 | msgstr "GrowToEdgeNorth" 421 | 422 | msgid "GrowToEdgeSouth" 423 | msgstr "GrowToEdgeSouth" 424 | 425 | msgid "GrowToEdgeWest" 426 | msgstr "GrowToEdgeWest" 427 | 428 | msgid "GrowToEdgeEast" 429 | msgstr "GrowToEdgeEast" 430 | 431 | msgid "ShadeLower" 432 | msgstr "ShadeLower" 433 | 434 | msgid "UnshadeRaise" 435 | msgstr "UnshadeRaise" 436 | 437 | msgid "ToggleAlwaysOnTop" 438 | msgstr "ToggleAlwaysOnTop" 439 | 440 | msgid "ToggleAlwaysOnBottom" 441 | msgstr "ToggleAlwaysOnBottom" 442 | 443 | msgid "SendToTopLayer" 444 | msgstr "SendToTopLayer" 445 | 446 | msgid "SendToBottomLayer" 447 | msgstr "SendToBottomLayer" 448 | 449 | msgid "SendToNormalLayer" 450 | msgstr "SendToNormalLayer" 451 | 452 | msgid "BreakChroot" 453 | msgstr "BreakChroot" 454 | 455 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | from glob import glob 3 | import os 4 | 5 | libdir = 'share/obkey/icons' 6 | localedir = 'share/locale' 7 | 8 | langs = [a[len("locale/"):] for a in glob('locale/*')] 9 | locales = [(os.path.join(localedir, l, 'LC_MESSAGES'), 10 | [os.path.join('locale', l, 'LC_MESSAGES', 'obkey.mo')]) for l in langs] 11 | 12 | setup(name='obkey', 13 | version='1.0', 14 | description='Openbox Key Editor', 15 | author='nsf', 16 | author_email='no.smile.face@gmail.com', 17 | scripts=['obkey'], 18 | py_modules=['obkey_classes'], 19 | data_files=[(libdir, ['icons/add_child.png', 'icons/add_sibling.png'])] + locales 20 | ) 21 | --------------------------------------------------------------------------------