From 2fcf0d51a19a5a3c062fd2612d17302ba9fdebf7 Mon Sep 17 00:00:00 2001 From: luffah Date: Sat, 5 Nov 2016 23:19:15 +0100 Subject: [PATCH 01/25] doc updated --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/README.md b/README.md index 92a985c..db4211e 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,46 @@ ObKey - Openbox Key Editor (PyGObject version) ![ObKey](wiki/screenshot_obkey.png) +# Installation + +```shell +git clone https://github.com/luffah/obkey.git + +# test it works (you can use it directly this way) +python obkey + +# INSTALLATION +sudo python setup.py + +######################################## +# Solve the INTERNATIONALIZATION PROBLEM +# obkey have translation, but currently there's a bug on installation +# due to an inconsistency between the lib 'gettext.py' and the installation made by 'setup.py' +# you may need to do next procedure to solve it + +# take the name of the translation file +tMSGFILE=LC_MESSAGES/obkey.mo + +# find the translation directory +tPYTHONPATH=`python -c "import sys; print '\n'.join(sys.path)" | grep 'lib/python2.7$'` +tPYLANGPATH=`find $tPYTHONPATH -name 'gettext.py' | xargs grep 'mofile_lp =' | sed 's/.*join("\(.*\)".*$/\1/'` +* +# shall return something like /usr/share/locale-langpack +echo $tPYLANGPATH + +if [ -d $tPYLANGPATH ] +then + ls -1 locale/ | xargs -I{} sudo cp locale/{}/$tMSGFILE $tPYLANGPATH/{}/$tMSGFILE +fi +############################# + +# Finally run obkey +obkey + +# to try other languages +LANGUAGE=fr obkey + +``` # About me After tried almost every window managers, and having recently left my 'awesomewm' configuration behind a sharp #. From 8b8ebb49e130ae3a78ccffff0153bc7ba782255e Mon Sep 17 00:00:00 2001 From: luffah Date: Sat, 5 Nov 2016 23:21:25 +0100 Subject: [PATCH 02/25] doc updated --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index db4211e..f80a783 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ tMSGFILE=LC_MESSAGES/obkey.mo # find the translation directory tPYTHONPATH=`python -c "import sys; print '\n'.join(sys.path)" | grep 'lib/python2.7$'` tPYLANGPATH=`find $tPYTHONPATH -name 'gettext.py' | xargs grep 'mofile_lp =' | sed 's/.*join("\(.*\)".*$/\1/'` -* + # shall return something like /usr/share/locale-langpack echo $tPYLANGPATH From dab0b5f08c3ea07c7e5374ab3cef39f5849d9a8e Mon Sep 17 00:00:00 2001 From: luffah Date: Sun, 6 Nov 2016 17:22:17 +0100 Subject: [PATCH 03/25] adding workaround for gettext --- README.md | 12 +++++++++--- obkey_classes.py | 8 ++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f80a783..dfea6bb 100644 --- a/README.md +++ b/README.md @@ -15,25 +15,31 @@ python obkey sudo python setup.py ######################################## -# Solve the INTERNATIONALIZATION PROBLEM +## To solve INTERNATIONALIZATION PROBLEM +# a workaround has been added in obkey_classes.py to find automaticaly the config_prefix +# if this problem persist read this part + +## Solve the INTERNATIONALIZATION PROBLEM # obkey have translation, but currently there's a bug on installation -# due to an inconsistency between the lib 'gettext.py' and the installation made by 'setup.py' +# due to an inconsistency between the lib 'obkey_classes.py' (line 48) and the installation made by 'setup.py' # you may need to do next procedure to solve it # take the name of the translation file tMSGFILE=LC_MESSAGES/obkey.mo # find the translation directory +# the default directory for python gettext shall be something like /usr/share/locale-langpack + tPYTHONPATH=`python -c "import sys; print '\n'.join(sys.path)" | grep 'lib/python2.7$'` tPYLANGPATH=`find $tPYTHONPATH -name 'gettext.py' | xargs grep 'mofile_lp =' | sed 's/.*join("\(.*\)".*$/\1/'` -# shall return something like /usr/share/locale-langpack echo $tPYLANGPATH if [ -d $tPYLANGPATH ] then ls -1 locale/ | xargs -I{} sudo cp locale/{}/$tMSGFILE $tPYLANGPATH/{}/$tMSGFILE fi + ############################# # Finally run obkey diff --git a/obkey_classes.py b/obkey_classes.py index 877ea15..da6dca9 100644 --- a/obkey_classes.py +++ b/obkey_classes.py @@ -44,11 +44,11 @@ # 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' +# workaround for config prefix problem +config_prefix = os.path.split(os.path.split(sys.argv[0])[0])[0] +# 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') - +config_locale_dir = os.path.join(config_prefix, 'share/locale') gettext.install('obkey', config_locale_dir) # init gettext # localized title From 27bf635bf951ac918a6f872dfc4c1940df808a2c Mon Sep 17 00:00:00 2001 From: Luffah Date: Sun, 6 Nov 2016 17:35:37 +0100 Subject: [PATCH 04/25] Adding notes for testing --- obkey_classes.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/obkey_classes.py b/obkey_classes.py index da6dca9..4d9c489 100644 --- a/obkey_classes.py +++ b/obkey_classes.py @@ -49,6 +49,12 @@ # config_prefix = '/usr' config_icons = os.path.join(config_prefix, 'share/obkey/icons') config_locale_dir = os.path.join(config_prefix, 'share/locale') + +# if testing obkey without using setup.py, uncomment these 2 lines +#config_icons='./icons' +#config_locale_dir = './locale' +# + gettext.install('obkey', config_locale_dir) # init gettext # localized title From 5f5e9df8bf7d388aa4d1b19e84e303f6f5602cdc Mon Sep 17 00:00:00 2001 From: luffah Date: Sun, 6 Nov 2016 17:57:07 +0100 Subject: [PATCH 05/25] adding modification made by dglava// i need to learn how to merge from the master' --- misc/obkey.desktop | 3 +++ obkey | 2 ++ 2 files changed, 5 insertions(+) diff --git a/misc/obkey.desktop b/misc/obkey.desktop index f59db24..6ad00f6 100644 --- a/misc/obkey.desktop +++ b/misc/obkey.desktop @@ -4,6 +4,9 @@ Type=Application Name=Openbox Key bindings Name[zh_TW]=Openbox 組態管理器 Comment=Configure and personalize the Openbox key bindings manager +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..f92020f 100755 --- a/obkey +++ b/obkey @@ -27,6 +27,8 @@ #----------------------------------------------------------------------- import sys, os +import gi +gi.require_version("Gtk", "3.0") from gi.repository import Gtk from gi.repository import GObject #~ import obkey_classes From 5a38b22345bccaab6b1820ee424005ad829c2a1f Mon Sep 17 00:00:00 2001 From: luffah Date: Sun, 6 Nov 2016 18:05:26 +0100 Subject: [PATCH 06/25] adding modification made by dglava// i need to learn how to merge from the master --- locale/bs/LC_MESSAGES/obkey.mo | Bin 0 -> 1704 bytes locale/hr/LC_MESSAGES/obkey.mo | Bin 0 -> 1704 bytes locale/sr/LC_MESSAGES/obkey.mo | Bin 0 -> 1938 bytes po/obkey.bs.po | 115 +++++++++++++++++++++++++++++++++ po/obkey.hr.po | 115 +++++++++++++++++++++++++++++++++ po/obkey.sr.po | 115 +++++++++++++++++++++++++++++++++ po/template.pot | 114 ++++++++++++++++++++++++++++++++ 7 files changed, 459 insertions(+) create mode 100644 locale/bs/LC_MESSAGES/obkey.mo create mode 100644 locale/hr/LC_MESSAGES/obkey.mo create mode 100644 locale/sr/LC_MESSAGES/obkey.mo create mode 100644 po/obkey.bs.po create mode 100644 po/obkey.hr.po create mode 100644 po/obkey.sr.po create mode 100644 po/template.pot diff --git a/locale/bs/LC_MESSAGES/obkey.mo b/locale/bs/LC_MESSAGES/obkey.mo new file mode 100644 index 0000000000000000000000000000000000000000..1e5c0ce16adc0db8387d297f7ca393979aa6a64c GIT binary patch literal 1704 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 W?dH6ZbzNWONR4+uX`TLmjQ;>nprC00 literal 0 HcmV?d00001 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.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 "" From 2bddd023dc1c3ba1c6d45f8e1f8b9109b0af95fb Mon Sep 17 00:00:00 2001 From: luffah Date: Sun, 6 Nov 2016 18:08:00 +0100 Subject: [PATCH 07/25] adding modification made by dglava// i need to learn how to merge from the master --- obkey_classes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/obkey_classes.py b/obkey_classes.py index 4d9c489..66a1eb0 100644 --- a/obkey_classes.py +++ b/obkey_classes.py @@ -51,8 +51,8 @@ config_locale_dir = os.path.join(config_prefix, 'share/locale') # if testing obkey without using setup.py, uncomment these 2 lines -#config_icons='./icons' -#config_locale_dir = './locale' +# config_icons='./icons' +# config_locale_dir = './locale' # gettext.install('obkey', config_locale_dir) # init gettext From d5b268acba311e9146fdb64144dd05c816d585d6 Mon Sep 17 00:00:00 2001 From: Luffah Date: Sun, 6 Nov 2016 18:13:36 +0100 Subject: [PATCH 08/25] Ajout description fr --- misc/obkey.desktop | 2 ++ 1 file changed, 2 insertions(+) diff --git a/misc/obkey.desktop b/misc/obkey.desktop index 6ad00f6..d2e61b5 100644 --- a/misc/obkey.desktop +++ b/misc/obkey.desktop @@ -2,8 +2,10 @@ Version=1.0 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 personalisations 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 From c02a35a262883dfec3c8c605188c3d55c544c55f Mon Sep 17 00:00:00 2001 From: Luffah Date: Sun, 6 Nov 2016 18:15:04 +0100 Subject: [PATCH 09/25] Orthographe.... --- misc/obkey.desktop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/obkey.desktop b/misc/obkey.desktop index d2e61b5..8b937fb 100644 --- a/misc/obkey.desktop +++ b/misc/obkey.desktop @@ -5,7 +5,7 @@ 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 personalisations des raccoucis clavier pour le gestionnaire de fenêtres Openbox +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 From 79accb4e39fcad2f63ebf1417b0ef058689eccc0 Mon Sep 17 00:00:00 2001 From: Luffah Date: Wed, 22 Feb 2017 15:35:11 +0100 Subject: [PATCH 10/25] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index dfea6bb..22a9667 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,4 @@ 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 : - -https://sourceforge.net/projects/obhotkey/ +see the next project (not updated since 2013...): https://sourceforge.net/projects/obhotkey/ From a26187f91adbf629e3097cc5ada664f937023710 Mon Sep 17 00:00:00 2001 From: Luffah Date: Mon, 15 May 2017 13:14:39 +0200 Subject: [PATCH 11/25] Update README.md adding alternative --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 22a9667..51122bc 100644 --- a/README.md +++ b/README.md @@ -88,3 +88,5 @@ http://openbox.org/wiki/Help:Bindings If you want to edit shortcut directly in command line interface, see the next project (not updated since 2013...): https://sourceforge.net/projects/obhotkey/ + +The more serious alternative to obkey is probably lxhotkey : https://github.com/lxde/lxhotkey From a61938328462e9dcaf03e79fb476978a82f4b2b0 Mon Sep 17 00:00:00 2001 From: loofa Date: Sat, 24 Jun 2017 20:07:49 +0200 Subject: [PATCH 12/25] Add automatic detection of rc file --- obkey | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/obkey b/obkey index f92020f..5929261 100755 --- a/obkey +++ b/obkey @@ -28,21 +28,38 @@ import sys, os import gi +import psutil + gi.require_version("Gtk", "3.0") 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" +path=None + +# get rc file from args if len(sys.argv) == 2: path = sys.argv[1] +# get rc file from current running openbox +for p in psutil.process_iter(): + proc = p.as_dict(attrs=['pid', 'name','cmdline']) + if proc['name'] == 'openbox': + ob_param=proc['cmdline'] + for i in range(len(ob_param)): + if ob_param[i] == '--config-file': + path=ob_param[i+1] + break + break + +# get default rc file +if not path : + path = os.getenv("HOME") + "/.config/openbox/rc.xml" + ob = obkey_classes.OpenboxConfig() ob.load(path) From 76ea7a313e97ea173f5f44817d6b0ccf35fb1ede Mon Sep 17 00:00:00 2001 From: loofa Date: Sun, 7 Jan 2018 14:44:52 +0100 Subject: [PATCH 13/25] stevenhoneyman : Check that obstr is not None before splitting --- obkey_classes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/obkey_classes.py b/obkey_classes.py index 66a1eb0..26641b9 100644 --- a/obkey_classes.py +++ b/obkey_classes.py @@ -101,7 +101,8 @@ } def key_openbox2gtk(obstr): - toks = obstr.split("-") + if obstr is not None: + toks = obstr.split("-") try: toksgdk = [replace_table_openbox2gtk[mod.lower()] for mod in toks[:-1]] except: From 0d8b8820c544c5178d0b6230e3a31b3a59c13651 Mon Sep 17 00:00:00 2001 From: loofa Date: Sun, 7 Jan 2018 15:06:33 +0100 Subject: [PATCH 14/25] remove a trailing whitespace... better syntax later... --- obkey_classes.py | 64 +++++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/obkey_classes.py b/obkey_classes.py index 26641b9..af29f4b 100644 --- a/obkey_classes.py +++ b/obkey_classes.py @@ -5,7 +5,7 @@ # 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) +# (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 @@ -82,7 +82,7 @@ "s" : "", "hyper" : "", "h" : "" - + } replace_table_gtk2openbox = { @@ -307,7 +307,17 @@ def create_views(self, model, cqk_model): c2 = Gtk.TreeViewColumn(_("Chroot"), r2, active=3, visible=4) c3 = Gtk.TreeViewColumn(_("Action"), r3, text=6) - c0.set_expand(True) + 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) @@ -330,8 +340,6 @@ def create_views(self, model, cqk_model): 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() @@ -358,12 +366,12 @@ def create_toolbar(self): 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")) @@ -374,7 +382,7 @@ def create_toolbar(self): 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']) @@ -386,7 +394,7 @@ def create_toolbar(self): 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()) @@ -413,16 +421,16 @@ def apply_cqk_initial_value(self): 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: + # 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']) + 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) + frst_action = _(kb.actions[0].name) else : frst_action = "." return frst_action @@ -431,7 +439,7 @@ 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))) @@ -559,7 +567,7 @@ def insert_sibling(self, keybind): 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 @@ -699,14 +707,14 @@ def create_model(self): def create_choices(self, ch): choices = ch action_list1 = {} - - #~ Version 1.1 + + #~ 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 + + # Version 1.2 for a in actions_choices: action_list1[_(a)] = a for a in sorted(action_list1.keys()): @@ -714,26 +722,26 @@ def create_choices(self, ch): 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): @@ -1003,7 +1011,7 @@ def set_actions(self, actionlist): 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)) @@ -1597,7 +1605,7 @@ def generate_widget(self, action): #~ "SessionLogout": [ OCBoolean("prompt", True) ], #~ "Debug": [ OCString("string", "") ], #~ "If": [ OCIf("", "") ], -#~ +#~ #~ "Focus": [], #~ "Raise": [], #~ "Lower": [], @@ -1667,7 +1675,7 @@ def generate_widget(self, action): #~ "SendToTopLayer": [], #~ "SendToBottomLayer": [], #~ "SendToNormalLayer": [], -#~ +#~ #~ "BreakChroot": [] #~ } @@ -1849,7 +1857,7 @@ def generate_widget(self, action): }; actions_choices["Window Properties"]=actions_window_set; actions_choices["Window/Session Management"]=actions_wm; - + #===================================================================================== # Config parsing and interaction From 58890c6be6e5f3a61c587e628c090eea3ccfab98 Mon Sep 17 00:00:00 2001 From: luffah Date: Mon, 12 Feb 2018 14:19:22 +0100 Subject: [PATCH 15/25] clean code : flakes 8 --- README.md | 62 +- obkey | 35 +- obkey_classes.py | 3972 ++++++++++++++++++++++++---------------------- 3 files changed, 2134 insertions(+), 1935 deletions(-) diff --git a/README.md b/README.md index 51122bc..32c4603 100644 --- a/README.md +++ b/README.md @@ -14,34 +14,6 @@ python obkey # INSTALLATION sudo python setup.py -######################################## -## To solve INTERNATIONALIZATION PROBLEM -# a workaround has been added in obkey_classes.py to find automaticaly the config_prefix -# if this problem persist read this part - -## Solve the INTERNATIONALIZATION PROBLEM -# obkey have translation, but currently there's a bug on installation -# due to an inconsistency between the lib 'obkey_classes.py' (line 48) and the installation made by 'setup.py' -# you may need to do next procedure to solve it - -# take the name of the translation file -tMSGFILE=LC_MESSAGES/obkey.mo - -# find the translation directory -# the default directory for python gettext shall be something like /usr/share/locale-langpack - -tPYTHONPATH=`python -c "import sys; print '\n'.join(sys.path)" | grep 'lib/python2.7$'` -tPYLANGPATH=`find $tPYTHONPATH -name 'gettext.py' | xargs grep 'mofile_lp =' | sed 's/.*join("\(.*\)".*$/\1/'` - -echo $tPYLANGPATH - -if [ -d $tPYLANGPATH ] -then - ls -1 locale/ | xargs -I{} sudo cp locale/{}/$tMSGFILE $tPYLANGPATH/{}/$tMSGFILE -fi - -############################# - # Finally run obkey obkey @@ -49,6 +21,20 @@ obkey LANGUAGE=fr obkey ``` + +# Dependencies + +### with PIP + +```shell +sudo pip install gi gettext +``` +### with APT + +```shell +sudo apt install python-gi python-gettext +``` + # About me After tried almost every window managers, and having recently left my 'awesomewm' configuration behind a sharp #. @@ -66,6 +52,26 @@ So i forked the project. - 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 +- if a problem occur about internationnalization (gettext), you may need to do next procedure to solve it : + +```shell +# check the lang is well detected in installation pack +./obkey ~/.config/openbox TESTING + +# if gettext is not found, you shall have a message saying that gettext is missing + +# take the name of the translation file +tMSGFILE=LC_MESSAGES/obkey.mo + +# find the translation directory +tPYTHONPATH=`python -c "import sys; print '\n'.join(sys.path)" | grep 'lib/python'` +tPYLANGPATH=`find $tPYTHONPATH -name 'gettext.py' | xargs grep 'mofile_lp =' | sed 's/.*join("\(.*\)".*$/\1/'` + +if [ -d $tPYLANGPATH ] +then + ls -1 locale/ | xargs -I{} sudo cp locale/{}/$tMSGFILE $tPYLANGPATH/{}/$tMSGFILE +fi +``` # Changes between obkey 1.1 and obkey 1.2 - sorted actions in edition pane - direct preview of relations between actions and keybind diff --git a/obkey b/obkey index 5929261..14e7427 100755 --- a/obkey +++ b/obkey @@ -28,11 +28,8 @@ import sys, os import gi -import psutil - -gi.require_version("Gtk", "3.0") +gi.require_versions({'Gtk': '3.0', 'GLib': '2.0', 'Gio': '2.0'}) from gi.repository import Gtk -from gi.repository import GObject import obkey_classes as obkey_classes import xml.dom.minidom @@ -42,20 +39,23 @@ def die(widget, data=None): path=None # get rc file from args -if len(sys.argv) == 2: +if len(sys.argv) > 1: path = sys.argv[1] -# get rc file from current running openbox -for p in psutil.process_iter(): - proc = p.as_dict(attrs=['pid', 'name','cmdline']) - if proc['name'] == 'openbox': - ob_param=proc['cmdline'] - for i in range(len(ob_param)): - if ob_param[i] == '--config-file': - path=ob_param[i+1] - break - break - +try: + import psutil + # get rc file from current running openbox + for p in psutil.process_iter(): + proc = p.as_dict(attrs=['pid', 'name','cmdline']) + if proc['name'] == 'openbox': + ob_param=proc['cmdline'] + for i in range(len(ob_param)): + if ob_param[i] == '--config-file': + path=ob_param[i+1] + break + break +except ImportError: + pass # get default rc file if not path : path = os.getenv("HOME") + "/.config/openbox/rc.xml" @@ -63,7 +63,8 @@ if not path : ob = obkey_classes.OpenboxConfig() ob.load(path) -win = Gtk.Window(Gtk.WindowType.TOPLEVEL) +# win = Gtk.Window(Gtk.WindowType.TOPLEVEL) # deprecated parameter +win = Gtk.Window() win.set_default_size(800,480) win.set_title(obkey_classes.config_title) win.connect("destroy", die) diff --git a/obkey_classes.py b/obkey_classes.py index af29f4b..39a2e4c 100644 --- a/obkey_classes.py +++ b/obkey_classes.py @@ -1,5 +1,5 @@ #!/usr/bin/python2 -#------------------------------------------------------------------------------ +# --------------------------------------------------------- # Openbox Key Editor # Copyright (C) 2009 nsf # v1.1 - Code migrated from PyGTK to PyGObject github.com/stevenhoneyman/obkey @@ -24,1107 +24,1211 @@ # 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 xml.sax.saxutils import escape from StringIO import StringIO -from string import strip -from gi.repository import GObject + +try: + 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 is missing : exit") + exit + import copy -from gi.repository import Gtk import sys import os -import gettext -from xml.sax.saxutils import escape +from os.path import join as path_join, dirname + -#===================================================================================== +# ======================================================== # Config -#===================================================================================== +# ======================================================== -# XXX: Sorry, for now this is it. If you know a better way to do this with setup.py: +# XXX: Sorry, for now this is it. +# If you know a better way to do this with setup.py: # please mail me. -# workaround for config prefix problem -config_prefix = os.path.split(os.path.split(sys.argv[0])[0])[0] -# config_prefix = '/usr' -config_icons = os.path.join(config_prefix, 'share/obkey/icons') -config_locale_dir = os.path.join(config_prefix, 'share/locale') +config_prefix = dirname(dirname(sys.argv[0])) +config_icons = path_join(config_prefix, 'share/obkey/icons') +config_locale_dir = path_join(config_prefix, 'share/locale') -# if testing obkey without using setup.py, uncomment these 2 lines -# config_icons='./icons' -# config_locale_dir = './locale' -# +# uncomment this for testing +if (len(sys.argv) > 2) and (sys.argv[2]=='TESTING'): + config_icons='./icons' + config_locale_dir = './locale' + +try: + from gettext import install as gettext_init + gettext_init('obkey', config_locale_dir) +except ImportError: + print("Gettext is missing") -gettext.install('obkey', config_locale_dir) # init gettext + def _(a): + return a # 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" : "" + "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" + "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): - if obstr is not None: - 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)) + if obstr is not None: + toks = obstr.split("-") + try: + toksgdk = [replace_table_openbox2gtk[mod.lower()] for mod in toks[:-1]] + except Exception: + 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 - -#===================================================================================== + 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 __init__(self, initial_state): + self.switchers = [] + self.state = initial_state + + def register_switcher(self, sw): + self.switchers.append(sw) - 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() - 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 = [] + def __init__(self, conditions): + self.conditions = conditions + self.widgets = [] - for c in conditions: - c.register_switcher(self) + for c in conditions: + c.register_switcher(self) - def append(self, widget): - self.widgets.append(widget) + def append(self, widget): + self.widgets.append(widget) - def set_sensitive(self, state): - for w in self.widgets: - w.set_sensitive(state) + 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) + def notify(self): + for c in self.conditions: + if not c.state: + self.set_sensitive(False) + return + self.set_sensitive(True) -#===================================================================================== -# KeyTable -#===================================================================================== +# ========================================================= +# 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_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): - 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) - - -#===================================================================================== + 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.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( + 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): + 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): + # 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): + 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()) + 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) + + 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) + + 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) + + 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): + 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( + path_join(icons_path, "add_sibling.png")) + icons['add_child'] = Gtk.Image.new_from_file( + 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)) + +# 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): + (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() - -#===================================================================================== + 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(NEVER, 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, + EXPAND | FILL, + 0, 5, 0) + self.table.attach(table, 1, 2, row, row+1, 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 - -#===================================================================================== + 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(TYPE_STRING, TYPE_PYOBJECT) + + def create_choices(self, ch): + ret = ch + actions_a = {} + + for a in actions_choices: + actions_a[_(a)] = a + for a in sorted(actions_a.keys()): + actions_b = {} + content_a = actions_choices[actions_a[a]] + if (type(content_a) is dict): + iter0 = ret.append(None, [a, ""]) + + 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, [a, actions_a[a]]) + + return ret + + def create_scroll(self, 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): + 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 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 b: 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 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): + 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: + nb = len(self.actions) + i = self.actions.index(act) + self.cond_can_move_up.set_state(i != 0) + self.cond_can_move_down.set_state(nb > 1 and i+1 < nb) + 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) + nb = len(self.model) + self.cond_can_move_up.set_state(i-1 != 0) + self.cond_can_move_down.set_state(nb > 1 and i < nb) + 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) + nb = len(self.model) + self.cond_can_move_up.set_state(i+1 != 0) + self.cond_can_move_down.set_state(nb > 1 and i+2 < nb) + if i+1 >= nb: + 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 - -#===================================================================================== + 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) + 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 + 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 "" + 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 + 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 + 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 + 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)) + # 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() + # 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() + 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. @@ -1138,926 +1242,1014 @@ def fixed_toprettyxml(self, indent="", addindent="\t", newl="\n"): # An array of Options: + # These actions are being applied to OBAction instances. -#===================================================================================== -# Option Class: String -#===================================================================================== +# ========================================================= +# 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 - -#===================================================================================== + __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 - -#===================================================================================== + __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(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 - -#===================================================================================== + __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 - -#===================================================================================== + __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(): + 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, + 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 - -#===================================================================================== + __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 - -#===================================================================================== + 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, 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): - 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": [] -#~ } - - + __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_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() ] + "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": [] + "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": [] + "RemoveDesktopLast": [], + "RemoveDesktopCurrent": [] } actions_desktop_nav_add = { - "AddDesktopLast": [], - "AddDesktopCurrent": [] + "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": [] + "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": [] + "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": [] + "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) ] + "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": [] + "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": [] + "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": [] + "Execute": [ + OCString("command", "", ['execute']), + OCString("prompt", ""), + OCStartupNotify() + ], + # IF Not fully supported yet + # "If": [ OCIf("", "") ], + "BreakChroot": [] } -actions={} +actions = {} for k in actions_choices: - actions[k]=actions_choices[k] + actions[k] = actions_choices[k] for k in actions_window_nav: - actions[k]=actions_window_nav[k] + actions[k] = actions_window_nav[k] for k in actions_window_focus: - actions[k]=actions_window_focus[k] + actions[k] = actions_window_focus[k] for k in actions_window_move: - actions[k]=actions_window_move[k] + actions[k] = actions_window_move[k] for k in actions_window_resize: - actions[k]=actions_window_resize[k] + actions[k] = actions_window_resize[k] for k in actions_window_send: - actions[k]=actions_window_send[k] + actions[k] = actions_window_send[k] for k in actions_desktop_nav_add: - actions[k]=actions_desktop_nav_add[k] + actions[k] = actions_desktop_nav_add[k] for k in actions_desktop_nav_del: - actions[k]=actions_desktop_nav_del[k] + actions[k] = actions_desktop_nav_del[k] for k in actions_desktop_nav_mov: - actions[k]=actions_desktop_nav_mov[k] + actions[k] = actions_desktop_nav_mov[k] for k in actions_window_set: - actions[k]=actions_window_set[k] + 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; - - -#===================================================================================== + 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 -#------------------------------------------------------------------------------------- - + 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 - -#------------------------------------------------------------------------------------- - + 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 = [] + def __init__(self, dom): + self.chainQuitKey = None + self.keybinds = [] - cqk = xml_find_node(dom, "chainQuitKey") - if cqk: - self.chainQuitKey = xml_parse_string(cqk) + 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) + 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) + 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()) + for k in self.keybinds: + root.appendChild(k.deparse()) - return root + 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") + 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") From a6c678826d6c0517806d18b484f0f1936f928aad Mon Sep 17 00:00:00 2001 From: luffah Date: Thu, 22 Feb 2018 17:10:12 +0100 Subject: [PATCH 16/25] add debian packaging procedure --- LICENSE | 23 +++++++++++++++++++++++ Makefile | 9 +++++++++ README.md | 2 +- obkey | 9 ++++++--- obkey_classes.py | 2 +- setup.py | 41 ++++++++++++++++++++++++++++++++++++----- stdeb.cfg | 3 +++ 7 files changed, 79 insertions(+), 10 deletions(-) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 stdeb.cfg 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..2f718b0 --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ + + +deb: + python setup.py \ + --command-packages=stdeb.command sdist_dsc \ + --package obkey \ + --section x11 + cd deb_dist/obkey-*/; \ + dpkg-buildpackage -rfakeroot -uc -us diff --git a/README.md b/README.md index 32c4603..31b933d 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ So i forked the project. ```shell # check the lang is well detected in installation pack -./obkey ~/.config/openbox TESTING +./obkey ~/.config/openbox/rc.xml TESTING # if gettext is not found, you shall have a message saying that gettext is missing diff --git a/obkey b/obkey index 14e7427..b5f61c0 100755 --- a/obkey +++ b/obkey @@ -26,7 +26,7 @@ # THE SOFTWARE. #----------------------------------------------------------------------- -import sys, os +import sys, os, re import gi gi.require_versions({'Gtk': '3.0', 'GLib': '2.0', 'Gio': '2.0'}) from gi.repository import Gtk @@ -39,8 +39,11 @@ def die(widget, data=None): path=None # get rc file from args -if len(sys.argv) > 1: - path = sys.argv[1] +rcfileregex = re.compile(r'.*\.xml.*') +rcfiles = filter(rcfileregex.match, sys.argv) +print(rcfiles) +if rcfiles: + path = rcfiles[0] try: import psutil diff --git a/obkey_classes.py b/obkey_classes.py index 39a2e4c..507f5f5 100644 --- a/obkey_classes.py +++ b/obkey_classes.py @@ -65,7 +65,7 @@ config_locale_dir = path_join(config_prefix, 'share/locale') # uncomment this for testing -if (len(sys.argv) > 2) and (sys.argv[2]=='TESTING'): +if ('TESTING' in sys.argv): config_icons='./icons' config_locale_dir = './locale' diff --git a/setup.py b/setup.py index caf23a1..27ccc71 100644 --- a/setup.py +++ b/setup.py @@ -8,13 +8,44 @@ 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] - +install_requires=['gi', 'gettext'] setup(name='obkey', version='1.2pre', description='Openbox Key Editor', - author='nsf', - author_email='no.smile.face@gmail.com', + url='https://github.com/luffah/obkey', + long_description="ObKey ease the keybindings configuration for Openbox.", + author='luffah', + author_email='luffah@runbox.com', scripts=['obkey'], py_modules=['obkey_classes'], - data_files=[(libdir, ['icons/add_child.png', 'icons/add_sibling.png'])] + locales - ) + data_files=[(libdir, ['icons/add_child.png', 'icons/add_sibling.png'])] + locales, + keywords='openbox keybindings keys shortcuts', # Optional + 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=[ # Optional + # How mature is this project? Common values are + # 4 - Beta + # 5 - Production/Stable + 'Development Status :: 4 - Beta', + + # Indicate who your project is intended for + 'Intended Audience :: Developers', + 'Topic :: Software Development :: Build Tools', + + # Pick your license as you wish + 'License :: OSI Approved :: MIT License', + + # Specify the Python versions you support here. In particular, ensure + # that you indicate whether you support Python 2, Python 3 or both. + 'Programming Language :: Python :: 2', + '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 From 9b5059a494cb909fe96d9a0dc1fff9975706e3c9 Mon Sep 17 00:00:00 2001 From: Luffah Date: Thu, 22 Feb 2018 17:15:06 +0100 Subject: [PATCH 17/25] Set theme jekyll-theme-hacker --- _config.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 _config.yml diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..fc24e7a --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-hacker \ No newline at end of file From 78a374ec7950b209b34a97c41528ab6c5e551cb6 Mon Sep 17 00:00:00 2001 From: Luffah Date: Thu, 22 Feb 2018 17:29:25 +0100 Subject: [PATCH 18/25] Set theme jekyll-theme-slate --- _config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_config.yml b/_config.yml index fc24e7a..c741881 100644 --- a/_config.yml +++ b/_config.yml @@ -1 +1 @@ -theme: jekyll-theme-hacker \ No newline at end of file +theme: jekyll-theme-slate \ No newline at end of file From 7715b97e404995e2892827e8a34506d8613bd6ad Mon Sep 17 00:00:00 2001 From: luffah Date: Sat, 24 Feb 2018 05:53:40 +0100 Subject: [PATCH 19/25] forking : code slightly refactored / some usefull things work now --- Makefile | 3 + README.md | 74 +- deb_dist/obkey_1.2-1_all.deb | Bin 0 -> 8848 bytes deb_dist/obkey_1.2pre-1_all.deb | Bin 0 -> 20036 bytes misc/obkey.desktop | 2 +- obkey | 193 +-- obkey_parts/Gui.py | 92 ++ obkey_parts/KeyTable.py | 537 +++++++ obkey_parts/KeyUtils.py | 80 + obkey_classes.py => obkey_parts/OBActions.py | 1385 +++-------------- obkey_parts/OBKeyboard.py | 155 ++ obkey_parts/OpenboxConfig.py | 70 + obkey_parts/PropertyTable.py | 83 + obkey_parts/Resources.py | 65 + obkey_parts/XmlUtils.py | 86 + obkey_parts/__init__.py | 36 + po/Makefile | 4 +- pylintrc | 260 ++++ {icons => resources/icons}/add_child.png | Bin {icons => resources/icons}/add_sibling.png | Bin .../locale}/bs/LC_MESSAGES/obkey.mo | Bin .../locale}/fr/LC_MESSAGES/obkey.mo | Bin .../locale}/hr/LC_MESSAGES/obkey.mo | Bin .../locale}/sr/LC_MESSAGES/obkey.mo | Bin .../locale}/uk/LC_MESSAGES/obkey.mo | Bin setup.py | 42 +- 26 files changed, 1850 insertions(+), 1317 deletions(-) create mode 100644 deb_dist/obkey_1.2-1_all.deb create mode 100644 deb_dist/obkey_1.2pre-1_all.deb create mode 100644 obkey_parts/Gui.py create mode 100644 obkey_parts/KeyTable.py create mode 100644 obkey_parts/KeyUtils.py rename obkey_classes.py => obkey_parts/OBActions.py (50%) create mode 100644 obkey_parts/OBKeyboard.py create mode 100644 obkey_parts/OpenboxConfig.py create mode 100644 obkey_parts/PropertyTable.py create mode 100644 obkey_parts/Resources.py create mode 100644 obkey_parts/XmlUtils.py create mode 100644 obkey_parts/__init__.py create mode 100644 pylintrc rename {icons => resources/icons}/add_child.png (100%) rename {icons => resources/icons}/add_sibling.png (100%) rename {locale => resources/locale}/bs/LC_MESSAGES/obkey.mo (100%) rename {locale => resources/locale}/fr/LC_MESSAGES/obkey.mo (100%) rename {locale => resources/locale}/hr/LC_MESSAGES/obkey.mo (100%) rename {locale => resources/locale}/sr/LC_MESSAGES/obkey.mo (100%) rename {locale => resources/locale}/uk/LC_MESSAGES/obkey.mo (100%) diff --git a/Makefile b/Makefile index 2f718b0..15a30c1 100644 --- a/Makefile +++ b/Makefile @@ -7,3 +7,6 @@ deb: --section x11 cd deb_dist/obkey-*/; \ dpkg-buildpackage -rfakeroot -uc -us + +lint: + pylint --output-format=parseable --reports=y obkey_classes | tee pylint.log diff --git a/README.md b/README.md index 31b933d..d05dec5 100644 --- a/README.md +++ b/README.md @@ -10,83 +10,53 @@ git clone https://github.com/luffah/obkey.git # test it works (you can use it directly this way) python obkey - -# INSTALLATION -sudo python setup.py - -# Finally run obkey -obkey - -# to try other languages -LANGUAGE=fr obkey - ``` -# Dependencies - -### with PIP - +## With PIP and setup.py ```shell sudo pip install gi gettext +sudo python setup.py install ``` -### with APT - +## With DEBIAN INSTALLER ```shell sudo apt install python-gi python-gettext +make deb +sudo dpkg -i deb_dist/obkey_1.2-1_all.deb ``` -# About me -After tried almost every window managers, -and having recently left my 'awesomewm' configuration behind a sharp #. +# Usage +```shell +# Minimalist +obkey + +# Custom file +obkey rc.xml -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. +# With foreign languages +LANGUAGE=fr obkey -Another easy to use capability in OpenBox, is the Emacs style multi-levels shorcut. +``` -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. +# Why ObKey ? +OpenBox is lightweight ! -# 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 +OpenBox is great ! -- if a problem occur about internationnalization (gettext), you may need to do next procedure to solve it : +OpenBox is one of the most customizable Window Manager in the World ! -```shell -# check the lang is well detected in installation pack -./obkey ~/.config/openbox/rc.xml TESTING +OpenBox allows to precisely place windows and to easily switch without clicking a mouse. -# if gettext is not found, you shall have a message saying that gettext is missing +But, OpenBox configuration is written in XML.. -# take the name of the translation file -tMSGFILE=LC_MESSAGES/obkey.mo +Hey ! no need to navigate too much in XML, there's ObKey ! -# find the translation directory -tPYTHONPATH=`python -c "import sys; print '\n'.join(sys.path)" | grep 'lib/python'` -tPYLANGPATH=`find $tPYTHONPATH -name 'gettext.py' | xargs grep 'mofile_lp =' | sed 's/.*join("\(.*\)".*$/\1/'` -if [ -d $tPYLANGPATH ] -then - ls -1 locale/ | xargs -I{} sudo cp locale/{}/$tMSGFILE $tPYLANGPATH/{}/$tMSGFILE -fi -``` # 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 - -This fork aims to continue the project since bugs and enhancement stills needed. - -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 - # About KeyBindings Key bindings in OpenBox official site : diff --git a/deb_dist/obkey_1.2-1_all.deb b/deb_dist/obkey_1.2-1_all.deb new file mode 100644 index 0000000000000000000000000000000000000000..67ad4b84810a788cb4e2077707dcacc56b6834b1 GIT binary patch literal 8848 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 literal 0 HcmV?d00001 diff --git a/misc/obkey.desktop b/misc/obkey.desktop index 8b937fb..3007307 100644 --- a/misc/obkey.desktop +++ b/misc/obkey.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Version=1.0 +Version=1.2 Type=Application Name=Openbox Key bindings Name[fr]=Raccourcis clavier Openbox diff --git a/obkey b/obkey index b5f61c0..790f6ab 100755 --- a/obkey +++ b/obkey @@ -1,93 +1,102 @@ #!/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, re -import gi -gi.require_versions({'Gtk': '3.0', 'GLib': '2.0', 'Gio': '2.0'}) -from gi.repository import Gtk -import obkey_classes as obkey_classes -import xml.dom.minidom - -def die(widget, data=None): - Gtk.main_quit() - -path=None - -# get rc file from args -rcfileregex = re.compile(r'.*\.xml.*') -rcfiles = filter(rcfileregex.match, sys.argv) -print(rcfiles) -if rcfiles: - path = rcfiles[0] - -try: - import psutil - # get rc file from current running openbox - for p in psutil.process_iter(): - proc = p.as_dict(attrs=['pid', 'name','cmdline']) - if proc['name'] == 'openbox': - ob_param=proc['cmdline'] - for i in range(len(ob_param)): - if ob_param[i] == '--config-file': - path=ob_param[i+1] - break - break -except ImportError: - pass -# get default rc file -if not path : - path = os.getenv("HOME") + "/.config/openbox/rc.xml" - -ob = obkey_classes.OpenboxConfig() -ob.load(path) - -# win = Gtk.Window(Gtk.WindowType.TOPLEVEL) # deprecated parameter -win = Gtk.Window() -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()) + + # win = Gtk.Window(Gtk.WindowType.TOPLEVEL) # deprecated parameter + window = Gtk.Window() + window.set_default_size(800, 480) + window.set_title('obkey') + window.connect("destroy", die) + + propertytable = PropertyTable() + actionlist = ActionList(propertytable) + keytable = KeyTable(actionlist, obkeyconf) + + verticalbox = Gtk.VPaned() + verticalbox.pack1(propertytable.widget, True, False) + verticalbox.pack2(actionlist.widget, True, False) + + horizontalbox = Gtk.HPaned() + horizontalbox.pack1(keytable.widget, True, False) + horizontalbox.pack2(verticalbox, False, False) + + window.add(horizontalbox) + window.show_all() + # get rid of stupid autocalculation + width, _ = window.get_size() + horizontalbox.set_position(width-250) + keytable.view.grab_focus() + Gtk.main() + + +view() diff --git a/obkey_parts/Gui.py b/obkey_parts/Gui.py new file mode 100644 index 0000000..356d295 --- /dev/null +++ b/obkey_parts/Gui.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. +""" +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): + 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) + + diff --git a/obkey_parts/KeyTable.py b/obkey_parts/KeyTable.py new file mode 100644 index 0000000..ef6dc5e --- /dev/null +++ b/obkey_parts/KeyTable.py @@ -0,0 +1,537 @@ +""" + 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 Gui import * +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: + + def __init__(self, actionlist, ob): + self.widget = Gtk.VBox() + self.ob = ob + if ob.keyboard_node: + self.keyboard = OBKeyboard(ob.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): + 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( + 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): + 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): + # 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): + 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) + + 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) + + 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) + + 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): + 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): + 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['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): + 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): + (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): + (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.keyboard.chain_quit_key = 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.keyboard.chain_quit_key = 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.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)) + +# 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): + (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..134d3e3 --- /dev/null +++ b/obkey_parts/KeyUtils.py @@ -0,0 +1,80 @@ +""" + 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 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): + if obstr is not None: + toks = obstr.split("-") + try: + toksgdk = [replace_table_openbox2gtk[mod.lower()] for mod in toks[:-1]] + except Exception: + 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 + + diff --git a/obkey_classes.py b/obkey_parts/OBActions.py similarity index 50% rename from obkey_classes.py rename to obkey_parts/OBActions.py index 507f5f5..1b82368 100644 --- a/obkey_classes.py +++ b/obkey_parts/OBActions.py @@ -1,750 +1,121 @@ -#!/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 xml.sax.saxutils import escape -from StringIO import StringIO - -try: - 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 is missing : exit") - exit - +""" + 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.XmlUtils import * +from obkey_parts.Gui import * import copy -import sys -import os -from os.path import join as path_join, dirname - - -# ======================================================== -# 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 = dirname(dirname(sys.argv[0])) -config_icons = path_join(config_prefix, 'share/obkey/icons') -config_locale_dir = path_join(config_prefix, 'share/locale') - -# uncomment this for testing -if ('TESTING' in sys.argv): - config_icons='./icons' - config_locale_dir = './locale' - -try: - from gettext import install as gettext_init - gettext_init('obkey', config_locale_dir) -except ImportError: - print("Gettext is missing") - - def _(a): - return a - -# 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): - if obstr is not None: - toks = obstr.split("-") - try: - toksgdk = [replace_table_openbox2gtk[mod.lower()] for mod in toks[:-1]] - except Exception: - 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.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( - 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): - 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): - # 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): - 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()) - 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) - - 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) - - 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) - - 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): - 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( - path_join(icons_path, "add_sibling.png")) - icons['add_child'] = Gtk.Image.new_from_file( - 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)) +class OBAction: - 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 __init__(self, name=None): + self.options = {} + self.option_defs = [] + self.name = name + if name: + self.mutate(name) - def _insert_keybind(self, keybind, parent=None, after=None): - keybind.parent = parent - if parent: - kbs = parent.children - else: - kbs = self.ob.keyboard.keybinds + def parse(self, dom): + # call parseChild if childNodes exist + if dom.hasChildNodes(): + for child in dom.childNodes: + self.parseChild(child) - if after: - kbs.insert(kbs.index(after)+1, keybind) - else: - kbs.append(keybind) + # parse 'name' attribute, get options hash and parse + self.name = dom.getAttribute("name") - def insert_sibling(self, keybind): - (model, it) = self.view.get_selection().get_selected() + try: + self.option_defs = actions[self.name] + except KeyError: + pass - accel_key, accel_mods = key_openbox2gtk(keybind.key) - show_chroot = len(keybind.children) > 0 or not len(keybind.actions) + for od in self.option_defs: + od.parse(self, dom) - 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))) + # 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: - 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)) - -# 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): - (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) + try: + dom.nodeValue = dom.nodeValue.strip() + except AttributeError: + pass + def deparse(self): + root = Element('action') + root.setAttribute('name',str(self.name)) + 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 -# ========================================================= -# PropertyTable -# ========================================================= + def mutate(self, newtype): + if ( + hasattr(self, "option_defs") and + actions[newtype] == self.option_defs): + self.options = {} + self.name = newtype + return -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(NEVER, 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, - EXPAND | FILL, - 0, 5, 0) - self.table.attach(table, 1, 2, row, row+1, FILL, 0, 5, 0) + self.options = {} + self.name = newtype + self.option_defs = actions[self.name] - 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() + 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 # ========================================================= # ActionList # ========================================================= class ActionList: + def __init__(self, proptable=None): self.widget = Gtk.VBox() self.actions = None @@ -766,13 +137,13 @@ def __init__(self, proptable=None): self.sw_paste_buffer = SensSwitcher([self.cond_paste_buffer]) self.sw_selection_available = SensSwitcher( - [self.cond_selection_available]) + [self.cond_selection_available]) self.sw_action_list_nonempty = SensSwitcher( - [self.cond_action_list_nonempty]) + [self.cond_action_list_nonempty]) self.sw_can_move_up = SensSwitcher( - [self.cond_can_move_up]) + [self.cond_can_move_up]) self.sw_can_move_down = SensSwitcher( - [self.cond_can_move_down]) + [self.cond_can_move_down]) self.model = self.create_model() self.view = self.create_view(self.model) @@ -780,10 +151,10 @@ def __init__(self, proptable=None): self.context_menu = self.create_context_menu() self.widget.pack_start( - self.create_scroll(self.view), + self.create_scroll(self.view), True, True, 0) self.widget.pack_start( - self.create_toolbar(), + self.create_toolbar(), False, True, 0) self.sw_paste_buffer.notify() @@ -815,7 +186,7 @@ def create_choices(self, ch): content_b = content_a[actions_b[b]] if (type(content_b) is dict): iter1 = ret.append( - iter0, [b, ""]) + iter0, [b, ""]) for c in content_b: actions_c[_(c)] = c @@ -840,6 +211,7 @@ def create_view(self, model): renderer = Gtk.CellRendererCombo() def editingstarted(cell, widget, path): + console.log('editing') widget.set_wrap_width(1) chs = Gtk.TreeStore(TYPE_STRING, TYPE_STRING) @@ -858,6 +230,10 @@ def editingstarted(cell, widget, path): view.connect('button-press-event', self.view_button_clicked) return view + def proptable_changed(self): + if self.actions_cb: + self.actions_cb() + def create_context_menu(self): context_menu = Gtk.Menu() self.context_items = {} @@ -899,7 +275,7 @@ def create_toolbar(self): but.set_tooltip_text(_("Insert action")) but.connect('clicked', lambda b: self.insert_action( - OBAction("Focus"))) + OBAction("Execute"))) toolbar.insert(but, -1) but = Gtk.ToolButton(Gtk.STOCK_REMOVE) @@ -948,13 +324,13 @@ def view_button_clicked(self, view, event): view.grab_focus() view.set_cursor(path, col, 0) self.context_menu.popup( - None, None, None, + None, None, None, event.button, time, False) else: view.grab_focus() view.get_selection().unselect_all() self.context_menu.popup( - None, None, None, + None, None, None, event.button, time, False) return 1 @@ -965,6 +341,8 @@ def action_class_changed(self, combo, path, it): 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): (model, it) = selection.get_selected() @@ -972,12 +350,12 @@ def view_cursor_changed(self, selection): if it: act = model.get_value(it, 1) if self.proptable: - self.proptable.set_action(act) + self.proptable.set_action(act,self.proptable_changed) if act: nb = len(self.actions) i = self.actions.index(act) self.cond_can_move_up.set_state(i != 0) - self.cond_can_move_down.set_state(nb > 1 and i+1 < nb) + self.cond_can_move_down.set_state(nb > 1 and i + 1 < nb) self.cond_selection_available.set_state(True) else: self.cond_can_move_up.set_state(False) @@ -1024,18 +402,18 @@ def move_selected_up(self): i, = self.model.get_path(it) nb = len(self.model) - self.cond_can_move_up.set_state(i-1 != 0) + self.cond_can_move_up.set_state(i - 1 != 0) self.cond_can_move_down.set_state(nb > 1 and i < nb) if i == 0: return - itprev = self.model.get_iter(i-1) + 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 + tmp = self.actions[i - 1] + self.actions[i - 1] = action self.actions[i] = tmp def move_selected_down(self): @@ -1048,9 +426,9 @@ def move_selected_down(self): i, = self.model.get_path(it) nb = len(self.model) - self.cond_can_move_up.set_state(i+1 != 0) - self.cond_can_move_down.set_state(nb > 1 and i+2 < nb) - if i+1 >= nb: + self.cond_can_move_up.set_state(i + 1 != 0) + self.cond_can_move_down.set_state(nb > 1 and i + 2 < nb) + if i + 1 >= nb: return itnext = self.model.iter_next(it) @@ -1058,8 +436,8 @@ def move_selected_down(self): action = self.model.get_value(it, 1) i = self.actions.index(action) - tmp = self.actions[i+1] - self.actions[i+1] = action + tmp = self.actions[i + 1] + self.actions[i + 1] = action self.actions[i] = tmp def insert_action(self, action): @@ -1113,7 +491,7 @@ def set_actions(self, actionlist): def _insert_action(self, action, after=None): if after: - self.actions.insert(self.actions.index(after)+1, action) + self.actions.insert(self.actions.index(after) + 1, action) else: self.actions.append(action) @@ -1125,6 +503,7 @@ def set_callback(self, cb): # MiniActionList # ========================================================= class MiniActionList(ActionList): + def __init__(self, proptable=None): ActionList.__init__(self, proptable) self.widget.set_size_request(-1, 120) @@ -1141,91 +520,6 @@ def create_choices(self, ch): return choices -# ========================================================= -# XML Utilites -# ========================================================= -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 # ========================================================= @@ -1265,7 +559,7 @@ def parse(self, action, dom): if node: break if node: - action.options[self.name] = xml_parse_string(node) + action.options[self.name] = xml_get_str(node) else: action.options[self.name] = self.default @@ -1273,17 +567,18 @@ 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) + + return parseString( + "<" + str(self.name) + ">" + + str(escape(val)) + "" - ).documentElement + ).documentElement - def generate_widget(self, action): + def generate_widget(self, action, cb=None): def changed(entry, action): text = entry.get_text() action.options[self.name] = text + if cb: + cb() entry = Gtk.Entry() entry.set_text(action.options[self.name]) @@ -1308,7 +603,7 @@ def apply_default(self, action): def parse(self, action, dom): node = xml_find_node(dom, self.name) if node: - action.options[self.name] = xml_parse_string(node) + action.options[self.name] = xml_get_str(node) else: action.options[self.name] = self.default @@ -1316,13 +611,13 @@ def deparse(self, action): val = action.options[self.name] if val == self.default: return None - return xml.dom.minidom.parseString( - "<" + str(self.name) + ">" + + return parseString( + "<" + str(self.name) + ">" + str(val) + "" - ).documentElement + ).documentElement - def generate_widget(self, action): + def generate_widget(self, action, cb=None): def changed(combo, action): text = combo.get_active() action.options[self.name] = self.choices[text] @@ -1359,7 +654,7 @@ def apply_default(self, action): def parse(self, action, dom): node = xml_find_node(dom, self.name) if node: - action.options[self.name] = int(float(xml_parse_string(node))) + action.options[self.name] = int(float(xml_get_str(node))) else: action.options[self.name] = self.default @@ -1367,13 +662,13 @@ 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) + ">" + + return parseString( + "<" + str(self.name) + ">" + str(val) + "" - ).documentElement + ).documentElement - def generate_widget(self, action): + def generate_widget(self, action, cb=None): def changed(num, action): n = num.get_value_as_int() action.options[self.name] = n @@ -1414,13 +709,13 @@ def parse(self, action, dom): for child in node.childNodes: if child.nodeName == "then": self.then = self._parseAction( - node, action, "then") + node, action, "then") elif child.nodeName == "else": self.els = self._parseAction( - node, action, "else") + node, action, "else") else: if not isinstance(child, - xml.dom.minidom.Text): + minidom.Text): self.props += [child] def _parseAction(self, dom, action, nodeName): @@ -1437,7 +732,7 @@ def deparse(self, action): frag.append(el) # conditions - # themEl = xml.dom.minidom.Element("then") + # themEl = minidom.Element("then") themEl = self.then.deparse(action) themEl.tagName = "then" @@ -1448,15 +743,15 @@ def deparse(self, action): frag.append(themEl) frag.append(elseEl) - # # print - # zz = xml.dom.minidom.Element("action") + # print + # zz = Element("action") # for el in frag: # zz.appendChild(el) # print zz.toxml() return frag - def generate_widget(self, action): + def generate_widget(self, action, cb=None): # label = Gtk.Label("IF Not fully supported yet") opts = [] @@ -1464,15 +759,15 @@ def generate_widget(self, action): 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 @@ -1500,17 +795,17 @@ 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) + + return parseString( + "<" + str(self.name) + ">yes" - ).documentElement + ).documentElement else: - return xml.dom.minidom.parseString( - "<" + str(self.name) + + return parseString( + "<" + str(self.name) + ">no" - ).documentElement + ).documentElement - def generate_widget(self, action): + def generate_widget(self, action, cb=None): def changed(checkbox, action): active = checkbox.get_active() action.options[self.name] = active @@ -1525,6 +820,7 @@ def changed(checkbox, action): # Option Class: StartupNotify # ========================================================= class OCStartupNotify(object): + def __init__(self): self.name = "startupnotify" @@ -1546,41 +842,41 @@ def parse(self, action, dom): action.options['startupnotify_enabled'] = xml_parse_bool(enabled) wmclass = xml_find_node(startupnotify, "wmclass") if wmclass: - action.options['startupnotify_wmclass'] = xml_parse_string(wmclass) + action.options['startupnotify_wmclass'] = xml_get_str(wmclass) name = xml_find_node(startupnotify, "name") if name: - action.options['startupnotify_name'] = xml_parse_string(name) + action.options['startupnotify_name'] = xml_get_str(name) icon = xml_find_node(startupnotify, "icon") if icon: - action.options['startupnotify_icon'] = xml_parse_string(icon) + action.options['startupnotify_icon'] = xml_get_str(icon) def deparse(self, action): if not action.options['startupnotify_enabled']: return None - root = xml.dom.minidom.parseString( - "yes" - ).documentElement + root = parseString( + "yes" + ).documentElement if action.options['startupnotify_wmclass'] != "": - root.appendChild(xml.dom.minidom.parseString( + root.appendChild(parseString( "" + action.options['startupnotify_wmclass'] + "" - ).documentElement) + ).documentElement) if action.options['startupnotify_name'] != "": - root.appendChild(xml.dom.minidom.parseString( + root.appendChild(parseString( "" + action.options['startupnotify_name'] + "" - ).documentElement) + ).documentElement) if action.options['startupnotify_icon'] != "": - root.appendChild(xml.dom.minidom.parseString( + root.appendChild(parseString( "" + action.options['startupnotify_icon'] + "" - ).documentElement) + ).documentElement) return root - def generate_widget(self, action): + def generate_widget(self, action, cb=None): def enabled_toggled(checkbox, action, sens_list): active = checkbox.get_active() action.options['startupnotify_enabled'] = active @@ -1594,32 +890,32 @@ def text_changed(textbox, action, var): wmclass = Gtk.Entry() wmclass.set_size_request(100, -1) wmclass.set_text( - action.options['startupnotify_wmclass']) + action.options['startupnotify_wmclass']) wmclass.connect( - 'changed', text_changed, action, + '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, + '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, + 'changed', text_changed, action, 'startupnotify_icon') sens_list = [wmclass, name, icon] enabled = Gtk.CheckButton() enabled.set_active( - action.options['startupnotify_enabled']) + action.options['startupnotify_enabled']) enabled.connect( - 'toggled', enabled_toggled, action, + 'toggled', enabled_toggled, action, sens_list) def put_table(table, label_text, widget, row, addtosens=True): @@ -1628,8 +924,8 @@ def put_table(table, label_text, widget, row, addtosens=True): 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.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) @@ -1687,14 +983,14 @@ def deparse(self, action): return None if len(a) == 0: return None - root = xml.dom.minidom.parseString( - "").documentElement + root = parseString( + "").documentElement for act in a: node = act.deparse() root.appendChild(node) return root - def generate_widget(self, action): + def generate_widget(self, action, cb=None): w = MiniActionList() w.set_actions(action.options[self.name]) frame = Gtk.Frame() @@ -1713,7 +1009,7 @@ def generate_widget(self, action): OCBoolean("desktop", False), OCBoolean("linear", False), OCFinalActions() - ], + ], "PreviousWindow": [ OCBoolean("dialog", True), OCBoolean("bar", True), @@ -1723,130 +1019,130 @@ def generate_widget(self, action): 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 = { @@ -1863,16 +1159,16 @@ def generate_widget(self, action): "Reconfigure": [], "Restart": [ OCString("command", "", ["execute"]) - ], + ], "Exit": [ OCBoolean("prompt", True) - ], + ], "SessionLogout": [ OCBoolean("prompt", True) - ], + ], "Debug": [ OCString("string", "") - ], + ], "ToggleShowDesktop": [] } actions_window_focus = { @@ -1916,31 +1212,31 @@ def generate_widget(self, action): "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": [], @@ -1968,7 +1264,7 @@ def generate_widget(self, action): 'none', "top", "left", "right", "bottom", "topleft", "topright", "bottomleft", "bottomright" - ]) + ]) ], "ResizeRelative": [ OCNumber("left", 0, -9999, 9999), @@ -2023,233 +1319,10 @@ def generate_widget(self, action): 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, + "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/OBKeyboard.py b/obkey_parts/OBKeyboard.py new file mode 100644 index 0000000..b8db96e --- /dev/null +++ b/obkey_parts/OBKeyboard.py @@ -0,0 +1,155 @@ +""" + 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.OBActions 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): + 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( + #'').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): + 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..1062576 --- /dev/null +++ b/obkey_parts/OpenboxConfig.py @@ -0,0 +1,70 @@ +""" + 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): + self.dom = None + self.keyboard = None + self.path = None + + def load(self, 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): + 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): + system("openbox --reconfigure") diff --git a/obkey_parts/PropertyTable.py b/obkey_parts/PropertyTable.py new file mode 100644 index 0000000..6eb663f --- /dev/null +++ b/obkey_parts/PropertyTable.py @@ -0,0 +1,83 @@ +""" + 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 Gui import * +# ========================================================= +# 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(NEVER, 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, + EXPAND | FILL, + 0, 5, 0) + self.table.attach(table, 1, 2, row, row + 1, 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, cb=None): + self.clear() + 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..af3bf19 --- /dev/null +++ b/obkey_parts/Resources.py @@ -0,0 +1,65 @@ +#!/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): + 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): + 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..0e6a8fd --- /dev/null +++ b/obkey_parts/XmlUtils.py @@ -0,0 +1,86 @@ +""" + 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): + return elt.firstChild.nodeValue if elt.hasChildNodes() else "" + + +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): + return [node for node in elt.childNodes if node.nodeName == name] + + +def xml_find_node(elt, name): + nodes = xml_find_nodes(elt, name) + return nodes[0] if len(nodes) == 1 else 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) + 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..45f9218 --- /dev/null +++ b/obkey_parts/__init__.py @@ -0,0 +1,36 @@ +#!/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.OBActions import ActionList +from obkey_parts.KeyTable import KeyTable +from obkey_parts.PropertyTable import PropertyTable +from obkey_parts.Gui import Gtk 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/pylintrc b/pylintrc new file mode 100644 index 0000000..7324e83 --- /dev/null +++ b/pylintrc @@ -0,0 +1,260 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[MESSAGES CONTROL] + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). +disable=I0011 + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Include message's id in output +include-ids=yes + +# Include symbolic ids of messages in output +symbols=no + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (RP0004). +comment=no + + +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,f,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata,data,data2 + +# Regular expression which should only match functions or classes name which do +# not require a docstring +no-docstring-rgx=__.*__ + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the beginning of the name of dummy variables +# (i.e. not used). +dummy-variables-rgx=_|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=SQLObject + +# When zope mode is activated, add a predefined set of Zope acquired attributes +# to generated-members. +zope=no + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. Python regular +# expressions are accepted. +generated-members=REQUEST,acl_users,aq_parent + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branchs=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,string,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception + 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/locale/bs/LC_MESSAGES/obkey.mo b/resources/locale/bs/LC_MESSAGES/obkey.mo similarity index 100% rename from locale/bs/LC_MESSAGES/obkey.mo rename to resources/locale/bs/LC_MESSAGES/obkey.mo diff --git a/locale/fr/LC_MESSAGES/obkey.mo b/resources/locale/fr/LC_MESSAGES/obkey.mo similarity index 100% rename from locale/fr/LC_MESSAGES/obkey.mo rename to resources/locale/fr/LC_MESSAGES/obkey.mo diff --git a/locale/hr/LC_MESSAGES/obkey.mo b/resources/locale/hr/LC_MESSAGES/obkey.mo similarity index 100% rename from locale/hr/LC_MESSAGES/obkey.mo rename to resources/locale/hr/LC_MESSAGES/obkey.mo diff --git a/locale/sr/LC_MESSAGES/obkey.mo b/resources/locale/sr/LC_MESSAGES/obkey.mo similarity index 100% rename from locale/sr/LC_MESSAGES/obkey.mo rename to resources/locale/sr/LC_MESSAGES/obkey.mo 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 27ccc71..6577cab 100644 --- a/setup.py +++ b/setup.py @@ -2,15 +2,19 @@ from glob import glob import os +# assuming applications are stored in /usr/ libdir = 'share/obkey/icons' localedir = 'share/locale' +desktopdir = 'share/applications' -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] -install_requires=['gi', 'gettext'] +res_icons = 'resources/icons' +res_locales = 'resources/locale' +res_desktop = 'misc' + +langs = [a[len(res_locales + '/'):] for a in glob(res_locales + '/*')] +install_requires = ['gi', 'gettext'] setup(name='obkey', - version='1.2pre', + version='1.2', description='Openbox Key Editor', url='https://github.com/luffah/obkey', long_description="ObKey ease the keybindings configuration for Openbox.", @@ -18,14 +22,24 @@ author_email='luffah@runbox.com', scripts=['obkey'], py_modules=['obkey_classes'], - data_files=[(libdir, ['icons/add_child.png', 'icons/add_sibling.png'])] + locales, + data_files=[( + libdir, + [res_icons + '/add_child.png', res_icons + '/add_sibling.png'] + ), ( + desktopdir, + [res_desktop + '/obkey.desktop'], + + )] + [( + os.path.join(localedir, l, 'LC_MESSAGES'), + [os.path.join(res_locales, l, 'LC_MESSAGES', 'obkey.mo')] + ) for l in langs], keywords='openbox keybindings keys shortcuts', # Optional - 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 + 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=[ # Optional # How mature is this project? Common values are # 4 - Beta @@ -47,5 +61,5 @@ 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', - ], -) + ], + ) From 75335e4bcaf9bf2d3e3b87e13c4d6d4e630d1364 Mon Sep 17 00:00:00 2001 From: luffah Date: Sat, 24 Feb 2018 06:10:19 +0100 Subject: [PATCH 20/25] add download --- README.md | 15 ++++++++++++++- _config.yml | 2 +- deb_dist/obkey_1.2-1_all.deb | Bin 8848 -> 9200 bytes 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d05dec5..550300c 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,10 @@ ObKey - Openbox Key Editor (PyGObject version) ![ObKey](wiki/screenshot_obkey.png) + # Installation +# With Git ```shell git clone https://github.com/luffah/obkey.git @@ -17,13 +19,24 @@ python obkey sudo pip install gi gettext sudo python setup.py install ``` -## With DEBIAN INSTALLER +## With Debian installer ```shell sudo apt install python-gi python-gettext make deb sudo dpkg -i deb_dist/obkey_1.2-1_all.deb ``` +# Without Git + +## Debian + +Download the package here : [Obkey for debian](https://raw.githubusercontent.com/downloads/luffah/obkey/deb_dist/obkey_1.2.-1_all.deb) + +```shell +sudo apt install python-gi python-gettext +sudo dpkg -i obkey_1.2-1_all.deb +``` + # Usage ```shell # Minimalist diff --git a/_config.yml b/_config.yml index c741881..f980e76 100644 --- a/_config.yml +++ b/_config.yml @@ -1 +1 @@ -theme: jekyll-theme-slate \ No newline at end of file +theme: jekyll-theme-slate diff --git a/deb_dist/obkey_1.2-1_all.deb b/deb_dist/obkey_1.2-1_all.deb index 67ad4b84810a788cb4e2077707dcacc56b6834b1..a79cc26c4c226ea8c2b8b5919198c1afac1dab20 100644 GIT binary patch delta 9137 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 delta 8800 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 From b809f021fb6ab42b5979b57a3bafc6f2ff1bd7f2 Mon Sep 17 00:00:00 2001 From: luffah Date: Sat, 24 Feb 2018 06:13:53 +0100 Subject: [PATCH 21/25] add download --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 550300c..f8692bb 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,19 @@ git clone https://github.com/luffah/obkey.git # test it works (you can use it directly this way) python obkey -``` + +# MANAGE DEPENDENCIES +# AND INSTALL ## With PIP and setup.py -```shell sudo pip install gi gettext + sudo python setup.py install -``` + ## With Debian installer -```shell sudo apt install python-gi python-gettext make deb + sudo dpkg -i deb_dist/obkey_1.2-1_all.deb ``` @@ -30,7 +32,7 @@ sudo dpkg -i deb_dist/obkey_1.2-1_all.deb ## Debian -Download the package here : [Obkey for debian](https://raw.githubusercontent.com/downloads/luffah/obkey/deb_dist/obkey_1.2.-1_all.deb) +Download the package here : [Obkey for debian](https://github.com/luffah/obkey/raw/master/deb_dist/obkey_1.2-1_all.deb) ```shell sudo apt install python-gi python-gettext From 18b1d9add98f63d50c63d6d97ef9a9f7da62535e Mon Sep 17 00:00:00 2001 From: luffah Date: Sat, 24 Feb 2018 06:32:35 +0100 Subject: [PATCH 22/25] Re-specify setup.py for inclusion of obkey_parts. --- deb_dist/obkey_1.2-1_all.deb | Bin 9200 -> 21612 bytes setup.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/deb_dist/obkey_1.2-1_all.deb b/deb_dist/obkey_1.2-1_all.deb index a79cc26c4c226ea8c2b8b5919198c1afac1dab20..90be6ef7a78dff0698d352873b688f2b4914af54 100644 GIT binary patch literal 21612 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

?A{Di7xfV8tCiq+6 z?F*8;`~9+KzXVc(fR08-dUxK9zU3I!!U~rn%aew5>LPdbyt@N^gqmzrqYATFR_n?` znY4uGG`xLKyLMUkUFjwbSI6rXRs9B8m)bRt#8nAoNC8dxVE7Vq~^6^nJzs?PGgsW26M}02z%y0 zO8*gm6PxdLlkyP(JQHBNjV85VW=Ycnx;xXgXvnG%*u(DE5$j^*s>SBK_S xhMiy;?;8c@pI_Jy006m3oe`g4@p1qGCG literal 9200 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 Date: Sat, 24 Feb 2018 06:44:34 +0100 Subject: [PATCH 23/25] Add md5 to check the debian package --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index f8692bb..20b3d84 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,13 @@ sudo dpkg -i deb_dist/obkey_1.2-1_all.deb Download the package here : [Obkey for debian](https://github.com/luffah/obkey/raw/master/deb_dist/obkey_1.2-1_all.deb) + ```shell +md5sum obkey_1.2-1_all.deb | grep 1fdcb1ee55fc8c5c0db445c1d8b1051c && echo OK + +#> 1fdcb1ee55fc8c5c0db445c1d8b1051c obkey_1.2-1_all.deb +#> OK + sudo apt install python-gi python-gettext sudo dpkg -i obkey_1.2-1_all.deb ``` From 6c83b778905758402a61a5d19d081bc0ed72dab3 Mon Sep 17 00:00:00 2001 From: luffah Date: Mon, 12 Mar 2018 14:36:38 +0100 Subject: [PATCH 24/25] make pylint pass - correct french translation --- Makefile | 12 +- deb_dist/obkey_1.2-1_all.deb | Bin 21612 -> 22530 bytes obkey | 33 +- obkey_parts/Gui.py | 29 +- obkey_parts/KeyTable.py | 199 +++- obkey_parts/KeyUtils.py | 68 +- obkey_parts/OBActions.py | 1328 ---------------------- obkey_parts/OBKeyboard.py | 34 +- obkey_parts/OpenboxConfig.py | 21 +- obkey_parts/PropertyTable.py | 30 +- obkey_parts/Resources.py | 21 +- obkey_parts/XmlUtils.py | 34 +- obkey_parts/__init__.py | 5 +- po/obkey.fr.po | 23 +- pylintrc | 260 ----- resources/locale/fr/LC_MESSAGES/obkey.mo | Bin 8846 -> 8929 bytes 16 files changed, 377 insertions(+), 1720 deletions(-) delete mode 100644 obkey_parts/OBActions.py delete mode 100644 pylintrc diff --git a/Makefile b/Makefile index 15a30c1..78f9978 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,14 @@ +lang: + cd po; make -deb: +installdeb: + dpkg -i deb_dist/obkey_1.2-1_all.deb + +installpy: + python setup.py install + +deb: lang python setup.py \ --command-packages=stdeb.command sdist_dsc \ --package obkey \ @@ -9,4 +17,4 @@ deb: dpkg-buildpackage -rfakeroot -uc -us lint: - pylint --output-format=parseable --reports=y obkey_classes | tee pylint.log + pylint --output-format=parseable --reports=y ./obkey_parts | tee pylint.log diff --git a/deb_dist/obkey_1.2-1_all.deb b/deb_dist/obkey_1.2-1_all.deb index 90be6ef7a78dff0698d352873b688f2b4914af54..8ff40af5b203c55d47acc6e1c3a601f5409d6bb8 100644 GIT binary patch literal 22530 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 literal 21612 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

?A{Di7xfV8tCiq+6 z?F*8;`~9+KzXVc(fR08-dUxK9zU3I!!U~rn%aew5>LPdbyt@N^gqmzrqYATFR_n?` znY4uGG`xLKyLMUkUFjwbSI6rXRs9B8m)bRt#8nAoNC8dxVE7Vq~^6^nJzs?PGgsW26M}02z%y0 zO8*gm6PxdLlkyP(JQHBNjV85VW=Ycnx;xXgXvnG%*u(DE5$j^*s>SBK_S xhMiy;?;8c@pI_Jy006m3oe`g4@p1qGCG diff --git a/obkey b/obkey index 790f6ab..3a75b36 100755 --- a/obkey +++ b/obkey @@ -52,7 +52,7 @@ def get_rcfile(): ob_params = proc['cmdline'] param_name = '--config-file' if param_name in ob_params: - ret = ob_params[ob_params.index(param_name)+1] + ret = ob_params[ob_params.index(param_name) + 1] except ImportError: pass return ret or (os.getenv("HOME") + "/.config/openbox/rc.xml") @@ -81,20 +81,25 @@ def view(): propertytable = PropertyTable() actionlist = ActionList(propertytable) keytable = KeyTable(actionlist, obkeyconf) - - verticalbox = Gtk.VPaned() - verticalbox.pack1(propertytable.widget, True, False) - verticalbox.pack2(actionlist.widget, True, False) - - horizontalbox = Gtk.HPaned() - horizontalbox.pack1(keytable.widget, True, False) - horizontalbox.pack2(verticalbox, False, False) - - window.add(horizontalbox) + # 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) + + 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) +# EXPAND | FILL, + window.add(grid) window.show_all() - # get rid of stupid autocalculation - width, _ = window.get_size() - horizontalbox.set_position(width-250) keytable.view.grab_focus() Gtk.main() diff --git a/obkey_parts/Gui.py b/obkey_parts/Gui.py index 356d295..3cd719c 100644 --- a/obkey_parts/Gui.py +++ b/obkey_parts/Gui.py @@ -49,16 +49,30 @@ # 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 @@ -69,6 +83,10 @@ def set_state(self, state): class SensSwitcher: def __init__(self, conditions): + """__init__ + + :param conditions: + """ self.conditions = conditions self.widgets = [] @@ -76,17 +94,24 @@ def __init__(self, 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 index ef6dc5e..e25bc3c 100644 --- a/obkey_parts/KeyTable.py +++ b/obkey_parts/KeyTable.py @@ -27,27 +27,42 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from Gui import * -from obkey_parts.Resources import res + +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__ - def __init__(self, actionlist, ob): + :param actionlist: + :param ob: + """ self.widget = Gtk.VBox() - self.ob = ob - if ob.keyboard_node: - self.keyboard = OBKeyboard(ob.keyboard_node) + 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 = self._create_models() + self.view, self.cqk_view = self._create_views( self.model, self.cqk_model) # copy & paste @@ -68,7 +83,7 @@ def __init__(self, actionlist, ob): self.sw_selection_available = SensSwitcher( [self.cond_selection_available]) - self.context_menu = self.create_context_menu() + self.context_menu = self._create_context_menu() for kb in self.keyboard.keybinds: self.apply_keybind(kb) @@ -76,15 +91,12 @@ def __init__(self, actionlist, ob): 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) + 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()) @@ -94,7 +106,11 @@ def __init__(self, actionlist, ob): self.sw_paste_buffer.notify() self.sw_selection_available.notify() - def create_cqk_hbox(self, cqk_view): + 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) @@ -106,7 +122,8 @@ def create_cqk_hbox(self, cqk_view): cqk_hbox.pack_start(cqk_frame, True, True, 0) return cqk_hbox - def create_context_menu(self): + def _create_context_menu(self): + """_create_context_menu""" context_menu = Gtk.Menu() self.context_items = {} @@ -148,31 +165,41 @@ def create_context_menu(self): context_menu.show_all() return context_menu - def create_models(self): + 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 - ) + 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) + TYPE_UINT, # accel key + TYPE_INT, # accel mods + TYPE_STRING) # accel string (openbox) return (model, cqk_model) - def create_scroll(self, view): + 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): + 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 @@ -236,7 +263,8 @@ def cqk_view_focus_lost(view, event): cqk_view.connect('focus-out-event', cqk_view_focus_lost) return (view, cqk_view) - def create_toolbar(self): + def _create_toolbar(self): + """_create_toolbar""" toolbar = Gtk.Toolbar() toolbar.set_style(Gtk.ToolbarStyle.ICONS) toolbar.set_show_arrow(False) @@ -260,6 +288,8 @@ def create_toolbar(self): 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")) @@ -275,6 +305,8 @@ def create_toolbar(self): 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")) @@ -290,6 +322,8 @@ def create_toolbar(self): 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', @@ -312,6 +346,7 @@ def create_toolbar(self): 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: @@ -321,6 +356,10 @@ def apply_cqk_initial_value(self): 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 = "[ "\ @@ -338,6 +377,11 @@ def get_action_desc(self, kb): 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) @@ -351,15 +395,23 @@ def apply_keybind(self, kb, parent=None): 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")) + 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) @@ -371,16 +423,20 @@ def view_button_clicked(self, view, event): view.set_cursor(path, col, 0) self.context_menu.popup( None, None, None, None, - event.button, time) + event.button, time) else: view.grab_focus() view.get_selection().unselect_all() self.context_menu.popup( None, None, None, None, - event.button, time) + 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) @@ -393,6 +449,10 @@ def actions_cb(self, val=None): 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: @@ -407,6 +467,14 @@ def view_cursor_changed(self, selection): 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) @@ -415,6 +483,12 @@ def cqk_accel_edited(self, cell, path, accel_key, accel_mods, keycode): 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 @@ -422,6 +496,14 @@ def cqk_key_edited(self, cell, path, 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) @@ -429,11 +511,22 @@ def accel_edited(self, cell, path, accel_key, accel_mods, keycode): 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] @@ -444,14 +537,17 @@ def chroot_toggled(self, cell, path): # ------------------------------------------------------------------------- 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) @@ -459,6 +555,12 @@ def copy_selected(self): 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 @@ -471,6 +573,10 @@ def _insert_keybind(self, keybind, parent=None, after=None): 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) @@ -487,9 +593,9 @@ def insert_sibling(self, keybind): newit = self.model.insert_after( parent_it, it, ( accel_key, accel_mods, keybind.key, - keybind.chroot, show_chroot, - keybind, - self.get_action_desc(keybind))) + keybind.chroot, show_chroot, + keybind, + self.get_action_desc(keybind))) else: self._insert_keybind(keybind) newit = self.model.append(None, ( @@ -504,6 +610,10 @@ def insert_sibling(self, keybind): 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) @@ -513,7 +623,8 @@ def insert_child(self, keybind): # newit = self.model.append(it, ( accel_key, accel_mods, keybind.key, - keybind.chroot, show_chroot, keybind)) + keybind.chroot, show_chroot, keybind, + self.get_action_desc(keybind))) # if newit: # for c in keybind.children: @@ -524,6 +635,7 @@ def insert_child(self, keybind): 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) @@ -534,4 +646,3 @@ def del_selected(self): 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 index 134d3e3..76e6ed2 100644 --- a/obkey_parts/KeyUtils.py +++ b/obkey_parts/KeyUtils.py @@ -27,54 +27,66 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from Gui import Gtk +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_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" - } +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 Exception: + 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: - s = Gtk.accelerator_name(0, mods) - svec = [replace_table_gtk2openbox[i] for i in s[1:-1].split('><')] - result = '-'.join(svec) + modtable = Gtk.accelerator_name(0, mods) + modtable = [ + REPLACE_TABLE_GTK2OPENBOX[i] + for i in modtable[1:-1].split('><') + ] + result = '-'.join(modtable) if key: - k = Gtk.accelerator_name(key, 0) + keychar = Gtk.accelerator_name(key, 0) if result != "": result += '-' - result += k + result += keychar return result - - diff --git a/obkey_parts/OBActions.py b/obkey_parts/OBActions.py deleted file mode 100644 index 1b82368..0000000 --- a/obkey_parts/OBActions.py +++ /dev/null @@ -1,1328 +0,0 @@ -""" - 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.XmlUtils import * -from obkey_parts.Gui import * -import copy - -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 = dom.getAttribute("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 = Element('action') - root.setAttribute('name',str(self.name)) - 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 - -# ========================================================= -# 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(TYPE_STRING, TYPE_PYOBJECT) - - def create_choices(self, ch): - ret = ch - actions_a = {} - - for a in actions_choices: - actions_a[_(a)] = a - for a in sorted(actions_a.keys()): - actions_b = {} - content_a = actions_choices[actions_a[a]] - if (type(content_a) is dict): - iter0 = ret.append(None, [a, ""]) - - 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, [a, actions_a[a]]) - - return ret - - def create_scroll(self, 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): - renderer = Gtk.CellRendererCombo() - - def editingstarted(cell, widget, path): - console.log('editing') - 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): - if self.actions_cb: - self.actions_cb() - - 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 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): - 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]) - if self.actions_cb : - self.actions_cb() - - 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,self.proptable_changed) - if act: - nb = len(self.actions) - i = self.actions.index(act) - self.cond_can_move_up.set_state(i != 0) - self.cond_can_move_down.set_state(nb > 1 and i + 1 < nb) - 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) - nb = len(self.model) - self.cond_can_move_up.set_state(i - 1 != 0) - self.cond_can_move_down.set_state(nb > 1 and i < nb) - 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) - nb = len(self.model) - self.cond_can_move_up.set_state(i + 1 != 0) - self.cond_can_move_down.set_state(nb > 1 and i + 2 < nb) - if i + 1 >= nb: - 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 - - -# ========================================================= -# 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_get_str(node) - else: - action.options[self.name] = self.default - - def deparse(self, 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, cb=None): - def changed(entry, action): - text = entry.get_text() - action.options[self.name] = text - if cb: - cb() - - 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_get_str(node) - else: - action.options[self.name] = self.default - - def deparse(self, 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, cb=None): - def changed(combo, action): - text = combo.get_active() - action.options[self.name] = self.choices[text] - - model = Gtk.ListStore(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_get_str(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 parseString( - "<" + str(self.name) + ">" + - str(val) + - "" - ).documentElement - - def generate_widget(self, action, cb=None): - 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(): - 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): - 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, cb=None): - # 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 parseString( - "<" + str(self.name) + - ">yes" - ).documentElement - else: - return parseString( - "<" + str(self.name) + - ">no" - ).documentElement - - def generate_widget(self, action, cb=None): - 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_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): - 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, cb=None): - 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): - 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 = parseString( - "").documentElement - for act in a: - node = act.deparse() - root.appendChild(node) - return root - - def generate_widget(self, action, cb=None): - w = MiniActionList() - 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 = {} -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 - diff --git a/obkey_parts/OBKeyboard.py b/obkey_parts/OBKeyboard.py index b8db96e..001f5a3 100644 --- a/obkey_parts/OBKeyboard.py +++ b/obkey_parts/OBKeyboard.py @@ -26,19 +26,25 @@ 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 """ -""" - OBKeyBind class definition - OBKeyboard is a collection of OBKeyBind + chainQuitKey -""" -from obkey_parts.OBActions import OBAction -from obkey_parts.XmlUtils import xml_find_nodes, xml_find_node, xml_get_str, parseString, Element +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" @@ -72,13 +78,13 @@ def deparse(self): if self.chroot: root.setAttribute('chroot', "yes") # root = parseString( - #'').documentElement + # ' < keybind key = "' + + # str(self.key) + + # '" chroot="yes"/ > ').documentElement # else: # root = parseString( # '').documentElement if len(self.children): @@ -98,9 +104,9 @@ def deparse(self): # newact.mutate("Execute") # if after: - # self.actions.insert(self.actions.index(after) + 1, newact) + # self.actions.insert(self.actions.index(after) + 1, newact) # else: - # self.actions.append(newact) + # self.actions.append(newact) # return newact def move_up(self, action): @@ -128,6 +134,10 @@ class OBKeyboard(object): """OBKeyboard""" def __init__(self, dom): + """__init__ + + :param dom: + """ self.chain_quit_key = None self.keybinds = [] diff --git a/obkey_parts/OpenboxConfig.py b/obkey_parts/OpenboxConfig.py index 1062576..db9a2b0 100644 --- a/obkey_parts/OpenboxConfig.py +++ b/obkey_parts/OpenboxConfig.py @@ -29,26 +29,42 @@ """ from StringIO import StringIO from os import system -from obkey_parts.XmlUtils import minidom, xml_find_node, fixed_writexml, parseString +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") + 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 @@ -67,4 +83,5 @@ def save(self, keyboard_node): self.reconfigure_openbox() def reconfigure_openbox(self): + """reconfigure_openbox""" system("openbox --reconfigure") diff --git a/obkey_parts/PropertyTable.py b/obkey_parts/PropertyTable.py index 6eb663f..47a7799 100644 --- a/obkey_parts/PropertyTable.py +++ b/obkey_parts/PropertyTable.py @@ -28,14 +28,18 @@ THE SOFTWARE. """ -from Gui import * +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) @@ -43,16 +47,22 @@ def __init__(self): 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) + 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: @@ -60,7 +70,19 @@ def clear(self): 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: @@ -75,9 +97,7 @@ def set_action(self, action, cb=None): 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 index af3bf19..4133b61 100644 --- a/obkey_parts/Resources.py +++ b/obkey_parts/Resources.py @@ -28,7 +28,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -# import __builtin__ +import __builtin__ from os.path import join as path_join, dirname, isdir import sys @@ -38,28 +38,39 @@ 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])) + 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") + 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) + +res = Resources(sys.argv) # trick for syntax checkers -# _ = __builtin__.__dict__['_'] +_ = __builtin__.__dict__['_'] diff --git a/obkey_parts/XmlUtils.py b/obkey_parts/XmlUtils.py index 0e6a8fd..5e5b3d6 100644 --- a/obkey_parts/XmlUtils.py +++ b/obkey_parts/XmlUtils.py @@ -31,11 +31,20 @@ 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 @@ -43,18 +52,32 @@ def xml_parse_bool(elt): 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=""): - # indent = current indentation - # addindent = indentation to add to higher levels - # newl = newline string + """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() @@ -76,11 +99,8 @@ def fixed_writexml(self, writer, indent="", addindent="", newl=""): writer.write(">%s" % newl) for node in self.childNodes: fixed_writexml( - node, writer, + 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 index 45f9218..7e6564c 100644 --- a/obkey_parts/__init__.py +++ b/obkey_parts/__init__.py @@ -30,7 +30,10 @@ """ from obkey_parts.OpenboxConfig import OpenboxConfig -from obkey_parts.OBActions import ActionList +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 _ 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/pylintrc b/pylintrc deleted file mode 100644 index 7324e83..0000000 --- a/pylintrc +++ /dev/null @@ -1,260 +0,0 @@ -[MASTER] - -# Specify a configuration file. -#rcfile= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Profiled execution. -profile=no - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS - -# Pickle collected data for later comparisons. -persistent=yes - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - - -[MESSAGES CONTROL] - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time. -#enable= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). -disable=I0011 - - -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Include message's id in output -include-ids=yes - -# Include symbolic ids of messages in output -symbols=no - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". -files-output=no - -# Tells whether to display a full report or only the messages -reports=yes - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Add a comment according to your evaluation note. This is used by the global -# evaluation report (RP0004). -comment=no - - -[BASIC] - -# Required attributes for module, separated by a comma -required-attributes= - -# List of builtins function names that should not be used, separated by a comma -bad-functions=map,filter,apply,input - -# Regular expression which should only match correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression which should only match correct module level names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression which should only match correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression which should only match correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct method names -method-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct instance attribute names -attr-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct argument names -argument-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct variable names -variable-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct list comprehension / -# generator expression variable names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,f,ex,Run,_ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata,data,data2 - -# Regular expression which should only match functions or classes name which do -# not require a docstring -no-docstring-rgx=__.*__ - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module -max-module-lines=1000 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - - -[VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the beginning of the name of dummy variables -# (i.e. not used). -dummy-variables-rgx=_|dummy - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - - -[TYPECHECK] - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# List of classes names for which member attributes should not be checked -# (useful for classes with attributes dynamically set). -ignored-classes=SQLObject - -# When zope mode is activated, add a predefined set of Zope acquired attributes -# to generated-members. -zope=no - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E0201 when accessed. Python regular -# expressions are accepted. -generated-members=REQUEST,acl_users,aq_parent - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=5 - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.* - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of branch for function / method body -max-branchs=12 - -# Maximum number of statements in function / method body -max-statements=50 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub,string,TERMIOS,Bastion,rexec - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - - -[CLASSES] - -# List of interface methods to ignore, separated by a comma. This is used for -# instance to not check methods defines in Zope's Interface base class. -ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception - diff --git a/resources/locale/fr/LC_MESSAGES/obkey.mo b/resources/locale/fr/LC_MESSAGES/obkey.mo index fa817361f4c6b3c778f8dce36597d1c7c61deb2e..5951476c44b6a9feee495de10a4eaf126e3bfcf2 100644 GIT binary patch literal 8929 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 literal 8846 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 Date: Wed, 28 Mar 2018 00:35:07 +0200 Subject: [PATCH 25/25] Add --help / obkey.appdata.xml / __version__.py --- Makefile | 9 +- README.md | 15 +- deb_dist/obkey_1.2-1_all.deb | Bin 22530 -> 0 bytes deb_dist/obkey_1.2pre-1_all.deb | Bin 20036 -> 0 bytes misc/obkey.appdata.xml | 43 + obkey | 82 +- obkey.deb | Bin 0 -> 24566 bytes obkey_parts/ActionList.py | 1525 +++++++++++++++++++++++++++++++ obkey_parts/__init__.py | 1 + obkey_parts/__version__.py | 28 + setup.py | 139 +-- 11 files changed, 1751 insertions(+), 91 deletions(-) delete mode 100644 deb_dist/obkey_1.2-1_all.deb delete mode 100644 deb_dist/obkey_1.2pre-1_all.deb create mode 100644 misc/obkey.appdata.xml create mode 100644 obkey.deb create mode 100644 obkey_parts/ActionList.py create mode 100644 obkey_parts/__version__.py diff --git a/Makefile b/Makefile index 78f9978..7e604ae 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,9 @@ lang: cd po; make -installdeb: - dpkg -i deb_dist/obkey_1.2-1_all.deb +installdeb: deb + sudo dpkg -i ./obkey.deb + # find . -mtime 0 -name '*.deb' -exec sudo dpkg -i {} + installpy: python setup.py install @@ -15,6 +16,10 @@ deb: lang --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 20b3d84..4514ae7 100644 --- a/README.md +++ b/README.md @@ -23,26 +23,21 @@ sudo python setup.py install ## With Debian installer sudo apt install python-gi python-gettext -make deb - -sudo dpkg -i deb_dist/obkey_1.2-1_all.deb +make installdeb ``` # Without Git ## Debian -Download the package here : [Obkey for debian](https://github.com/luffah/obkey/raw/master/deb_dist/obkey_1.2-1_all.deb) - +Download the package here : [Obkey for debian](https://github.com/luffah/obkey/raw/master/obkey.deb) +Below the last checksum. ```shell -md5sum obkey_1.2-1_all.deb | grep 1fdcb1ee55fc8c5c0db445c1d8b1051c && echo OK - -#> 1fdcb1ee55fc8c5c0db445c1d8b1051c obkey_1.2-1_all.deb -#> OK +md5sum obkey.deb | grep d4ab76711cbea8afcf88cb138e07a90d && echo OK sudo apt install python-gi python-gettext -sudo dpkg -i obkey_1.2-1_all.deb +sudo dpkg -i obkey.deb ``` # Usage diff --git a/deb_dist/obkey_1.2-1_all.deb b/deb_dist/obkey_1.2-1_all.deb deleted file mode 100644 index 8ff40af5b203c55d47acc6e1c3a601f5409d6bb8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22530 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 diff --git a/deb_dist/obkey_1.2pre-1_all.deb b/deb_dist/obkey_1.2pre-1_all.deb deleted file mode 100644 index bcf98c8b0649b492c18f841c76902dc23358ec8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20036 zcmaf)Q;aZ55T(bqZQHhO+qP}~W81cE+qP|cX74KdxF?;e^h4KE)t7XiG$D_nld%OK zl!>X4rJ)_Ik)@rXlP3WI0V69TCo>~6Cks0P0mFa${~H+@7}!`?2nhaL|3?N;OmqxT z#`bnDPWCo*E{0BY=3f8*^Nb9PO#i!p0Tg}|1i=3k5b#Z!u6;2ZN|-aYUuaiXrB(&- zZ$DeEU6EK;c}-`PE~S$FCKb~wV?ILw!_ut{zn?d+@aBwIQZu_`W(vL~2*%SVL-?D6 zL{l&~I5^2!m+k(iTbn$_3?@1XGumhg$uq!1u(!btIWLh5!|RXBR<}R*pM)gylKawn zn|ro@!WsK%p7NvNFQj1zE&CB;WA^qs^*ViUpyU4L5$iT!9PewHP2 zA%1I7r)=+yI>WVHd2ZEdu8!9^3gcn=(Jy zxoZb6&<&t0Yrcc*hD+-ORa}s{j&hB7PY+gy-?uh_EMCr^dg|B9B!BjwpHed8O!52OlGqMP(v)0rbESGC(mzr(C>FV&dgr zxwoCx4ez_lI!Wlzw1pWhW6)xvwH5$sP#^^(IZGa?Z;kl4jlEgm^m1oa{!P_0$fTIN zauQUFbeBjILtIj_<$7$rGtgOFh+b(M^uD#s}yEa^OwQx|>Z%ZMMPF~@b4YqRlEDejA@DdQ$B+YlC>s71h^SScl zNvq{$;?Ns1+R(O1;V|o#sT_CIb8gA^_MvZa*w^2Oe~9Bt{@$O=xb^Rt(&$~^*x?_V zi+j|Ud)b~hz83sM0WKX?vxg_=J9niR#=LxiwKp1EkP`(uu|<^5I&{<5@x6miL8!2N z?oTW3*U>~=>EFj>C8Us{7I;Ie2vLFr2~y<$OXJh}(0+`j02BZKCWbDC{|go#|H0xv z{r`Bw$iczc2QIYjy&eBD7>G z)pRg6dH)e?-N`(FKOSl3PQtEe)G#3HYwcu-2LSkyERDa#Gnv7DK}rFgJ~60Mudk9= zNGsHJ#a;IG!HO}m0_d;Z!yUDp9yO+ENi-hO%nfTfzwNwMF)BmoQMp_PJhv}+#}Tyw6CWwF@r{^A3Wx zz*x!4gsB|b3mZVuV>Zv$5-2%*9EU-&qF?GI6s655=OO9>iR7esu#!6tUn}){6{({I^c{JPpQVQN}QVXY|n}9SFuOBFNq|= zj4nxt3cjc%D=p1-ED04~*1{=k7m$)aWi^Ma-rS4R#M5-&0M4Qmfmewfzu<;Kx885sw>x|Vs1lem; zAbebTgr&X0REp5=asZM;Y60DH0hXZR3@Z@A&HP27K!40vcvXMIp^@}%K~~zWZw`G3 z_YBy`4(J%gJ+P$Fx65dOT^oB(?g_TMQvaX2M%~73s*S<9$lD$DpVC3PRgcudn-;7K zbND0~PlL#AwdC2ZVlQce01TZ7v*>q_J#8Hy2eGIofa`SU{_w(VxU@#0U**FevZ^-gBNMXusB`-tH-}_W!zk744G`&Dx}Q#CV*qr)`~aEeM?G~yb^}=@FKEr}@!}@-Qv!u=7YTClZQ~BNawLng@&u%J!F$AFz z6fT&96Ki7Z=;4i-r3Qm{e4lx(Ux#$2&W1Cy=J@&I|HTBhb2`|P9_sAj0 z*?JDbWB)6^ZXqk^|&ehG#(EN8ipQI_2o87kJ$RuH!&O<#zHT~2B z_8OFO^SatVoeFdJozh3s6#)qW{zYKS?F@89_MDUT-L*q@_t6c_hJ?BJ6+dd^|FrYL z7XN&u*`)r9-nBv53$+MO>0vxwbY4(&_yrr~Ro>#+$@- z?#^*@4L>;OBWP9uFj7gE&{#)wgPi9%v-?H*(KF^#z(VnY< z1*q`X2f`$|({QoaMAQsO6!(=j^LVZ+on?A;yYTb>cf03%8JIe`PQ@>zXokI;W+BQx zW2rD!YEL$vR0dka&z9@|$I!LbjvlX@;nRD<$q-Ob@`o;{fLpycpF5=@JpI%KJQLx_ zZ#xX%4@4jKq_0g2tGqM@dauq zcRTrfiS(rV0E8M$X&}M=vVLgpBB@h$8zKp$z%SXx^OPMJ=bf@G3U6EQh~y1hFQ3&) zYs$`&^xWon+m`&)1EN;%dWFfmO{X z1)Raz=u)#@r~9s4*i$z}KzpZeuUdJ@HQ zx^K7a?-FLtPT*Oc4K~kiZ4@E>&v>f=A@C_2Xe7r5+WI4yD#SyhlnANMS!&hctS>q) zGhS_CM^`!#4aNSCMAGe9L#rbfI@(vtRn+v7hSl{gTUT+8tkz1xU$H=EQt#j46zn? z1z8=%_Q$INmpNwujEBzT{dx$2a=SC;z-jV{?zF2tT{%cMng7oB5OJ%V=g3Mjd5cBlt+uH0ngjNHWX zB(*EBz(BdsX!B;dcf>|>WoP$mw|>NqGB5?AdEVLgaz1E}^XTOj-ij3R@Tlm`*>2hk zmyJd?mX%Puc<}AdfSlwSYEwNa&Bv*~BeOTB1+U`d-mRA`XbSKp+(x7)rQ}xC$!oGR zf(Ji+3Fq3xWQ+d&CO&!eAmshazSo)(_&COw2*rnYUh^Q2d%70VXW9yg1)%m84a({j)M8^#0p%fG2+z1LOZezv7`{7d8lXn+l%P^8K;V0 zeK37W&T-F)7Avi$BbxBy<_&vb8Ul<1?7pJZ7Cx|o>Za&giH+YH6!YDBAQJ2?ELAp8 zpMvry_OO5WjW>_l_j;&v0^^Aw3JezmM(%x$Y%duYxn~sI>`$7BO+MKed|knh>)K#p zLMWnOr0)NT9&n$+ne?|*u7x*-UrqogBKS-rs59kaW0q$Ju2w$nL#BM zfc<*cyJtvk+V9Pfoj>ZBF|Vt+LYR411;kKi7aVb3$13RY{!sC^&sZ8ZRW~`bYFCmh z2w@JR!n9mLK0L&iwSgrxW`Jcy4NVk5)Lod(Qy(Ij|5SF|Qpb%H^dTyrWa}0qN<0Z2 z#ThuX${QF0YX7s8F+sD112;ngau=5sEpr5{gtNrhm&HJY(={q<*6L!Xn6rm$xzD$) z?Wc7GTX!+@tNJr^B%@08cF9)8%D3Kl$EvwMJQ2-TN`Z_Vu#5KGJi_`p(GQiN8L3aQ z8ykLLJOOhNc8-+&OcH$Tg89s1A7}lhq*+D6=5_AgEi|aRc^ED&!H^rj(GEHtpA{zn1YPmv=@a)A6C3k3CM_^T`SjxmC)|J5MQY*eUS znm5jD#?}c0huaU{5El7@C2NsxDR|CqA->HkU=`E>4{D;^U4$)n3V4 znw&3Jd+?${aX;UZ=k2M+(a%+$7|?Q^=RSDn!sz_UOFWnGUZ8_f#d0@p^rQZm%5R1j z8kgzF?)q9^@Lrpkiun&wNA3q_7y&>tMb9k45<4W%utT!)%2ClvC%WIkyiB-8(qj!T z?6J*9d`x=rx9GN#2TD<)jlOtH<2cxwC41PSmPfP!I`264QBS^$8Y35D;1HAfzKIJn z3WIm!>)>@GgB!A*!Pvx7)BjA2f@68M6HM!6e@73315FyReoU%hWcAg7i z6{e{*B!s;8q62rnO`+Ijg9YVI7mhTsNoKbR`1b<;NSec&Mv@knG21KrwB-w{V!=FNU_*IZ6Nh5gXtJsu}z+y^e2^i?pC{G5LA*y&KFG%g5+R`$TrQayKV=2bk zvM}mVq$8eyge|!B#ITF`ocD+XNqqt#(;TSs_9Gyj0NaGXEogk(AsI+nuqvkX;4$U%@G33*RDShgPPT!l1k5^ZKFPurc6LDp z3d~x`l&w)I2kG>Ok9s&deimbadD04ZNk)V?ZU(`Asm-e46v|%ei_=#N4Xx+3yRzs@ zh1U1MO~a)p1g+#x`6!DL=IP<=_;dxk|F=9pnZQzXGOL48jDgL{p+sr0#+}L{V(C1CpZ9#O3!eexB zqifAt-82)Nx0)`D_r8g#{eOe~rW^pic&|s`TV04{F#&@|6xNxOjf>RF?lYzApZd*K zgKmuuh03ypnBt_u=jvx+q{YIeUB@sMsIg&(3f`k{ODiqWb z{#5n*>?{l-#Ipo@`>B`{vy8S+a9t0T=eK@M4W_-@@ky4KSdU*_MTSym_wOvlAaOyg zw<|#ZBCKna*k2;{BN|u9JCvW4Jk9TE3xdc3`Kq{st;JnEk$a&yNL`;C5ABAF9kt%0 zXbnH~dT)4~IEaJDp%2b0NT0E}Fx|gh*CAHo#58ENFg6KTBI6}x7s!78t^156s{rNz znAo8pf@ExCD9}`6R21)JlHdHpCSR~h4KwM&o0gDcA6P^Q626gVk8)!pReaLF3LTMC zpj*nx%yvA#?+gPANUXJ`3Qim*)R3e$X~Jy=B-Y$!Ji_1_l?kr21qS-Xj!W!_Y8ZgS zPK6;V3Oto@aFtiM>E5JaMk-56Z-BYP7mGs64*F*D?iew;isNY(GC0D36Y%u$F! zuyOp2q;babD4>|ko`BxopE35CHex0k&oF-!ml9*U8n369iU6tr-y0(s%bsARKclrq zLn)GuJAZ-mo1G&k)LeNX+~;d)YX?~kWxT;`DU_jz{{5>Q&40=G9OSt-{=YoOV()0d8<>b z#H4W;;4ev}CS_ok>n1YdbM{+n0zLy0e^%`p&3ACGi($PPOO7NkaezEhXAGwT6;x9~ z27n-&SR>k#5Rq2l_^cVKOO8w{>QlZ$(+zq28)`2)M>t_m0B}Mc&q{9x*>mm)HwZ`| zQID8ro?-f@BQ~LMqBj7f++?Msxrmg4b1|coITe4oXRf#kDKe3!I2rX~%n)OMdA*2{ z4~|djiB(MvZvT}V|IqAs$PgBHTn(*jV+#|h*I);RN@0nu@;7tPAE^Wd8{z5WZs-J@ zIzp0!4DJt0hx#k(gaj1i#J?r2;KT7J=W3$B7wf{Y|8N;$0TEBDuN|@wdH?9f^kSk| zA>qHjFpk-kc$Uthb57Q*vMX z8qbjF=)(VAIpxkAQbIr0AS5`!8o|Wl~-r!7`J`MEjO*jZQf(^Y9GAq#eE=vm+KL z7Yg!=kbj)k{T{0^@QTQb-%cV}&S1^G@pXqVBP-CVN_gxELays|3ps&6a^IS9KEvaf^g$sK{%l{CD@d&tC7X2Rb^WuR>bI+U4@RduXvT433ebMsm^~~I z0tP^}I5Eb;$9gs3p81Wn^GQXed{s;yM;dS$@3_hW)nW`^y`+K>NFe+%bqQZB>9xw7 z{K`%jS$VWb#GQaSZ~6ltqv59S5(zz$)xQs-O!u-BB*5IXzJF*abo=0$-ci zW2BVKI^nv1FoHcM6~=}j@qR-<6-bf_E=etd^+%=0P*8(tT^upEoRn|egq{T+P2W?Q zlF#T(K$Eu5-ib!_eSICDPsic1*(K}|cDI z!&|`Sov&lk_Jo#*!~nxA58d>iIucqbYiTjbDn2Ay2izkHV01ZZV3sXoHk$Mk6t$jP zk{mJjWXHED?k_Tro+_L=IBUX3tRJoqk5N1Tr}1pJ=k3<%DWPQCOMhXGr?T5>Kb{Ei5O-D%mN0Lbj79sX^;zCqfYPLSw&>Bd3ND*tu*%?(-cuF^q z>EEZ+r|Q_G=7jyyZH5|j>fc0Yg0Qu6555u5p@u#fjYw>LVM>;m%d_Q-3jf6f`Y_0A z=*lqOYbvrb8)Nf!Sm_L_2UtVHVK>GRGBTmg=H%bxyI_R-hMoZqohwvj9^R4hMQ+B_ znVIP(fRW~J_qm1h)#EwDdzD|9h+1usGd_Y(!D0Jle8kpqJpOvnno&YyS5YuP!W_OW z{ELsV58Sf?td>v2J0fm~J3A|PTW6yE6k?%V&yPi^z%zCU9y`T6kI5Z(k|QMBzb;f?=!Vpwvp8s` z>Oq0xm$%++)V6h)!c~o%d(3zrR)0&k(}90I_^u|y>V!*$#pv{Vs!e9mvuYcV9FTg- zeyHyK0P*W06#{AjOP1LWODLU~l>;KU#_nFD`o$P*me;qOtX9?J7YL~A01f@U6wzZyIiesOKAfj_&%TO_X@Uw$FS$|Mt%$7a|tQ#YELaP!bwJKG{u z=~q^LP5KPE8s~7jUW0wJG(^RYJ4>cH{5iittXogUVFRa2aKpT~YCN>s&NKq12_AOE z8hV$b&akmWC)ZfNuvkJgWJY8_pGBBv%~OVKSbVaN(4zc{*@T_qUZV=PtUMaNeO^w2DfPQQmAYcPS`%t)`Gfy^JWzqyvu?T^ zx?5)Ac{ zdxPcSi`&?dt{2G6@hcM(KcoI$npY4-u%?dB+{z(QkV?`EFYYDi; z5gY!yS5$Z*xUD-q;q5Caq^|laq1|Jf7R|9M#|{4M(dqB($1gQ&1HhHi8L{(4v}_%a zr2qw{DKEiPt-A$WFxzY$>z2HHfe~IS5!vq^P~S}FA9Fk9Uhy!k?&Daev_Be80^9S_ zCBf_dh0eZ7OBG=t!nz(4FGMc*{+^8t(vxg||VHPUdscQdYB@F)BU8;^FUJeNU zIo{ER)y?ro5p#oWAtOt_yO`HsSo3QD!sw61K;5m6vwv*wzuZnkTOaZGgwa zKCbD!PijI#v=BgCMg!5+$IY|gtS7QfZ%pbm9FuqSR#t8!FE@}=VXgjMj&0!P6mnbh zj*cW@uqS81vgp4C{3sB`B@#c~!G1x@!V-ua4AESX&kJnfJNl5aJIYq&Jg1ll?fFjc!APDTxfxY_n7Iw-(8S1 z?J=X-$~5VgN@ni=2|tP^MD?(!(V`IxsZkUuxWdH-KsSN9Eki&|>S|I=^Y>(tXuHSm zI5-o65V#)dPv+1`A67_a+`Qwn6yPnP8H$LfIW->L=>ZS8c&h`#N*Jb&hAuo<80>|U z^T$bLiI*#nYfc5V^ihVKdlkoR{85e;5Ob$vzL2z}5P+thWGO=}XoA zp3g5A#$8pdn!B;V_Zgh$Ug($}mWQ|@K2DzY3akPlSMUhb1k)MW3evjkGqBbYIdqu2 z!gx~SooiBK{j1ho!*bl1!3VBE)P6$;cG?tKmK9_>Qx=`ivScKZ#89Cgg_ej01T_K~ zGmH^o5QLL(ck1FeVM(RMsn=3WduF^2xKEt~V2i~y7n?M;KdW4FCL1dNmRWKi;w|XB zyDw*lVx$@Kx2&D2aj}SQ*X!EHZSI+wK`$Br@!gt<`8o$Om@UXwObak@4D@FLkViPi zmyKHWpcyHge6q9GiREO!?6>9#LaIlaqO2p0Z!#lu{QC_Oe-ym;o1gx^;@(3M9`CW3 zFEaX*vXe&-UsiK8+crGqhK+k@6)i=9C7&gHB6K9SiX-#I@W7kI4-mQ4rbcu1PYw+s z;CD!;8}lmiJ7pvsA~dzZ98XgNqH66yXV?kUc2MX#Eux}l%Vj_~O> z<3dIFX|kV-bm37qHXQnjohDm;$NPpRb(apA75z z%N-lKCkH&xlQdC|vwJDhVCx$rZ(+Q72T>N?o^;j;!h{RNoxkLVj+n!14i$;XI3Uav zN5V+>-Onr7w}%`(Cy8mf-!g+Uf-q5DzCU2ssq-skt!7<16PjyWE`H4S3;+>l0IYzi%=UwaAhmi z6}|{dtFDJ9y01UEczTvj_X{)z5vfztic@2$oW8W zn*D7MOZ!I$wbzbO=q=ce{}80A8M>ZD*wlmH2>$LcF>Ur{3vYBhWJv`1Qj4XF>Heu@ zeby7qhP@#>Wj6-Yh!9g8sIgp10n|f5fcW?3CGhe{gvztcSS2YiL%^>@^v8Etx8fu! zj8c=y=4TqxoF007K2-*nlU_suAHJ0djvim+kO#Yes55^YQM3u4TE~x?R%N!d>^+oK zq{YLuWAf0}qmH*(CqcO8Da{X5fuh>woJ!s?LX5?tz9(AFn?es+%Fc4N^mH`1L1i?E zJNZBY-_ZcVj1-nh1s%xao&GECt>}Gk5~P5@?+eeiV7G5BPObiZtYty9p&_Uw75^YI z6U8EOq!^E(va_hOlxL!X9+jQq#$n99Ihe*N3jw(P7YF*3bwrS;TYNUwJ{Ol1N6^(z zo)bG+XPFk+*!?7xT%+FMUd-g5W=55%-Fgcfao>wSC)ow1omIg8;Q%@0aAKE|wGr;F zzDf)&g4~iMI~&?pTy@E$X~G#jpY{c1{IB)gRS&6F)!g3`|9tk}DVCo} zGmz&xNfhu1q>F?}YQ~~<8StC@Y$|1YgVt`P^ujPRVUS9sY%0j{tA6Zz3j=juAx2hSZU9t3 z{y}7*-#~#clDYj78rieB_&7277zSShXe_pU?%@6-kj`~= zgp#JNrINFGFxOdlCwux`Y9y%>#tRvj-B}*6X*7&$)n!2BIgG4GS{xWPN7q_Wm&N;r zIKFB{<=ZrgofWY$_S3b%3y0uW{58rquv&J491*avTQcbLKoiKZRmLCH&m8VAsjs;! z&IZnMu*n2;?-j!Wp_U_VNufdD2A@Xy&#g!q-D)Ne$vbUmt^W(ca+y3C_5KGWzyRPo zp92Nrq(p5d6A(G%0~5^<4h;I6;T4TG;P_&){8e>fR_V=XThoi-ea6%%ei<|J9Ap`B zvfj`J1jnKMBuWy2zI@*>^7BB!KIFm#Lgh5|Ex2_?0&VMKbXf5(_r-3ll%}|pl^(t9`V$*&DsGJQ6U4;@EesR{D@2I?Fm?RQH5WPz(^l#jG>o_yT=8$?9 zQ7!DV^KNMGf5rh|#u-U{yhTY<^&hQ`eXZi%T<;7%a>51?g7_1vS5msPF*y2jmi%6C zvI&SKYvJe&4uf0{VVB2nGo!*T8t~NDj#+JWJZnHF*M{^}vd>8yJ=xQi?9+4j!vzD- zkgc+4`Y{v}#rz#mf*}Z60$Y%oc_KeNShG(ifoC0hj+*>;avb6Iaf_);yl&dDi}1{k zn+7IPPW;(-iaNJqOGNh*sI9O#k4qLvZEIf^Z*bE{I*2!xw;4v#8%83z6ge}fycbQD z(n;_@fkF#r;bKHo0dS~mo78+)QndT*n%Jg+v-A~Alo(zHU%gkdUWf+9;^!A2TCC~@ z6{0{8?Q>yY%1dMHWrk@*&naTw?e<2$Cl`~x&mp@h6fKr<680B~HfQ;^s@VE~mYTHI z7l6=&Gl5rl$t0S$o^~63C(q4;A3mVb#Rhx z!Q6J?Sv=@n!lYrbj?oT_BSYi!4$Pde&-)zrj8DE7d5HQ%>sPfN1O&7COcR&VQ=L7n zN8@Ae>XA~JJF5%gj&{4j+6}SwxW!I$Jl=(+CFD9{h5FeSOfJQ^wF71s1QdVph>S|Q zlEBJWGw$x$!^=4|fs+KgEtnox*k;<|Q z5VzPuGPw;z9f8$NPGmQrWg0vLP$@`(_56P$6Z$qCCx6r<_C9oMTGRsaPkRe$In;T6 zNw22_hHC^mlvhzw%uax~Qr7+zbVYuxOwM433cgL)ejrhQa2_pX&OJilEz&K$J4U_l z#FkNu(F*KTNuEAF=i@L$)#yZ2KCDAWDpvOu8LsuDs5uIGz$NmrWPgLhKGLrS0V4ZE z{A=^cBlokzJHsDn=au_(E>a?B2t@ZlVc%(S6?ymdo zP_>hq*07Dtg{zFQ(QM{%{d(G} zZXPiXBuJWa!&~=Bw)R+ye z`QUTod{dvJIX$@hC$``ofdB}oWigrLL_)68jzf&MGU$LG07hV6B7UnLP1D#Oe7PYm z2y5h0rM~mRHNR4da|&z!QwaH#CL%>Nht+eNr1BDu0A>GTTo$V_Rh4!}%te_J$ZrgM z)q<+?5#g+|%*HqaF9tl8oug}f$=2N(_4Pc!{Faa!$QZ8Tn-1m5VwCa2p}+O%Ch{cw zkjt2;9f43(DSiR4X+698d-wQf>%sm>rc@r6beK|;Ug|#IVBrgqj%{Wgb$9Qm=*hX9 zel(D40cr)X@-|Dl|RJ=1SVjBD}h$S+U%fXA)~`Ugs|!n%tT)NctY2`r^*i@ zgw+OBt1BCGYy-Exk5MCyUw#v-O(c3lI{+Lp7IcxXhv-rjJRZ*k6$NQQ1TuV$Ra$J> zYsvRhhT(=r($sTZ@@LL2CUg;e%`HZYM>a&kAB=OyJ&(DUl7t5QJGn#UQd8vQ6y?-x zq1aKv2w%UEnMg_n{9iKKWcKiO#qHUxD-ST}`g^latfGyCa0}widPuRR_rbCNcQZZ7 z<=_=80AdnQAc>lOxTD7Rg8Zm=l?7V+sDshvvfA?+C}0c%K~HwH)jL>~fv7@s47Gh^ za{CI~h|Td+OB7NWW}ZBNVarxZ7BAJ4dUOa;~)T z@zcz@9M_p_DY?LrhZD&mwKgfcwK1}?9v+27|Gy^lHjYTc`1W@vbXuA(TuIfwp$O{j zYUjNJ!q+qzsy*`iJfkvJ<`D1v<_Oc)@&X>H=f~VL@Sl1=3OCCO!%^CKO2bjHq8aXm zrL61G1IQ+2_kl=8X6o?S`f3&H2dodwX*-9ux@-|^qe!+6e$8Zo`B)Iz(H~k?giHPi zLoU74%0=BZ(m72XZ5OIz|7~70Zee_6A=ity!K>}t#0#gC+?bFSXa-A`>7*f0CIZ?< zqTowzn0;xS@ZOR}v+0Um(p{sww-;Tbiz9S3{LdyN$Uns#bK#C`i+9E{UJ~h{;=2xf zD#>=B0<3WW&Jw=ZQ(XQdB2{(-aGb`zjR(1|ECe20^_fqWtsHkO3`GE{Qosd~$;Jb3 zqrF?RCnXZlZ|x&4HIv{2j9F}X)nJ^A8~escI|~C3uK`m)P!u4IcXTOT0%T)5Zy?x_ z0VDuC9>d3C#$wtIJyImD7({8?E%b#LKGB6h-@*@grtF8Fg%%PKx;YR9^9>5YpIp1B zT=z4+y&e^;lMtl}l|*q_8#-xJ()o3cdgMK40RCQ7kHwaa9lU2#mR zHq_|~LoXyj1&^fqo;ZC<-pfo@GL!Wvqr3Wgc5sNGzY9tMp~)>)u=sSPdT`69^nfwr z=&ndM#A2ySCwdrIZf$<>Nvo~%pZ_}#;PZvQq5 zc7hYbu~4n}$h2cNXck`N=oa;koA)E{k0)zJD)gCn*C{Ai0Ti_;#j;F=^01?I(@3fD*0-W?q|ei3yG9kcCCx z9eDFNfq|z+I)I|Q*}R-!UA=C0&XY~x$+SD6;~yTw0_n4hey>O{Z?z;rYxCwiDUTuG z*tDHEjMR{TOMnd?cykg@y{V>yVmId{q;ljf*5K%TYm&~X>5#iA{0Rvk+1RgOAUxDL z|2-4)?C(pT9|>u^=q z3zRCf0S&c9_k7$wp!7{yD!zR;Jg@oX;3kdZkwWn&o+1txCcy;LRVk*fs{N++-(MEn zKJ97kfiuW~hlnV-)=6&C1Hn=X>&TpEUKO^C8wJ>3b5nISV~aUi1jyDz_IE9{YGp?1 zY6oKhAS3Vzh`4$#IE}8McInk9vWgwUF6*76Z{{P0y`jhO2l*q2w9C3OM$loerf6Z; zpiT}P&YXny!h4})Xh!ZU;T4DXq}!@dW{mt_PM76USmvC->^}C+AEOIg+qtOMpKzgM ze~pM-D5_W04q;&Q?3~=6s2LEafayF2D#Q3M!mQoRtre|)6 z-J%}UA)M=n8CKR<;V4|+Dd{Wz1rt%GjEDZKu}|3KKgU;x9NORambOmQkRYi zpPs&Z^DhDxQ5xZt)e7sslW+^PI**99Q{;&KtHT$%41BY5eIgG0R-eVkzvg3<#KT_= zMKTc4@Jx=+Zv!p7rXQo$4_#7)bKF0tZ1cOJ$6AK{GdI)?H_#m&=yGRb2!MUJCAhs$ zT;!PQ3N@!lA!_xPLBn0zHy-xxTran!>ouU@2+s7>5;?*t`K>(K)z(M`EK@TvCom3u<*y zO53}Xm0Yn`sJVf-6fY#BJxWKYes-6(Wb;VOXl*ze?CQo17qQYcxSBjuO#{Fp?i#pR z>WVmE@hQ~YZ=PZ=es1D`%Wgw}e@fSJpv~Vfl383Py}WH4>)iU_FhC{D4X)&&(%G`3 zA$A8MisnAHV{wpVJ~qTNcn@p{__bV=?Jh4JvDeoBfM$s*4<04WdTmaTz@$9opt=gf zYe$-LvfeMVF=RW0e?*{DoP3}|53ae3(tgt10wtJz0wJ04v@41zj9#AGrWXqI;75Pa z76pM`r;(c0VTjA|Pbg)5r#aSQ%nr{l@#qd`TmLEsr0t_k2Es!x9!&ppzGg5-@dDDS zv-VSHMi+8o2+uuULup%GODv`;k~a-^z%+Y<2UOZE{(MBLdkCz|<}Odmf<{~* zVb-O8jHmz;F%nrFq~Ep)uGi|j3+(-Ap625*4Jk|oE_F0nYRw_!VX}05N$!F!!#yPY zpL=AiJwIFhxsyA-h{fufp72!FTpm-PTNg&Wl=xi;N+MGY9geH+A61wbz`#-JsZT7_ zoZUgA{S4FJugT41Qg7F5zz@gx-rotz#Zf;YSk@(hyBv=^p-N?FCTJ6RI2TbWL_uQk z-f8A9&3Cj=?1ZP9ep&%uOa8m7*6h0?qMeW7g9-nm;M=^5E2r{MDPT;5TB!ID`%DKG zYT<0ET^?ZfF^GCdZ%SI0y%`&$0}Xa$p~&b*V3qEc|4@fK8*`Irzgz5?=c}3}lz3){ zwn|iO3)v|tRnB4zG;M1Lx+}JcJL!Xx1vO;e)(z?X>=X4Rv1=}F*(|=y8ONV2^K5xk z5%5K|Zdoi4T$(mIa1Pp!<-x!a>zBp2uqwV1sfV=h+A0`i$13b#86C`M&jbb-zje{n zG?#q^l*;=8t0@)rBjykAIW}EgvaUpB^|+d1`XCXK$4Z|4p?lG^EO{;MpTY&jVlqD> z%#)WMjYhZ9I=9mzIg@#+;ws5@eyf376A#9(ndQ3@J}|14r;YEr$>KnZd%Op>lT=5Z zUp+g7oHAsNNv4q;&8TVzmL;^cUq9ypAUv?B9l`*-I(4%Em? zjP+xw5#4QElq{Rb%&+$4F8y-U@`)^Q&wXE#jX#wN{jg8HzaAlZ5N8uO==Pxhj5~sr7Z4p3^^>5h}c@Inj^SZ0e=!|1743- z6<|;A98CIq#j#eEA)~x5NfzZjn+nlHE59+T`G4=oIVj7+m4Ge z(UAz%FG7}4uYH%&$0TU81BNok+lJ+s3teyr8L@mtlv27l=D=abI9%V zrCZFR$I{V`lp`nc!1F}F#V!o0opzucQ%|9#n&fJn&n^vLm6!uRBQSpeq>?4qN&v&N zeCH(to#oH7$QPC7MD$O;v^6<#w}R|tCQ8_+;`^XAZQLciRVI{1mzGT3a5@*`{Yweb zxtX(?FTp5{qOdLgE>B9FM>Q2Y=bo$Kj>@Kt`9l0Ig3_AOFtX*)1T-83J@QQPGI%}T z@WiHEnBNq2-7^CKX3-;`iJHc)8IHEcy2L5Pi%jdTL6?NMYo8dm6yv+Jr$aHu8E=Dk6_yL-2CKNvapthKkVVn=f3?4h73U^d%P89M zxa5hhSVyX~U^VIpBgR*fKphM{*xmC)yo$$IM4a6DsM40_8UNU?RuuE@m$XV*KLu27SQ1frrW>aKEFw8Y6c3RWAxP(1lrW z6Tt7KOxY?%eBR(~-ug-0+_uR)$l3a&F1PymN4WIa6S6?qxmg^uNW>EH#+4L>ZfQaR z){m+pIRus(`tTfK4bBg4Y^(O;>(_8)b!+v>*k*mj8MnIikKxHer zY0G~d8J~6wA=j)}=M-TdBY^!FJx$$!&TM0O3uh+6YG#E#0EB(C9bpZx_?(z^^giD*94l) z&63pA2R!iLNP56(rRCxFLyqJS=meX;5~kV_hss}%gi&XR;Wf3?LJU)XxhvyP<2Q}$ zeDy^9@%a71cN=MSRZIx#`JOY-ojiz?&vZo9Z6_P^k-rZlOR0Jn0Y~yigORgP5CI)F zdEt>!uh8%=@=O3@9`dzn;~B~}zS+y8+-ny~i$2GYGm#Dn412G{{Z zlMe|En>f1Q^4X_y4LaA@Zp^McMp4q}Ja#~~nsxAxg5*eQf4W{B+Co4rI%HqScfrn# zqmX!!4c+f9#<92&>-J`=RDGM3Z?oP-mO$5L1l7F+GjtWGOm?tJb*Rc8lN4cBl)$pX zy288-xBjQsXmF?SkJt#X62dwx72VXan=AB!H4Zj(NG)dCY7e_jJ-5!Lu+zGc2~U)} zU!(d^^qb0h2Q+4M(VDpydQ^$FeEK}pb3!wa(+dH3r||3r z;$wWE=aBx*vp113>)`~s6x!j-U1ysf)8L!V81=crU`q9f%qa3Ni1=S4+t)eqy+!)b zt#EybT>bZObZ0q`KvH`_xm5>THnrt-7bwwak%*Mt;FogBXaz-dq8ZcIzZM?JX;PbW z3x>zf2X+^ozM~eRY0IIl7s4zwox}E3P-ai%hS6eXz9Q5gpw*e8VFHY%XQ`fx0uWzb zVmTd{o#J$-@7}9=zT8a10;U%X{r5T9JTps7uoa#gKH8n>2W`U@QVf*28jn)m_cPt9 z?FuWSoMHY4D6pKtU?eJ)|&b>a*xsq zTtrlv-L2I)n_ld)H!8o{jVH$0VK%v|% z_w~%{_os48z(c97QvAzAiTXP~qHNzUV`|6Mrzc5E?IpXRTAQ0)=Z!LWq?AiLN)CIc z2S|%HVf9Mb1V*Aj#+yb$^AJs5B5>&9@tsf-7Ab_QV8a&K zO+NYUNGlW}L_QkczU5 z+R@jp5zXXW*g;C28}v56*jWRR#4AkxO4`;X@2m1=$UBc0x=>W6Pt!^m7si;GJa_A* zQPSgXPvc-IT^U|t{=)orOmn&toK7WP=rr_T-}&GMMt9JY9^*3NY918Y-ZjDd);Is~ zC!#pEHNp(fPrF$=|Bc*K1ao?6pz;wp@c&-3khX7@u(BPMGLnL9h{~ZnvV5{0Yc>Mh3b+z>vrrdgdQEfWne(wX zriI~PiLK+~7PeVg2>sEwV^FWg4dj3b%YyvU%7;C>D@&EI=;_680J)+}%pG9FHSt_@ zzWe|htjjzePWdb@gywnFYfSx}1TSI7$E)9-Wl@TH_Bi&5QG8&Q+tbuoQ+64W@_4xj zT8VvRN{P@!lz0|m2;1(?;Z1$e4HWA;E*{!FRyU0&jle|zmPp1~CXp`gy+oFKdTj|m z9g##R$Qe`wjRlUNw{}bO!(FJT#7v_cQ~7@RlY7IAVIA1O$O}~jj@<9qQz_d&qHGa1 zP>u$d!AKi3iB_0@=?609rPQ2Q zn^St1_G09o#m58VMWh()Ct2%%`Q-CmWjEFk`}(*yiFGI3)YYw2)MwOocmcKFSTyII zzTbH@R$A+WNn$5Wy`6~7daQT<>`H06f_-9I#sA+SMklGylF|C|JZ0E2p{sIgQwbbp zz?naBCGxdpmBj=)GvuKE=*7%8A=f4>s$3s=K7RP<``huRDZ{O%4k;JYH$MqY9H$uIhvnFuSU>cYaDOQp}_hvk!Cxqeo+JIu0ef+T&iT=||KGxNtx|;ggBOsm7GUK`;W|G-r=zL;C!bR>l}>Fg~iWeMQtNmhYVM(2`NcZ(dCcI=sVfc1 zZ2H-%5E4}2yTmxu3RS_8m2Tjyd*}^>IFBiPaRD~CvwkX{ji|3RkAup76`RFFXI#yB z--G$1+zg%2`Qox1;6v9&Yunyfk?dlO3)nM3=l<#=DMah-OTKnlyK_j*?L}R4DDmg*%#J2KQ0@xiBpLne%fty@000000N}2= g6sE_X%>V)Ek^z9g2k+F+Rk6fp`vL#}000D8T9kRp3;+NC 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/obkey b/obkey index 3a75b36..4dff5f0 100755 --- a/obkey +++ b/obkey @@ -72,36 +72,76 @@ def view(): obkeyconf = OpenboxConfig() obkeyconf.load(get_rcfile()) - # win = Gtk.Window(Gtk.WindowType.TOPLEVEL) # deprecated parameter window = Gtk.Window() - window.set_default_size(800, 480) + 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) - # 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) - - 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) -# EXPAND | FILL, - window.add(grid) + 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() -view() +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 0000000000000000000000000000000000000000..04a8bc63686cb762e8815cf5d8a8da0e19f57237 GIT binary patch literal 24566 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 literal 0 HcmV?d00001 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/__init__.py b/obkey_parts/__init__.py index 7e6564c..f52dbcc 100644 --- a/obkey_parts/__init__.py +++ b/obkey_parts/__init__.py @@ -37,3 +37,4 @@ # 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/setup.py b/setup.py index f40644e..ee49970 100644 --- a/setup.py +++ b/setup.py @@ -1,65 +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 - -# assuming applications are stored in /usr/ -libdir = 'share/obkey/icons' -localedir = 'share/locale' -desktopdir = 'share/applications' +from distutils.core import setup +from obkey_parts import __version__, __description__, __long_description__ +# Tests pass when applications are stored in /usr/ -res_icons = 'resources/icons' -res_locales = 'resources/locale' -res_desktop = 'misc' +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' -langs = [a[len(res_locales + '/'):] for a in glob(res_locales + '/*')] -install_requires = ['gi', 'gettext'] -setup(name='obkey', - version='1.2', - description='Openbox Key Editor', - url='https://github.com/luffah/obkey', - long_description="ObKey ease the keybindings configuration for Openbox.", - author='luffah', - author_email='luffah@runbox.com', - scripts=['obkey'], - py_modules=[a.replace('/','.').replace('.py','') for a in glob('obkey_parts/*.py')], - data_files=[( - libdir, - [res_icons + '/add_child.png', res_icons + '/add_sibling.png'] - ), ( - desktopdir, - [res_desktop + '/obkey.desktop'], +RES_ICONS = ('resources/icons', 'share/obkey/icons') +RES_LOCALES = ('resources/locale', 'share/locale') +RES_DESKTOP = ('misc', 'share/applications') +RES_APPDATA = ('misc', 'share/appdata') - )] + [( - os.path.join(localedir, l, 'LC_MESSAGES'), - [os.path.join(res_locales, l, 'LC_MESSAGES', 'obkey.mo')] - ) for l in langs], - keywords='openbox keybindings keys shortcuts', # Optional - 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=[ # Optional - # How mature is this project? Common values are - # 4 - Beta - # 5 - Production/Stable - 'Development Status :: 4 - Beta', +LANGS = [a[len(RES_LOCALES[0] + '/'):] for a in glob(RES_LOCALES[0] + '/*')] - # Indicate who your project is intended for - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Build Tools', +INSTALL_REQUIRES = ['gi', 'gettext'] - # Pick your license as you wish - 'License :: OSI Approved :: MIT License', +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 +] - # Specify the Python versions you support here. In particular, ensure - # that you indicate whether you support Python 2, Python 3 or both. - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - ], - ) +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', + ] + )