Merge lp:~dcaro/clicompanion/fix-608608 into lp:clicompanion
- fix-608608
- Merge into trunk
Status: | Merged | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Approved by: | Marek Bardoński | ||||||||||||||||||||
Approved revision: | 102 | ||||||||||||||||||||
Merged at revision: | 102 | ||||||||||||||||||||
Proposed branch: | lp:~dcaro/clicompanion/fix-608608 | ||||||||||||||||||||
Merge into: | lp:clicompanion | ||||||||||||||||||||
Prerequisite: | lp:~dcaro/clicompanion/fix-910355 | ||||||||||||||||||||
Diff against target: |
5891 lines (+3850/-1637) 18 files modified
.bzrignore (+3/-0) MANIFEST (+9/-1) clicompanion (+4/-5) clicompanionlib/__init__.py (+1/-1) clicompanionlib/config.py (+314/-237) clicompanionlib/controller.py (+0/-665) clicompanionlib/helpers.py (+186/-0) clicompanionlib/menus_buttons.py (+115/-132) clicompanionlib/plugins.py (+207/-0) clicompanionlib/preferences.py (+708/-0) clicompanionlib/tabs.py (+375/-133) clicompanionlib/utils.py (+181/-125) clicompanionlib/view.py (+488/-337) plugins/CommandLineFU.py (+282/-0) plugins/LocalCommandList.py (+710/-0) plugins/__init__.py (+23/-0) plugins/clfu.py (+243/-0) setup.py (+1/-1) |
||||||||||||||||||||
To merge this branch: | bzr merge lp:~dcaro/clicompanion/fix-608608 | ||||||||||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Marek Bardoński | Approve | ||
David Caro | Needs Information | ||
Review via email: mp+87868@code.launchpad.net |
Commit message
Description of the change
Done a lot (and I mean a lot) of changes in the code, this new version has a lot of improvements and now it's easy to extend and mantain. A lot of things can still be done but I think that it is now usable and stable enough for at least replace the version wthat we have not.
See the commit message for the features, i've tested it during two days, but as usual, there's no hurry, test it until you are convinced that it is good to go.
Thanks!
David Caro (dcaro) wrote : | # |
Marek Bardoński (bdfhjk) wrote : | # |
Code reviewed - no errors reported.
Questions:
1. What with notebook/netbook mode, did You tested clicompanion on small resolutions like (1024x600) ?
Bugs solved:
Bug #608608: Wishlist: Integration with Snipplr & Commandlinefu Services [OK, but icons not work, desc. below]
Bug #673556: tabs should be renameable [Sorry, I don't see that tab can be renamed]
Bug #689046: Function keys [OK]
Bug #790047: help button corrupts partially entered command [OK]
Bug #910687: Change font type and size [OK, very small bug here desc. below]
New bugs:
1. Low ERROR: Wrong config value for font: Ubuntu Mono 13 ,using default one Ubuntu Mono 13 - on startup
2. Medium Icons on the left side in tab Commandline FU not working, clicking on they have no effects
Regresion:
1. Low /usr/local/
2. Don't remeber exactly how to reproduce :
"""
Traceback (most recent call last):
File "/usr/local/
self.
File "/usr/local/
choose_
NameError: global name 'choose_row_error' is not defined
Traceback (most recent call last):
File "/usr/local/
self.
File "/usr/local/
choose_
NameError: global name 'choose_row_error' is not defined
"""
One more time excellent work David!
It need a few 5 min patches, but a lot is done.
Thanks!
David Caro (dcaro) wrote : | # |
Hi Marek! Thanks for the fast review!
New errors:
1. Very strange, what it does is get the list of allowed fonts in the system and if the font is not there, tries to guess the system font. Can you try to execute this on a python console and pass me the results?
------ Get the default system font -------
import gconf
gconf_cli = gconf.client_
value = gconf_cli.
value.get_string()
----- Show available fonts -----------
import gtk
families = gtk.TextView(
[fam.get_name() for fam in families]
.... OK, now i've seen where is the error, when parsing the font name, I get the first part of the space separated font as the name and the second as the size, so for ubuntu mono 10, ubuntu is the font and mono 10 the size XD -- Fixed (revision 102)
Thanks!
2. They work for me :(, the should open a new tab (or window) on the current web browser with the info page of the command (just like the help-online menu option), can you try to execute this on a python console and see what happens? It should open a new tab...
import webbrowser
webbrowser.open('https:/
Regresion bugs:
1. This is caused by the new tooltips, when the command has something that can be interpreted as markup by the tooltip class (a & for example), it shows this warning. It's not stright forward to fix this, and is commentesd in the commit message. But i'm going to open a new bug report to avoid it getting lost. Can you pass me your cheatsheet file to see what command makes the warning show up? (i've seen it with &, but the error is slightly different).
2. This is caused when trying to show the man page for a command when no command is selected (usually after adding a new command), fixed (revision 101)
David Caro (dcaro) wrote : | # |
I forgot!
Bug #673556: tabs should be renameable [Sorry, I don't see that tab can be renamed] -- right click in the terminal zone and the option 'rename' in the menu
Marek Bardoński (bdfhjk) wrote : | # |
I noticed, that icons work after double-click. So it is OK, but in my opinion single click will be more intuitive. But this is only my opinion.
The rest of bugs are fixed.
Good work!
Preview Diff
1 | === modified file '.bzrignore' | |||
2 | --- .bzrignore 2010-08-22 00:14:56 +0000 | |||
3 | +++ .bzrignore 2012-01-08 10:37:24 +0000 | |||
4 | @@ -3,3 +3,6 @@ | |||
5 | 3 | .ropeproject/globalnames | 3 | .ropeproject/globalnames |
6 | 4 | .ropeproject/history | 4 | .ropeproject/history |
7 | 5 | .ropeproject/objectdb | 5 | .ropeproject/objectdb |
8 | 6 | ./tags | ||
9 | 7 | ./build | ||
10 | 8 | *~ | ||
11 | 6 | 9 | ||
12 | === modified file 'MANIFEST' | |||
13 | --- MANIFEST 2011-11-18 08:58:05 +0000 | |||
14 | +++ MANIFEST 2012-01-08 10:37:24 +0000 | |||
15 | @@ -1,8 +1,11 @@ | |||
16 | 1 | setup.py | 1 | setup.py |
17 | 2 | clicompanion | 2 | clicompanion |
18 | 3 | clicompanionlib/__init__.py | 3 | clicompanionlib/__init__.py |
20 | 4 | clicompanionlib/controller.py | 4 | clicompanionlib/config.py |
21 | 5 | clicompanionlib/helpers.py | ||
22 | 5 | clicompanionlib/menus_buttons.py | 6 | clicompanionlib/menus_buttons.py |
23 | 7 | clicompanionlib/plugins.py | ||
24 | 8 | clicompanionlib/preferences.py | ||
25 | 6 | clicompanionlib/tabs.py | 9 | clicompanionlib/tabs.py |
26 | 7 | clicompanionlib/utils.py | 10 | clicompanionlib/utils.py |
27 | 8 | clicompanionlib/view.py | 11 | clicompanionlib/view.py |
28 | @@ -10,3 +13,8 @@ | |||
29 | 10 | data/clicompanion2.config | 13 | data/clicompanion2.config |
30 | 11 | data/clicompanion.16.png | 14 | data/clicompanion.16.png |
31 | 12 | data/clicompanion.64.png | 15 | data/clicompanion.64.png |
32 | 16 | plugins/clfu.py | ||
33 | 17 | plugins/CommandLineFU.py | ||
34 | 18 | plugins/__init__.py | ||
35 | 19 | plugins/LocalCommandList.py | ||
36 | 20 | |||
37 | 13 | 21 | ||
38 | === modified file 'clicompanion' | |||
39 | --- clicompanion 2012-01-02 00:23:11 +0000 | |||
40 | +++ clicompanion 2012-01-08 10:37:24 +0000 | |||
41 | @@ -25,13 +25,12 @@ | |||
42 | 25 | 25 | ||
43 | 26 | 26 | ||
44 | 27 | parser = OptionParser(usage="%prog [-f] [-q]", version="%prog 1.1") | 27 | parser = OptionParser(usage="%prog [-f] [-q]", version="%prog 1.1") |
47 | 28 | parser.add_option("-f", "--file", dest="filename", | 28 | parser.add_option("-f", "--file", dest="conffile", |
48 | 29 | help="Write report to FILE", metavar="FILE") | 29 | help="Configuration file to use.", metavar="FILE") |
49 | 30 | parser.add_option("-c", "--cheatsheet", dest="cheatsheet", | 30 | parser.add_option("-c", "--cheatsheet", dest="cheatsheet", |
50 | 31 | help="Read cheatsheet from FILE", metavar="FILE") | 31 | help="Read cheatsheet from FILE", metavar="FILE") |
54 | 32 | parser.add_option("-q", "--quiet", | 32 | parser.add_option("-d", "--debug", dest="debug", action='store_true', |
55 | 33 | action="store_false", dest="verbose", default=True, | 33 | default=False, help="Print debug messages",) |
53 | 34 | help="Don't print status messages to stdout") | ||
56 | 35 | 34 | ||
57 | 36 | (options, args) = parser.parse_args() | 35 | (options, args) = parser.parse_args() |
58 | 37 | 36 | ||
59 | 38 | 37 | ||
60 | === modified file 'clicompanionlib/__init__.py' | |||
61 | --- clicompanionlib/__init__.py 2010-11-30 16:03:59 +0000 | |||
62 | +++ clicompanionlib/__init__.py 2012-01-08 10:37:24 +0000 | |||
63 | @@ -1,7 +1,7 @@ | |||
64 | 1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python |
65 | 2 | # -*- coding: utf-8 -*- | 2 | # -*- coding: utf-8 -*- |
66 | 3 | # | 3 | # |
68 | 4 | # clicompanion.py - commandline tool. | 4 | # __init__.py |
69 | 5 | # | 5 | # |
70 | 6 | # Copyright 2010 Duane Hinnen, Kenny Meyer | 6 | # Copyright 2010 Duane Hinnen, Kenny Meyer |
71 | 7 | # | 7 | # |
72 | 8 | 8 | ||
73 | === modified file 'clicompanionlib/config.py' | |||
74 | --- clicompanionlib/config.py 2012-01-02 00:23:11 +0000 | |||
75 | +++ clicompanionlib/config.py 2012-01-08 10:37:24 +0000 | |||
76 | @@ -1,9 +1,9 @@ | |||
77 | 1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python |
78 | 2 | # -*- coding: utf-8 -*- | 2 | # -*- coding: utf-8 -*- |
79 | 3 | # | 3 | # |
81 | 4 | # clicompanion.py - commandline tool. | 4 | # config.py - Configuration classes for the clicompanion |
82 | 5 | # | 5 | # |
84 | 6 | # Copyright 2010 Duane Hinnen | 6 | # Copyright 2012 David Caro <david.caro.estevez@gmail.com> |
85 | 7 | # | 7 | # |
86 | 8 | # This program is free software: you can redistribute it and/or modify it | 8 | # This program is free software: you can redistribute it and/or modify it |
87 | 9 | # under the terms of the GNU General Public License version 3, as published | 9 | # under the terms of the GNU General Public License version 3, as published |
88 | @@ -18,246 +18,323 @@ | |||
89 | 18 | # with this program. If not, see <http://www.gnu.org/licenses/>. | 18 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
90 | 19 | # | 19 | # |
91 | 20 | # | 20 | # |
92 | 21 | # This file has the CLIConfig class definition, and the CLIConfigView, the | ||
93 | 22 | # first is the main configuration model of the progran, where all the config | ||
94 | 23 | # is stored and processed to be correct, also sets up all the required | ||
95 | 24 | # configuration (default sections and keybindings) if they are not set. | ||
96 | 25 | # | ||
97 | 26 | # The CLIConfigViewer, is something similar to a view in MySQL, is an object | ||
98 | 27 | # that has the same (almos all) methods than the normal CLIConfig, but only | ||
99 | 28 | # shows a part of it, used to allow the plugins to handle their own | ||
100 | 29 | # configurations (a prefix will be added, like the 'profiles::' prefix or the | ||
101 | 30 | # name of the plugin that stores the config). | ||
102 | 31 | |||
103 | 32 | |||
104 | 21 | import os | 33 | import os |
105 | 22 | import ConfigParser | 34 | import ConfigParser |
107 | 23 | import clicompanionlib.utils as utils | 35 | import collections |
108 | 36 | import gtk | ||
109 | 37 | import pango | ||
110 | 38 | import clicompanionlib.utils as cc_utils | ||
111 | 24 | from clicompanionlib.utils import dbg | 39 | from clicompanionlib.utils import dbg |
112 | 25 | import collections | ||
113 | 26 | 40 | ||
114 | 27 | CHEATSHEET = os.path.expanduser("~/.clicompanion2") | ||
115 | 28 | CONFIGDIR = os.path.expanduser("~/.config/clicompanion/") | 41 | CONFIGDIR = os.path.expanduser("~/.config/clicompanion/") |
116 | 29 | CONFIGFILE = os.path.expanduser("~/.config/clicompanion/config") | 42 | CONFIGFILE = os.path.expanduser("~/.config/clicompanion/config") |
117 | 30 | CONFIG_ORIG = "/etc/clicompanion.d/clicompanion2.config" | 43 | CONFIG_ORIG = "/etc/clicompanion.d/clicompanion2.config" |
296 | 31 | DEFAULTS = { "scrollb": '500', | 44 | |
297 | 32 | "colorf": '#FFFFFF', | 45 | |
298 | 33 | "colorb": '#000000', | 46 | ## All the options (except keybindings) passed as name: (default, test), where |
299 | 34 | "encoding": 'UTF-8', | 47 | ## test can be one of 'bool', 'str', 'encoding', 'font', or a function to test |
300 | 35 | "debug": 'False'} | 48 | ## the value (the function must throw an exception on fail) |
301 | 36 | 49 | DEFAULTS = {'profile': {"scrollb": ('500', 'int'), | |
302 | 37 | ## To avoid parsing the config file each time, we store the loaded config here | 50 | "color_scheme": ("Custom", 'str'), |
303 | 38 | CONFIG = None | 51 | "colorf": ('#FFFFFF', gtk.gdk.color_parse), |
304 | 39 | 52 | "colorb": ('#000000', gtk.gdk.color_parse), | |
305 | 40 | def create_config(conffile=CONFIGFILE): | 53 | "use_system_colors": ("False", 'bool'), |
306 | 41 | global CONFIG | 54 | "encoding": ('UTF-8', 'encoding'), |
307 | 42 | config = CONFIG | 55 | "font": (cc_utils.get_system_font(), 'font'), |
308 | 43 | configdir = conffile.rsplit(os.sep,1)[0] | 56 | "use_system_font": ("False", 'bool'), |
309 | 44 | if not os.path.exists(configdir): | 57 | "use_system_colors": ("False", 'bool'), |
310 | 45 | try: | 58 | "bold_text": ("False", 'bool'), |
311 | 46 | os.makedirs(configdir) | 59 | "antialias": ("True", 'bool'), |
312 | 47 | except Exception, e: | 60 | "sel_word": (u"-A-Za-z0-9,./?%&#:_", 'str'), |
313 | 48 | print _('Unable to create config at dir %s (%s)')%(configdir,e) | 61 | "update_login_records": ("True", 'bool'), |
314 | 49 | return False | 62 | }, |
315 | 50 | # reuse the config if able | 63 | 'general': {"debug": ('False', 'bool'), |
316 | 51 | if not config: | 64 | "plugins": ('LocalCommandList, CommandLineFU', 'str') |
317 | 52 | config = ConfigParser.SafeConfigParser(DEFAULTS) | 65 | }, |
318 | 53 | # set a number of parameters | 66 | 'LocalCommandList': {"cheatsheet": |
319 | 54 | if os.path.isfile(conffile): | 67 | (os.path.expanduser("~/.clicompanion2"), 'str'), |
320 | 55 | config.read([conffile]) | 68 | }, |
321 | 56 | else: | 69 | } |
322 | 57 | config.add_section("terminal") | 70 | |
323 | 58 | for option, value in DEFAULTS.items(): | 71 | ## Note that the modifiers must be specified as 'mod1+mod2+key', where the |
324 | 59 | config.set("terminal", option, value) | 72 | ## modifiers are 'shift', 'alt','ctrl', in that order (shift+alt+ctrl+key), and |
325 | 60 | CONFIG = config | 73 | ## that the key pressed is the key affecteed by the modifiers, for example, |
326 | 61 | # Writing our configuration file | 74 | ## shift+ctrl+D (not shift+ctrl+d). And the function keys go uppercase (F10). |
327 | 62 | save_config(config, conffile) | 75 | DEFAULT_KEY_BINDINGS = { |
328 | 63 | print _("INFO: Created config file at %s.")%conffile | 76 | 'run_command': 'F4', |
329 | 64 | return config | 77 | 'add_command': 'F5', |
330 | 65 | 78 | 'remove_command': 'F6', | |
331 | 66 | 79 | 'edit_command': 'unused', | |
332 | 67 | def get_config_copy(config=None): | 80 | 'add_tab': 'F7', |
333 | 68 | global CONFIG | 81 | 'close_tab': 'unused', |
334 | 69 | if not config: | 82 | 'toggle_fullscreen': 'F12', |
335 | 70 | config = CONFIG | 83 | 'toggle_maximize': 'F11', |
336 | 71 | new_cfg = ConfigParser.SafeConfigParser(DEFAULTS) | 84 | 'toggle_hide_ui': 'F9', |
337 | 72 | for section in config.sections(): | 85 | } |
338 | 73 | new_cfg.add_section(section) | 86 | |
339 | 74 | for option in config.options(section): | 87 | ### funcname : labelname |
340 | 75 | new_cfg.set(section, option, config.get(section, option)) | 88 | ## a function with the name funcname and signature void(void) must exist in the |
341 | 76 | return new_cfg | 89 | ## main window class, and is the one that will be called when the keybinding is |
342 | 77 | 90 | ## actibated | |
343 | 78 | 91 | KEY_BINDINGS = { | |
344 | 79 | def get_config(conffile=CONFIGFILE, confdir=CONFIGDIR): | 92 | 'run_command': 'Run command', |
345 | 80 | global CONFIG | 93 | 'add_command': 'Add command', |
346 | 81 | config = CONFIG | 94 | 'remove_command': 'Remove command', |
347 | 82 | if not config: | 95 | 'edit_command': 'Edit command', |
348 | 83 | dbg('Loading new config') | 96 | 'add_tab': 'Add tab', |
349 | 84 | if not os.path.isfile(conffile): | 97 | 'close_tab': 'Close tab', |
350 | 85 | config = create_config(conffile) | 98 | 'toggle_fullscreen': 'Toggle fullscreen', |
351 | 86 | config = ConfigParser.SafeConfigParser(DEFAULTS) | 99 | 'toggle_maximize': 'Maximize', |
352 | 87 | config.add_section("terminal") | 100 | 'toggle_hide_ui': 'Hide UI', |
353 | 88 | config.read([conffile]) | 101 | } |
354 | 89 | CONFIG = config | 102 | |
355 | 90 | else: | 103 | |
356 | 91 | dbg('Reusing already loaded config') | 104 | class CLIConfig(ConfigParser.RawConfigParser): |
357 | 92 | return config | 105 | def __init__(self, defaults=DEFAULTS, conffile=CONFIGFILE): |
358 | 93 | 106 | ConfigParser.RawConfigParser.__init__(self) | |
359 | 94 | 107 | self.conffile = os.path.abspath(conffile) | |
360 | 95 | def save_config(config, conffile=CONFIGFILE): | 108 | configdir = self.conffile.rsplit(os.sep, 1)[0] |
361 | 96 | global CONFIG | 109 | if not os.path.exists(configdir): |
184 | 97 | dbg('Saving conffile at %s'%conffile) | ||
185 | 98 | with open(CONFIGFILE, 'wb') as f: | ||
186 | 99 | config.write(f) | ||
187 | 100 | CONFIG = config | ||
188 | 101 | |||
189 | 102 | class Cheatsheet: | ||
190 | 103 | ''' | ||
191 | 104 | comtainer class for the cheatsheet | ||
192 | 105 | |||
193 | 106 | Example of usage: | ||
194 | 107 | >>> c = config.Cheatsheet() | ||
195 | 108 | >>> c.load('/home/cascara/.clicompanion2') | ||
196 | 109 | >>> c[3] | ||
197 | 110 | ['uname -a', '', 'What kernel am I running\n'] | ||
198 | 111 | >>> c.file | ||
199 | 112 | '/home/cascara/.clicompanion2' | ||
200 | 113 | >>> c[2]=[ 'mycmd', 'userui', 'desc' ] | ||
201 | 114 | >>> c[2] | ||
202 | 115 | ['mycmd', 'userui', 'desc'] | ||
203 | 116 | >>> del c[2] | ||
204 | 117 | >>> c[2] | ||
205 | 118 | ['ps aux | grep ?', 'search string', 'Search active processes for search string\n'] | ||
206 | 119 | >>> c.insert('cmd2','ui2','desc2',2) | ||
207 | 120 | >>> c[2] | ||
208 | 121 | ['cmd2', 'ui2', 'desc2'] | ||
209 | 122 | |||
210 | 123 | ''' | ||
211 | 124 | def __init__(self): | ||
212 | 125 | self.file = CHEATSHEET | ||
213 | 126 | self.commands = [] | ||
214 | 127 | |||
215 | 128 | def __repr__(self): | ||
216 | 129 | return 'Config: %s - %s'%(self.file, self.commands) | ||
217 | 130 | |||
218 | 131 | def load(self, cheatfile=None): | ||
219 | 132 | if not cheatfile: | ||
220 | 133 | self.file = CHEATSHEET | ||
221 | 134 | if not os.path.exists(CHEATSHEET): | ||
222 | 135 | if os.path.exists(CONFIG_ORIG): | ||
223 | 136 | os.system ("cp %s %s" % (CONFIG_ORIG, CHEATSHEET)) | ||
224 | 137 | else: | ||
225 | 138 | # Oops! Looks like there's no default cheatsheet. | ||
226 | 139 | # Then, create an empty cheatsheet. | ||
227 | 140 | open(CHEATSHEET, 'w').close() | ||
228 | 141 | else: | ||
229 | 142 | self.file = cheatfile | ||
230 | 143 | try: | ||
231 | 144 | dbg('Reading cheatsheet from file %s'%self.file) | ||
232 | 145 | with open(self.file, 'r') as ch_fd: | ||
233 | 146 | ## try to detect if the line is a old fashines config line | ||
234 | 147 | ## (separated by ':'), when saved will rewrite it | ||
235 | 148 | no_tabs = True | ||
236 | 149 | some_colon = False | ||
237 | 150 | for line in ch_fd: | ||
238 | 151 | line = line.strip() | ||
239 | 152 | if not line: | ||
240 | 153 | continue | ||
241 | 154 | cmd, ui, desc = [ l.strip() for l in line.split('\t',2)] \ | ||
242 | 155 | + ['',]*(3-len(line.split('\t',2))) | ||
243 | 156 | if ':' in cmd: | ||
244 | 157 | some_colon = True | ||
245 | 158 | if ui or desc: | ||
246 | 159 | no_tabs = False | ||
247 | 160 | if cmd and [ cmd, ui, desc ] not in self.commands: | ||
248 | 161 | self.commands.append([cmd, ui, desc]) | ||
249 | 162 | dbg('Adding command %s'%[cmd, ui, desc]) | ||
250 | 163 | if no_tabs and some_colon: | ||
251 | 164 | ## None of the commands had tabs, and all had ':' in the | ||
252 | 165 | ## cmd... most probably old config style | ||
253 | 166 | print _("Detected old cheatsheet style at")\ | ||
254 | 167 | +" %s"%self.file+_(", parsing to new one.") | ||
255 | 168 | for i in range(len(self.commands)): | ||
256 | 169 | cmd, ui, desc = self.commands[i] | ||
257 | 170 | cmd, ui, desc = [ l.strip() for l in cmd.split(':',2)] \ | ||
258 | 171 | + ['',]*(3-len(cmd.split(':',2))) | ||
259 | 172 | self.commands[i] = [cmd, ui, desc] | ||
260 | 173 | self.save() | ||
261 | 174 | except IOError, e: | ||
262 | 175 | print _("Error while loading cheatfile")+" %s: %s"%(self.file, e) | ||
263 | 176 | |||
264 | 177 | def save(self, cheatfile=None): | ||
265 | 178 | ''' | ||
266 | 179 | Saves the current config to the file cheatfile, or the file that was | ||
267 | 180 | loaded. | ||
268 | 181 | NOTE: It does not overwrite the value self.file, that points to the file | ||
269 | 182 | that was loaded | ||
270 | 183 | ''' | ||
271 | 184 | if not cheatfile and self.file: | ||
272 | 185 | cheatfile = self.file | ||
273 | 186 | elif not cheatfile: | ||
274 | 187 | return False | ||
275 | 188 | try: | ||
276 | 189 | with open(cheatfile, 'wb') as ch_fd: | ||
277 | 190 | for command in self.commands: | ||
278 | 191 | ch_fd.write('\t'.join(command)+'\n') | ||
279 | 192 | except IOError, e: | ||
280 | 193 | print _("Error writing cheatfile")+" %s: %s"%(cheatfile, e) | ||
281 | 194 | return False | ||
282 | 195 | return True | ||
283 | 196 | |||
284 | 197 | def __len__(self): | ||
285 | 198 | return len(self.commands) | ||
286 | 199 | |||
287 | 200 | def __getitem__(self, key): | ||
288 | 201 | return self.commands[key] | ||
289 | 202 | |||
290 | 203 | def __setitem__(self, key, value): | ||
291 | 204 | if not isinstance(value, collections.Iterable) or len(value) < 3: | ||
292 | 205 | raise ValueError('Value must be a container with three items, but got %s'%value) | ||
293 | 206 | if key < len(self.commands): | ||
294 | 207 | self.commands[key]=list(value) | ||
295 | 208 | else: | ||
362 | 209 | try: | 110 | try: |
417 | 210 | self.insert(*value, pos=key) | 111 | os.makedirs(configdir) |
418 | 211 | except ValueError, e: | 112 | except Exception, e: |
419 | 212 | raise ValueError('Value must be a container with three items, but got %s'%value) | 113 | print _('Unable to create config at dir %s (%s)') \ |
420 | 213 | 114 | % (configdir, e) | |
421 | 214 | def __iter__(self): | 115 | return False |
422 | 215 | for command in self.commands: | 116 | # set a number of default parameters, and fill the missing ones |
423 | 216 | yield command | 117 | if os.path.isfile(self.conffile): |
424 | 217 | 118 | self.read([self.conffile]) | |
425 | 218 | def insert(self, cmd, ui, desc, pos=None): | 119 | print _("INFO: Reading config file at %s.") % self.conffile |
426 | 219 | if not [cmd, ui, desc] in self.commands: | 120 | else: |
427 | 220 | if not pos: | 121 | print _("INFO: Creating config file at %s.") % self.conffile |
428 | 221 | self.commands.append([cmd, ui, desc]) | 122 | |
429 | 222 | else: | 123 | for section in DEFAULTS.keys(): |
430 | 223 | self.commands.insert(pos, [cmd, ui, desc]) | 124 | fullsection = section + '::default' |
431 | 224 | 125 | ## Set default profile options | |
432 | 225 | def append(self, cmd, ui, desc): | 126 | if fullsection not in self.sections(): |
433 | 226 | self.insert(cmd, ui, desc) | 127 | self.add_section(fullsection) |
434 | 227 | 128 | for option, optdesc in DEFAULTS[section].items(): | |
435 | 228 | def index(self, cmd, ui, value): | 129 | value, test = optdesc |
436 | 229 | return self.commands.index([cmd, ui, desc]) | 130 | self.set(fullsection, option, value) |
437 | 230 | 131 | ## Set default keybindings | |
438 | 231 | def __delitem__(self, key): | 132 | if 'keybindings' not in self.sections(): |
439 | 232 | del self.commands[key] | 133 | self.add_section("keybindings") |
440 | 233 | 134 | for option, value in DEFAULT_KEY_BINDINGS.items(): | |
441 | 234 | def pop(self, key): | 135 | if not self.has_option('keybindings', option): |
442 | 235 | return self.commands.pop(key) | 136 | self.set('keybindings', option, value) |
443 | 236 | 137 | self.parse() | |
444 | 237 | def del_by_value(self, cmd, ui, desc): | 138 | # Writing our configuration file |
445 | 238 | if [cmd, ui, desc] in self.commands: | 139 | self.save() |
446 | 239 | return self.commands.pop(self.commands.index([cmd, ui, desc])) | 140 | |
447 | 240 | 141 | def parse(self): | |
448 | 241 | def drag_n_drop(self, cmd1, cmd2, before=True): | 142 | ## clean the default options to avoid seeing options where they are not |
449 | 242 | if cmd1 in self.commands: | 143 | for option in self.defaults().keys(): |
450 | 243 | dbg('Dropping command from inside %s'%'_\t_'.join(cmd1)) | 144 | self.remove_option('DEFAULT', option) |
451 | 244 | i1 = self.commands.index(cmd1) | 145 | ## now parse the rest of sections |
452 | 245 | del self.commands[i1] | 146 | for section in self.sections(): |
453 | 246 | if cmd2: | 147 | for option in self.options(section): |
454 | 247 | i2 = self.commands.index(cmd2) | 148 | if section == 'keybindings': |
455 | 248 | if before: | 149 | if option not in KEY_BINDINGS.keys(): |
456 | 249 | self.commands.insert(i2, cmd1) | 150 | print _("Option %s:%s not recognised, deleting." \ |
457 | 250 | else: | 151 | % (section, option)) |
458 | 251 | self.commands.insert(i2+1, cmd1) | 152 | self.remove_option(section, option) |
459 | 252 | else: | 153 | else: |
460 | 253 | self.commands.append(cmd1) | 154 | if not '::' in section: |
461 | 254 | else: | 155 | print _("Deleting unrecognzed section %s." % section) |
462 | 255 | dbg('Dropping command from outside %s'%'_\t_'.join(cmd1)) | 156 | self.remove_section(section) |
463 | 256 | if cmd2: | 157 | break |
464 | 257 | i2 = self.commands.index(cmd2) | 158 | secttype = section.split('::')[0] |
465 | 258 | if before: | 159 | if secttype not in DEFAULTS: |
466 | 259 | self.commands.insert(i2, cmd1) | 160 | print _("Deleting unrecognized section %s." % section) |
467 | 260 | else: | 161 | self.remove_section(section) |
468 | 261 | self.commands.insert(i2+1, cmd1) | 162 | break |
469 | 262 | else: | 163 | if option not in DEFAULTS[secttype].keys(): |
470 | 263 | self.commands.append(cmd1) | 164 | print _("Option %s:%s not recognised, deleting." \ |
471 | 165 | % (section, option)) | ||
472 | 166 | self.remove_option(section, option) | ||
473 | 167 | else: | ||
474 | 168 | val = self.get(section, option) | ||
475 | 169 | defval, test = DEFAULTS[secttype][option] | ||
476 | 170 | try: | ||
477 | 171 | if test == 'str': | ||
478 | 172 | continue | ||
479 | 173 | elif test == 'int': | ||
480 | 174 | res = self.getint(section, option) | ||
481 | 175 | elif test == 'bool': | ||
482 | 176 | res = self.getboolean(section, option) | ||
483 | 177 | elif test == 'encoding': | ||
484 | 178 | if val.lower() not in [enc.lower() | ||
485 | 179 | for enc, desc | ||
486 | 180 | in cc_utils.encodings]: | ||
487 | 181 | raise ValueError( | ||
488 | 182 | _('Option %s is not valid.') % test) | ||
489 | 183 | elif test == 'font': | ||
490 | 184 | fname, fsize = val.rsplit(' ', 1) | ||
491 | 185 | fsize = int(fsize) | ||
492 | 186 | cont = gtk.TextView().create_pango_context() | ||
493 | 187 | avail_fonts = cont.list_families() | ||
494 | 188 | found = False | ||
495 | 189 | for font in avail_fonts: | ||
496 | 190 | if fname == font.get_name(): | ||
497 | 191 | found = True | ||
498 | 192 | break | ||
499 | 193 | if not found: | ||
500 | 194 | raise ValueError( | ||
501 | 195 | _('Option %s is not valid.') % type) | ||
502 | 196 | elif callable(test): | ||
503 | 197 | res = test(val) | ||
504 | 198 | if not res: | ||
505 | 199 | raise Exception | ||
506 | 200 | else: | ||
507 | 201 | print _("Wrong specification for " | ||
508 | 202 | "option %s in file %s") \ | ||
509 | 203 | % (option, __file__) | ||
510 | 204 | except Exception, e: | ||
511 | 205 | print (_('ERROR: Wrong config value for %s: %s ') \ | ||
512 | 206 | % (option, val) + | ||
513 | 207 | _(',using default one %s.') % defval) | ||
514 | 208 | self.set(section, option, defval) | ||
515 | 209 | |||
516 | 210 | def set(self, section, option, value): | ||
517 | 211 | if section == 'DEFAULT': | ||
518 | 212 | raise ConfigParser.NoSectionError( | ||
519 | 213 | 'Section "DEFAULT" is not allowed. Use section ' | ||
520 | 214 | '"TYPE::default instead"') | ||
521 | 215 | else: | ||
522 | 216 | return ConfigParser.RawConfigParser.set(self, section, | ||
523 | 217 | option, value) | ||
524 | 218 | |||
525 | 219 | def get(self, section, option): | ||
526 | 220 | if '::' in section: | ||
527 | 221 | sectiontag = section.split('::')[0] | ||
528 | 222 | if not self.has_option(section, option): | ||
529 | 223 | if not self.has_option(sectiontag + '::default', option): | ||
530 | 224 | raise ConfigParser.NoOptionError(option, section) | ||
531 | 225 | return ConfigParser.RawConfigParser.get(self, | ||
532 | 226 | sectiontag + '::default', option) | ||
533 | 227 | elif not self.has_option(section, option): | ||
534 | 228 | raise ConfigParser.NoOptionError(option, section) | ||
535 | 229 | return ConfigParser.RawConfigParser.get(self, section, option) | ||
536 | 230 | |||
537 | 231 | def get_config_copy(self): | ||
538 | 232 | new_cfg = CLIConfig(DEFAULTS) | ||
539 | 233 | for section in self.sections(): | ||
540 | 234 | if section not in new_cfg.sections(): | ||
541 | 235 | new_cfg.add_section(section) | ||
542 | 236 | for option in self.options(section): | ||
543 | 237 | new_cfg.set(section, option, self.get(section, option)) | ||
544 | 238 | return new_cfg | ||
545 | 239 | |||
546 | 240 | def save(self, conffile=None): | ||
547 | 241 | if not conffile: | ||
548 | 242 | conffile = self.conffile | ||
549 | 243 | dbg('Saving conffile at %s' % conffile) | ||
550 | 244 | with open(conffile, 'wb') as f: | ||
551 | 245 | self.write(f) | ||
552 | 246 | |||
553 | 247 | def get_plugin_conf(self, plugin): | ||
554 | 248 | return CLIConfigView(plugin, self) | ||
555 | 249 | |||
556 | 250 | |||
557 | 251 | class CLIConfigView(): | ||
558 | 252 | ''' | ||
559 | 253 | This class implements an editable view (hiding unwanted options) of the | ||
560 | 254 | CLIConfig class, for example, to avoid the plugins editing other options | ||
561 | 255 | but their own, some methods of the configRaw Parser are not implemented. | ||
562 | 256 | ''' | ||
563 | 257 | def __init__(self, sectionkey, config): | ||
564 | 258 | self.key = sectionkey + '::' | ||
565 | 259 | self._config = config | ||
566 | 260 | |||
567 | 261 | def get(self, section, option): | ||
568 | 262 | if section == 'DEFAULT': | ||
569 | 263 | section = 'default' | ||
570 | 264 | if self.key + section not in self._config.sections(): | ||
571 | 265 | raise ConfigParser.NoSectionError( | ||
572 | 266 | 'The section %s does not exist.' % section) | ||
573 | 267 | return self._config.get(self.key + section, option) | ||
574 | 268 | |||
575 | 269 | def getint(self, section, option): | ||
576 | 270 | return self._config.getint(self.key + section, option) | ||
577 | 271 | |||
578 | 272 | def getfloat(self, section, option): | ||
579 | 273 | return self._config.getfloat(self.key + section, option) | ||
580 | 274 | |||
581 | 275 | def getboolean(self, section, option): | ||
582 | 276 | return self._config.getboolean(self.key + section, option) | ||
583 | 277 | |||
584 | 278 | def items(self, section): | ||
585 | 279 | return self._config.items(self.key + section) | ||
586 | 280 | |||
587 | 281 | def write(self, fileobject): | ||
588 | 282 | pass | ||
589 | 283 | |||
590 | 284 | def remove_option(self, section, option): | ||
591 | 285 | return self._config.remove_option(self.key + section, option) | ||
592 | 286 | |||
593 | 287 | def optionxform(self, option): | ||
594 | 288 | pass | ||
595 | 289 | |||
596 | 290 | def set(self, section, option, value): | ||
597 | 291 | if section == 'DEFAULT': | ||
598 | 292 | section = 'default' | ||
599 | 293 | if self.key + section not in self._config.sections(): | ||
600 | 294 | raise ConfigParser.NoSectionError( | ||
601 | 295 | 'The section %s does not exist.' % section) | ||
602 | 296 | return self._config.set(self.key + section, option, value) | ||
603 | 297 | |||
604 | 298 | def add_section(self, section): | ||
605 | 299 | return self._config.add_section(self.key + section) | ||
606 | 300 | |||
607 | 301 | def remove_section(self, section): | ||
608 | 302 | return self._config.remove_section(self.key + section) | ||
609 | 303 | |||
610 | 304 | def sections(self): | ||
611 | 305 | sections = [] | ||
612 | 306 | for section in self._config.sections(): | ||
613 | 307 | if section.startswith(self.key): | ||
614 | 308 | sections.append(section.split('::', 1)[1]) | ||
615 | 309 | return sections | ||
616 | 310 | |||
617 | 311 | def options(self, section): | ||
618 | 312 | return self._config.options(self.key + section) | ||
619 | 313 | |||
620 | 314 | def defaults(self): | ||
621 | 315 | return self._config.options(self.key + 'default') | ||
622 | 316 | |||
623 | 317 | def has_section(self, section): | ||
624 | 318 | return self._config.has_section(self.key + section) | ||
625 | 319 | |||
626 | 320 | def has_option(self, section, option): | ||
627 | 321 | return self._config.has_option(self.key + section, option) | ||
628 | 322 | |||
629 | 323 | def readfp(self, filedesc, name='<???>'): | ||
630 | 324 | tempconf = ConfigParser.RawConfigParser() | ||
631 | 325 | tempconf.readfp(filedesc, name) | ||
632 | 326 | for option in tempconf.defaults(): | ||
633 | 327 | self.set('DEFAULT', option, tempconf.get('DEFAULT', option)) | ||
634 | 328 | for section in tempconf.sections(): | ||
635 | 329 | if not self.has_section(section): | ||
636 | 330 | self.add_section(section) | ||
637 | 331 | for option in tempconf.options(): | ||
638 | 332 | self.set(section, option) | ||
639 | 333 | |||
640 | 334 | def read(self, files): | ||
641 | 335 | for file in files: | ||
642 | 336 | with open(file, 'r') as fd: | ||
643 | 337 | self.readfp(fd) | ||
644 | 338 | |||
645 | 339 | def save(self): | ||
646 | 340 | self._config.save() | ||
647 | 264 | 341 | ||
648 | === removed file 'clicompanionlib/controller.py' | |||
649 | --- clicompanionlib/controller.py 2012-01-02 00:23:11 +0000 | |||
650 | +++ clicompanionlib/controller.py 1970-01-01 00:00:00 +0000 | |||
651 | @@ -1,665 +0,0 @@ | |||
652 | 1 | #!/usr/bin/env python | ||
653 | 2 | # -*- coding: utf-8 -*- | ||
654 | 3 | # | ||
655 | 4 | # clicompanion - commandline tool. | ||
656 | 5 | # | ||
657 | 6 | # Copyright 2010 Duane Hinnen, Kenny Meyer | ||
658 | 7 | # | ||
659 | 8 | # This program is free software: you can redistribute it and/or modify it | ||
660 | 9 | # under the terms of the GNU General Public License version 3, as published | ||
661 | 10 | # by the Free Software Foundation. | ||
662 | 11 | # | ||
663 | 12 | # This program is distributed in the hope that it will be useful, but | ||
664 | 13 | # WITHOUT ANY WARRANTY; without even the implied warranties of | ||
665 | 14 | # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
666 | 15 | # PURPOSE. See the GNU General Public License for more details. | ||
667 | 16 | # | ||
668 | 17 | # You should have received a copy of the GNU General Public License along | ||
669 | 18 | # with this program. If not, see <http://www.gnu.org/licenses/>. | ||
670 | 19 | # | ||
671 | 20 | # | ||
672 | 21 | |||
673 | 22 | import os | ||
674 | 23 | import pygtk | ||
675 | 24 | pygtk.require('2.0') | ||
676 | 25 | import re | ||
677 | 26 | import webbrowser | ||
678 | 27 | import view | ||
679 | 28 | import copy | ||
680 | 29 | import clicompanionlib.tabs | ||
681 | 30 | import clicompanionlib.config as cc_config | ||
682 | 31 | import clicompanionlib.utils as utils | ||
683 | 32 | from clicompanionlib.utils import get_user_shell, dbg | ||
684 | 33 | |||
685 | 34 | #if cc_config.get_config().get('terminal','debug') == 'True': | ||
686 | 35 | # utils.DEBUG = True | ||
687 | 36 | |||
688 | 37 | # import vte and gtk or print error | ||
689 | 38 | try: | ||
690 | 39 | import gtk | ||
691 | 40 | except: | ||
692 | 41 | error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, | ||
693 | 42 | _("You need to install the python gtk bindings package 'python-gtk2'")) | ||
694 | 43 | error.run() | ||
695 | 44 | sys.exit (1) | ||
696 | 45 | |||
697 | 46 | try: | ||
698 | 47 | import vte | ||
699 | 48 | except: | ||
700 | 49 | error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, | ||
701 | 50 | _("You need to install 'python-vte' the python bindings for libvte.")) | ||
702 | 51 | error.run() | ||
703 | 52 | sys.exit (1) | ||
704 | 53 | |||
705 | 54 | |||
706 | 55 | |||
707 | 56 | class Actions(object): | ||
708 | 57 | ## Info Dialog Box | ||
709 | 58 | ## if a command needs more info EX: a package name, a path | ||
710 | 59 | def get_info(self, cmd, ui, desc): | ||
711 | 60 | dbg('Got command with user input') | ||
712 | 61 | ## Create Dialog object | ||
713 | 62 | dialog = gtk.MessageDialog( | ||
714 | 63 | None, | ||
715 | 64 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, | ||
716 | 65 | gtk.MESSAGE_QUESTION, | ||
717 | 66 | gtk.BUTTONS_OK_CANCEL, | ||
718 | 67 | None) | ||
719 | 68 | |||
720 | 69 | # Primary text | ||
721 | 70 | dialog.set_markup(_("This command requires more information.")) | ||
722 | 71 | |||
723 | 72 | ## create the text input field | ||
724 | 73 | entry = gtk.Entry() | ||
725 | 74 | ## allow the user to press enter to do ok | ||
726 | 75 | entry.connect("activate", self.responseToDialog, dialog, gtk.RESPONSE_OK) | ||
727 | 76 | |||
728 | 77 | ## create a horizontal box to pack the entry and a label | ||
729 | 78 | hbox = gtk.HBox() | ||
730 | 79 | hbox.pack_start(gtk.Label(ui+":"), False, 5, 5) | ||
731 | 80 | hbox.pack_end(entry) | ||
732 | 81 | ## some secondary text | ||
733 | 82 | dialog.format_secondary_markup(_("Please provide a "+ui)) | ||
734 | 83 | ## add it and show it | ||
735 | 84 | dialog.vbox.pack_end(hbox, True, True, 0) | ||
736 | 85 | dialog.show_all() | ||
737 | 86 | |||
738 | 87 | ## Show the dialog | ||
739 | 88 | response = dialog.run() | ||
740 | 89 | |||
741 | 90 | ## user text assigned to a variable | ||
742 | 91 | text = entry.get_text() | ||
743 | 92 | user_input = text.split(' ') | ||
744 | 93 | |||
745 | 94 | ## The destroy method must be called otherwise the 'Close' button will | ||
746 | 95 | ## not work. | ||
747 | 96 | dialog.destroy() | ||
748 | 97 | if response != gtk.RESPONSE_OK: | ||
749 | 98 | user_input = None | ||
750 | 99 | return user_input | ||
751 | 100 | |||
752 | 101 | def responseToDialog(self, text, dialog, response): | ||
753 | 102 | dialog.response(response) | ||
754 | 103 | |||
755 | 104 | ## Add command dialog box | ||
756 | 105 | def add_command(self, mw): | ||
757 | 106 | |||
758 | 107 | ## Create Dialog object | ||
759 | 108 | dialog = gtk.MessageDialog( | ||
760 | 109 | None, | ||
761 | 110 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, | ||
762 | 111 | gtk.MESSAGE_QUESTION, | ||
763 | 112 | gtk.BUTTONS_OK, | ||
764 | 113 | None) | ||
765 | 114 | |||
766 | 115 | ## primaary text | ||
767 | 116 | dialog.set_markup(_("Add a command to your command list")) | ||
768 | 117 | |||
769 | 118 | #create the text input field | ||
770 | 119 | entry1 = gtk.Entry() | ||
771 | 120 | entry2 = gtk.Entry() | ||
772 | 121 | entry3 = gtk.Entry() | ||
773 | 122 | ## allow the user to press enter to do ok | ||
774 | 123 | entry1.connect("activate", self.responseToDialog, dialog, gtk.RESPONSE_OK) | ||
775 | 124 | entry2.connect("activate", self.responseToDialog, dialog, gtk.RESPONSE_OK) | ||
776 | 125 | entry3.connect("activate", self.responseToDialog, dialog, gtk.RESPONSE_OK) | ||
777 | 126 | |||
778 | 127 | ## create three labels | ||
779 | 128 | hbox1 = gtk.HBox() | ||
780 | 129 | hbox1.pack_start(gtk.Label(_("Command")), False, 5, 5) | ||
781 | 130 | hbox1.pack_start(entry1, False, 5, 5) | ||
782 | 131 | |||
783 | 132 | hbox1.pack_start(gtk.Label(_("User Input")), False, 5, 5) | ||
784 | 133 | hbox1.pack_start(entry2, False, 5, 5) | ||
785 | 134 | |||
786 | 135 | hbox2 = gtk.HBox() | ||
787 | 136 | hbox2.pack_start(gtk.Label(_("Description")), False, 5, 5) | ||
788 | 137 | hbox2.pack_start(entry3, True, 5, 5) | ||
789 | 138 | |||
790 | 139 | ## cancel button | ||
791 | 140 | dialog.add_button(_('Cancel'), gtk.RESPONSE_DELETE_EVENT) | ||
792 | 141 | ## some secondary text | ||
793 | 142 | dialog.format_secondary_markup( | ||
794 | 143 | _("When entering a command use question marks(?) as placeholders if" | ||
795 | 144 | " user input is required when the command runs. Example: ls " | ||
796 | 145 | "/any/directory would be entered as, ls ? .For each question " | ||
797 | 146 | "mark(?) in your command, if any, use the User Input field to " | ||
798 | 147 | "provide a hint for each variable. Using our example ls ? you " | ||
799 | 148 | "could put directory as the User Input. Lastly provide a brief " | ||
800 | 149 | "Description.")) | ||
801 | 150 | |||
802 | 151 | ## add it and show it | ||
803 | 152 | dialog.vbox.pack_end(hbox2, True, True, 0) | ||
804 | 153 | dialog.vbox.pack_end(hbox1, True, True, 0) | ||
805 | 154 | dialog.show_all() | ||
806 | 155 | ## Show the dialog | ||
807 | 156 | result = dialog.run() | ||
808 | 157 | |||
809 | 158 | if result == gtk.RESPONSE_OK: | ||
810 | 159 | ## user text assigned to a variable | ||
811 | 160 | text1 = entry1.get_text() | ||
812 | 161 | text2 = entry2.get_text() | ||
813 | 162 | text3 = entry3.get_text() | ||
814 | 163 | ## update commandsand sync with screen ''' | ||
815 | 164 | view.CMNDS.append(text1, text2, text3) | ||
816 | 165 | mw.sync_cmnds() | ||
817 | 166 | view.CMNDS.save() | ||
818 | 167 | |||
819 | 168 | ## The destroy method must be called otherwise the 'Close' button will | ||
820 | 169 | ## not work. | ||
821 | 170 | dialog.destroy() | ||
822 | 171 | #return text | ||
823 | 172 | |||
824 | 173 | ## This the edit function | ||
825 | 174 | def edit_command(self, mw): | ||
826 | 175 | if not view.ROW: | ||
827 | 176 | return | ||
828 | 177 | lst_index = int(view.ROW[0][0]) | ||
829 | 178 | model = mw.treeview.get_model() | ||
830 | 179 | cmd = ''.join(model[lst_index][0]) | ||
831 | 180 | ui = ''.join(model[lst_index][1]) | ||
832 | 181 | desc = ''.join(model[lst_index][2]) | ||
833 | 182 | |||
834 | 183 | ## Create Dialog object | ||
835 | 184 | dialog = gtk.MessageDialog( | ||
836 | 185 | None, | ||
837 | 186 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, | ||
838 | 187 | gtk.MESSAGE_QUESTION, | ||
839 | 188 | gtk.BUTTONS_OK, | ||
840 | 189 | None) | ||
841 | 190 | |||
842 | 191 | # primary text | ||
843 | 192 | dialog.set_markup(_("Edit a command in your command list")) | ||
844 | 193 | |||
845 | 194 | ## create the text input fields | ||
846 | 195 | entry1 = gtk.Entry() | ||
847 | 196 | entry1.set_text(cmd) | ||
848 | 197 | entry2 = gtk.Entry() | ||
849 | 198 | entry2.set_text(ui) | ||
850 | 199 | entry3 = gtk.Entry() | ||
851 | 200 | entry3.set_text(desc) | ||
852 | 201 | ## allow the user to press enter to do ok | ||
853 | 202 | entry1.connect("activate", self.responseToDialog, dialog, gtk.RESPONSE_OK) | ||
854 | 203 | |||
855 | 204 | ## create three labels | ||
856 | 205 | hbox1 = gtk.HBox() | ||
857 | 206 | hbox1.pack_start(gtk.Label(_("Command")), False, 5, 5) | ||
858 | 207 | hbox1.pack_start(entry1, False, 5, 5) | ||
859 | 208 | |||
860 | 209 | hbox1.pack_start(gtk.Label(_("User Input")), False, 5, 5) | ||
861 | 210 | hbox1.pack_start(entry2, False, 5, 5) | ||
862 | 211 | |||
863 | 212 | hbox2 = gtk.HBox() | ||
864 | 213 | hbox2.pack_start(gtk.Label(_("Description")), False, 5, 5) | ||
865 | 214 | hbox2.pack_start(entry3, True, 5, 5) | ||
866 | 215 | |||
867 | 216 | ## cancel button | ||
868 | 217 | dialog.add_button(_('Cancel'), gtk.RESPONSE_DELETE_EVENT) | ||
869 | 218 | ## some secondary text | ||
870 | 219 | dialog.format_secondary_markup(_("Please provide a command, description, and what type of user variable, if any, is required.")) | ||
871 | 220 | |||
872 | 221 | ## add it and show it | ||
873 | 222 | dialog.vbox.pack_end(hbox2, True, True, 0) | ||
874 | 223 | dialog.vbox.pack_end(hbox1, True, True, 0) | ||
875 | 224 | dialog.show_all() | ||
876 | 225 | ## Show the dialog | ||
877 | 226 | result = dialog.run() | ||
878 | 227 | |||
879 | 228 | if result == gtk.RESPONSE_OK: | ||
880 | 229 | ## user text assigned to a variable | ||
881 | 230 | cmd = entry1.get_text() | ||
882 | 231 | ui = entry2.get_text() | ||
883 | 232 | desc = entry3.get_text() | ||
884 | 233 | |||
885 | 234 | if cmd != "": | ||
886 | 235 | cmd_index = model[lst_index][3] | ||
887 | 236 | dbg('Got index %d for command at pos %d'%(cmd_index, lst_index)) | ||
888 | 237 | view.CMNDS[cmd_index] = [cmd, ui, desc] | ||
889 | 238 | mw.sync_cmnds() | ||
890 | 239 | view.CMNDS.save() | ||
891 | 240 | ## The destroy method must be called otherwise the 'Close' button will | ||
892 | 241 | ## not work. | ||
893 | 242 | dialog.destroy() | ||
894 | 243 | |||
895 | 244 | |||
896 | 245 | ## Remove command from command file and GUI | ||
897 | 246 | def remove_command(self, mw): | ||
898 | 247 | if not view.ROW: | ||
899 | 248 | return | ||
900 | 249 | ## get selected row | ||
901 | 250 | lst_index = int(view.ROW[0][0]) | ||
902 | 251 | ## get selected element index, even from search filter | ||
903 | 252 | model = mw.treeview.get_model() | ||
904 | 253 | cmd_index = model[lst_index][3] | ||
905 | 254 | ## delete element from liststore and CMNDS | ||
906 | 255 | del view.CMNDS[cmd_index] | ||
907 | 256 | mw.sync_cmnds() | ||
908 | 257 | ## save changes | ||
909 | 258 | view.CMNDS.save() | ||
910 | 259 | |||
911 | 260 | |||
912 | 261 | def _filter_commands(self, widget, liststore, treeview): | ||
913 | 262 | """ | ||
914 | 263 | Show commands matching a given search term. | ||
915 | 264 | The user should enter a term in the search box and the treeview should | ||
916 | 265 | only display the rows which contain the search term. | ||
917 | 266 | Pretty straight-forward. | ||
918 | 267 | """ | ||
919 | 268 | search_term = widget.get_text().lower() | ||
920 | 269 | ## If the search term is empty, restore the liststore | ||
921 | 270 | if search_term == "": | ||
922 | 271 | view.FILTER = 0 | ||
923 | 272 | treeview.set_model(liststore) | ||
924 | 273 | return | ||
925 | 274 | |||
926 | 275 | view.FILTER = 1 | ||
927 | 276 | ## Create a TreeModelFilter object which provides auxiliary functions for | ||
928 | 277 | ## filtering data. | ||
929 | 278 | ## http://www.pygtk.org/pygtk2tutorial/sec-TreeModelSortAndTreeModelFilter.html | ||
930 | 279 | modelfilter = liststore.filter_new() | ||
931 | 280 | def search(modelfilter, iter, search_term): | ||
932 | 281 | try: | ||
933 | 282 | ## Iterate through every column and row and check if the search | ||
934 | 283 | ## term is there: | ||
935 | 284 | if search_term in modelfilter.get_value(iter, 0).lower() or \ | ||
936 | 285 | search_term in modelfilter.get_value(iter, 1).lower() or \ | ||
937 | 286 | search_term in modelfilter.get_value(iter, 2).lower() : | ||
938 | 287 | return True | ||
939 | 288 | |||
940 | 289 | except TypeError: | ||
941 | 290 | ## Python raises a TypeError if row data doesn't exist. Catch | ||
942 | 291 | ## that and fail silently. | ||
943 | 292 | pass | ||
944 | 293 | except AttributeError: | ||
945 | 294 | ## Python raises a AttributeError if row data was modified . Catch | ||
946 | 295 | ## that and fail silently. | ||
947 | 296 | pass | ||
948 | 297 | modelfilter.set_visible_func(search, search_term) | ||
949 | 298 | ## save the old liststore and cmnds | ||
950 | 299 | treeview.set_model(modelfilter) | ||
951 | 300 | |||
952 | 301 | ## send the command to the terminal | ||
953 | 302 | def run_command(self, mw): | ||
954 | 303 | |||
955 | 304 | ## if called without selecting a command from the list return | ||
956 | 305 | if not view.ROW: | ||
957 | 306 | return | ||
958 | 307 | text = "" | ||
959 | 308 | lst_index = int(view.ROW[0][0]) ## removes everything but number from [5,] | ||
960 | 309 | |||
961 | 310 | ## get the current notebook page so the function knows which terminal to run the command in. | ||
962 | 311 | pagenum = mw.notebook.get_current_page() | ||
963 | 312 | widget = mw.notebook.get_nth_page(pagenum) | ||
964 | 313 | page_widget = widget.get_child() | ||
965 | 314 | |||
966 | 315 | model = mw.treeview.get_model() | ||
967 | 316 | cmd = ''.join(model[lst_index][0]) | ||
968 | 317 | ui = ''.join(model[lst_index][1]) | ||
969 | 318 | desc = ''.join(model[lst_index][2]) | ||
970 | 319 | |||
971 | 320 | ## find how many ?(user arguments) are in command | ||
972 | 321 | match = re.findall('\?', cmd) | ||
973 | 322 | ''' | ||
974 | 323 | Make sure user arguments were found. Replace ? with something | ||
975 | 324 | .format can read. This is done so the user can just enter ?, when | ||
976 | 325 | adding a command where arguments are needed, instead | ||
977 | 326 | of {0[1]}, {0[1]}, {0[2]} | ||
978 | 327 | ''' | ||
979 | 328 | if match == False: | ||
980 | 329 | pass | ||
981 | 330 | else: | ||
982 | 331 | num = len(match) | ||
983 | 332 | ran = 0 | ||
984 | 333 | new_cmnd = self.replace(cmd, num, ran) | ||
985 | 334 | |||
986 | 335 | if len(match) > 0: # command with user input | ||
987 | 336 | dbg('command with ui') | ||
988 | 337 | f_cmd = "" | ||
989 | 338 | while True: | ||
990 | 339 | try: | ||
991 | 340 | ui_text = self.get_info(cmd, ui, desc) | ||
992 | 341 | if ui_text == None: | ||
993 | 342 | return | ||
994 | 343 | dbg('Got ui "%s"'%' '.join(ui_text)) | ||
995 | 344 | if ''.join(ui_text) == '': | ||
996 | 345 | raise IndexError | ||
997 | 346 | f_cmd = new_cmnd.format(ui_text) | ||
998 | 347 | except IndexError, e: | ||
999 | 348 | error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, \ | ||
1000 | 349 | gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, | ||
1001 | 350 | _("You need to enter full input. Space separated.")) | ||
1002 | 351 | error.connect('response', lambda err, *x: err.destroy()) | ||
1003 | 352 | error.run() | ||
1004 | 353 | continue | ||
1005 | 354 | break | ||
1006 | 355 | page_widget.feed_child(f_cmd+"\n") #send command w/ input | ||
1007 | 356 | page_widget.show() | ||
1008 | 357 | page_widget.grab_focus() | ||
1009 | 358 | else: ## command that has no user input | ||
1010 | 359 | page_widget.feed_child(cmd+"\n") #send command | ||
1011 | 360 | page_widget.show() | ||
1012 | 361 | page_widget.grab_focus() | ||
1013 | 362 | |||
1014 | 363 | ## replace ? with {0[n]} | ||
1015 | 364 | def replace(self, cmnd, num, ran): | ||
1016 | 365 | replace_cmnd=re.sub('\?', '{0['+str(ran)+']}', cmnd, count=1) | ||
1017 | 366 | cmnd = replace_cmnd | ||
1018 | 367 | ran += 1 | ||
1019 | 368 | if ran < num: | ||
1020 | 369 | return self.replace(cmnd, num, ran) | ||
1021 | 370 | else: | ||
1022 | 371 | pass | ||
1023 | 372 | return cmnd | ||
1024 | 373 | |||
1025 | 374 | ## open the man page for selected command | ||
1026 | 375 | def man_page(self, notebook): | ||
1027 | 376 | import subprocess as sp | ||
1028 | 377 | import shlex | ||
1029 | 378 | try: | ||
1030 | 379 | row_int = int(view.ROW[0][0]) # removes everything but number from EX: [5,] | ||
1031 | 380 | except IndexError: | ||
1032 | 381 | ## When user not choose row, when is in filter mode | ||
1033 | 382 | dialog = gtk.MessageDialog( | ||
1034 | 383 | None, | ||
1035 | 384 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, | ||
1036 | 385 | gtk.MESSAGE_QUESTION, | ||
1037 | 386 | gtk.BUTTONS_OK, | ||
1038 | 387 | None) | ||
1039 | 388 | dialog.set_markup(_('You must choose a row to view the help')) | ||
1040 | 389 | dialog.show_all() | ||
1041 | 390 | dialog.run() | ||
1042 | 391 | dialog.destroy() | ||
1043 | 392 | return | ||
1044 | 393 | ## get the manpage for the command | ||
1045 | 394 | cmnd = view.CMNDS[row_int][0] #CMNDS is where commands are store | ||
1046 | 395 | ## get each command for each pipe, It's not 100 accurate, but good | ||
1047 | 396 | ## enough (by now) | ||
1048 | 397 | commands = [] | ||
1049 | 398 | next_part = True | ||
1050 | 399 | found_sudo = False | ||
1051 | 400 | for part in shlex.split(cmnd): | ||
1052 | 401 | if next_part: | ||
1053 | 402 | if part == 'sudo' and not found_sudo: | ||
1054 | 403 | found_sudo = True | ||
1055 | 404 | commands.append('sudo') | ||
1056 | 405 | else: | ||
1057 | 406 | if part not in commands: | ||
1058 | 407 | commands.append(part) | ||
1059 | 408 | next_part = False | ||
1060 | 409 | else: | ||
1061 | 410 | if part in [ '||', '&&', '&', '|']: | ||
1062 | 411 | next_part = True | ||
1063 | 412 | |||
1064 | 413 | notebook = gtk.Notebook() | ||
1065 | 414 | notebook.set_scrollable(True) | ||
1066 | 415 | notebook.popup_enable() | ||
1067 | 416 | notebook.set_properties(group_id=0, tab_vborder=0, tab_hborder=1, tab_pos=gtk.POS_TOP) | ||
1068 | 417 | ## create a tab for each command | ||
1069 | 418 | for command in commands: | ||
1070 | 419 | scrolled_page = gtk.ScrolledWindow() | ||
1071 | 420 | scrolled_page.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) | ||
1072 | 421 | tab = gtk.HBox() | ||
1073 | 422 | tab_label = gtk.Label(command) | ||
1074 | 423 | tab_label.show() | ||
1075 | 424 | tab.pack_start(tab_label) | ||
1076 | 425 | page = gtk.TextView() | ||
1077 | 426 | page.set_wrap_mode(gtk.WRAP_WORD) | ||
1078 | 427 | page.set_editable(False) | ||
1079 | 428 | page.set_cursor_visible(False) | ||
1080 | 429 | try: | ||
1081 | 430 | manpage = sp.check_output(["man",command]) | ||
1082 | 431 | except sp.CalledProcessError, e: | ||
1083 | 432 | manpage = _('Failed to get manpage for command "%s"\nReason:\n%s')%( | ||
1084 | 433 | command, e) | ||
1085 | 434 | textbuffer = page.get_buffer() | ||
1086 | 435 | textbuffer.set_text(manpage) | ||
1087 | 436 | scrolled_page.add(page) | ||
1088 | 437 | notebook.append_page(scrolled_page, tab) | ||
1089 | 438 | |||
1090 | 439 | help_win = gtk.Dialog() | ||
1091 | 440 | help_win.set_title(_("Man page for %s")%cmnd) | ||
1092 | 441 | help_win.vbox.pack_start(notebook, True, True, 0) | ||
1093 | 442 | button = gtk.Button("close") | ||
1094 | 443 | button.connect_object("clicked", lambda self: self.destroy(), help_win) | ||
1095 | 444 | button.set_flags(gtk.CAN_DEFAULT) | ||
1096 | 445 | help_win.action_area.pack_start( button, True, True, 0) | ||
1097 | 446 | button.grab_default() | ||
1098 | 447 | help_win.set_default_size(500,600) | ||
1099 | 448 | help_win.show_all() | ||
1100 | 449 | |||
1101 | 450 | |||
1102 | 451 | @staticmethod | ||
1103 | 452 | def _filter_sudo_from(command): | ||
1104 | 453 | """Filter the sudo from `command`, where `command` is a list. | ||
1105 | 454 | Return the command list with the "sudo" filtered out. | ||
1106 | 455 | """ | ||
1107 | 456 | if command[0].startswith("sudo"): | ||
1108 | 457 | del command[0] | ||
1109 | 458 | return command | ||
1110 | 459 | return command | ||
1111 | 460 | |||
1112 | 461 | |||
1113 | 462 | #TODO: Move to menus_buttons | ||
1114 | 463 | def copy_paste(self, vte, event, data=None): | ||
1115 | 464 | if event.button == 3: | ||
1116 | 465 | |||
1117 | 466 | time = event.time | ||
1118 | 467 | ## right-click popup menu Copy | ||
1119 | 468 | popupMenu = gtk.Menu() | ||
1120 | 469 | menuPopup1 = gtk.ImageMenuItem (gtk.STOCK_COPY) | ||
1121 | 470 | popupMenu.add(menuPopup1) | ||
1122 | 471 | menuPopup1.connect('activate', lambda x: vte.copy_clipboard()) | ||
1123 | 472 | ## right-click popup menu Paste | ||
1124 | 473 | menuPopup2 = gtk.ImageMenuItem (gtk.STOCK_PASTE) | ||
1125 | 474 | popupMenu.add(menuPopup2) | ||
1126 | 475 | menuPopup2.connect('activate', lambda x: vte.paste_clipboard()) | ||
1127 | 476 | |||
1128 | 477 | ## Show popup menu | ||
1129 | 478 | popupMenu.show_all() | ||
1130 | 479 | popupMenu.popup( None, None, None, event.button, time) | ||
1131 | 480 | return True | ||
1132 | 481 | else: | ||
1133 | 482 | pass | ||
1134 | 483 | |||
1135 | 484 | ## close the window and quit | ||
1136 | 485 | def delete_event(self, widget, data=None): | ||
1137 | 486 | gtk.main_quit() | ||
1138 | 487 | return False | ||
1139 | 488 | |||
1140 | 489 | ## Help --> About and Help --> Help menus | ||
1141 | 490 | def about_event(self, widget, data=None): | ||
1142 | 491 | # Create AboutDialog object | ||
1143 | 492 | dialog = gtk.AboutDialog() | ||
1144 | 493 | |||
1145 | 494 | # Add the application name to the dialog | ||
1146 | 495 | dialog.set_name('CLI Companion ') | ||
1147 | 496 | |||
1148 | 497 | # Set the application version | ||
1149 | 498 | dialog.set_version('1.1') | ||
1150 | 499 | |||
1151 | 500 | # Pass a list of authors. This is then connected to the 'Credits' | ||
1152 | 501 | # button. When clicked the buttons opens a new window showing | ||
1153 | 502 | # each author on their own line. | ||
1154 | 503 | dialog.set_authors(['Duane Hinnen', 'Kenny Meyer', 'Marcos Vanettai', 'Marek Bardoński']) | ||
1155 | 504 | |||
1156 | 505 | # Add a short comment about the application, this appears below the application | ||
1157 | 506 | # name in the dialog | ||
1158 | 507 | dialog.set_comments(_('This is a CLI Companion program.')) | ||
1159 | 508 | |||
1160 | 509 | # Add license information, this is connected to the 'License' button | ||
1161 | 510 | # and is displayed in a new window. | ||
1162 | 511 | dialog.set_license(_('Distributed under the GNU license. You can see it at <http://www.gnu.org/licenses/>.')) | ||
1163 | 512 | |||
1164 | 513 | # Show the dialog | ||
1165 | 514 | dialog.run() | ||
1166 | 515 | |||
1167 | 516 | # The destroy method must be called otherwise the 'Close' button will | ||
1168 | 517 | # not work. | ||
1169 | 518 | dialog.destroy() | ||
1170 | 519 | |||
1171 | 520 | |||
1172 | 521 | def help_event(self, widget, data=None): | ||
1173 | 522 | webbrowser.open("http://launchpad.net/clicompanion") | ||
1174 | 523 | |||
1175 | 524 | |||
1176 | 525 | def usage_event(self, widget, data=None): | ||
1177 | 526 | dialog = gtk.Dialog("Usage", | ||
1178 | 527 | None, | ||
1179 | 528 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, | ||
1180 | 529 | (gtk.STOCK_CANCEL, gtk.RESPONSE_CLOSE)) | ||
1181 | 530 | |||
1182 | 531 | hbox1 = gtk.HBox() | ||
1183 | 532 | hbox2 = gtk.HBox() | ||
1184 | 533 | hbox21 = gtk.HBox() | ||
1185 | 534 | hbox3 = gtk.HBox() | ||
1186 | 535 | hbox4 = gtk.HBox() | ||
1187 | 536 | hbox5 = gtk.HBox() | ||
1188 | 537 | hbox6 = gtk.HBox() | ||
1189 | 538 | |||
1190 | 539 | hbox1.pack_start(gtk.Label(_("To maximize window, press F11")), False, 5, 5) | ||
1191 | 540 | hbox2.pack_start(gtk.Label(_("To hide UI, press F12")), False, 5, 5) | ||
1192 | 541 | hbox21.pack_start(gtk.Label(_("--------------------")), False, 5, 5) | ||
1193 | 542 | hbox3.pack_start(gtk.Label(_("Run command - F4")), False, 5, 5) | ||
1194 | 543 | hbox4.pack_start(gtk.Label(_("Add command - F5")), False, 5, 5) | ||
1195 | 544 | hbox5.pack_start(gtk.Label(_("Remove command - F6")), False, 5, 5) | ||
1196 | 545 | hbox6.pack_start(gtk.Label(_("Add tab - F7")), False, 5, 5) | ||
1197 | 546 | |||
1198 | 547 | dialog.vbox.pack_end(hbox1, True, True, 0) | ||
1199 | 548 | dialog.vbox.pack_end(hbox2, True, True, 0) | ||
1200 | 549 | dialog.vbox.pack_end(hbox21, True, True, 0) | ||
1201 | 550 | dialog.vbox.pack_end(hbox3, True, True, 0) | ||
1202 | 551 | dialog.vbox.pack_end(hbox4, True, True, 0) | ||
1203 | 552 | dialog.vbox.pack_end(hbox5, True, True, 0) | ||
1204 | 553 | dialog.vbox.pack_end(hbox6, True, True, 0) | ||
1205 | 554 | |||
1206 | 555 | dialog.show_all() | ||
1207 | 556 | |||
1208 | 557 | result = dialog.run() | ||
1209 | 558 | ## The destroy method must be called otherwise the 'Close' button will | ||
1210 | 559 | ## not work. | ||
1211 | 560 | dialog.destroy() | ||
1212 | 561 | |||
1213 | 562 | |||
1214 | 563 | ## File --> Preferences | ||
1215 | 564 | def changed_cb(self, combobox, config): | ||
1216 | 565 | dbg('Changed encoding') | ||
1217 | 566 | model = combobox.get_model() | ||
1218 | 567 | index = combobox.get_active() | ||
1219 | 568 | if index>=0: | ||
1220 | 569 | text_e = model[index][0] | ||
1221 | 570 | encoding = text_e.split(':',1)[0].strip() | ||
1222 | 571 | dbg('Setting encoding to "%s"'%encoding) | ||
1223 | 572 | config.set("terminal", "encoding", encoding) | ||
1224 | 573 | |||
1225 | 574 | |||
1226 | 575 | def color_set_fg_cb(self, colorbutton_fg, config, tabs): | ||
1227 | 576 | dbg('Changing fg color') | ||
1228 | 577 | colorf = self.color2hex(colorbutton_fg) | ||
1229 | 578 | config.set("terminal", "colorf", str(colorf)) | ||
1230 | 579 | tabs.update_all_term_config(config) | ||
1231 | 580 | |||
1232 | 581 | |||
1233 | 582 | def color_set_bg_cb(self, colorbutton_bg, config, tabs): | ||
1234 | 583 | dbg('Changing bg color') | ||
1235 | 584 | colorb = self.color2hex(colorbutton_bg) | ||
1236 | 585 | config.set("terminal", "colorb", str(colorb)) | ||
1237 | 586 | tabs.update_all_term_config(config) | ||
1238 | 587 | |||
1239 | 588 | |||
1240 | 589 | def color2hex(self, widget): | ||
1241 | 590 | """Pull the colour values out of a Gtk ColorPicker widget and return them | ||
1242 | 591 | as 8bit hex values, sinces its default behaviour is to give 16bit values""" | ||
1243 | 592 | widcol = widget.get_color() | ||
1244 | 593 | return('#%02x%02x%02x' % (widcol.red>>8, widcol.green>>8, widcol.blue>>8)) | ||
1245 | 594 | |||
1246 | 595 | def preferences(self, tabs, data=None): | ||
1247 | 596 | ''' | ||
1248 | 597 | Preferences window | ||
1249 | 598 | ''' | ||
1250 | 599 | dialog = gtk.Dialog(_("User Preferences"), | ||
1251 | 600 | None, | ||
1252 | 601 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, | ||
1253 | 602 | (gtk.STOCK_CANCEL, gtk.RESPONSE_CLOSE, | ||
1254 | 603 | gtk.STOCK_OK, gtk.RESPONSE_OK)) | ||
1255 | 604 | |||
1256 | 605 | config = cc_config.get_config_copy() | ||
1257 | 606 | |||
1258 | 607 | ##create the text input fields | ||
1259 | 608 | entry1 = gtk.Entry() | ||
1260 | 609 | entry1.set_text(config.get('terminal', 'scrollb')) | ||
1261 | 610 | |||
1262 | 611 | ##combobox for selecting encoding | ||
1263 | 612 | combobox = gtk.combo_box_new_text() | ||
1264 | 613 | i=0 | ||
1265 | 614 | for encoding, desc in utils.encodings: | ||
1266 | 615 | combobox.append_text(encoding + ': '+desc) | ||
1267 | 616 | if encoding.strip().upper() == config.get('terminal','encoding').upper(): | ||
1268 | 617 | active = i | ||
1269 | 618 | i=i+1 | ||
1270 | 619 | combobox.set_active(active) | ||
1271 | 620 | combobox.connect('changed', self.changed_cb, config) | ||
1272 | 621 | |||
1273 | 622 | ##colorbox for selecting text and background color | ||
1274 | 623 | colorbutton_fg = gtk.ColorButton( | ||
1275 | 624 | gtk.gdk.color_parse(config.get('terminal','colorf'))) | ||
1276 | 625 | colorbutton_bg = gtk.ColorButton( | ||
1277 | 626 | gtk.gdk.color_parse(config.get('terminal','colorb'))) | ||
1278 | 627 | |||
1279 | 628 | colorbutton_fg.connect('color-set', self.color_set_fg_cb, config, tabs) | ||
1280 | 629 | colorbutton_bg.connect('color-set', self.color_set_bg_cb, config, tabs) | ||
1281 | 630 | |||
1282 | 631 | ## allow the user to press enter to do ok | ||
1283 | 632 | entry1.connect("activate", self.responseToDialog, dialog, gtk.RESPONSE_OK) | ||
1284 | 633 | |||
1285 | 634 | ## create the labels | ||
1286 | 635 | hbox1 = gtk.HBox() | ||
1287 | 636 | hbox1.pack_start(gtk.Label(_("Scrollback")), False, 5, 5) | ||
1288 | 637 | hbox1.pack_start(entry1, False, 5, 5) | ||
1289 | 638 | |||
1290 | 639 | hbox1.pack_start(gtk.Label(_("Encoding")), False, 5, 5) | ||
1291 | 640 | hbox1.pack_start(combobox, False, 5, 5) | ||
1292 | 641 | |||
1293 | 642 | hbox2 = gtk.HBox() | ||
1294 | 643 | hbox2.pack_start(gtk.Label(_("Font color")), False, 5, 5) | ||
1295 | 644 | hbox2.pack_start(colorbutton_fg, True, 5, 5) | ||
1296 | 645 | |||
1297 | 646 | hbox2.pack_start(gtk.Label(_("Background color")), False, 5, 5) | ||
1298 | 647 | hbox2.pack_start(colorbutton_bg, True, 5, 5) | ||
1299 | 648 | |||
1300 | 649 | ## add it and show it | ||
1301 | 650 | dialog.vbox.pack_end(hbox2, True, True, 0) | ||
1302 | 651 | dialog.vbox.pack_end(hbox1, True, True, 0) | ||
1303 | 652 | dialog.show_all() | ||
1304 | 653 | |||
1305 | 654 | result = dialog.run() | ||
1306 | 655 | if result == gtk.RESPONSE_OK: | ||
1307 | 656 | ## user text assigned to a variable | ||
1308 | 657 | text_sb = entry1.get_text() | ||
1309 | 658 | config.set("terminal", "scrollb", text_sb) | ||
1310 | 659 | cc_config.save_config(config) | ||
1311 | 660 | tabs.update_all_term_config() | ||
1312 | 661 | |||
1313 | 662 | ## The destroy method must be called otherwise the 'Close' button will | ||
1314 | 663 | ## not work. | ||
1315 | 664 | dialog.destroy() | ||
1316 | 665 | |||
1317 | 666 | 0 | ||
1318 | === added file 'clicompanionlib/helpers.py' | |||
1319 | --- clicompanionlib/helpers.py 1970-01-01 00:00:00 +0000 | |||
1320 | +++ clicompanionlib/helpers.py 2012-01-08 10:37:24 +0000 | |||
1321 | @@ -0,0 +1,186 @@ | |||
1322 | 1 | #!/usr/bin/env python | ||
1323 | 2 | # -*- coding: utf-8 -*- | ||
1324 | 3 | # | ||
1325 | 4 | # helpers.py - Helper dialogs for clicompanion | ||
1326 | 5 | # | ||
1327 | 6 | # Copyright 2012 Duane Hinnen, Kenny Meyer, Marcos Vanetta, Marek Bardoński, | ||
1328 | 7 | # David Caro | ||
1329 | 8 | # | ||
1330 | 9 | # This program is free software: you can redistribute it and/or modify it | ||
1331 | 10 | # under the terms of the GNU General Public License version 3, as published | ||
1332 | 11 | # by the Free Software Foundation. | ||
1333 | 12 | # | ||
1334 | 13 | # This program is distributed in the hope that it will be useful, but | ||
1335 | 14 | # WITHOUT ANY WARRANTY; without even the implied warranties of | ||
1336 | 15 | # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
1337 | 16 | # PURPOSE. See the GNU General Public License for more details. | ||
1338 | 17 | # | ||
1339 | 18 | # You should have received a copy of the GNU General Public License along | ||
1340 | 19 | # with this program. If not, see <http://www.gnu.org/licenses/>. | ||
1341 | 20 | # | ||
1342 | 21 | # | ||
1343 | 22 | #### | ||
1344 | 23 | ## This file keeps some popups that are shown across the program execution, | ||
1345 | 24 | ## that aren't directly related with a class, like the edit comand popup or the | ||
1346 | 25 | ## about popup, but are too small to be kept in a separate file | ||
1347 | 26 | |||
1348 | 27 | import os | ||
1349 | 28 | import re | ||
1350 | 29 | import pygtk | ||
1351 | 30 | pygtk.require('2.0') | ||
1352 | 31 | import gtk | ||
1353 | 32 | import subprocess as sp | ||
1354 | 33 | import shlex | ||
1355 | 34 | from clicompanionlib.utils import dbg | ||
1356 | 35 | |||
1357 | 36 | |||
1358 | 37 | class ManPage(gtk.Dialog): | ||
1359 | 38 | def __init__(self, cmd): | ||
1360 | 39 | if not cmd: | ||
1361 | 40 | choose_row_error() | ||
1362 | 41 | gtk.Dialog.__init__(self) | ||
1363 | 42 | self.cmd = None | ||
1364 | 43 | return | ||
1365 | 44 | self.cmd = cmd | ||
1366 | 45 | notebook = gtk.Notebook() | ||
1367 | 46 | notebook.set_scrollable(True) | ||
1368 | 47 | notebook.popup_enable() | ||
1369 | 48 | notebook.set_properties(group_id=0, tab_vborder=0, | ||
1370 | 49 | tab_hborder=1, tab_pos=gtk.POS_TOP) | ||
1371 | 50 | ## create a tab for each command | ||
1372 | 51 | for command in self.get_commands(): | ||
1373 | 52 | scrolled_page = gtk.ScrolledWindow() | ||
1374 | 53 | scrolled_page.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) | ||
1375 | 54 | tab = gtk.HBox() | ||
1376 | 55 | tab_label = gtk.Label(command) | ||
1377 | 56 | tab_label.show() | ||
1378 | 57 | tab.pack_start(tab_label) | ||
1379 | 58 | page = gtk.TextView() | ||
1380 | 59 | page.set_wrap_mode(gtk.WRAP_WORD) | ||
1381 | 60 | page.set_editable(False) | ||
1382 | 61 | page.set_cursor_visible(False) | ||
1383 | 62 | try: | ||
1384 | 63 | manpage = sp.check_output(["man", command]) | ||
1385 | 64 | except sp.CalledProcessError, e: | ||
1386 | 65 | manpage = _('Failed to get manpage for command ' | ||
1387 | 66 | '"%s"\nReason:\n%s') % (command, e) | ||
1388 | 67 | textbuffer = page.get_buffer() | ||
1389 | 68 | textbuffer.set_text(manpage) | ||
1390 | 69 | scrolled_page.add(page) | ||
1391 | 70 | notebook.append_page(scrolled_page, tab) | ||
1392 | 71 | self.set_title(_("Man page for %s") % cmd) | ||
1393 | 72 | self.vbox.pack_start(notebook, True, True, 0) | ||
1394 | 73 | button = gtk.Button("close") | ||
1395 | 74 | button.connect_object("clicked", lambda *x: self.destroy(), self) | ||
1396 | 75 | button.set_flags(gtk.CAN_DEFAULT) | ||
1397 | 76 | self.action_area.pack_start(button, True, True, 0) | ||
1398 | 77 | button.grab_default() | ||
1399 | 78 | self.set_default_size(500, 600) | ||
1400 | 79 | self.show_all() | ||
1401 | 80 | |||
1402 | 81 | def run(self): | ||
1403 | 82 | if not self.cmd: | ||
1404 | 83 | return | ||
1405 | 84 | gtk.Dialog.run(self) | ||
1406 | 85 | |||
1407 | 86 | def get_commands(self): | ||
1408 | 87 | commands = [] | ||
1409 | 88 | next_part = True | ||
1410 | 89 | found_sudo = False | ||
1411 | 90 | try: | ||
1412 | 91 | for part in shlex.split(self.cmd): | ||
1413 | 92 | if next_part: | ||
1414 | 93 | if part == 'sudo' and not found_sudo: | ||
1415 | 94 | found_sudo = True | ||
1416 | 95 | commands.append('sudo') | ||
1417 | 96 | else: | ||
1418 | 97 | if part not in commands: | ||
1419 | 98 | commands.append(part) | ||
1420 | 99 | next_part = False | ||
1421 | 100 | else: | ||
1422 | 101 | if part in ['||', '&&', '&', '|']: | ||
1423 | 102 | next_part = True | ||
1424 | 103 | except Exception, e: | ||
1425 | 104 | return [self.cmd] | ||
1426 | 105 | return commands | ||
1427 | 106 | |||
1428 | 107 | |||
1429 | 108 | def show_about(): | ||
1430 | 109 | dialog = gtk.AboutDialog() | ||
1431 | 110 | dialog.set_name('CLI Companion') | ||
1432 | 111 | dialog.set_version('1.1') | ||
1433 | 112 | dialog.set_authors([u'Duane Hinnen', u'Kenny Meyer', u'Marcos Vanettai', | ||
1434 | 113 | u'Marek Bardoński', u'David Caro']) | ||
1435 | 114 | dialog.set_comments(_('This is a CLI Companion program.')) | ||
1436 | 115 | dialog.set_license(_('Distributed under the GNU license. You can see it at' | ||
1437 | 116 | '<http://www.gnu.org/licenses/>.')) | ||
1438 | 117 | dialog.run() | ||
1439 | 118 | dialog.destroy() | ||
1440 | 119 | |||
1441 | 120 | |||
1442 | 121 | ## Some hlper popus like edit command and so | ||
1443 | 122 | class CommandInfoWindow(gtk.MessageDialog): | ||
1444 | 123 | def __init__(self, cmd, ui, desc): | ||
1445 | 124 | self.cmd, self.ui, self.desc = cmd, ui, desc | ||
1446 | 125 | gtk.MessageDialog.__init__(self, | ||
1447 | 126 | None, | ||
1448 | 127 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, | ||
1449 | 128 | gtk.MESSAGE_QUESTION, | ||
1450 | 129 | gtk.BUTTONS_OK_CANCEL, | ||
1451 | 130 | None) | ||
1452 | 131 | self.set_markup(_("This command requires more information.")) | ||
1453 | 132 | ## create the text input field | ||
1454 | 133 | self.entry = gtk.Entry() | ||
1455 | 134 | ## allow the user to press enter to do ok | ||
1456 | 135 | self.entry.connect("activate", lambda *x: self.response( | ||
1457 | 136 | gtk.RESPONSE_OK)) | ||
1458 | 137 | ## create a horizontal box to pack the entry and a label | ||
1459 | 138 | hbox = gtk.HBox() | ||
1460 | 139 | hbox.pack_start(gtk.Label(self.ui + ":"), False, 5, 5) | ||
1461 | 140 | hbox.pack_end(self.entry) | ||
1462 | 141 | ## some secondary text | ||
1463 | 142 | self.format_secondary_markup(_("Please provide a " + self.ui)) | ||
1464 | 143 | ## add it and show it | ||
1465 | 144 | self.vbox.pack_end(hbox, True, True, 0) | ||
1466 | 145 | self.show_all() | ||
1467 | 146 | ## The destroy method must be called otherwise the 'Close' button will | ||
1468 | 147 | ## not work. | ||
1469 | 148 | |||
1470 | 149 | def run(self): | ||
1471 | 150 | result = False | ||
1472 | 151 | while not result: | ||
1473 | 152 | result = gtk.MessageDialog.run(self) | ||
1474 | 153 | if result == gtk.RESPONSE_OK: | ||
1475 | 154 | ui = self.entry.get_text().strip() | ||
1476 | 155 | dbg('Got ui "%s"' % ui) | ||
1477 | 156 | if not ui: | ||
1478 | 157 | self.show_error() | ||
1479 | 158 | result = None | ||
1480 | 159 | try: | ||
1481 | 160 | cmd = self.cmd.format(ui.split(' ')) | ||
1482 | 161 | except: | ||
1483 | 162 | result = None | ||
1484 | 163 | else: | ||
1485 | 164 | cmd = None | ||
1486 | 165 | self.destroy() | ||
1487 | 166 | return cmd | ||
1488 | 167 | |||
1489 | 168 | def show_error(self): | ||
1490 | 169 | error = gtk.MessageDialog(None, gtk.DIALOG_MODAL, \ | ||
1491 | 170 | gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, | ||
1492 | 171 | _("You need to enter full input. Space separated.")) | ||
1493 | 172 | error.connect('response', lambda *x: error.destroy()) | ||
1494 | 173 | error.run() | ||
1495 | 174 | |||
1496 | 175 | |||
1497 | 176 | def choose_row_error(): | ||
1498 | 177 | dialog = gtk.MessageDialog( | ||
1499 | 178 | None, | ||
1500 | 179 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, | ||
1501 | 180 | gtk.MESSAGE_QUESTION, | ||
1502 | 181 | gtk.BUTTONS_OK, | ||
1503 | 182 | None) | ||
1504 | 183 | dialog.set_markup(_('You must choose a row to view the help')) | ||
1505 | 184 | dialog.show_all() | ||
1506 | 185 | dialog.run() | ||
1507 | 186 | dialog.destroy() | ||
1508 | 0 | 187 | ||
1509 | === modified file 'clicompanionlib/menus_buttons.py' | |||
1510 | --- clicompanionlib/menus_buttons.py 2012-01-02 00:23:11 +0000 | |||
1511 | +++ clicompanionlib/menus_buttons.py 2012-01-08 10:37:24 +0000 | |||
1512 | @@ -1,7 +1,9 @@ | |||
1513 | 1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python |
1514 | 2 | # -*- coding: utf-8 -*- | 2 | # -*- coding: utf-8 -*- |
1515 | 3 | # | 3 | # |
1517 | 4 | # Copyright 2010 Duane Hinnen, Kenny Meyer, Marcos Vanetta | 4 | # menus_buttons.py - Menus and Buttons for the clicompanion |
1518 | 5 | # | ||
1519 | 6 | # Copyright 2010 Duane Hinnen, Kenny Meyer, Marcos Vanetta, David Caro | ||
1520 | 5 | # | 7 | # |
1521 | 6 | # This program is free software: you can redistribute it and/or modify it | 8 | # This program is free software: you can redistribute it and/or modify it |
1522 | 7 | # under the terms of the GNU General Public License version 3, as published | 9 | # under the terms of the GNU General Public License version 3, as published |
1523 | @@ -16,199 +18,180 @@ | |||
1524 | 16 | # with this program. If not, see <http://www.gnu.org/licenses/>. | 18 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
1525 | 17 | # | 19 | # |
1526 | 18 | # | 20 | # |
1530 | 19 | # | 21 | # This file contains the upper menus (class FileMenu), and the lower buttons |
1531 | 20 | # This file contains the menus, buttons, and right clicks | 22 | # (class Buttons) used in the CLI Companion main window |
1529 | 21 | # | ||
1532 | 22 | 23 | ||
1533 | 23 | import gtk | 24 | import gtk |
1544 | 24 | import tabs | 25 | import gobject |
1545 | 25 | 26 | import webbrowser | |
1546 | 26 | 27 | import clicompanionlib.helpers as cc_helpers | |
1547 | 27 | class FileMenu(object): | 28 | |
1548 | 28 | 29 | ||
1549 | 29 | def the_menu(self, mw): | 30 | class FileMenu(gtk.MenuBar): |
1550 | 30 | actions = mw.actions | 31 | __gsignals__ = { |
1551 | 31 | liststore = mw.liststore | 32 | 'run_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, |
1552 | 32 | tabs = mw.tabs | 33 | ()), |
1553 | 33 | notebook = mw.notebook | 34 | 'add_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, |
1554 | 35 | ()), | ||
1555 | 36 | 'remove_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
1556 | 37 | ()), | ||
1557 | 38 | 'edit_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
1558 | 39 | ()), | ||
1559 | 40 | 'add_tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
1560 | 41 | ()), | ||
1561 | 42 | 'close_tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
1562 | 43 | ()), | ||
1563 | 44 | 'preferences': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
1564 | 45 | ()), | ||
1565 | 46 | 'quit': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
1566 | 47 | ()), | ||
1567 | 48 | } | ||
1568 | 49 | |||
1569 | 50 | def __init__(self, config): | ||
1570 | 51 | gtk.MenuBar.__init__(self) | ||
1571 | 34 | menu = gtk.Menu() | 52 | menu = gtk.Menu() |
1572 | 35 | #color = gtk.gdk.Color(65555, 62000, 65555) | 53 | #color = gtk.gdk.Color(65555, 62000, 65555) |
1573 | 36 | #menu.modify_bg(gtk.STATE_NORMAL, color) | 54 | #menu.modify_bg(gtk.STATE_NORMAL, color) |
1575 | 37 | root_menu = gtk.MenuItem(_("File")) | 55 | root_menu = gtk.MenuItem(_("File")) |
1576 | 38 | root_menu.set_submenu(menu) | 56 | root_menu.set_submenu(menu) |
1579 | 39 | 57 | ||
1580 | 40 | menu2 = gtk.Menu() | 58 | menu2 = gtk.Menu() |
1581 | 41 | #color = gtk.gdk.Color(65555, 62000, 60000) | 59 | #color = gtk.gdk.Color(65555, 62000, 60000) |
1582 | 42 | #menu2.modify_bg(gtk.STATE_NORMAL, color) | 60 | #menu2.modify_bg(gtk.STATE_NORMAL, color) |
1583 | 43 | root_menu2 = gtk.MenuItem(_("Help")) | 61 | root_menu2 = gtk.MenuItem(_("Help")) |
1584 | 44 | root_menu2.set_submenu(menu2) | 62 | root_menu2.set_submenu(menu2) |
1585 | 45 | 63 | ||
1587 | 46 | ##FILE MENU ## | 64 | ##FILE MENU ## |
1588 | 47 | ## Make 'Run' menu entry | 65 | ## Make 'Run' menu entry |
1590 | 48 | menu_item1 = gtk.MenuItem(_("Run Command [F4]")) | 66 | menu_item1 = gtk.MenuItem(_("Run Command")) |
1591 | 49 | menu.append(menu_item1) | 67 | menu.append(menu_item1) |
1593 | 50 | menu_item1.connect("activate", lambda *x: actions.run_command(mw)) | 68 | menu_item1.connect("activate", lambda *x: self.emit('run_command')) |
1594 | 51 | menu_item1.show() | 69 | menu_item1.show() |
1595 | 52 | 70 | ||
1596 | 53 | ## Make 'Add' file menu entry | 71 | ## Make 'Add' file menu entry |
1598 | 54 | menu_item2 = gtk.MenuItem(_("Add Command [F5]")) | 72 | menu_item2 = gtk.MenuItem(_("Add Command")) |
1599 | 55 | menu.append(menu_item2) | 73 | menu.append(menu_item2) |
1601 | 56 | menu_item2.connect("activate", lambda *x: actions.add_command(mw)) | 74 | menu_item2.connect("activate", lambda *x: self.emit('add_command')) |
1602 | 57 | menu_item2.show() | 75 | menu_item2.show() |
1604 | 58 | 76 | ||
1605 | 59 | ## Make 'Remove' file menu entry | 77 | ## Make 'Remove' file menu entry |
1607 | 60 | menu_item3 = gtk.MenuItem(_("Remove Command [F6]")) | 78 | menu_item3 = gtk.MenuItem(_("Remove Command")) |
1608 | 61 | menu.append(menu_item3) | 79 | menu.append(menu_item3) |
1610 | 62 | menu_item3.connect("activate", lambda *x: actions.remove_command(mw)) | 80 | menu_item3.connect("activate", lambda *x: self.emit('remove_command')) |
1611 | 63 | menu_item3.show() | 81 | menu_item3.show() |
1613 | 64 | 82 | ||
1614 | 65 | ## Make 'Add Tab' file menu entry | 83 | ## Make 'Add Tab' file menu entry |
1620 | 66 | menu_item4 = gtk.MenuItem(_("Add Tab [F7]")) | 84 | menu_item4 = gtk.MenuItem(_("Add Tab")) |
1621 | 67 | menu.append(menu_item4) | 85 | menu.append(menu_item4) |
1622 | 68 | menu_item4.connect("activate", lambda *x: tabs.add_tab(notebook)) | 86 | menu_item4.connect("activate", lambda *x: self.emit('add_tab')) |
1623 | 69 | menu_item4.show() | 87 | menu_item4.show() |
1624 | 70 | 88 | ||
1625 | 89 | ## Make 'Close Tab' file menu entry | ||
1626 | 90 | menu_item4 = gtk.MenuItem(_("Close Tab")) | ||
1627 | 91 | menu.append(menu_item4) | ||
1628 | 92 | menu_item4.connect("activate", lambda *x: self.emit('close_tab')) | ||
1629 | 93 | menu_item4.show() | ||
1630 | 94 | |||
1631 | 71 | ## Make 'User Preferences' file menu entry | 95 | ## Make 'User Preferences' file menu entry |
1632 | 72 | menu_item5 = gtk.MenuItem(_("Preferences")) | 96 | menu_item5 = gtk.MenuItem(_("Preferences")) |
1633 | 73 | menu.append(menu_item5) | 97 | menu.append(menu_item5) |
1635 | 74 | menu_item5.connect("activate", lambda *x: actions.preferences(tabs)) | 98 | menu_item5.connect("activate", lambda *x: self.emit('preferences')) |
1636 | 75 | menu_item5.show() | 99 | menu_item5.show() |
1637 | 76 | 100 | ||
1638 | 77 | ## Make 'Quit' file menu entry | 101 | ## Make 'Quit' file menu entry |
1639 | 78 | menu_item6 = gtk.MenuItem(_("Quit")) | 102 | menu_item6 = gtk.MenuItem(_("Quit")) |
1640 | 79 | menu.append(menu_item6) | 103 | menu.append(menu_item6) |
1642 | 80 | menu_item6.connect("activate", actions.delete_event) | 104 | menu_item6.connect("activate", lambda *x: self.emit('quit')) |
1643 | 81 | menu_item6.show() | 105 | menu_item6.show() |
1646 | 82 | 106 | ||
1645 | 83 | |||
1647 | 84 | ## HELP MENU ## | 107 | ## HELP MENU ## |
1648 | 85 | ## Make 'About' file menu entry | 108 | ## Make 'About' file menu entry |
1649 | 86 | menu_item11 = gtk.MenuItem(_("About")) | 109 | menu_item11 = gtk.MenuItem(_("About")) |
1650 | 87 | menu2.append(menu_item11) | 110 | menu2.append(menu_item11) |
1652 | 88 | menu_item11.connect("activate", actions.about_event) | 111 | menu_item11.connect("activate", lambda *x: cc_helpers.show_about()) |
1653 | 89 | menu_item11.show() | 112 | menu_item11.show() |
1654 | 90 | 113 | ||
1655 | 91 | ## Make 'Usage' file menu entry | ||
1656 | 92 | menu_item22 = gtk.MenuItem(_("Usage")) | ||
1657 | 93 | menu2.append(menu_item22) | ||
1658 | 94 | menu_item22.connect("activate", actions.usage_event) | ||
1659 | 95 | menu_item22.show() | ||
1660 | 96 | |||
1661 | 97 | ## Make 'Help' file menu entry | 114 | ## Make 'Help' file menu entry |
1662 | 98 | menu_item22 = gtk.MenuItem(_("Help-online")) | 115 | menu_item22 = gtk.MenuItem(_("Help-online")) |
1663 | 99 | menu2.append(menu_item22) | 116 | menu2.append(menu_item22) |
1665 | 100 | menu_item22.connect("activate", actions.help_event) | 117 | menu_item22.connect("activate", lambda *x: webbrowser.open( |
1666 | 118 | "http://launchpad.net/clicompanion")) | ||
1667 | 101 | menu_item22.show() | 119 | menu_item22.show() |
1687 | 102 | 120 | ||
1688 | 103 | 121 | self.append(root_menu) # Menu bar(file) | |
1689 | 104 | 122 | self.append(root_menu2) # Menu bar(help) | |
1690 | 105 | menu_bar = gtk.MenuBar() | 123 | self.show_all() |
1691 | 106 | #color = gtk.gdk.Color(60000, 65533, 60000) | 124 | |
1692 | 107 | #menu_bar.modify_bg(gtk.STATE_NORMAL, color) | 125 | |
1693 | 108 | 126 | class Buttons(gtk.Frame): | |
1694 | 109 | menu_bar.append (root_menu) ##Menu bar(file) | 127 | __gsignals__ = { |
1695 | 110 | menu_bar.append (root_menu2) ##Menu bar(help) | 128 | 'run_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, |
1696 | 111 | #menu_bar.show() ##show File Menu # Menu Bar | 129 | ()), |
1697 | 112 | ##Show 'File' Menu | 130 | 'add_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, |
1698 | 113 | #root_menu.show() | 131 | ()), |
1699 | 114 | return menu_bar | 132 | 'remove_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, |
1700 | 115 | 133 | ()), | |
1701 | 116 | 134 | 'edit_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | |
1702 | 117 | 135 | ()), | |
1703 | 118 | def buttons(self, mw, spacing, layout): | 136 | 'add_tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, |
1704 | 119 | #button box at bottom of main window | 137 | ()), |
1705 | 120 | frame = gtk.Frame() | 138 | 'show_man': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, |
1706 | 139 | ()), | ||
1707 | 140 | 'quit': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
1708 | 141 | ()), | ||
1709 | 142 | } | ||
1710 | 143 | |||
1711 | 144 | #button box at bottom of main window | ||
1712 | 145 | def __init__(self, spacing, layout): | ||
1713 | 146 | gtk.Frame.__init__(self) | ||
1714 | 121 | bbox = gtk.HButtonBox() | 147 | bbox = gtk.HButtonBox() |
1715 | 122 | bbox.set_border_width(5) | 148 | bbox.set_border_width(5) |
1717 | 123 | frame.add(bbox) | 149 | self.add(bbox) |
1718 | 124 | 150 | ||
1719 | 125 | # Set the appearance of the Button Box | 151 | # Set the appearance of the Button Box |
1720 | 126 | #color = gtk.gdk.Color(65000, 61000, 61000) | ||
1721 | 127 | bbox.set_layout(layout) | 152 | bbox.set_layout(layout) |
1722 | 128 | bbox.set_spacing(spacing) | 153 | bbox.set_spacing(spacing) |
1723 | 129 | # Run button | 154 | # Run button |
1725 | 130 | buttonRun = gtk.Button('_'+_("Run")) | 155 | buttonRun = gtk.Button(stock=gtk.STOCK_EXECUTE) |
1726 | 131 | bbox.add(buttonRun) | 156 | bbox.add(buttonRun) |
1728 | 132 | buttonRun.connect("clicked", lambda *x: mw.actions.run_command(mw)) | 157 | buttonRun.connect("clicked", lambda *x: self.emit('run_command')) |
1729 | 133 | buttonRun.set_tooltip_text(_("Click to run a highlighted command")) | 158 | buttonRun.set_tooltip_text(_("Click to run a highlighted command")) |
1730 | 134 | #buttonRun.modify_bg(gtk.STATE_NORMAL, color) | ||
1731 | 135 | #buttonRun.modify_bg(gtk.STATE_PRELIGHT, color) | ||
1732 | 136 | #buttonRun.modify_bg(gtk.STATE_INSENSITIVE, color) | ||
1733 | 137 | # Add button | 159 | # Add button |
1734 | 138 | buttonAdd = gtk.Button(stock=gtk.STOCK_ADD) | 160 | buttonAdd = gtk.Button(stock=gtk.STOCK_ADD) |
1735 | 139 | bbox.add(buttonAdd) | 161 | bbox.add(buttonAdd) |
1741 | 140 | buttonAdd.connect("clicked", lambda *x: mw.actions.add_command(mw)) | 162 | buttonAdd.connect("clicked", lambda *x: self.emit('add_command')) |
1742 | 141 | buttonAdd.set_tooltip_text(_("Click to add a command to your command list")) | 163 | buttonAdd.set_tooltip_text(_("Click to add a command to your" |
1743 | 142 | #buttonAdd.modify_bg(gtk.STATE_NORMAL, color) | 164 | "command list")) |
1739 | 143 | #buttonAdd.modify_bg(gtk.STATE_PRELIGHT, color) | ||
1740 | 144 | #buttonAdd.modify_bg(gtk.STATE_INSENSITIVE, color) | ||
1744 | 145 | # Edit button | 165 | # Edit button |
1746 | 146 | buttonEdit = gtk.Button('_'+_("Edit")) | 166 | buttonEdit = gtk.Button(stock=gtk.STOCK_EDIT) |
1747 | 147 | bbox.add(buttonEdit) | 167 | bbox.add(buttonEdit) |
1753 | 148 | buttonEdit.connect("clicked", lambda *x: mw.actions.edit_command(mw)) | 168 | buttonEdit.connect("clicked", lambda *x: self.emit('edit_command')) |
1754 | 149 | buttonEdit.set_tooltip_text(_("Click to edit a command in your command list")) | 169 | buttonEdit.set_tooltip_text(_("Click to edit a command in your " |
1755 | 150 | #buttonEdit.modify_bg(gtk.STATE_NORMAL, color) | 170 | "command list")) |
1751 | 151 | #buttonEdit.modify_bg(gtk.STATE_PRELIGHT, color) | ||
1752 | 152 | #buttonEdit.modify_bg(gtk.STATE_INSENSITIVE, color) | ||
1756 | 153 | # Delete button | 171 | # Delete button |
1757 | 154 | buttonDelete = gtk.Button(stock=gtk.STOCK_DELETE) | 172 | buttonDelete = gtk.Button(stock=gtk.STOCK_DELETE) |
1758 | 155 | bbox.add(buttonDelete) | 173 | bbox.add(buttonDelete) |
1764 | 156 | buttonDelete.connect("clicked", lambda *x: mw.actions.remove_command(mw)) | 174 | buttonDelete.connect("clicked", lambda *x: self.emit('remove_command')) |
1765 | 157 | buttonDelete.set_tooltip_text(_("Click to delete a command in your command list")) | 175 | buttonDelete.set_tooltip_text(_("Click to delete a command in your " |
1766 | 158 | #buttonDelete.modify_bg(gtk.STATE_NORMAL, color) | 176 | "command list")) |
1762 | 159 | #buttonDelete.modify_bg(gtk.STATE_PRELIGHT, color) | ||
1763 | 160 | #buttonDelete.modify_bg(gtk.STATE_INSENSITIVE, color) | ||
1767 | 161 | #Help Button | 177 | #Help Button |
1768 | 162 | buttonHelp = gtk.Button(stock=gtk.STOCK_HELP) | 178 | buttonHelp = gtk.Button(stock=gtk.STOCK_HELP) |
1769 | 163 | bbox.add(buttonHelp) | 179 | bbox.add(buttonHelp) |
1775 | 164 | buttonHelp.connect("clicked", lambda *x: mw.actions.man_page(mw.notebook)) | 180 | buttonHelp.connect("clicked", lambda *x: self.emit('show_man')) |
1776 | 165 | buttonHelp.set_tooltip_text(_("Click to get help with a command in your command list")) | 181 | buttonHelp.set_tooltip_text(_("Click to get help with a command in " |
1777 | 166 | #buttonHelp.modify_bg(gtk.STATE_NORMAL, color) | 182 | "your command list")) |
1778 | 167 | #buttonHelp.modify_bg(gtk.STATE_PRELIGHT, color) | 183 | #AddTab Button |
1779 | 168 | #buttonHelp.modify_bg(gtk.STATE_INSENSITIVE, color) | 184 | button_addtab = gtk.Button(stock=gtk.STOCK_NEW) |
1780 | 185 | bbox.add(button_addtab) | ||
1781 | 186 | # Very ugly and nasty hack... | ||
1782 | 187 | box = button_addtab.get_children()[0].get_children()[0] | ||
1783 | 188 | lbl = box.get_children()[1] | ||
1784 | 189 | lbl.set_text(_('Add tab')) | ||
1785 | 190 | button_addtab.connect("clicked", lambda *x: self.emit('add_tab')) | ||
1786 | 191 | button_addtab.set_tooltip_text(_("Click to add a terminal tab")) | ||
1787 | 169 | # Cancel button | 192 | # Cancel button |
1788 | 170 | buttonCancel = gtk.Button(stock=gtk.STOCK_QUIT) | 193 | buttonCancel = gtk.Button(stock=gtk.STOCK_QUIT) |
1789 | 171 | bbox.add(buttonCancel) | 194 | bbox.add(buttonCancel) |
1791 | 172 | buttonCancel.connect("clicked", mw.actions.delete_event) | 195 | buttonCancel.connect("clicked", lambda *x: self.emit('quit')) |
1792 | 173 | buttonCancel.set_tooltip_text(_("Click to quit CLI Companion")) | 196 | buttonCancel.set_tooltip_text(_("Click to quit CLI Companion")) |
1834 | 174 | #buttonCancel.modify_bg(gtk.STATE_NORMAL, color) | 197 | self.show_all() |
1794 | 175 | #buttonCancel.modify_bg(gtk.STATE_PRELIGHT, color) | ||
1795 | 176 | #buttonCancel.modify_bg(gtk.STATE_INSENSITIVE, color) | ||
1796 | 177 | return frame | ||
1797 | 178 | |||
1798 | 179 | |||
1799 | 180 | #right-click popup menu for the Liststore(command list) | ||
1800 | 181 | def right_click(self, widget, event, mw): | ||
1801 | 182 | if event.button == 3: | ||
1802 | 183 | x = int(event.x) | ||
1803 | 184 | y = int(event.y) | ||
1804 | 185 | time = event.time | ||
1805 | 186 | pthinfo = mw.treeview.get_path_at_pos(x, y) | ||
1806 | 187 | if pthinfo is not None: | ||
1807 | 188 | path, col, cellx, celly = pthinfo | ||
1808 | 189 | mw.treeview.grab_focus() | ||
1809 | 190 | mw.treeview.set_cursor( path, col, 0) | ||
1810 | 191 | |||
1811 | 192 | # right-click popup menu Apply(run) | ||
1812 | 193 | popupMenu = gtk.Menu() | ||
1813 | 194 | menuPopup1 = gtk.ImageMenuItem (gtk.STOCK_APPLY) | ||
1814 | 195 | popupMenu.add(menuPopup1) | ||
1815 | 196 | menuPopup1.connect("activate", lambda self, *x: mw.actions.run_command(mw)) | ||
1816 | 197 | # right-click popup menu Edit | ||
1817 | 198 | menuPopup2 = gtk.ImageMenuItem (gtk.STOCK_EDIT) | ||
1818 | 199 | popupMenu.add(menuPopup2) | ||
1819 | 200 | menuPopup2.connect("activate", lambda self, *x: mw.actions.edit_command(mw)) | ||
1820 | 201 | # right-click popup menu Delete | ||
1821 | 202 | menuPopup3 = gtk.ImageMenuItem (gtk.STOCK_DELETE) | ||
1822 | 203 | popupMenu.add(menuPopup3) | ||
1823 | 204 | menuPopup3.connect("activate", lambda self, *x: mw.actions.remove_command(mw)) | ||
1824 | 205 | # right-click popup menu Help | ||
1825 | 206 | menuPopup4 = gtk.ImageMenuItem (gtk.STOCK_HELP) | ||
1826 | 207 | popupMenu.add(menuPopup4) | ||
1827 | 208 | menuPopup4.connect("activate", lambda self, *x: mw.actions.man_page(mw.notebook)) | ||
1828 | 209 | # Show popup menu | ||
1829 | 210 | popupMenu.show_all() | ||
1830 | 211 | popupMenu.popup( None, None, None, event.button, time) | ||
1831 | 212 | return True | ||
1832 | 213 | |||
1833 | 214 | |||
1835 | 215 | 198 | ||
1836 | === added file 'clicompanionlib/plugins.py' | |||
1837 | --- clicompanionlib/plugins.py 1970-01-01 00:00:00 +0000 | |||
1838 | +++ clicompanionlib/plugins.py 2012-01-08 10:37:24 +0000 | |||
1839 | @@ -0,0 +1,207 @@ | |||
1840 | 1 | #!/usr/bin/env python | ||
1841 | 2 | # -*- coding: utf-8 -*- | ||
1842 | 3 | # | ||
1843 | 4 | # plugins.py - Plugin related clases for the clicompanion | ||
1844 | 5 | # | ||
1845 | 6 | # Copyright 2012 David Caro <david.caro.estevez@gmail.com> | ||
1846 | 7 | # | ||
1847 | 8 | # This program is free software: you can redistribute it and/or modify it | ||
1848 | 9 | # under the terms of the GNU General Public License version 3, as published | ||
1849 | 10 | # by the Free Software Foundation. | ||
1850 | 11 | # | ||
1851 | 12 | # This program is distributed in the hope that it will be useful, but | ||
1852 | 13 | # WITHOUT ANY WARRANTY; without even the implied warranties of | ||
1853 | 14 | # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
1854 | 15 | # PURPOSE. See the GNU General Public License for more details. | ||
1855 | 16 | # | ||
1856 | 17 | # You should have received a copy of the GNU General Public License along | ||
1857 | 18 | # with this program. If not, see <http://www.gnu.org/licenses/>. | ||
1858 | 19 | # | ||
1859 | 20 | ################################################# | ||
1860 | 21 | ## The plugins | ||
1861 | 22 | ## | ||
1862 | 23 | ## Here are defined the PluginLoader class and all the plugin base classes. | ||
1863 | 24 | ## | ||
1864 | 25 | ## The PluginLoader class | ||
1865 | 26 | ## This class handles the loading and handpling of the plugins, it get a | ||
1866 | 27 | ## directory and a list of the allowed plugins and loads all the allowed | ||
1867 | 28 | ## plugins that it found on the dir *.py files (a plugin is a class that | ||
1868 | 29 | ## inherits from the base class Plugin defined in this file). | ||
1869 | 30 | ## | ||
1870 | 31 | ## The Plugin class is the base class for all the plugins, if the plugin does | ||
1871 | 32 | ## not inherit from that class, it will not be loaded. | ||
1872 | 33 | ## | ||
1873 | 34 | ## TabPlugin: This is a plugin that will be used as a tab in the upper notebook | ||
1874 | 35 | ## of the application, like the LocalCommandList o CommandLineFU plugins | ||
1875 | 36 | ## | ||
1876 | 37 | ## PluginConfig: this will be the config tab in the preferences for the plugin. | ||
1877 | 38 | ## | ||
1878 | 39 | ## About the plugins: | ||
1879 | 40 | ## The plugins have some attributes that should be set, like the __authors__, | ||
1880 | 41 | ## and the __info__ attributes, that will be shown in the info page for the | ||
1881 | 42 | ## plugin, and the __title__ that will be the plugin name shown in the plugins | ||
1882 | 43 | ## list. | ||
1883 | 44 | |||
1884 | 45 | import gobject | ||
1885 | 46 | import gtk | ||
1886 | 47 | import sys | ||
1887 | 48 | import os | ||
1888 | 49 | import inspect | ||
1889 | 50 | from clicompanionlib.utils import dbg | ||
1890 | 51 | |||
1891 | 52 | |||
1892 | 53 | class PluginLoader: | ||
1893 | 54 | def __init__(self): | ||
1894 | 55 | self.plugins = {} | ||
1895 | 56 | self.allowed = [] | ||
1896 | 57 | |||
1897 | 58 | def load(self, pluginsdir, allowed): | ||
1898 | 59 | self.allowed = allowed | ||
1899 | 60 | dbg('Allowing only the plugins %s' % allowed.__repr__()) | ||
1900 | 61 | sys.path.insert(0, pluginsdir) | ||
1901 | 62 | try: | ||
1902 | 63 | files = os.listdir(pluginsdir) | ||
1903 | 64 | except OSError: | ||
1904 | 65 | sys.path.remove(pluginsdir) | ||
1905 | 66 | return False | ||
1906 | 67 | for plugin in files: | ||
1907 | 68 | pluginpath = os.path.join(pluginsdir, plugin) | ||
1908 | 69 | if not os.path.isfile(pluginpath) or not plugin[-3:] == '.py': | ||
1909 | 70 | continue | ||
1910 | 71 | dbg('Searching plugin file %s for plugins...' % plugin) | ||
1911 | 72 | try: | ||
1912 | 73 | module = __import__(plugin[:-3], globals(), locals(), ['']) | ||
1913 | 74 | for cname, mclass \ | ||
1914 | 75 | in inspect.getmembers(module, inspect.isclass): | ||
1915 | 76 | dbg(' Checking if class %s is a plugin.' % cname) | ||
1916 | 77 | if issubclass(mclass, Plugin): | ||
1917 | 78 | if cname not in self.plugins.keys(): | ||
1918 | 79 | dbg(' Found plugin %s' % cname) | ||
1919 | 80 | self.plugins[cname] = mclass | ||
1920 | 81 | continue | ||
1921 | 82 | except Exception, ex: | ||
1922 | 83 | print 'Error searching plugin file %s: %s' % (plugin, ex) | ||
1923 | 84 | |||
1924 | 85 | def enable(self, plugins): | ||
1925 | 86 | for plugin in plugins: | ||
1926 | 87 | if plugin not in self.allowed: | ||
1927 | 88 | self.allowed.append(plugin) | ||
1928 | 89 | |||
1929 | 90 | def get_plugins(self, capabilities=None): | ||
1930 | 91 | plugins = [] | ||
1931 | 92 | if capabilities == None: | ||
1932 | 93 | return [(pg, cs) for pg, cs in self.plugins.items() | ||
1933 | 94 | if pg in self.allowed()] | ||
1934 | 95 | for plugin, pclass in self.plugins.items(): | ||
1935 | 96 | for capability in pclass.__capabilities__: | ||
1936 | 97 | if capability in capabilities \ | ||
1937 | 98 | and plugin in self.allowed: | ||
1938 | 99 | plugins.append((plugin, pclass)) | ||
1939 | 100 | dbg('Matching plugin %s for %s' % (plugin, capability)) | ||
1940 | 101 | return plugins | ||
1941 | 102 | |||
1942 | 103 | def get_plugin_conf(self, plugin): | ||
1943 | 104 | if plugin + 'Config' in self.plugins: | ||
1944 | 105 | return self.plugins[plugin + 'Config'] | ||
1945 | 106 | |||
1946 | 107 | def is_enabled(self, plugin): | ||
1947 | 108 | return plugin in self.allowed | ||
1948 | 109 | |||
1949 | 110 | def get_allowed(self): | ||
1950 | 111 | return self.allowed | ||
1951 | 112 | |||
1952 | 113 | def get_disallowed(self): | ||
1953 | 114 | disallowed = [] | ||
1954 | 115 | for plugin, pclass in self.plugins.items(): | ||
1955 | 116 | if plugin not in self.allowed \ | ||
1956 | 117 | and pclass.__capabilities__ != ['Config']: | ||
1957 | 118 | disallowed.append(plugin) | ||
1958 | 119 | return disallowed | ||
1959 | 120 | |||
1960 | 121 | def get_available_plugins(self): | ||
1961 | 122 | return [pg for pg, cl | ||
1962 | 123 | in self.plugins.items() | ||
1963 | 124 | if ['Config'] == cl.__capabilities__] | ||
1964 | 125 | |||
1965 | 126 | def get_info(self, plugin): | ||
1966 | 127 | if plugin in self.plugins.keys(): | ||
1967 | 128 | return self.plugins[plugin].__info__ | ||
1968 | 129 | |||
1969 | 130 | def get_authors(self, plugin): | ||
1970 | 131 | if plugin in self.plugins.keys(): | ||
1971 | 132 | return self.plugins[plugin].__authors__ | ||
1972 | 133 | |||
1973 | 134 | |||
1974 | 135 | ## To make all the classe sinherit from this one | ||
1975 | 136 | class Plugin(gtk.VBox): | ||
1976 | 137 | def __init__(self): | ||
1977 | 138 | gtk.VBox.__init__(self) | ||
1978 | 139 | |||
1979 | 140 | |||
1980 | 141 | class TabPlugin(Plugin): | ||
1981 | 142 | ''' | ||
1982 | 143 | Generic Tab plugin that implements all the possible signals. | ||
1983 | 144 | The *command signals are used to interact mainly with the LocalCommandList | ||
1984 | 145 | plugin that handles the locally stored commands. | ||
1985 | 146 | The add_tab is used to add anew terminal tab. | ||
1986 | 147 | ''' | ||
1987 | 148 | __gsignals__ = { | ||
1988 | 149 | 'run_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
1989 | 150 | (str, str, str)), | ||
1990 | 151 | 'add_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
1991 | 152 | (str, str, str)), | ||
1992 | 153 | 'remove_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
1993 | 154 | (str, str, str)), | ||
1994 | 155 | 'edit_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
1995 | 156 | (str, str, str)), | ||
1996 | 157 | 'add_tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
1997 | 158 | ()), | ||
1998 | 159 | 'preferences': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
1999 | 160 | ()), | ||
2000 | 161 | 'show_man': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
2001 | 162 | (str,)), | ||
2002 | 163 | 'quit': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
2003 | 164 | ()), | ||
2004 | 165 | } | ||
2005 | 166 | __capabilities__ = ['CommandTab'] | ||
2006 | 167 | __title__ = '' | ||
2007 | 168 | __authors__ = '' | ||
2008 | 169 | __info__ = '' | ||
2009 | 170 | |||
2010 | 171 | def reload(self): | ||
2011 | 172 | ''' | ||
2012 | 173 | This method is called when a signal 'reload' is sent from it's | ||
2013 | 174 | configurator | ||
2014 | 175 | ''' | ||
2015 | 176 | pass | ||
2016 | 177 | |||
2017 | 178 | def get_command(self): | ||
2018 | 179 | ''' | ||
2019 | 180 | This method is uset to retrieve a command, not sure if it's needed yet | ||
2020 | 181 | ''' | ||
2021 | 182 | return None, None, None | ||
2022 | 183 | |||
2023 | 184 | def filter(self, string): | ||
2024 | 185 | ''' | ||
2025 | 186 | This function is used to filter the commandslist, usually by the | ||
2026 | 187 | search box | ||
2027 | 188 | ''' | ||
2028 | 189 | pass | ||
2029 | 190 | |||
2030 | 191 | |||
2031 | 192 | class PluginConfig(Plugin): | ||
2032 | 193 | ''' | ||
2033 | 194 | Generic plugin configuration window, to be used in the preferences plugins | ||
2034 | 195 | tab | ||
2035 | 196 | ''' | ||
2036 | 197 | __gsignals__ = { | ||
2037 | 198 | ## when emited, this signal forces the reload of it's associated plugin | ||
2038 | 199 | ## to reload the plugin config without having to restart the program | ||
2039 | 200 | 'reload': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
2040 | 201 | ()) | ||
2041 | 202 | } | ||
2042 | 203 | __capabilities__ = ['Config'] | ||
2043 | 204 | |||
2044 | 205 | def __init__(self, config): | ||
2045 | 206 | Plugin.__init__(self) | ||
2046 | 207 | self.config = config | ||
2047 | 0 | 208 | ||
2048 | === added file 'clicompanionlib/preferences.py' | |||
2049 | --- clicompanionlib/preferences.py 1970-01-01 00:00:00 +0000 | |||
2050 | +++ clicompanionlib/preferences.py 2012-01-08 10:37:24 +0000 | |||
2051 | @@ -0,0 +1,708 @@ | |||
2052 | 1 | #!/usr/bin/env python | ||
2053 | 2 | # -*- coding: utf-8 -*- | ||
2054 | 3 | # | ||
2055 | 4 | # preferences.py - Preferences dialogs for clicompanion | ||
2056 | 5 | # | ||
2057 | 6 | # Copyright 2012 Duane Hinnen, Kenny Meyer, Marcos Vanettai, Marek Bardoński, | ||
2058 | 7 | # David Caro <david.caro.estevez@gmail.com> | ||
2059 | 8 | # | ||
2060 | 9 | # This program is free software: you can redistribute it and/or modify it | ||
2061 | 10 | # under the terms of the GNU General Public License version 3, as published | ||
2062 | 11 | # by the Free Software Foundation. | ||
2063 | 12 | # | ||
2064 | 13 | # This program is distributed in the hope that it will be useful, but | ||
2065 | 14 | # WITHOUT ANY WARRANTY; without even the implied warranties of | ||
2066 | 15 | # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
2067 | 16 | # PURPOSE. See the GNU General Public License for more details. | ||
2068 | 17 | # | ||
2069 | 18 | # You should have received a copy of the GNU General Public License along | ||
2070 | 19 | # with this program. If not, see <http://www.gnu.org/licenses/>. | ||
2071 | 20 | # | ||
2072 | 21 | ############################################ | ||
2073 | 22 | ## The preferences window is a popup that shows all the configuration options | ||
2074 | 23 | ## allowing the user to change them, also handles the profiles creatin and | ||
2075 | 24 | ## delete. | ||
2076 | 25 | ## | ||
2077 | 26 | ## The main class is the PreferencesWindow, that has all the other packed. Each | ||
2078 | 27 | ## as a tab inside the preferences window. | ||
2079 | 28 | ## The other classes are: | ||
2080 | 29 | ## - PluginsTab: handles the plugin activation and configuration | ||
2081 | 30 | ## - KeybindingsTab: handles the keybindings | ||
2082 | 31 | ## - ProfilesTab: The main tab for the profiles setting, this class is a | ||
2083 | 32 | ## notebook with three classes (tabs) packed | ||
2084 | 33 | ## - ProfScrollingTab: the scrolling options tab inside the profiles tab | ||
2085 | 34 | ## - ProfColorsTab: the colors tab | ||
2086 | 35 | ## - ProfGeneralTab: the general setting tab | ||
2087 | 36 | ## | ||
2088 | 37 | ## About the profiles: each profile is a configuration section in the config | ||
2089 | 38 | ## file with the name 'profile::profilename', having always the profile | ||
2090 | 39 | ## 'profile::default', the will have all the default options (if it is not | ||
2091 | 40 | ## found, it will be created with the harcoded options inside the code. | ||
2092 | 41 | |||
2093 | 42 | import pygtk | ||
2094 | 43 | pygtk.require('2.0') | ||
2095 | 44 | import gtk | ||
2096 | 45 | import gobject | ||
2097 | 46 | import clicompanionlib.config as cc_config | ||
2098 | 47 | import clicompanionlib.utils as cc_utils | ||
2099 | 48 | from clicompanionlib.utils import dbg | ||
2100 | 49 | |||
2101 | 50 | COLOR_SCHEMES = { | ||
2102 | 51 | 'Grey on Black': ['#aaaaaa', '#000000'], | ||
2103 | 52 | 'Black on Yellow': ['#000000', '#ffffdd'], | ||
2104 | 53 | 'Black on White': ['#000000', '#ffffff'], | ||
2105 | 54 | 'White on Black': ['#ffffff', '#000000'], | ||
2106 | 55 | 'Green on Black': ['#00ff00', '#000000'], | ||
2107 | 56 | 'Orange on Black': ['#e53c00', '#000000'], | ||
2108 | 57 | 'Custom': [] | ||
2109 | 58 | } | ||
2110 | 59 | |||
2111 | 60 | |||
2112 | 61 | def color2hex(color_16b): | ||
2113 | 62 | """ | ||
2114 | 63 | Pull the colour values out of a Gtk ColorPicker widget and return them | ||
2115 | 64 | as 8bit hex values, sinces its default behaviour is to give 16bit values | ||
2116 | 65 | """ | ||
2117 | 66 | return('#%02x%02x%02x' % (color_16b.red >> 8, | ||
2118 | 67 | color_16b.green >> 8, | ||
2119 | 68 | color_16b.blue >> 8)) | ||
2120 | 69 | |||
2121 | 70 | |||
2122 | 71 | class ProfGeneralTab(gtk.VBox): | ||
2123 | 72 | def __init__(self, config, profile='default'): | ||
2124 | 73 | gtk.VBox.__init__(self) | ||
2125 | 74 | self.config = config | ||
2126 | 75 | self.profile = profile | ||
2127 | 76 | self.draw_all() | ||
2128 | 77 | |||
2129 | 78 | def draw_all(self): | ||
2130 | 79 | ## 'use_system_font' | ||
2131 | 80 | self.systemfont = gtk.CheckButton(label=_('Use system fixed' | ||
2132 | 81 | 'width font')) | ||
2133 | 82 | self.pack_start(self.systemfont, False, False, 8) | ||
2134 | 83 | self.systemfont.set_active( | ||
2135 | 84 | self.config.getboolean('profile::' + self.profile, | ||
2136 | 85 | 'use_system_font')) | ||
2137 | 86 | self.systemfont.connect('toggled', lambda *x: self.update_font_btn()) | ||
2138 | 87 | |||
2139 | 88 | ## 'font' | ||
2140 | 89 | font_box = gtk.HBox() | ||
2141 | 90 | font_box.pack_start(gtk.Label('Font:'), False, False, 8) | ||
2142 | 91 | self.fontbtn = gtk.FontButton(self.config.get('profile::' | ||
2143 | 92 | + self.profile, 'font')) | ||
2144 | 93 | font_box.pack_start(self.fontbtn, False, False, 8) | ||
2145 | 94 | self.pack_start(font_box, False, False, 8) | ||
2146 | 95 | ## 'bold_text' | ||
2147 | 96 | self.bold_text = gtk.CheckButton(label=_('Allow bold text')) | ||
2148 | 97 | self.pack_start(self.bold_text, False, False, 8) | ||
2149 | 98 | self.bold_text.set_active(self.config.getboolean('profile::' | ||
2150 | 99 | + self.profile, 'bold_text')) | ||
2151 | 100 | ## 'antialias' | ||
2152 | 101 | self.antialias = gtk.CheckButton(label=_('Anti-alias text')) | ||
2153 | 102 | self.pack_start(self.antialias, False, False, 8) | ||
2154 | 103 | self.antialias.set_active(self.config.getboolean('profile::' | ||
2155 | 104 | + self.profile, 'antialias')) | ||
2156 | 105 | ## 'sel_word' | ||
2157 | 106 | sel_word_box = gtk.HBox() | ||
2158 | 107 | sel_word_box.pack_start(gtk.Label('Select-by-word characters:')) | ||
2159 | 108 | self.sel_word_text = gtk.Entry() | ||
2160 | 109 | self.sel_word_text.set_text(self.config.get('profile::' | ||
2161 | 110 | + self.profile, 'sel_word')) | ||
2162 | 111 | sel_word_box.pack_start(self.sel_word_text, False, False, 0) | ||
2163 | 112 | self.pack_start(sel_word_box, False, False, 8) | ||
2164 | 113 | ## System subsection | ||
2165 | 114 | sys_lbl = gtk.Label() | ||
2166 | 115 | sys_lbl.set_markup('<b>System Configuration</b>') | ||
2167 | 116 | self.pack_start(sys_lbl, False, False, 4) | ||
2168 | 117 | ## 'update_login_records' | ||
2169 | 118 | self.update_login_records = gtk.CheckButton( | ||
2170 | 119 | label=_('Update login records')) | ||
2171 | 120 | self.pack_start(self.update_login_records, False, False, 8) | ||
2172 | 121 | self.update_login_records.set_active( | ||
2173 | 122 | self.config.getboolean('profile::' + self.profile, | ||
2174 | 123 | 'update_login_records')) | ||
2175 | 124 | self.update_font_btn() | ||
2176 | 125 | |||
2177 | 126 | def update_font_btn(self): | ||
2178 | 127 | if self.systemfont.get_active(): | ||
2179 | 128 | self.fontbtn.set_sensitive(False) | ||
2180 | 129 | else: | ||
2181 | 130 | self.fontbtn.set_sensitive(True) | ||
2182 | 131 | |||
2183 | 132 | def save_changes(self): | ||
2184 | 133 | if 'profile::' + self.profile in self.config.sections(): | ||
2185 | 134 | self.config.set('profile::' + self.profile, | ||
2186 | 135 | 'use_system_font', '%s' % self.systemfont.get_active()) | ||
2187 | 136 | self.config.set('profile::' + self.profile, | ||
2188 | 137 | 'font', '%s' % self.fontbtn.get_font_name()) | ||
2189 | 138 | self.config.set('profile::' + self.profile, | ||
2190 | 139 | 'bold_text', '%s' % self.bold_text.get_active()) | ||
2191 | 140 | self.config.set('profile::' + self.profile, | ||
2192 | 141 | 'antialias', '%s' % self.antialias.get_active()) | ||
2193 | 142 | self.config.set('profile::' + self.profile, | ||
2194 | 143 | 'sel_word', self.sel_word_text.get_text()) | ||
2195 | 144 | self.config.set('profile::' + self.profile, | ||
2196 | 145 | 'update_login_records', | ||
2197 | 146 | '%s' % self.update_login_records.get_active()) | ||
2198 | 147 | |||
2199 | 148 | def set_profile(self, profile='default'): | ||
2200 | 149 | self.save_changes() | ||
2201 | 150 | if profile != self.profile: | ||
2202 | 151 | self.profile = profile | ||
2203 | 152 | self.update() | ||
2204 | 153 | self.show_all() | ||
2205 | 154 | |||
2206 | 155 | def update(self): | ||
2207 | 156 | for child in self.get_children(): | ||
2208 | 157 | self.remove(child) | ||
2209 | 158 | self.draw_all() | ||
2210 | 159 | |||
2211 | 160 | |||
2212 | 161 | class ProfColorsTab(gtk.VBox): | ||
2213 | 162 | def __init__(self, config, profile='default'): | ||
2214 | 163 | gtk.VBox.__init__(self) | ||
2215 | 164 | self.config = config | ||
2216 | 165 | self.profile = profile | ||
2217 | 166 | self.draw_all() | ||
2218 | 167 | |||
2219 | 168 | def draw_all(self): | ||
2220 | 169 | ## 'use_system_colors' | ||
2221 | 170 | self.systemcols = gtk.CheckButton( | ||
2222 | 171 | label=_('Use colors from system theme')) | ||
2223 | 172 | self.pack_start(self.systemcols, False, False, 8) | ||
2224 | 173 | self.systemcols.set_active( | ||
2225 | 174 | self.config.getboolean('profile::' + self.profile, | ||
2226 | 175 | 'use_system_colors')) | ||
2227 | 176 | self.systemcols.connect('toggled', lambda *x: self.update_sys_colors()) | ||
2228 | 177 | |||
2229 | 178 | ## 'color_scheme' | ||
2230 | 179 | hbox = gtk.HBox() | ||
2231 | 180 | hbox.pack_start(gtk.Label('Color scheme:'), False, False, 8) | ||
2232 | 181 | self.colsch_combo = gtk.combo_box_new_text() | ||
2233 | 182 | color_scheme = self.config.get('profile::' + self.profile, | ||
2234 | 183 | 'color_scheme') | ||
2235 | 184 | self.colsch_combo.append_text('Custom') | ||
2236 | 185 | if color_scheme == 'Custom': | ||
2237 | 186 | self.colsch_combo.set_active(0) | ||
2238 | 187 | hbox.pack_start(self.colsch_combo, False, False, 8) | ||
2239 | 188 | i = 0 | ||
2240 | 189 | for cs, colors in COLOR_SCHEMES.items(): | ||
2241 | 190 | i = i + 1 | ||
2242 | 191 | self.colsch_combo.append_text(cs) | ||
2243 | 192 | if color_scheme == cs: | ||
2244 | 193 | self.colsch_combo.set_active(i) | ||
2245 | 194 | self.pack_start(hbox, False, False, 8) | ||
2246 | 195 | self.colsch_combo.connect('changed', | ||
2247 | 196 | lambda *x: self.update_color_btns()) | ||
2248 | 197 | |||
2249 | 198 | ## 'colorf' | ||
2250 | 199 | hbox = gtk.HBox() | ||
2251 | 200 | hbox.pack_start(gtk.Label('Font color:'), False, False, 8) | ||
2252 | 201 | self.colorf = gtk.ColorButton() | ||
2253 | 202 | hbox.pack_start(self.colorf, False, False, 8) | ||
2254 | 203 | self.pack_start(hbox, False, False, 8) | ||
2255 | 204 | self.colorf.connect('color-set', lambda *x: self.update_custom_color()) | ||
2256 | 205 | |||
2257 | 206 | ## 'colorb' | ||
2258 | 207 | hbox = gtk.HBox() | ||
2259 | 208 | hbox.pack_start(gtk.Label('Background color:'), False, False, 8) | ||
2260 | 209 | self.colorb = gtk.ColorButton() | ||
2261 | 210 | hbox.pack_start(self.colorb, False, False, 8) | ||
2262 | 211 | self.pack_start(hbox, False, False, 8) | ||
2263 | 212 | self.colorb.connect('color-set', lambda *x: self.update_custom_color()) | ||
2264 | 213 | |||
2265 | 214 | self.update_sys_colors() | ||
2266 | 215 | |||
2267 | 216 | def update_sys_colors(self): | ||
2268 | 217 | if not self.systemcols.get_active(): | ||
2269 | 218 | self.colsch_combo.set_sensitive(True) | ||
2270 | 219 | self.update_color_btns() | ||
2271 | 220 | else: | ||
2272 | 221 | self.colsch_combo.set_sensitive(False) | ||
2273 | 222 | self.update_color_btns() | ||
2274 | 223 | self.colorb.set_sensitive(False) | ||
2275 | 224 | self.colorf.set_sensitive(False) | ||
2276 | 225 | |||
2277 | 226 | def update_color_btns(self): | ||
2278 | 227 | color_scheme = self.colsch_combo.get_active_text() | ||
2279 | 228 | if color_scheme != 'Custom': | ||
2280 | 229 | self.colorb.set_sensitive(False) | ||
2281 | 230 | self.colorf.set_sensitive(False) | ||
2282 | 231 | self.colorf.set_color(gtk.gdk.color_parse( | ||
2283 | 232 | COLOR_SCHEMES[color_scheme][0])) | ||
2284 | 233 | self.colorb.set_color(gtk.gdk.color_parse( | ||
2285 | 234 | COLOR_SCHEMES[color_scheme][1])) | ||
2286 | 235 | else: | ||
2287 | 236 | self.colorb.set_sensitive(True) | ||
2288 | 237 | self.colorf.set_sensitive(True) | ||
2289 | 238 | self.colorf.set_color(gtk.gdk.color_parse( | ||
2290 | 239 | self.config.get('profile::' + self.profile, 'colorf'))) | ||
2291 | 240 | self.colorb.set_color(gtk.gdk.color_parse( | ||
2292 | 241 | self.config.get('profile::' + self.profile, 'colorb'))) | ||
2293 | 242 | |||
2294 | 243 | def update_custom_color(self): | ||
2295 | 244 | color_scheme = self.colsch_combo.get_active_text() | ||
2296 | 245 | if color_scheme == 'Custom': | ||
2297 | 246 | self.config.set('profile::' + self.profile, 'colorf', | ||
2298 | 247 | color2hex(self.colorf.get_color())) | ||
2299 | 248 | self.config.set('profile::' + self.profile, 'colorb', | ||
2300 | 249 | color2hex(self.colorb.get_color())) | ||
2301 | 250 | |||
2302 | 251 | def save_changes(self): | ||
2303 | 252 | if 'profile::' + self.profile in self.config.sections(): | ||
2304 | 253 | self.config.set('profile::' + self.profile, 'use_system_colors', | ||
2305 | 254 | self.systemcols.get_active().__repr__()) | ||
2306 | 255 | self.config.set('profile::' + self.profile, 'color_scheme', | ||
2307 | 256 | self.colsch_combo.get_active_text()) | ||
2308 | 257 | if self.colsch_combo.get_active_text() == 'Custom': | ||
2309 | 258 | self.config.set('profile::' + self.profile, 'colorf', | ||
2310 | 259 | color2hex(self.colorf.get_color())) | ||
2311 | 260 | self.config.set('profile::' + self.profile, 'colorb', | ||
2312 | 261 | color2hex(self.colorb.get_color())) | ||
2313 | 262 | |||
2314 | 263 | def set_profile(self, profile='default'): | ||
2315 | 264 | self.save_changes() | ||
2316 | 265 | if profile != self.profile: | ||
2317 | 266 | self.profile = profile | ||
2318 | 267 | self.update() | ||
2319 | 268 | self.show_all() | ||
2320 | 269 | |||
2321 | 270 | def update(self): | ||
2322 | 271 | for child in self.get_children(): | ||
2323 | 272 | self.remove(child) | ||
2324 | 273 | self.draw_all() | ||
2325 | 274 | |||
2326 | 275 | |||
2327 | 276 | class ProfScrollingTab(gtk.VBox): | ||
2328 | 277 | def __init__(self, config, profile='default'): | ||
2329 | 278 | gtk.VBox.__init__(self) | ||
2330 | 279 | self.config = config | ||
2331 | 280 | self.profile = 'profile::' + profile | ||
2332 | 281 | self.draw_all() | ||
2333 | 282 | |||
2334 | 283 | def draw_all(self): | ||
2335 | 284 | hbox = gtk.HBox() | ||
2336 | 285 | hbox.pack_start(gtk.Label('Number of history lines:'), False, False, 8) | ||
2337 | 286 | self.scrollb_sb = gtk.SpinButton( | ||
2338 | 287 | adjustment=gtk.Adjustment(upper=9999, step_incr=1)) | ||
2339 | 288 | self.scrollb_sb.set_wrap(True) | ||
2340 | 289 | self.scrollb_sb.set_numeric(True) | ||
2341 | 290 | self.scrollb_sb.set_value(self.config.getint(self.profile, 'scrollb')) | ||
2342 | 291 | hbox.pack_start(self.scrollb_sb, False, False, 8) | ||
2343 | 292 | self.pack_start(hbox, False, False, 8) | ||
2344 | 293 | |||
2345 | 294 | def set_profile(self, profile='default'): | ||
2346 | 295 | if 'profile::' + profile != self.profile: | ||
2347 | 296 | self.profile = 'profile::' + profile | ||
2348 | 297 | self.update() | ||
2349 | 298 | self.show_all() | ||
2350 | 299 | |||
2351 | 300 | def update(self): | ||
2352 | 301 | for child in self.get_children(): | ||
2353 | 302 | self.remove(child) | ||
2354 | 303 | self.draw_all() | ||
2355 | 304 | |||
2356 | 305 | def save_changes(self): | ||
2357 | 306 | if self.profile in self.config.sections(): | ||
2358 | 307 | dbg('Setting scrollb to %d' % int(self.scrollb_sb.get_value())) | ||
2359 | 308 | self.config.set(self.profile, | ||
2360 | 309 | 'scrollb', | ||
2361 | 310 | str(int(self.scrollb_sb.get_value()))) | ||
2362 | 311 | |||
2363 | 312 | |||
2364 | 313 | class ProfilesTab(gtk.HBox): | ||
2365 | 314 | def __init__(self, config): | ||
2366 | 315 | gtk.HBox.__init__(self) | ||
2367 | 316 | self.config = config | ||
2368 | 317 | self.tabs = [] | ||
2369 | 318 | self.gprofs = 0 | ||
2370 | 319 | |||
2371 | 320 | vbox = gtk.VBox() | ||
2372 | 321 | self.proflist = gtk.TreeView(gtk.ListStore(str)) | ||
2373 | 322 | self.init_proflist() | ||
2374 | 323 | hbox = gtk.HBox() | ||
2375 | 324 | add_btn = gtk.Button() | ||
2376 | 325 | add_btn.add(self.get_img_box('Add', gtk.STOCK_ADD)) | ||
2377 | 326 | add_btn.connect('clicked', lambda *x: self.add_profile()) | ||
2378 | 327 | del_btn = gtk.Button() | ||
2379 | 328 | del_btn.add(self.get_img_box('Remove', gtk.STOCK_DELETE)) | ||
2380 | 329 | del_btn.connect('clicked', lambda *x: self.del_profile()) | ||
2381 | 330 | hbox.pack_start(add_btn, False, False, 0) | ||
2382 | 331 | hbox.pack_start(del_btn, False, False, 0) | ||
2383 | 332 | |||
2384 | 333 | vbox.pack_start(self.proflist, True, True, 0) | ||
2385 | 334 | vbox.pack_start(hbox, False, False, 0) | ||
2386 | 335 | self.pack_start(vbox) | ||
2387 | 336 | |||
2388 | 337 | self.tabs.append(('General', ProfGeneralTab(config))) | ||
2389 | 338 | self.tabs.append(('Colors', ProfColorsTab(config))) | ||
2390 | 339 | self.tabs.append(('Scrolling', ProfScrollingTab(config))) | ||
2391 | 340 | |||
2392 | 341 | self.options = gtk.Notebook() | ||
2393 | 342 | for name, tab in self.tabs: | ||
2394 | 343 | self.options.append_page(tab, gtk.Label(_(name))) | ||
2395 | 344 | |||
2396 | 345 | self.proflist.connect('cursor-changed', lambda *x: self.update_tabs()) | ||
2397 | 346 | self.pack_start(self.options) | ||
2398 | 347 | |||
2399 | 348 | def get_img_box(self, text, img): | ||
2400 | 349 | box = gtk.HBox() | ||
2401 | 350 | image = gtk.Image() | ||
2402 | 351 | image.set_from_stock(img, gtk.ICON_SIZE_BUTTON) | ||
2403 | 352 | label = gtk.Label(text) | ||
2404 | 353 | box.pack_start(image, False, False, 0) | ||
2405 | 354 | box.pack_start(label, False, False, 0) | ||
2406 | 355 | return box | ||
2407 | 356 | |||
2408 | 357 | def add_text_col(self, colname, n=0): | ||
2409 | 358 | col = gtk.TreeViewColumn() | ||
2410 | 359 | col.set_title(_(colname)) | ||
2411 | 360 | self.render = gtk.CellRendererText() | ||
2412 | 361 | self.render.connect('edited', | ||
2413 | 362 | lambda cell, path, text: self.added_profile(path, text)) | ||
2414 | 363 | col.pack_start(self.render, expand=True) | ||
2415 | 364 | col.add_attribute(self.render, 'text', n) | ||
2416 | 365 | col.set_resizable(True) | ||
2417 | 366 | col.set_sort_column_id(n) | ||
2418 | 367 | self.proflist.append_column(col) | ||
2419 | 368 | |||
2420 | 369 | def init_proflist(self): | ||
2421 | 370 | self.add_text_col('Profile') | ||
2422 | 371 | model = self.proflist.get_model() | ||
2423 | 372 | for section in self.config.sections(): | ||
2424 | 373 | if section.startswith('profile::'): | ||
2425 | 374 | last = model.append((section[9:],)) | ||
2426 | 375 | if section == 'profile::default': | ||
2427 | 376 | self.proflist.set_cursor_on_cell( | ||
2428 | 377 | model.get_path(last), | ||
2429 | 378 | self.proflist.get_column(0)) | ||
2430 | 379 | self.gprofs += 1 | ||
2431 | 380 | |||
2432 | 381 | def update_tabs(self): | ||
2433 | 382 | selection = self.proflist.get_selection() | ||
2434 | 383 | model, iterator = selection.get_selected() | ||
2435 | 384 | if not iterator: | ||
2436 | 385 | return | ||
2437 | 386 | profile = model.get(iterator, 0)[0] | ||
2438 | 387 | for name, tab in self.tabs: | ||
2439 | 388 | if 'profile::' + profile in self.config.sections(): | ||
2440 | 389 | tab.set_profile(profile) | ||
2441 | 390 | |||
2442 | 391 | def save_all(self): | ||
2443 | 392 | for name, tab in self.tabs: | ||
2444 | 393 | tab.save_changes() | ||
2445 | 394 | |||
2446 | 395 | def add_profile(self): | ||
2447 | 396 | self.gprofs += 1 | ||
2448 | 397 | model = self.proflist.get_model() | ||
2449 | 398 | iterator = model.append(('New Profile %d' % self.gprofs,)) | ||
2450 | 399 | self.render.set_property('editable', True) | ||
2451 | 400 | self.proflist.grab_focus() | ||
2452 | 401 | self.proflist.set_cursor_on_cell(model.get_path(iterator), | ||
2453 | 402 | self.proflist.get_column(0), | ||
2454 | 403 | start_editing=True) | ||
2455 | 404 | |||
2456 | 405 | def added_profile(self, path, text): | ||
2457 | 406 | dbg('Added profile %s' % text) | ||
2458 | 407 | if 'profile::' + text in self.config.sections(): | ||
2459 | 408 | return | ||
2460 | 409 | model = self.proflist.get_model() | ||
2461 | 410 | model[path][0] = text | ||
2462 | 411 | self.render.set_property('editable', False) | ||
2463 | 412 | self.config.add_section('profile::' + text) | ||
2464 | 413 | self.update_tabs() | ||
2465 | 414 | |||
2466 | 415 | def del_profile(self): | ||
2467 | 416 | selection = self.proflist.get_selection() | ||
2468 | 417 | model, iterator = selection.get_selected() | ||
2469 | 418 | profile = model.get(iterator, 0)[0] | ||
2470 | 419 | if profile != 'default': | ||
2471 | 420 | self.config.remove_section('profile::' + profile) | ||
2472 | 421 | model.remove(iterator) | ||
2473 | 422 | |||
2474 | 423 | |||
2475 | 424 | class KeybindingsTab(gtk.VBox): | ||
2476 | 425 | def __init__(self, config): | ||
2477 | 426 | gtk.VBox.__init__(self) | ||
2478 | 427 | self.config = config | ||
2479 | 428 | self.draw_all() | ||
2480 | 429 | |||
2481 | 430 | def draw_all(self): | ||
2482 | 431 | self.labels = [] | ||
2483 | 432 | for kb_func, kb_name in cc_config.KEY_BINDINGS.items(): | ||
2484 | 433 | hbox = gtk.HBox() | ||
2485 | 434 | lbl = gtk.Label(_(kb_name)) | ||
2486 | 435 | self.labels.append(lbl) | ||
2487 | 436 | btn = gtk.Button(self.config.get('keybindings', kb_func)) | ||
2488 | 437 | btn.connect('clicked', | ||
2489 | 438 | lambda wg, func: self.get_key(func), kb_func) | ||
2490 | 439 | btn.set_size_request(100, -1) | ||
2491 | 440 | hbox.pack_start(btn, False, False, 8) | ||
2492 | 441 | del_btn = gtk.Button() | ||
2493 | 442 | del_img = gtk.Image() | ||
2494 | 443 | del_img.set_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_BUTTON) | ||
2495 | 444 | del_btn.add(del_img) | ||
2496 | 445 | del_btn.connect('clicked', | ||
2497 | 446 | lambda wg, func: self.get_key(func, 'not used'), kb_func) | ||
2498 | 447 | hbox.pack_start(del_btn, False, False, 8) | ||
2499 | 448 | hbox.pack_start(lbl, True, True, 8) | ||
2500 | 449 | self.pack_start(hbox) | ||
2501 | 450 | |||
2502 | 451 | def update(self): | ||
2503 | 452 | for child in self.children(): | ||
2504 | 453 | self.remove(child) | ||
2505 | 454 | self.draw_all() | ||
2506 | 455 | self.show_all() | ||
2507 | 456 | |||
2508 | 457 | def get_key(self, func, key=None): | ||
2509 | 458 | if key != None: | ||
2510 | 459 | self.config.set('keybindings', func, key) | ||
2511 | 460 | self.update() | ||
2512 | 461 | return | ||
2513 | 462 | self.md = gtk.Dialog("Press the new key for '%s'" % func, | ||
2514 | 463 | None, | ||
2515 | 464 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT) | ||
2516 | 465 | lbl = gtk.Label('Press a key') | ||
2517 | 466 | self.md.get_content_area().pack_start((lbl), True, True, 0) | ||
2518 | 467 | lbl.set_size_request(100, 100) | ||
2519 | 468 | self.md.connect('key-press-event', | ||
2520 | 469 | lambda w, event: self.key_pressed(func, event, lbl)) | ||
2521 | 470 | self.md.connect('key-release-event', | ||
2522 | 471 | lambda w, event: self.key_released(event, lbl)) | ||
2523 | 472 | self.md.show_all() | ||
2524 | 473 | self.md.run() | ||
2525 | 474 | |||
2526 | 475 | def key_released(self, event, lbl): | ||
2527 | 476 | keycomb = cc_utils.get_keycomb(event) | ||
2528 | 477 | combname = '' | ||
2529 | 478 | activemods = keycomb.split('+')[:-1] | ||
2530 | 479 | released = keycomb.rsplit('+', 1)[-1] | ||
2531 | 480 | mods = {'shift': 'shift', | ||
2532 | 481 | 'control': 'ctrl', | ||
2533 | 482 | 'alt': 'alt', | ||
2534 | 483 | 'super': 'super'} | ||
2535 | 484 | for mod in mods.keys(): | ||
2536 | 485 | if mod in released.lower(): | ||
2537 | 486 | if mods[mod] in activemods: | ||
2538 | 487 | activemods.pop(activemods.index(mods[mod])) | ||
2539 | 488 | combname = '+'.join(activemods) + '+' | ||
2540 | 489 | if combname == '+': | ||
2541 | 490 | combname = 'Press a key' | ||
2542 | 491 | lbl.set_text(combname) | ||
2543 | 492 | |||
2544 | 493 | def key_pressed(self, func, event, lbl): | ||
2545 | 494 | keycomb = cc_utils.get_keycomb(event) | ||
2546 | 495 | if not cc_utils.only_modifier(event): | ||
2547 | 496 | self.md.destroy() | ||
2548 | 497 | self.config.set('keybindings', func, keycomb) | ||
2549 | 498 | self.update() | ||
2550 | 499 | else: | ||
2551 | 500 | combname = '' | ||
2552 | 501 | activemods = keycomb.split('+')[:-1] | ||
2553 | 502 | pressed = keycomb.rsplit('+', 1)[-1] | ||
2554 | 503 | mods = {'shift': 'shift', | ||
2555 | 504 | 'control': 'ctrl', | ||
2556 | 505 | 'alt': 'alt', | ||
2557 | 506 | 'super': 'super'} | ||
2558 | 507 | for mod in mods.keys(): | ||
2559 | 508 | if mod in pressed.lower(): | ||
2560 | 509 | if mods[mod] not in activemods: | ||
2561 | 510 | activemods.append(mods[mod]) | ||
2562 | 511 | combname = '+'.join(activemods) + '+' | ||
2563 | 512 | if combname == '+': | ||
2564 | 513 | combname = 'Press a key' | ||
2565 | 514 | lbl.set_text(combname) | ||
2566 | 515 | |||
2567 | 516 | def save_all(self): | ||
2568 | 517 | pass | ||
2569 | 518 | |||
2570 | 519 | |||
2571 | 520 | class PluginsTab(gtk.HBox): | ||
2572 | 521 | __gsignals__ = { | ||
2573 | 522 | 'changed-plugin': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
2574 | 523 | (str, )) | ||
2575 | 524 | } | ||
2576 | 525 | |||
2577 | 526 | def __init__(self, config, plugins): | ||
2578 | 527 | gtk.HBox.__init__(self) | ||
2579 | 528 | self.config = config | ||
2580 | 529 | self.plugins = plugins | ||
2581 | 530 | |||
2582 | 531 | self.pluginlist = gtk.TreeView(gtk.ListStore(bool, str)) | ||
2583 | 532 | first = self.init_pluginlist() | ||
2584 | 533 | self.pack_start(self.pluginlist, False, False, 8) | ||
2585 | 534 | |||
2586 | 535 | self.infonb = gtk.Notebook() | ||
2587 | 536 | self.generate_infotabs(first) | ||
2588 | 537 | self.pack_start(self.infonb, True, True, 8) | ||
2589 | 538 | |||
2590 | 539 | self.pluginlist.connect('row_activated', | ||
2591 | 540 | lambda *x: self.toggle_plugin()) | ||
2592 | 541 | self.pluginlist.connect('cursor-changed', | ||
2593 | 542 | lambda *x: self.update_info()) | ||
2594 | 543 | |||
2595 | 544 | def init_pluginlist(self): | ||
2596 | 545 | self.add_cbox_col('Enabled', 0) | ||
2597 | 546 | self.add_text_col('Plugin Name', 1) | ||
2598 | 547 | model = self.pluginlist.get_model() | ||
2599 | 548 | first = None | ||
2600 | 549 | for plugin in self.plugins.get_allowed(): | ||
2601 | 550 | if not first: | ||
2602 | 551 | first = plugin | ||
2603 | 552 | model.append((True, plugin)) | ||
2604 | 553 | for plugin in self.plugins.get_disallowed(): | ||
2605 | 554 | if not first: | ||
2606 | 555 | first = plugin | ||
2607 | 556 | model.append((False, plugin)) | ||
2608 | 557 | self.pluginlist.set_cursor((0,)) | ||
2609 | 558 | return first | ||
2610 | 559 | |||
2611 | 560 | def add_cbox_col(self, colname, n=0): | ||
2612 | 561 | col = gtk.TreeViewColumn() | ||
2613 | 562 | col.set_title(_(colname)) | ||
2614 | 563 | render = gtk.CellRendererToggle() | ||
2615 | 564 | col.pack_start(render, expand=True) | ||
2616 | 565 | col.add_attribute(render, 'active', n) | ||
2617 | 566 | col.set_resizable(True) | ||
2618 | 567 | col.set_sort_column_id(n) | ||
2619 | 568 | self.pluginlist.append_column(col) | ||
2620 | 569 | |||
2621 | 570 | def add_text_col(self, colname, n=0): | ||
2622 | 571 | col = gtk.TreeViewColumn() | ||
2623 | 572 | col.set_title(_(colname)) | ||
2624 | 573 | render = gtk.CellRendererText() | ||
2625 | 574 | col.pack_start(render, expand=True) | ||
2626 | 575 | col.add_attribute(render, 'text', n) | ||
2627 | 576 | col.set_resizable(True) | ||
2628 | 577 | col.set_sort_column_id(n) | ||
2629 | 578 | self.pluginlist.append_column(col) | ||
2630 | 579 | |||
2631 | 580 | def toggle_plugin(self): | ||
2632 | 581 | selection = self.pluginlist.get_selection() | ||
2633 | 582 | model, iterator = selection.get_selected() | ||
2634 | 583 | oldvalue, plugin = model.get(iterator, 0, 1) | ||
2635 | 584 | if plugin == 'LocalCommandList': | ||
2636 | 585 | self.show_warning() | ||
2637 | 586 | return | ||
2638 | 587 | model.set(iterator, 0, not oldvalue) | ||
2639 | 588 | |||
2640 | 589 | def generate_infotabs(self, plugin): | ||
2641 | 590 | ''' | ||
2642 | 591 | Adds the plugins info and config page (if any) to the plugins info | ||
2643 | 592 | notebook | ||
2644 | 593 | ''' | ||
2645 | 594 | confplg = self.plugins.get_plugin_conf(plugin) | ||
2646 | 595 | |||
2647 | 596 | if confplg: | ||
2648 | 597 | conf_tab = confplg(self.config.get_plugin_conf(plugin)) | ||
2649 | 598 | self.infonb.append_page( | ||
2650 | 599 | conf_tab, | ||
2651 | 600 | gtk.Label(_('Configutarion'))) | ||
2652 | 601 | conf_tab.connect('reload', | ||
2653 | 602 | lambda wg, pl: self.emit('changed-plugin', plugin), | ||
2654 | 603 | plugin) | ||
2655 | 604 | |||
2656 | 605 | self.infonb.append_page(self.generate_infopage(plugin), | ||
2657 | 606 | gtk.Label(_('About'))) | ||
2658 | 607 | |||
2659 | 608 | def generate_infopage(self, plugin): | ||
2660 | 609 | ''' | ||
2661 | 610 | Generates the plugins info page | ||
2662 | 611 | ''' | ||
2663 | 612 | info = self.plugins.get_info(plugin) | ||
2664 | 613 | authors = self.plugins.get_authors(plugin) | ||
2665 | 614 | page = gtk.TextView() | ||
2666 | 615 | page.set_wrap_mode(gtk.WRAP_WORD) | ||
2667 | 616 | page.set_editable(False) | ||
2668 | 617 | buffer = page.get_buffer() | ||
2669 | 618 | scrolled_window = gtk.ScrolledWindow() | ||
2670 | 619 | scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) | ||
2671 | 620 | scrolled_window.add(page) | ||
2672 | 621 | iter = buffer.get_iter_at_offset(0) | ||
2673 | 622 | buffer.insert(iter, info + '\n\nAuthors:\n' + authors) | ||
2674 | 623 | return scrolled_window | ||
2675 | 624 | |||
2676 | 625 | def update_info(self): | ||
2677 | 626 | selection = self.pluginlist.get_selection() | ||
2678 | 627 | model, iterator = selection.get_selected() | ||
2679 | 628 | enabled, plugin = model.get(iterator, 0, 1) | ||
2680 | 629 | for child in self.infonb.get_children(): | ||
2681 | 630 | self.infonb.remove(child) | ||
2682 | 631 | self.generate_infotabs(plugin) | ||
2683 | 632 | self.show_all() | ||
2684 | 633 | |||
2685 | 634 | def save_all(self): | ||
2686 | 635 | newenabled = [] | ||
2687 | 636 | model = self.pluginlist.get_model() | ||
2688 | 637 | elem = model.get_iter_first() | ||
2689 | 638 | while elem: | ||
2690 | 639 | enabled, plugin = model.get(elem, 0, 1) | ||
2691 | 640 | if enabled: | ||
2692 | 641 | newenabled.append(plugin) | ||
2693 | 642 | elem = model.iter_next(elem) | ||
2694 | 643 | self.config.set('general::default', 'plugins', ', '.join(newenabled)) | ||
2695 | 644 | |||
2696 | 645 | def show_warning(self): | ||
2697 | 646 | dlg = gtk.MessageDialog( | ||
2698 | 647 | None, | ||
2699 | 648 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, | ||
2700 | 649 | gtk.MESSAGE_ERROR, | ||
2701 | 650 | gtk.BUTTONS_CLOSE, | ||
2702 | 651 | message_format=_('Can\'t disable "LocalCommandList')) | ||
2703 | 652 | dlg.format_secondary_text(_('The plugin "LocalCommandList is the main' | ||
2704 | 653 | ' plugin for CLI Companion, and can\'t be disalbed, sorry.')) | ||
2705 | 654 | dlg.run() | ||
2706 | 655 | dlg.destroy() | ||
2707 | 656 | |||
2708 | 657 | |||
2709 | 658 | class PreferencesWindow(gtk.Dialog): | ||
2710 | 659 | ''' | ||
2711 | 660 | Preferences window, the tabs are needed for the preview | ||
2712 | 661 | ''' | ||
2713 | 662 | __gsignals__ = { | ||
2714 | 663 | 'preview': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
2715 | 664 | (str, str)) | ||
2716 | 665 | } | ||
2717 | 666 | |||
2718 | 667 | def __init__(self, config, plugins): | ||
2719 | 668 | gtk.Dialog.__init__(self, _("User Preferences"), | ||
2720 | 669 | None, | ||
2721 | 670 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, | ||
2722 | 671 | (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, | ||
2723 | 672 | gtk.STOCK_OK, gtk.RESPONSE_OK)) | ||
2724 | 673 | self.config = config | ||
2725 | 674 | self.plugins = plugins | ||
2726 | 675 | self.config_bkp = self.config.get_config_copy() | ||
2727 | 676 | self.changed_plugins = [] | ||
2728 | 677 | |||
2729 | 678 | mainwdg = self.get_content_area() | ||
2730 | 679 | self.tabs = gtk.Notebook() | ||
2731 | 680 | mainwdg.pack_start(self.tabs, True, True, 0) | ||
2732 | 681 | |||
2733 | 682 | ## profiles | ||
2734 | 683 | proftab = ProfilesTab(config) | ||
2735 | 684 | self.tabs.append_page(proftab, gtk.Label('Profiles')) | ||
2736 | 685 | ## keybindings | ||
2737 | 686 | keybind_tab = KeybindingsTab(config) | ||
2738 | 687 | self.tabs.append_page(keybind_tab, gtk.Label('Keybindings')) | ||
2739 | 688 | ## plugins | ||
2740 | 689 | plug_tab = PluginsTab(config, plugins) | ||
2741 | 690 | plug_tab.connect('changed-plugin', | ||
2742 | 691 | lambda wg, pl: self.mark_changed_plugins(pl)) | ||
2743 | 692 | self.tabs.append_page(plug_tab, gtk.Label('Plugins')) | ||
2744 | 693 | |||
2745 | 694 | def mark_changed_plugins(self, plugin): | ||
2746 | 695 | if plugin not in self.changed_plugins: | ||
2747 | 696 | self.changed_plugins.append(plugin) | ||
2748 | 697 | |||
2749 | 698 | def run(self): | ||
2750 | 699 | self.show_all() | ||
2751 | 700 | response = gtk.Dialog.run(self) | ||
2752 | 701 | if response == gtk.RESPONSE_OK: | ||
2753 | 702 | for i in range(self.tabs.get_n_pages()): | ||
2754 | 703 | self.tabs.get_nth_page(i).save_all() | ||
2755 | 704 | config = self.config | ||
2756 | 705 | else: | ||
2757 | 706 | config = None | ||
2758 | 707 | self.destroy() | ||
2759 | 708 | return config, self.changed_plugins | ||
2760 | 0 | 709 | ||
2761 | === modified file 'clicompanionlib/tabs.py' | |||
2762 | --- clicompanionlib/tabs.py 2012-01-02 00:23:11 +0000 | |||
2763 | +++ clicompanionlib/tabs.py 2012-01-08 10:37:24 +0000 | |||
2764 | @@ -1,6 +1,9 @@ | |||
2765 | 1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python |
2768 | 2 | # | 2 | # -*- coding: utf-8 -*- |
2769 | 3 | # Copyright 2010 Duane Hinnen, Kenny Meyer, Marcos Vanetta | 3 | # |
2770 | 4 | # tabs.py - Terminal tab handling classes for clicompanion | ||
2771 | 5 | # | ||
2772 | 6 | # Copyright 2010 Duane Hinnen, Kenny Meyer, Marcos Vanetta, David Caro | ||
2773 | 4 | # | 7 | # |
2774 | 5 | # This program is free software: you can redistribute it and/or modify it | 8 | # This program is free software: you can redistribute it and/or modify it |
2775 | 6 | # under the terms of the GNU General Public License version 3, as published | 9 | # under the terms of the GNU General Public License version 3, as published |
2776 | @@ -15,6 +18,14 @@ | |||
2777 | 15 | # with this program. If not, see <http://www.gnu.org/licenses/>. | 18 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
2778 | 16 | # | 19 | # |
2779 | 17 | # | 20 | # |
2780 | 21 | # This file contains the classes used to provide the terminals secion of the | ||
2781 | 22 | # main window. | ||
2782 | 23 | # | ||
2783 | 24 | # TerminalTab: This class implements one tab of the terminals notebook, with | ||
2784 | 25 | # it's own profile and vte associated. | ||
2785 | 26 | # | ||
2786 | 27 | # TerminalsNotebook: This class implements the notebook, where the terminals | ||
2787 | 28 | # will be added. | ||
2788 | 18 | # | 29 | # |
2789 | 19 | 30 | ||
2790 | 20 | import os | 31 | import os |
2791 | @@ -22,157 +33,388 @@ | |||
2792 | 22 | pygtk.require('2.0') | 33 | pygtk.require('2.0') |
2793 | 23 | import gtk | 34 | import gtk |
2794 | 24 | import vte | 35 | import vte |
2800 | 25 | import clicompanionlib.config as cc_config | 36 | import re |
2796 | 26 | |||
2797 | 27 | from clicompanionlib.utils import get_user_shell, dbg | ||
2798 | 28 | import clicompanionlib.controller | ||
2799 | 29 | import clicompanionlib.utils as utils | ||
2801 | 30 | import view | 37 | import view |
2812 | 31 | 38 | import gobject | |
2813 | 32 | 39 | import pango | |
2814 | 33 | class Tabs(object): | 40 | import gconf |
2815 | 34 | ''' | 41 | from clicompanionlib.utils import dbg |
2816 | 35 | add a new terminal in a tab above the current terminal | 42 | import clicompanionlib.utils as cc_utils |
2817 | 36 | ''' | 43 | import clicompanionlib.helpers as cc_helpers |
2818 | 37 | def __init__(self): | 44 | import clicompanionlib.preferences as cc_pref |
2819 | 38 | #definition nop - (no of pages) reflects no of terminal tabs left (some may be closed by the user) | 45 | |
2820 | 39 | self.nop = 0 | 46 | |
2821 | 40 | #definition gcp - how many pages is visible | 47 | class TerminalTab(gtk.ScrolledWindow): |
2822 | 48 | __gsignals__ = { | ||
2823 | 49 | 'quit': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
2824 | 50 | ()), | ||
2825 | 51 | 'add_tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
2826 | 52 | ()), | ||
2827 | 53 | 'rename': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
2828 | 54 | (str, )), | ||
2829 | 55 | 'preferences': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
2830 | 56 | ()), | ||
2831 | 57 | } | ||
2832 | 58 | |||
2833 | 59 | def __init__(self, title, config, profile='default'): | ||
2834 | 60 | gtk.ScrolledWindow.__init__(self) | ||
2835 | 61 | self.config = config | ||
2836 | 62 | self.title = title | ||
2837 | 63 | self.profile = 'profile::' + profile | ||
2838 | 64 | self.vte = vte.Terminal() | ||
2839 | 65 | self.add(self.vte) | ||
2840 | 66 | self.vte.connect("child-exited", lambda *x: self.emit('quit')) | ||
2841 | 67 | self.update_records = self.config.getboolean(self.profile, | ||
2842 | 68 | 'update_login_records') | ||
2843 | 69 | dbg('Updating login records: ' + self.update_records.__repr__()) | ||
2844 | 70 | self.vte.fork_command(cc_utils.shell_lookup(), | ||
2845 | 71 | logutmp=self.update_records, | ||
2846 | 72 | logwtmp=self.update_records, | ||
2847 | 73 | loglastlog=self.update_records) | ||
2848 | 74 | self.vte.connect("button_press_event", self.copy_paste_menu) | ||
2849 | 75 | self.update_config() | ||
2850 | 76 | self.show_all() | ||
2851 | 77 | |||
2852 | 78 | def update_config(self, config=None, preview=False): | ||
2853 | 79 | if not config: | ||
2854 | 80 | config = self.config | ||
2855 | 81 | elif not preview: | ||
2856 | 82 | self.config = config | ||
2857 | 83 | if self.profile not in config.sections(): | ||
2858 | 84 | self.profile = 'profile::default' | ||
2859 | 85 | if self.profile not in config.sections(): | ||
2860 | 86 | config.add_section(self.profile) | ||
2861 | 87 | dbg(self.profile) | ||
2862 | 88 | dbg(','.join([config.get(self.profile, option) | ||
2863 | 89 | for option in config.options(self.profile)])) | ||
2864 | 90 | |||
2865 | 91 | ## Scrollback | ||
2866 | 92 | try: | ||
2867 | 93 | config_scrollback = config.getint(self.profile, 'scrollb') | ||
2868 | 94 | except ValueError: | ||
2869 | 95 | print _("WARNING: Invalid value for property '%s', int expected:" | ||
2870 | 96 | " got '%s', using default '%s'") % ( | ||
2871 | 97 | 'scrollb', | ||
2872 | 98 | config.get(self.profile, 'scrollb'), | ||
2873 | 99 | config.get('DEFAULT', 'scrollb')) | ||
2874 | 100 | config.set(self.profile, 'scrollb', | ||
2875 | 101 | config.get('DEFAULT', 'scrollb')) | ||
2876 | 102 | config_scrollback = config.getint('DEFAULT', 'scrollb') | ||
2877 | 103 | self.vte.set_scrollback_lines(config_scrollback) | ||
2878 | 104 | |||
2879 | 105 | color = ('#2e3436:#cc0000:#4e9a06:#c4a000:#3465a4:#75507b:#06989a:' | ||
2880 | 106 | '#d3d7cf:#555753:#ef2929:#8ae234:#fce94f:#729fcf:#ad7fa8:' | ||
2881 | 107 | '#34e2e2:#eeeeec') | ||
2882 | 108 | colors = color.split(':') | ||
2883 | 109 | palette = [] | ||
2884 | 110 | for color in colors: | ||
2885 | 111 | if color: | ||
2886 | 112 | palette.append(gtk.gdk.color_parse(color)) | ||
2887 | 113 | |||
2888 | 114 | #### Colors | ||
2889 | 115 | if config.getboolean(self.profile, 'use_system_colors'): | ||
2890 | 116 | config_color_fore = self.vte.get_style().text[gtk.STATE_NORMAL] | ||
2891 | 117 | config_color_back = self.vte.get_style().base[gtk.STATE_NORMAL] | ||
2892 | 118 | else: | ||
2893 | 119 | color_scheme = config.get(self.profile, 'color_scheme') | ||
2894 | 120 | if color_scheme != 'Custom': | ||
2895 | 121 | fgcolor, bgcolor = cc_pref.COLOR_SCHEMES[color_scheme] | ||
2896 | 122 | else: | ||
2897 | 123 | fgcolor = config.get(self.profile, 'colorf') | ||
2898 | 124 | bgcolor = config.get(self.profile, 'colorb') | ||
2899 | 125 | |||
2900 | 126 | try: | ||
2901 | 127 | config_color_fore = gtk.gdk.color_parse(fgcolor) | ||
2902 | 128 | except ValueError, e: | ||
2903 | 129 | print _("WARNING: Invalid value for property '%s':" | ||
2904 | 130 | " got '%s', using default '%s'.") % ( | ||
2905 | 131 | 'colorf', | ||
2906 | 132 | fgcolor, | ||
2907 | 133 | config.get('DEFAULT', 'colorf')) | ||
2908 | 134 | config.set(self.profile, 'colorf', | ||
2909 | 135 | config.get('DEFAULT', 'colorf')) | ||
2910 | 136 | config_color_fore = gtk.gdk.color_parse(config.get('DEFAULT', | ||
2911 | 137 | 'colorf')) | ||
2912 | 138 | |||
2913 | 139 | try: | ||
2914 | 140 | config_color_back = gtk.gdk.color_parse(bgcolor) | ||
2915 | 141 | except ValueError, e: | ||
2916 | 142 | print _("WARNING: Invalid value for property '%s':" | ||
2917 | 143 | " got '%s', using default '%s'.") % ( | ||
2918 | 144 | 'colorb', | ||
2919 | 145 | bgcolor, | ||
2920 | 146 | config.get('DEFAULT', 'colorb')) | ||
2921 | 147 | config.set(self.profile, 'colorb', | ||
2922 | 148 | config.get('DEFAULT', 'colorb')) | ||
2923 | 149 | config_color_back = gtk.gdk.color_parse(config.get('DEFAULT', | ||
2924 | 150 | 'colorb')) | ||
2925 | 151 | self.vte.set_colors(config_color_fore, config_color_back, palette) | ||
2926 | 152 | |||
2927 | 153 | ### Encoding | ||
2928 | 154 | config_encoding = config.get(self.profile, 'encoding') | ||
2929 | 155 | if config_encoding.upper() not in [enc.upper() | ||
2930 | 156 | for enc, desc | ||
2931 | 157 | in cc_utils.encodings]: | ||
2932 | 158 | print _("WARNING: Invalid value for property '%s':" | ||
2933 | 159 | " got '%s', using default '%s'") \ | ||
2934 | 160 | % ('encoding', config_encoding, | ||
2935 | 161 | config.get('DEFAULT', 'encoding')) | ||
2936 | 162 | config.set(self.profile, 'encoding', | ||
2937 | 163 | config.get('DEFAULT', 'encoding')) | ||
2938 | 164 | config_encoding = config.get('DEFAULT', 'encoding') | ||
2939 | 165 | self.vte.set_encoding(config_encoding) | ||
2940 | 166 | |||
2941 | 167 | ## Font | ||
2942 | 168 | if config.getboolean(self.profile, 'use_system_font'): | ||
2943 | 169 | fontname = cc_utils.get_system_font( | ||
2944 | 170 | lambda *x: self.update_config()) | ||
2945 | 171 | else: | ||
2946 | 172 | fontname = config.get(self.profile, 'font') | ||
2947 | 173 | font = pango.FontDescription(fontname) | ||
2948 | 174 | if not font or not fontname: | ||
2949 | 175 | print _("WARNING: Invalid value for property '%s':" | ||
2950 | 176 | " got '%s', using default '%s'") % ( | ||
2951 | 177 | 'font', | ||
2952 | 178 | fontname, | ||
2953 | 179 | cc_utils.get_system_font()) | ||
2954 | 180 | config.set('DEFAULT', 'font', c_utils.get_system_font()) | ||
2955 | 181 | fontname = config.get('DEFAULT', 'font') | ||
2956 | 182 | font = pango.FontDescription(fontname) | ||
2957 | 183 | if font: | ||
2958 | 184 | self.vte.set_font_full(font, | ||
2959 | 185 | config.getboolean(self.profile, 'antialias')) | ||
2960 | 186 | |||
2961 | 187 | update_records = config.getboolean(self.profile, | ||
2962 | 188 | 'update_login_records') | ||
2963 | 189 | if update_records != self.update_records: | ||
2964 | 190 | if not preview: | ||
2965 | 191 | self.update_records = update_records | ||
2966 | 192 | dbg('Updating login records: ' + update_records.__repr__()) | ||
2967 | 193 | self.vte.feed('\n\r') | ||
2968 | 194 | self.vte.fork_command(cc_utils.shell_lookup(), | ||
2969 | 195 | logutmp=update_records, | ||
2970 | 196 | logwtmp=update_records, | ||
2971 | 197 | loglastlog=update_records) | ||
2972 | 198 | |||
2973 | 199 | self.vte.set_allow_bold(config.getboolean(self.profile, 'bold_text')) | ||
2974 | 200 | self.vte.set_word_chars(config.get(self.profile, 'sel_word')) | ||
2975 | 201 | |||
2976 | 202 | def copy_paste_menu(self, vte, event): | ||
2977 | 203 | if event.button == 3: | ||
2978 | 204 | time = event.time | ||
2979 | 205 | ## right-click popup menu Copy | ||
2980 | 206 | popupMenu = gtk.Menu() | ||
2981 | 207 | menuPopup1 = gtk.ImageMenuItem(gtk.STOCK_COPY) | ||
2982 | 208 | popupMenu.add(menuPopup1) | ||
2983 | 209 | menuPopup1.connect('activate', lambda x: vte.copy_clipboard()) | ||
2984 | 210 | ## right-click popup menu Paste | ||
2985 | 211 | menuPopup2 = gtk.ImageMenuItem(gtk.STOCK_PASTE) | ||
2986 | 212 | popupMenu.add(menuPopup2) | ||
2987 | 213 | menuPopup2.connect('activate', lambda x: vte.paste_clipboard()) | ||
2988 | 214 | ## right-click popup menu Rename | ||
2989 | 215 | menuPopup3 = gtk.ImageMenuItem(gtk.STOCK_EDIT) | ||
2990 | 216 | menuPopup3.set_label(_('Rename')) | ||
2991 | 217 | popupMenu.add(menuPopup3) | ||
2992 | 218 | menuPopup3.connect('activate', lambda x: self.rename()) | ||
2993 | 219 | ## right-click popup menu Add Tab | ||
2994 | 220 | menuPopup3 = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES) | ||
2995 | 221 | menuPopup3.set_label(_('Configure')) | ||
2996 | 222 | popupMenu.add(menuPopup3) | ||
2997 | 223 | menuPopup3.connect('activate', lambda x: self.emit('preferences')) | ||
2998 | 224 | ## right-click popup menu Add Tab | ||
2999 | 225 | menuPopup3 = gtk.ImageMenuItem(gtk.STOCK_ADD) | ||
3000 | 226 | menuPopup3.set_label(_('Add tab')) | ||
3001 | 227 | popupMenu.add(menuPopup3) | ||
3002 | 228 | menuPopup3.connect('activate', lambda x: self.emit('add_tab')) | ||
3003 | 229 | ## right-click popup menu Profiles | ||
3004 | 230 | menuit_prof = gtk.MenuItem() | ||
3005 | 231 | menuit_prof.set_label(_('Profiles')) | ||
3006 | 232 | submenu_prof = gtk.Menu() | ||
3007 | 233 | menuit_prof.set_submenu(submenu_prof) | ||
3008 | 234 | popupMenu.add(menuit_prof) | ||
3009 | 235 | for section in self.config.sections(): | ||
3010 | 236 | if section.startswith('profile::'): | ||
3011 | 237 | subitem = gtk.MenuItem() | ||
3012 | 238 | subitem.set_label(_(section[9:])) | ||
3013 | 239 | submenu_prof.add(subitem) | ||
3014 | 240 | subitem.connect('activate', | ||
3015 | 241 | lambda wg, *x: self.change_profile( | ||
3016 | 242 | wg.get_label())) | ||
3017 | 243 | ## right-click popup menu Close Tab | ||
3018 | 244 | menuPopup3 = gtk.ImageMenuItem(gtk.STOCK_CLOSE) | ||
3019 | 245 | menuPopup3.set_label(_('Close tab')) | ||
3020 | 246 | popupMenu.add(menuPopup3) | ||
3021 | 247 | menuPopup3.connect('activate', lambda x: self.emit('quit')) | ||
3022 | 248 | ## Show popup menu | ||
3023 | 249 | popupMenu.show_all() | ||
3024 | 250 | popupMenu.popup(None, None, None, event.button, time) | ||
3025 | 251 | return True | ||
3026 | 252 | |||
3027 | 253 | def rename(self): | ||
3028 | 254 | dlg = gtk.Dialog("Enter the new name", | ||
3029 | 255 | None, | ||
3030 | 256 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, | ||
3031 | 257 | (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, | ||
3032 | 258 | gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) | ||
3033 | 259 | entry = gtk.Entry() | ||
3034 | 260 | entry.connect("activate", lambda *x: dlg.response(gtk.RESPONSE_ACCEPT)) | ||
3035 | 261 | entry.set_text(self.title) | ||
3036 | 262 | dlg.vbox.pack_start(entry) | ||
3037 | 263 | dlg.show_all() | ||
3038 | 264 | response = dlg.run() | ||
3039 | 265 | if response == gtk.RESPONSE_ACCEPT: | ||
3040 | 266 | text = entry.get_text() | ||
3041 | 267 | self.emit('rename', text) | ||
3042 | 268 | dlg.destroy() | ||
3043 | 269 | |||
3044 | 270 | def run_command(self, cmd, ui, desc): | ||
3045 | 271 | if not cmd: | ||
3046 | 272 | dbg('Empty command... doing nothing') | ||
3047 | 273 | return | ||
3048 | 274 | ''' | ||
3049 | 275 | Make sure user arguments were found. Replace ? with something | ||
3050 | 276 | .format can read. This is done so the user can just enter ?, when | ||
3051 | 277 | adding a command where arguments are needed, instead | ||
3052 | 278 | of {0[1]}, {0[1]}, {0[2]} | ||
3053 | 279 | ''' | ||
3054 | 280 | ## find how many ?(user arguments) are in command | ||
3055 | 281 | match = re.findall('\?', cmd) | ||
3056 | 282 | if match: | ||
3057 | 283 | num = len(match) | ||
3058 | 284 | ran = 0 | ||
3059 | 285 | new_cmd = cc_utils.replace(cmd, num, ran) | ||
3060 | 286 | |||
3061 | 287 | if len(match) > 0: # command with user input | ||
3062 | 288 | dbg('command with ui') | ||
3063 | 289 | cmd_info_win = cc_helpers.CommandInfoWindow(new_cmd, ui, desc) | ||
3064 | 290 | cmd = cmd_info_win.run() | ||
3065 | 291 | if cmd == None: | ||
3066 | 292 | return | ||
3067 | 293 | self.vte.feed_child(cmd + "\n") # send command | ||
3068 | 294 | self.show() | ||
3069 | 295 | self.grab_focus() | ||
3070 | 296 | |||
3071 | 297 | def change_profile(self, profile): | ||
3072 | 298 | dbg(profile) | ||
3073 | 299 | self.profile = 'profile::' + profile | ||
3074 | 300 | self.update_config() | ||
3075 | 301 | |||
3076 | 302 | |||
3077 | 303 | class TerminalsNotebook(gtk.Notebook): | ||
3078 | 304 | __gsignals__ = { | ||
3079 | 305 | 'quit': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
3080 | 306 | ()), | ||
3081 | 307 | 'preferences': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, | ||
3082 | 308 | ()), | ||
3083 | 309 | } | ||
3084 | 310 | |||
3085 | 311 | def __init__(self, config): | ||
3086 | 312 | gtk.Notebook.__init__(self) | ||
3087 | 313 | #definition gcp - global page count, how many pages have been created | ||
3088 | 41 | self.gcp = 0 | 314 | self.gcp = 0 |
3091 | 42 | 315 | self.global_config = config | |
3092 | 43 | def add_tab(self, notebook): | 316 | ## The "Add Tab" tab |
3093 | 317 | add_tab_button = gtk.Button("+") | ||
3094 | 318 | ## tooltip for "Add Tab" tab | ||
3095 | 319 | add_tab_button.set_tooltip_text(_("Click to add another tab")) | ||
3096 | 320 | ## create first tab | ||
3097 | 321 | self.append_page(gtk.Label(""), add_tab_button) | ||
3098 | 322 | add_tab_button.connect("clicked", lambda *x: self.add_tab()) | ||
3099 | 323 | self.set_size_request(700, 120) | ||
3100 | 324 | |||
3101 | 325 | def focus(self): | ||
3102 | 326 | num = self.get_current_page() | ||
3103 | 327 | self.get_nth_page(num).vte.grab_focus() | ||
3104 | 328 | |||
3105 | 329 | def add_tab(self, title=None): | ||
3106 | 44 | dbg('Adding a new tab') | 330 | dbg('Adding a new tab') |
3107 | 45 | _vte = vte.Terminal() | ||
3108 | 46 | if view.NETBOOKMODE == 1: | ||
3109 | 47 | _vte.set_size_request(700, 120) | ||
3110 | 48 | else: | ||
3111 | 49 | _vte.set_size_request(700, 220) | ||
3112 | 50 | |||
3113 | 51 | _vte.connect("child-exited", lambda term: gtk.main_quit()) | ||
3114 | 52 | _vte.fork_command(get_user_shell()) # Get the user's default shell | ||
3115 | 53 | |||
3116 | 54 | self.update_term_config(_vte) | ||
3117 | 55 | |||
3118 | 56 | vte_tab = gtk.ScrolledWindow() | ||
3119 | 57 | vte_tab.add(_vte) | ||
3120 | 58 | #notebook.set_show_tabs(True) | ||
3121 | 59 | #notebook.set_show_border(True) | ||
3122 | 60 | |||
3123 | 61 | self.nop += 1 | ||
3124 | 62 | self.gcp += 1 | 331 | self.gcp += 1 |
3127 | 63 | pagenum = ('Tab %d') % self.gcp | 332 | if title == None: |
3128 | 64 | if self.nop > 1: | 333 | title = 'Tab %d' % self.gcp |
3129 | 334 | |||
3130 | 335 | newtab = TerminalTab(title, self.global_config) | ||
3131 | 336 | |||
3132 | 337 | if self.get_n_pages() > 1: | ||
3133 | 65 | dbg('More than one tab, showing them.') | 338 | dbg('More than one tab, showing them.') |
3135 | 66 | view.MainWindow.notebook.set_show_tabs(True) | 339 | self.set_show_tabs(True) |
3136 | 340 | label = self.create_tab_label(title, newtab) | ||
3137 | 341 | self.insert_page(newtab, label, self.get_n_pages() - 1) | ||
3138 | 342 | self.set_current_page(self.get_n_pages() - 2) | ||
3139 | 343 | self.set_scrollable(True) | ||
3140 | 344 | # signal handler for tab | ||
3141 | 345 | newtab.connect("quit", lambda *x: self.quit_tab(newtab)) | ||
3142 | 346 | newtab.connect("add_tab", lambda *x: self.add_tab()) | ||
3143 | 347 | newtab.connect("preferences", lambda *x: self.emit('preferences')) | ||
3144 | 348 | newtab.connect("rename", | ||
3145 | 349 | lambda wg, text: self.rename_tab(newtab, text)) | ||
3146 | 350 | self.focus() | ||
3147 | 351 | return newtab | ||
3148 | 352 | |||
3149 | 353 | def create_tab_label(self, title, tab): | ||
3150 | 354 | ## Create the tab's labe with button | ||
3151 | 67 | box = gtk.HBox() | 355 | box = gtk.HBox() |
3153 | 68 | label = gtk.Label(pagenum) | 356 | label = gtk.Label(title) |
3154 | 69 | box.pack_start(label, True, True) | 357 | box.pack_start(label, True, True) |
3155 | 70 | |||
3156 | 71 | |||
3157 | 72 | ## x image for tab close button | 358 | ## x image for tab close button |
3159 | 73 | close_image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) | 359 | close_image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, |
3160 | 360 | gtk.ICON_SIZE_MENU) | ||
3161 | 74 | ## close button | 361 | ## close button |
3162 | 75 | closebtn = gtk.Button() | 362 | closebtn = gtk.Button() |
3163 | 76 | closebtn.set_relief(gtk.RELIEF_NONE) | 363 | closebtn.set_relief(gtk.RELIEF_NONE) |
3164 | 77 | closebtn.set_focus_on_click(True) | 364 | closebtn.set_focus_on_click(True) |
3165 | 78 | |||
3166 | 79 | closebtn.add(close_image) | 365 | closebtn.add(close_image) |
3167 | 80 | ## put button in a box and show box | 366 | ## put button in a box and show box |
3168 | 81 | box.pack_end(closebtn, False, False) | 367 | box.pack_end(closebtn, False, False) |
3169 | 82 | box.show_all() | 368 | box.show_all() |
3183 | 83 | 369 | closebtn.connect("clicked", lambda *x: self.close_tab(tab)) | |
3184 | 84 | view.MainWindow.notebook.prepend_page(vte_tab, box) # add tab | 370 | return box |
3185 | 85 | view.MainWindow.notebook.set_scrollable(True) | 371 | |
3186 | 86 | actions = clicompanionlib.controller.Actions() | 372 | def rename_tab(self, tab, newname): |
3187 | 87 | _vte.connect("button_press_event", actions.copy_paste, None) | 373 | dbg('Renaming tab to %s' % newname) |
3188 | 88 | vte_tab.grab_focus() | 374 | label = self.create_tab_label(newname, tab) |
3189 | 89 | # signal handler for tab | 375 | self.set_tab_label(tab, label) |
3190 | 90 | closebtn.connect("clicked", lambda *x: self.close_tab(vte_tab, notebook)) | 376 | self.focus() |
3178 | 91 | |||
3179 | 92 | vte_tab.show_all() | ||
3180 | 93 | |||
3181 | 94 | return vte_tab | ||
3182 | 95 | |||
3191 | 96 | 377 | ||
3192 | 97 | ## Remove a page from the notebook | 378 | ## Remove a page from the notebook |
3194 | 98 | def close_tab(self, vte_tab, notebook): | 379 | def close_tab(self, tab): |
3195 | 99 | ## get the page number of the tab we wanted to close | 380 | ## get the page number of the tab we wanted to close |
3197 | 100 | pagenum = view.MainWindow.notebook.page_num(vte_tab) | 381 | pagenum = self.page_num(tab) |
3198 | 101 | ## and close it | 382 | ## and close it |
3208 | 102 | view.MainWindow.notebook.remove_page(pagenum) | 383 | self.remove_page(pagenum) |
3209 | 103 | self.nop -= 1 | 384 | if self.get_n_pages() < 3: |
3210 | 104 | if self.nop <= 1: | 385 | self.set_show_tabs(False) |
3211 | 105 | view.MainWindow.notebook.set_show_tabs(False) | 386 | |
3212 | 106 | 387 | # check if the focus does not go to the last page (ie with only a + | |
3213 | 107 | # check if the focus does not go to the last page (ie with only a + sign) | 388 | # sign) |
3214 | 108 | if view.MainWindow.notebook.get_current_page() == self.nop: | 389 | if self.get_current_page() == self.get_n_pages() - 1: |
3215 | 109 | view.MainWindow.notebook.prev_page() | 390 | self.prev_page() |
3216 | 110 | 391 | if self.get_n_pages() != 1: | |
3217 | 392 | self.focus() | ||
3218 | 393 | |||
3219 | 394 | def quit_tab(self, tab=None): | ||
3220 | 395 | if not tab: | ||
3221 | 396 | tab = self.get_nth_page(self.get_current_page()) | ||
3222 | 397 | self.close_tab(tab) | ||
3223 | 398 | if self.get_n_pages() == 1: | ||
3224 | 399 | self.emit('quit') | ||
3225 | 400 | |||
3226 | 401 | def run_command(self, cmd, ui, desc): | ||
3227 | 402 | ## get the current notebook page so the function knows which terminal | ||
3228 | 403 | ## to run the command in. | ||
3229 | 404 | pagenum = self.get_current_page() | ||
3230 | 405 | page = self.get_nth_page(pagenum) | ||
3231 | 406 | page.run_command(cmd, ui, desc) | ||
3232 | 407 | self.focus() | ||
3233 | 408 | |||
3234 | 111 | def update_all_term_config(self, config=None): | 409 | def update_all_term_config(self, config=None): |
3245 | 112 | for pagenum in range(view.MainWindow.notebook.get_n_pages()): | 410 | self.global_config = config |
3246 | 113 | page = view.MainWindow.notebook.get_nth_page(pagenum) | 411 | for pagenum in range(self.get_n_pages()): |
3247 | 114 | dbg(page) | 412 | page = self.get_nth_page(pagenum) |
3248 | 115 | if isinstance(page, gtk.ScrolledWindow): | 413 | if isinstance(page, TerminalTab): |
3249 | 116 | for grandson in page.get_children(): | 414 | self.update_term_config(page, config) |
3250 | 117 | dbg(grandson) | 415 | |
3251 | 118 | if isinstance(grandson,vte.Terminal): | 416 | def update_term_config(self, tab, config=None): |
3242 | 119 | self.update_term_config(grandson, config) | ||
3243 | 120 | |||
3244 | 121 | def update_term_config(self, _vte, config=None): | ||
3252 | 122 | ##set terminal preferences from conig file data | 417 | ##set terminal preferences from conig file data |
3253 | 123 | if not config: | 418 | if not config: |
3309 | 124 | config = cc_config.get_config() | 419 | config = self.global_config |
3310 | 125 | try: | 420 | tab.update_config(config) |
3256 | 126 | config_scrollback = config.getint('terminal', 'scrollb') | ||
3257 | 127 | except ValueError: | ||
3258 | 128 | print _("WARNING: Invalid value for property 'terminal', int expected:" | ||
3259 | 129 | " got '%s', using default '%s'")%( | ||
3260 | 130 | config.get('terminal', 'scrollb'), | ||
3261 | 131 | config.get('DEFAULT', 'scrollb')) | ||
3262 | 132 | config.set('terminal','scrollb',config.get('DEFAULT', 'scrollb')) | ||
3263 | 133 | config_scrollback = config.getint('DEFAULT', 'scrollb') | ||
3264 | 134 | _vte.set_scrollback_lines(config_scrollback) | ||
3265 | 135 | |||
3266 | 136 | color = '#2e3436:#cc0000:#4e9a06:#c4a000:#3465a4:#75507b:#06989a:#d3d7cf:#555753:#ef2929:#8ae234:#fce94f:#729fcf:#ad7fa8:#34e2e2:#eeeeec' | ||
3267 | 137 | colors = color.split(':') | ||
3268 | 138 | palette = [] | ||
3269 | 139 | for color in colors: | ||
3270 | 140 | if color: | ||
3271 | 141 | palette.append(gtk.gdk.color_parse(color)) | ||
3272 | 142 | |||
3273 | 143 | try: | ||
3274 | 144 | config_color_fore = gtk.gdk.color_parse(config.get('terminal', 'colorf')) | ||
3275 | 145 | except ValueError, e: | ||
3276 | 146 | print _("WARNING: Invalid value for property '%s':" | ||
3277 | 147 | " got '%s', using default '%s'.")%( | ||
3278 | 148 | 'colorf', | ||
3279 | 149 | config.get('terminal', 'colorf'), | ||
3280 | 150 | config.get('DEFAULT', 'colorf')) | ||
3281 | 151 | config.set('terminal','colorf',config.get('DEFAULT', 'colorf')) | ||
3282 | 152 | config_color_fore = gtk.gdk.color_parse(config.get('DEFAULT', 'colorf')) | ||
3283 | 153 | |||
3284 | 154 | try: | ||
3285 | 155 | config_color_back = gtk.gdk.color_parse(config.get('terminal', 'colorb')) | ||
3286 | 156 | except ValueError, e: | ||
3287 | 157 | print _("WARNING: Invalid value for property '%s':" | ||
3288 | 158 | " got '%s', using default '%s'.")%( | ||
3289 | 159 | 'colorb', | ||
3290 | 160 | config.get('terminal', 'colorb'), | ||
3291 | 161 | config.get('DEFAULT', 'colorb')) | ||
3292 | 162 | config.set('terminal','colorb',config.get('DEFAULT', 'colorb')) | ||
3293 | 163 | config_color_back = gtk.gdk.color_parse(config.get('DEFAULT', 'colorb')) | ||
3294 | 164 | _vte.set_colors(config_color_fore, config_color_back, palette) | ||
3295 | 165 | |||
3296 | 166 | config_encoding = config.get('terminal', 'encoding') | ||
3297 | 167 | if config_encoding.upper() not in [ enc.upper() for enc, desc in utils.encodings]: | ||
3298 | 168 | print _("WARNING: Invalid value for property '%s':" | ||
3299 | 169 | " got '%s', using default '%s'")%( | ||
3300 | 170 | 'encoding', | ||
3301 | 171 | config_encoding, | ||
3302 | 172 | config.get('DEFAULT', 'encoding')) | ||
3303 | 173 | config.set('terminal','encoding',config.get('DEFAULT', 'encoding')) | ||
3304 | 174 | config_encoding = config.get('DEFAULT', 'encoding') | ||
3305 | 175 | _vte.set_encoding(config_encoding) | ||
3306 | 176 | |||
3307 | 177 | |||
3308 | 178 | |||
3311 | 179 | 421 | ||
3312 | === modified file 'clicompanionlib/utils.py' | |||
3313 | --- clicompanionlib/utils.py 2012-01-02 00:23:11 +0000 | |||
3314 | +++ clicompanionlib/utils.py 2012-01-08 10:37:24 +0000 | |||
3315 | @@ -1,9 +1,9 @@ | |||
3316 | 1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python |
3317 | 2 | # -*- coding: utf-8 -*- | 2 | # -*- coding: utf-8 -*- |
3318 | 3 | # | 3 | # |
3320 | 4 | # clicompanion.py - commandline tool. | 4 | # utils.py - Some helpful functions for clicompanion |
3321 | 5 | # | 5 | # |
3323 | 6 | # Copyright 2010 Duane Hinnen, Kenny Meyer, Marcos Vanetta | 6 | # Copyright 2010 Duane Hinnen, Kenny Meyer, Marcos Vanetta, David Caro |
3324 | 7 | # | 7 | # |
3325 | 8 | # This program is free software: you can redistribute it and/or modify it | 8 | # This program is free software: you can redistribute it and/or modify it |
3326 | 9 | # under the terms of the GNU General Public License version 3, as published | 9 | # under the terms of the GNU General Public License version 3, as published |
3327 | @@ -18,17 +18,19 @@ | |||
3328 | 18 | # with this program. If not, see <http://www.gnu.org/licenses/>. | 18 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
3329 | 19 | # | 19 | # |
3330 | 20 | # | 20 | # |
3337 | 21 | 21 | # In this file are implemented some functions that do not need any clicompanion | |
3338 | 22 | """ | 22 | # class and can somehow be useful in more than one module. |
3339 | 23 | A collection of useful functions. | 23 | |
3334 | 24 | """ | ||
3335 | 25 | |||
3336 | 26 | import getpass | ||
3340 | 27 | import os | 24 | import os |
3344 | 28 | import sys | 25 | import sys |
3345 | 29 | import gtk | 26 | import gtk |
3346 | 30 | import pwd | 27 | import pwd |
3347 | 31 | import inspect | 28 | import inspect |
3348 | 29 | import re | ||
3349 | 30 | try: | ||
3350 | 31 | import gconf | ||
3351 | 32 | except ImportError: | ||
3352 | 33 | pass | ||
3353 | 32 | 34 | ||
3354 | 33 | 35 | ||
3355 | 34 | ## set to True if you want to see more logs | 36 | ## set to True if you want to see more logs |
3356 | @@ -37,95 +39,97 @@ | |||
3357 | 37 | DEBUGCLASSES = [] | 39 | DEBUGCLASSES = [] |
3358 | 38 | DEBUGMETHODS = [] | 40 | DEBUGMETHODS = [] |
3359 | 39 | 41 | ||
3360 | 42 | gconf_cli = None | ||
3361 | 43 | |||
3362 | 40 | ## list gotten from terminator (https://launchpad.net/terminator) | 44 | ## list gotten from terminator (https://launchpad.net/terminator) |
3363 | 41 | encodings = [ | 45 | encodings = [ |
3364 | 42 | ["ISO-8859-1", _("Western")], | 46 | ["ISO-8859-1", _("Western")], |
3365 | 43 | ["ISO-8859-2", _("Central European")], | 47 | ["ISO-8859-2", _("Central European")], |
3451 | 44 | ["ISO-8859-3", _("South European") ], | 48 | ["ISO-8859-3", _("South European")], |
3452 | 45 | ["ISO-8859-4", _("Baltic") ], | 49 | ["ISO-8859-4", _("Baltic")], |
3453 | 46 | ["ISO-8859-5", _("Cyrillic") ], | 50 | ["ISO-8859-5", _("Cyrillic")], |
3454 | 47 | ["ISO-8859-6", _("Arabic") ], | 51 | ["ISO-8859-6", _("Arabic")], |
3455 | 48 | ["ISO-8859-7", _("Greek") ], | 52 | ["ISO-8859-7", _("Greek")], |
3456 | 49 | ["ISO-8859-8", _("Hebrew Visual") ], | 53 | ["ISO-8859-8", _("Hebrew Visual")], |
3457 | 50 | ["ISO-8859-8-I", _("Hebrew") ], | 54 | ["ISO-8859-8-I", _("Hebrew")], |
3458 | 51 | ["ISO-8859-9", _("Turkish") ], | 55 | ["ISO-8859-9", _("Turkish")], |
3459 | 52 | ["ISO-8859-10", _("Nordic") ], | 56 | ["ISO-8859-10", _("Nordic")], |
3460 | 53 | ["ISO-8859-13", _("Baltic") ], | 57 | ["ISO-8859-13", _("Baltic")], |
3461 | 54 | ["ISO-8859-14", _("Celtic") ], | 58 | ["ISO-8859-14", _("Celtic")], |
3462 | 55 | ["ISO-8859-15", _("Western") ], | 59 | ["ISO-8859-15", _("Western")], |
3463 | 56 | ["ISO-8859-16", _("Romanian") ], | 60 | ["ISO-8859-16", _("Romanian")], |
3464 | 57 | # ["UTF-7", _("Unicode") ], | 61 | # ["UTF-7", _("Unicode")], |
3465 | 58 | ["UTF-8", _("Unicode") ], | 62 | ["UTF-8", _("Unicode")], |
3466 | 59 | # ["UTF-16", _("Unicode") ], | 63 | # ["UTF-16", _("Unicode")], |
3467 | 60 | # ["UCS-2", _("Unicode") ], | 64 | # ["UCS-2", _("Unicode")], |
3468 | 61 | # ["UCS-4", _("Unicode") ], | 65 | # ["UCS-4", _("Unicode")], |
3469 | 62 | ["ARMSCII-8", _("Armenian") ], | 66 | ["ARMSCII-8", _("Armenian")], |
3470 | 63 | ["BIG5", _("Chinese Traditional") ], | 67 | ["BIG5", _("Chinese Traditional")], |
3471 | 64 | ["BIG5-HKSCS", _("Chinese Traditional") ], | 68 | ["BIG5-HKSCS", _("Chinese Traditional")], |
3472 | 65 | ["CP866", _("Cyrillic/Russian") ], | 69 | ["CP866", _("Cyrillic/Russian")], |
3473 | 66 | ["EUC-JP", _("Japanese") ], | 70 | ["EUC-JP", _("Japanese")], |
3474 | 67 | ["EUC-KR", _("Korean") ], | 71 | ["EUC-KR", _("Korean")], |
3475 | 68 | ["EUC-TW", _("Chinese Traditional") ], | 72 | ["EUC-TW", _("Chinese Traditional")], |
3476 | 69 | ["GB18030", _("Chinese Simplified") ], | 73 | ["GB18030", _("Chinese Simplified")], |
3477 | 70 | ["GB2312", _("Chinese Simplified") ], | 74 | ["GB2312", _("Chinese Simplified")], |
3478 | 71 | ["GBK", _("Chinese Simplified") ], | 75 | ["GBK", _("Chinese Simplified")], |
3479 | 72 | ["GEORGIAN-PS", _("Georgian") ], | 76 | ["GEORGIAN-PS", _("Georgian")], |
3480 | 73 | ["HZ", _("Chinese Simplified") ], | 77 | ["HZ", _("Chinese Simplified")], |
3481 | 74 | ["IBM850", _("Western") ], | 78 | ["IBM850", _("Western")], |
3482 | 75 | ["IBM852", _("Central European") ], | 79 | ["IBM852", _("Central European")], |
3483 | 76 | ["IBM855", _("Cyrillic") ], | 80 | ["IBM855", _("Cyrillic")], |
3484 | 77 | ["IBM857", _("Turkish") ], | 81 | ["IBM857", _("Turkish")], |
3485 | 78 | ["IBM862", _("Hebrew") ], | 82 | ["IBM862", _("Hebrew")], |
3486 | 79 | ["IBM864", _("Arabic") ], | 83 | ["IBM864", _("Arabic")], |
3487 | 80 | ["ISO-2022-JP", _("Japanese") ], | 84 | ["ISO-2022-JP", _("Japanese")], |
3488 | 81 | ["ISO-2022-KR", _("Korean") ], | 85 | ["ISO-2022-KR", _("Korean")], |
3489 | 82 | ["EUC-TW", _("Chinese Traditional") ], | 86 | ["EUC-TW", _("Chinese Traditional")], |
3490 | 83 | ["GB18030", _("Chinese Simplified") ], | 87 | ["GB18030", _("Chinese Simplified")], |
3491 | 84 | ["GB2312", _("Chinese Simplified") ], | 88 | ["GB2312", _("Chinese Simplified")], |
3492 | 85 | ["GBK", _("Chinese Simplified") ], | 89 | ["GBK", _("Chinese Simplified")], |
3493 | 86 | ["GEORGIAN-PS", _("Georgian") ], | 90 | ["GEORGIAN-PS", _("Georgian")], |
3494 | 87 | ["HZ", _("Chinese Simplified") ], | 91 | ["HZ", _("Chinese Simplified")], |
3495 | 88 | ["IBM850", _("Western") ], | 92 | ["IBM850", _("Western")], |
3496 | 89 | ["IBM852", _("Central European") ], | 93 | ["IBM852", _("Central European")], |
3497 | 90 | ["IBM855", _("Cyrillic") ], | 94 | ["IBM855", _("Cyrillic")], |
3498 | 91 | ["IBM857", _("Turkish") ], | 95 | ["IBM857", _("Turkish")], |
3499 | 92 | ["IBM862", _("Hebrew") ], | 96 | ["IBM862", _("Hebrew")], |
3500 | 93 | ["IBM864", _("Arabic") ], | 97 | ["IBM864", _("Arabic")], |
3501 | 94 | ["ISO-2022-JP", _("Japanese") ], | 98 | ["ISO-2022-JP", _("Japanese")], |
3502 | 95 | ["ISO-2022-KR", _("Korean") ], | 99 | ["ISO-2022-KR", _("Korean")], |
3503 | 96 | ["ISO-IR-111", _("Cyrillic") ], | 100 | ["ISO-IR-111", _("Cyrillic")], |
3504 | 97 | # ["JOHAB", _("Korean") ], | 101 | # ["JOHAB", _("Korean")], |
3505 | 98 | ["KOI8-R", _("Cyrillic") ], | 102 | ["KOI8-R", _("Cyrillic")], |
3506 | 99 | ["KOI8-U", _("Cyrillic/Ukrainian") ], | 103 | ["KOI8-U", _("Cyrillic/Ukrainian")], |
3507 | 100 | ["MAC_ARABIC", _("Arabic") ], | 104 | ["MAC_ARABIC", _("Arabic")], |
3508 | 101 | ["MAC_CE", _("Central European") ], | 105 | ["MAC_CE", _("Central European")], |
3509 | 102 | ["MAC_CROATIAN", _("Croatian") ], | 106 | ["MAC_CROATIAN", _("Croatian")], |
3510 | 103 | ["MAC-CYRILLIC", _("Cyrillic") ], | 107 | ["MAC-CYRILLIC", _("Cyrillic")], |
3511 | 104 | ["MAC_DEVANAGARI", _("Hindi") ], | 108 | ["MAC_DEVANAGARI", _("Hindi")], |
3512 | 105 | ["MAC_FARSI", _("Persian") ], | 109 | ["MAC_FARSI", _("Persian")], |
3513 | 106 | ["MAC_GREEK", _("Greek") ], | 110 | ["MAC_GREEK", _("Greek")], |
3514 | 107 | ["MAC_GUJARATI", _("Gujarati") ], | 111 | ["MAC_GUJARATI", _("Gujarati")], |
3515 | 108 | ["MAC_GURMUKHI", _("Gurmukhi") ], | 112 | ["MAC_GURMUKHI", _("Gurmukhi")], |
3516 | 109 | ["MAC_HEBREW", _("Hebrew") ], | 113 | ["MAC_HEBREW", _("Hebrew")], |
3517 | 110 | ["MAC_ICELANDIC", _("Icelandic") ], | 114 | ["MAC_ICELANDIC", _("Icelandic")], |
3518 | 111 | ["MAC_ROMAN", _("Western") ], | 115 | ["MAC_ROMAN", _("Western")], |
3519 | 112 | ["MAC_ROMANIAN", _("Romanian") ], | 116 | ["MAC_ROMANIAN", _("Romanian")], |
3520 | 113 | ["MAC_TURKISH", _("Turkish") ], | 117 | ["MAC_TURKISH", _("Turkish")], |
3521 | 114 | ["MAC_UKRAINIAN", _("Cyrillic/Ukrainian") ], | 118 | ["MAC_UKRAINIAN", _("Cyrillic/Ukrainian")], |
3522 | 115 | ["SHIFT-JIS", _("Japanese") ], | 119 | ["SHIFT-JIS", _("Japanese")], |
3523 | 116 | ["TCVN", _("Vietnamese") ], | 120 | ["TCVN", _("Vietnamese")], |
3524 | 117 | ["TIS-620", _("Thai") ], | 121 | ["TIS-620", _("Thai")], |
3525 | 118 | ["UHC", _("Korean") ], | 122 | ["UHC", _("Korean")], |
3526 | 119 | ["VISCII", _("Vietnamese") ], | 123 | ["VISCII", _("Vietnamese")], |
3527 | 120 | ["WINDOWS-1250", _("Central European") ], | 124 | ["WINDOWS-1250", _("Central European")], |
3528 | 121 | ["WINDOWS-1251", _("Cyrillic") ], | 125 | ["WINDOWS-1251", _("Cyrillic")], |
3529 | 122 | ["WINDOWS-1252", _("Western") ], | 126 | ["WINDOWS-1252", _("Western")], |
3530 | 123 | ["WINDOWS-1253", _("Greek") ], | 127 | ["WINDOWS-1253", _("Greek")], |
3531 | 124 | ["WINDOWS-1254", _("Turkish") ], | 128 | ["WINDOWS-1254", _("Turkish")], |
3532 | 125 | ["WINDOWS-1255", _("Hebrew") ], | 129 | ["WINDOWS-1255", _("Hebrew")], |
3533 | 126 | ["WINDOWS-1256", _("Arabic") ], | 130 | ["WINDOWS-1256", _("Arabic")], |
3534 | 127 | ["WINDOWS-1257", _("Baltic") ], | 131 | ["WINDOWS-1257", _("Baltic")], |
3535 | 128 | ["WINDOWS-1258", _("Vietnamese") ] | 132 | ["WINDOWS-1258", _("Vietnamese")] |
3536 | 129 | ] | 133 | ] |
3537 | 130 | 134 | ||
3538 | 131 | 135 | ||
3539 | @@ -135,8 +139,9 @@ | |||
3540 | 135 | method = None | 139 | method = None |
3541 | 136 | for stackitem in stack: | 140 | for stackitem in stack: |
3542 | 137 | parent_frame = stackitem[0] | 141 | parent_frame = stackitem[0] |
3545 | 138 | names, varargs, keywords, local_vars = inspect.getargvalues(parent_frame) | 142 | names, varargs, keywords, local_vars = \ |
3546 | 139 | ## little trick to get the second stackline method, in case we do | 143 | inspect.getargvalues(parent_frame) |
3547 | 144 | ## little trick to get the second stackline method, in case we do | ||
3548 | 140 | ## not find self | 145 | ## not find self |
3549 | 141 | if not method and method != None: | 146 | if not method and method != None: |
3550 | 142 | method = stackitem[3] | 147 | method = stackitem[3] |
3551 | @@ -163,37 +168,88 @@ | |||
3552 | 163 | if DEBUGMETHODS != [] and method not in DEBUGMETHODS: | 168 | if DEBUGMETHODS != [] and method not in DEBUGMETHODS: |
3553 | 164 | return | 169 | return |
3554 | 165 | try: | 170 | try: |
3556 | 166 | print >> sys.stderr, "%s::%s: %s%s" % (classname, method, log, extra) | 171 | print >> sys.stderr, "%s::%s: %s%s" % (classname, method, |
3557 | 172 | log, extra) | ||
3558 | 167 | except IOError: | 173 | except IOError: |
3559 | 168 | pass | 174 | pass |
3566 | 169 | 175 | ||
3567 | 170 | 176 | ||
3568 | 171 | #TODO: Move this to controller.py | 177 | def shell_lookup(): |
3569 | 172 | def get_user_shell(): | 178 | """Find an appropriate shell for the user |
3570 | 173 | """Get the user's shell defined in /etc/passwd .""" | 179 | Function copied from the terminator project source code |
3571 | 174 | data = None | 180 | www.launchpad.net/terminator""" |
3572 | 175 | try: | 181 | try: |
3593 | 176 | # Read out the data in /etc/passwd | 182 | usershell = pwd.getpwuid(os.getuid())[6] |
3594 | 177 | with open('/etc/passwd') as f: | 183 | except KeyError: |
3595 | 178 | data = f.readlines() | 184 | usershell = None |
3596 | 179 | except e: | 185 | shells = [os.getenv('SHELL'), usershell, 'bash', |
3597 | 180 | print "Something unexpected happened!" | 186 | 'zsh', 'tcsh', 'ksh', 'csh', 'sh'] |
3598 | 181 | raise e | 187 | |
3599 | 182 | 188 | for shell in shells: | |
3600 | 183 | for i in data: | 189 | if shell is None: |
3601 | 184 | tmp = i.split(":") | 190 | continue |
3602 | 185 | # Check for the entry of the currently logged in user | 191 | elif os.path.isfile(shell): |
3603 | 186 | if tmp[0] == getpass.getuser(): | 192 | dbg('Found shell %s' % (shell)) |
3604 | 187 | # Columns are separated by colons, so split each column. | 193 | return shell |
3605 | 188 | # Sample /etc/passwd entry for a user: | 194 | else: |
3606 | 189 | # | 195 | rshell = path_lookup(shell) |
3607 | 190 | # jorge:x:1001:1002:,,,:/home/jorge:/bin/bash | 196 | if rshell is not None: |
3608 | 191 | # | 197 | dbg('Found shell %s at %s' % (shell, rshell)) |
3609 | 192 | # The last column is relevant for us. | 198 | return rshell |
3610 | 193 | # Don't forget to strip the newline at the end of the string! | 199 | dbg('Unable to locate a shell') |
3611 | 194 | return i.split(":")[-1:][0].strip('\n') | 200 | |
3612 | 195 | 201 | ||
3613 | 202 | ## replace ? with {0[n]} | ||
3614 | 203 | def replace(cmnd, num, ran): | ||
3615 | 204 | while ran < num: | ||
3616 | 205 | replace_cmnd = re.sub('\?', '{0[' + str(ran) + ']}', cmnd, count=1) | ||
3617 | 206 | cmnd = replace_cmnd | ||
3618 | 207 | ran += 1 | ||
3619 | 208 | return cmnd | ||
3620 | 209 | |||
3621 | 210 | |||
3622 | 211 | def get_system_font(callback=None): | ||
3623 | 212 | """Look up the system font""" | ||
3624 | 213 | global gconf_cli | ||
3625 | 214 | if 'gconf' not in globals(): | ||
3626 | 215 | return 'Monospace 10' | ||
3627 | 216 | else: | ||
3628 | 217 | if not gconf_cli: | ||
3629 | 218 | gconf_cli = gconf.client_get_default() | ||
3630 | 219 | value = gconf_cli.get( | ||
3631 | 220 | '/desktop/gnome/interface/monospace_font_name') | ||
3632 | 221 | system_font = value.get_string() | ||
3633 | 222 | if callback: | ||
3634 | 223 | gconf_cli.notify_add( | ||
3635 | 224 | '/desktop/gnome/interface/monospace_font_name', | ||
3636 | 225 | callback) | ||
3637 | 226 | return system_font | ||
3638 | 227 | |||
3639 | 228 | |||
3640 | 229 | ## WARNING: the altgr key is detected as a normal key | ||
3641 | 230 | def get_keycomb(event): | ||
3642 | 231 | keyname = gtk.gdk.keyval_name(event.keyval) | ||
3643 | 232 | if event.state & gtk.gdk.CONTROL_MASK: | ||
3644 | 233 | keyname = 'ctrl+' + keyname | ||
3645 | 234 | if event.state & gtk.gdk.MOD1_MASK: | ||
3646 | 235 | keyname = 'alt+' + keyname | ||
3647 | 236 | if event.state & gtk.gdk.SHIFT_MASK: | ||
3648 | 237 | keyname = 'shift+' + keyname | ||
3649 | 238 | return keyname | ||
3650 | 239 | |||
3651 | 240 | |||
3652 | 241 | ## WARNING: the altgr key is detected as shift | ||
3653 | 242 | def only_modifier(event): | ||
3654 | 243 | key = gtk.gdk.keyval_name(event.keyval) | ||
3655 | 244 | return 'shift' in key.lower() \ | ||
3656 | 245 | or 'control' in key.lower() \ | ||
3657 | 246 | or 'super' in key.lower() \ | ||
3658 | 247 | or 'alt' in key.lower() | ||
3659 | 248 | |||
3660 | 249 | |||
3661 | 250 | ### Singleton implementation (kind of) | ||
3662 | 196 | class Borg: | 251 | class Borg: |
3663 | 197 | __shared_state = {} | 252 | __shared_state = {} |
3664 | 253 | |||
3665 | 198 | def __init__(self): | 254 | def __init__(self): |
3666 | 199 | self.__dict__ = self.__shared_state | 255 | self.__dict__ = self.__shared_state |
3667 | 200 | 256 | ||
3668 | === modified file 'clicompanionlib/view.py' | |||
3669 | --- clicompanionlib/view.py 2012-01-02 00:23:11 +0000 | |||
3670 | +++ clicompanionlib/view.py 2012-01-08 10:37:24 +0000 | |||
3671 | @@ -1,9 +1,9 @@ | |||
3672 | 1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python |
3673 | 2 | # -*- coding: utf-8 -*- | 2 | # -*- coding: utf-8 -*- |
3674 | 3 | # | 3 | # |
3676 | 4 | # clicompanion.py - commandline tool. | 4 | # view.py - Main window for the clicompanon |
3677 | 5 | # | 5 | # |
3679 | 6 | # Copyright 2010 Duane Hinnen, Kenny Meyer | 6 | # Copyright 2010 Duane Hinnen, Kenny Meyer, David Caro |
3680 | 7 | # | 7 | # |
3681 | 8 | # This program is free software: you can redistribute it and/or modify it | 8 | # This program is free software: you can redistribute it and/or modify it |
3682 | 9 | # under the terms of the GNU General Public License version 3, as published | 9 | # under the terms of the GNU General Public License version 3, as published |
3683 | @@ -17,45 +17,47 @@ | |||
3684 | 17 | # You should have received a copy of the GNU General Public License along | 17 | # You should have received a copy of the GNU General Public License along |
3685 | 18 | # with this program. If not, see <http://www.gnu.org/licenses/>. | 18 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
3686 | 19 | # | 19 | # |
3689 | 20 | 20 | # This file is where the main window is defined, depends on all the modules of | |
3690 | 21 | 21 | # the clicompanion libraries. | |
3691 | 22 | # | ||
3692 | 23 | # Also holds the class that implements the commands notebook (the upper | ||
3693 | 24 | # notebook), where all the tab plugins will be added (like LocalCommandList and | ||
3694 | 25 | # CommandLineFU) | ||
3695 | 26 | |||
3696 | 27 | |||
3697 | 28 | import os | ||
3698 | 22 | import pygtk | 29 | import pygtk |
3699 | 23 | pygtk.require('2.0') | 30 | pygtk.require('2.0') |
3701 | 24 | import os | 31 | import gobject |
3702 | 32 | import webbrowser | ||
3703 | 25 | 33 | ||
3704 | 26 | # import vte and gtk or print error | 34 | # import vte and gtk or print error |
3705 | 27 | try: | 35 | try: |
3706 | 28 | import gtk | 36 | import gtk |
3707 | 29 | except: | 37 | except: |
3713 | 30 | error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, | 38 | ## do not use gtk, just print |
3714 | 31 | _("You need to install the python gtk bindings package 'python-gtk2'")) | 39 | print _("You need to install the python gtk bindings package" |
3715 | 32 | error.run() | 40 | "'python-gtk2'") |
3716 | 33 | sys.exit (1) | 41 | sys.exit(1) |
3717 | 34 | 42 | ||
3718 | 35 | try: | 43 | try: |
3719 | 36 | import vte | 44 | import vte |
3720 | 37 | except: | 45 | except: |
3722 | 38 | error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, | 46 | error = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, |
3723 | 47 | gtk.BUTTONS_OK, | ||
3724 | 39 | _("You need to install 'python-vte' the python bindings for libvte.")) | 48 | _("You need to install 'python-vte' the python bindings for libvte.")) |
3725 | 40 | error.run() | 49 | error.run() |
3733 | 41 | sys.exit (1) | 50 | sys.exit(1) |
3734 | 42 | 51 | ||
3735 | 43 | import clicompanionlib.menus_buttons | 52 | import clicompanionlib.menus_buttons as cc_menus_buttons |
3736 | 44 | import clicompanionlib.controller | 53 | import clicompanionlib.tabs as cc_tabs |
3737 | 45 | from clicompanionlib.utils import get_user_shell , Borg, dbg | 54 | import clicompanionlib.utils as cc_utils |
3738 | 46 | import clicompanionlib.tabs | 55 | from clicompanionlib.utils import dbg |
3732 | 47 | import clicompanionlib.utils as utils | ||
3739 | 48 | import clicompanionlib.config as cc_config | 56 | import clicompanionlib.config as cc_config |
3750 | 49 | 57 | import clicompanionlib.helpers as cc_helpers | |
3751 | 50 | 58 | import clicompanionlib.plugins as cc_plugins | |
3752 | 51 | ## Changed two->three columns | 59 | import clicompanionlib.preferences as cc_pref |
3753 | 52 | CMNDS = cc_config.Cheatsheet() | 60 | |
3744 | 53 | ## will hold the commands. Actually the first three columns | ||
3745 | 54 | ## note that this commands list will not change with searchers and filters, | ||
3746 | 55 | ## instead, when adding a command to the liststore, we will add also the index | ||
3747 | 56 | ## of the command in the CMND list | ||
3748 | 57 | |||
3749 | 58 | ROW = '0' ## holds the currently selected row | ||
3754 | 59 | TARGETS = [ | 61 | TARGETS = [ |
3755 | 60 | ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0), | 62 | ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0), |
3756 | 61 | ('text/plain', 0, 1), | 63 | ('text/plain', 0, 1), |
3757 | @@ -63,250 +65,180 @@ | |||
3758 | 63 | ('STRING', 0, 3), | 65 | ('STRING', 0, 3), |
3759 | 64 | ] | 66 | ] |
3760 | 65 | 67 | ||
3950 | 66 | FILTER = 0 | 68 | |
3951 | 67 | NETBOOKMODE = 0 | 69 | class CommandsNotebook(gtk.Notebook): |
3952 | 68 | HIDEUI = 0 | 70 | ''' |
3953 | 69 | FULLSCREEN = 0 | 71 | This is the notebook where the commands list and the commandlinefu commands |
3954 | 70 | 72 | are displayed | |
3955 | 71 | menu_search_hbox = '' | 73 | ''' |
3956 | 72 | button_box = '' | 74 | ### We need a way to tell the main window that a command must be runned on |
3957 | 73 | 75 | ## the selected terminal tab | |
3958 | 74 | 76 | __gsignals__ = { | |
3959 | 75 | class MainWindow(Borg): | 77 | 'run_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, |
3960 | 76 | window = gtk.Window(gtk.WINDOW_TOPLEVEL) | 78 | (str, str, str)), |
3961 | 77 | #color = gtk.gdk.Color(60000, 65533, 60000) | 79 | 'add_tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, |
3962 | 78 | #window.modify_bg(gtk.STATE_NORMAL, color) | 80 | ()), |
3963 | 79 | liststore = gtk.ListStore(str, str, str, int) | 81 | 'preferences': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, |
3964 | 80 | treeview = gtk.TreeView() | 82 | ()), |
3965 | 81 | expander = gtk.Expander() | 83 | 'show_man': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, |
3966 | 82 | scrolledwindow = gtk.ScrolledWindow() | 84 | (str, )), |
3967 | 83 | notebook = gtk.Notebook() | 85 | 'quit': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, |
3968 | 84 | 86 | ()), | |
3969 | 85 | screen = gtk.gdk.display_get_default().get_default_screen() | 87 | } |
3970 | 86 | screen_size = screen.get_monitor_geometry(0) | 88 | |
3971 | 87 | height = screen.get_height() ## screen height ## | 89 | def __init__(self, config, pluginloader): |
3972 | 88 | global NETBOOKMODE | 90 | self.config = config |
3973 | 89 | if height < 750: | 91 | self.pluginloader = pluginloader |
3974 | 90 | NETBOOKMODE = 1 | 92 | gtk.Notebook.__init__(self) |
3975 | 91 | 93 | self.loaded_plugins = {} | |
3976 | 92 | 94 | self.draw_all() | |
3977 | 93 | def sync_cmnds(self, rld=False): | 95 | |
3978 | 94 | global CMNDS | 96 | def draw_all(self): |
3979 | 95 | dbg('syncing commands') | 97 | ## Load the needed LocalCommandList plugin |
3980 | 96 | if rld: | 98 | if 'LocalCommandList' not in self.pluginloader.plugins.keys(): |
3981 | 97 | ## reload the commands list from the file | 99 | print _("ERROR: LocalCommandList plugin is needed for the " |
3982 | 98 | CMNDS.load() | 100 | "execution of CLI Companion, please retore the plugin or " |
3983 | 99 | self.liststore.clear() | 101 | "reinstall.") |
3984 | 100 | ## Store also the index of the command in the CMNDS list | 102 | self.emit('quit') |
3985 | 101 | i = 0 | 103 | else: |
3986 | 102 | for cmd, ui, desc in CMNDS: | 104 | self.commandstab = self.pluginloader.plugins['LocalCommandList']( |
3987 | 103 | self.liststore.append((cmd, ui, desc, i)) | 105 | self.config.get_plugin_conf('LocalCommandList')) |
3988 | 104 | i = i +1 | 106 | for pname, pclass in self.pluginloader.get_plugins('CommandTab'): |
3989 | 105 | 107 | if pname == 'LocalCommandList': | |
3990 | 106 | 108 | tab = self.commandstab | |
3991 | 107 | #liststore in a scrolled window in an expander | 109 | else: |
3992 | 108 | def expanded_cb(self, expander, params, window, search_box): | 110 | tab = pclass(self.config.get_plugin_conf('LocalCommandList')) |
3993 | 109 | if expander.get_expanded(): | 111 | self.append_tab(tab, pname) |
3994 | 110 | 112 | ||
3995 | 111 | # Activate the search box when expanded | 113 | def append_tab(self, tab, pname): |
3996 | 112 | search_box.set_sensitive(True) | 114 | ## save the tab related to the plugin name |
3997 | 113 | else: | 115 | self.loaded_plugins[pname] = tab |
3998 | 114 | # De-activate the search box when not expanded | 116 | self.append_page(tab, gtk.Label(tab.__title__ or pname)) |
3999 | 115 | self.search_box.set_sensitive(False) | 117 | ##All the available signals for the plugins |
4000 | 116 | expander.set_expanded(False) | 118 | tab.connect('run_command', |
4001 | 117 | #expander.remove(expander.child) | 119 | lambda wg, *args: self.run_command(*args)) |
4002 | 118 | ##reset the size of the window to its original one | 120 | tab.connect('add_command', |
4003 | 119 | window.resize(1, 1) | 121 | lambda wg, *args: self.commandstab.add_command(*args)) |
4004 | 120 | return | 122 | tab.connect('remove_command', |
4005 | 121 | 123 | lambda wg, *args: self.remove_command(*args)) | |
4006 | 122 | 124 | tab.connect('edit_command', | |
4007 | 123 | # close the window and quit | 125 | lambda wg, *args: self.edit_command(*args)) |
4008 | 124 | def delete_event(self, widget, data=None): | 126 | tab.connect('add_tab', |
4009 | 125 | gtk.main_quit() | 127 | lambda wg, *args: self.emit('add_tab', *args)) |
4010 | 126 | return False | 128 | tab.connect('preferences', |
4011 | 127 | 129 | lambda wg, *args: self.emit('preferences', *args)) | |
4012 | 128 | def key_clicked(self, widget, event): | 130 | tab.connect('show_man', |
4013 | 129 | actions = clicompanionlib.controller.Actions() | 131 | lambda wg, *args: self.emit('show_man', *args)) |
4014 | 130 | global HIDEUI | 132 | tab.connect('quit', |
4015 | 131 | global FULLSCREEN | 133 | lambda wg, *args: self.emit('quit', *args)) |
4016 | 132 | global menu_search_hbox | 134 | |
4017 | 133 | global button_box | 135 | def filter(self, filter_str, pagenum=None): |
4018 | 134 | keyname = gtk.gdk.keyval_name(event.keyval).upper() | 136 | if pagenum == None: |
4019 | 135 | if keyname == "F12": | 137 | pagenum = self.get_current_page() |
4020 | 136 | HIDEUI = 1 - HIDEUI | 138 | page = self.get_nth_page(pagenum) |
4021 | 137 | if HIDEUI == 1: | 139 | dbg('filtering by %s' % filter_str) |
4022 | 138 | self.treeview.hide_all() | 140 | page.filter(filter_str) |
4023 | 139 | self.expander.hide_all() | 141 | |
4024 | 140 | self.scrolledwindow.hide_all() | 142 | def set_netbook(netbookmode=False): |
4025 | 141 | menu_search_hbox.hide_all() | 143 | if netbookmode: |
4026 | 142 | button_box.hide_all() | 144 | self.set_size_request(700, 200) |
4027 | 143 | else: | 145 | else: |
4028 | 144 | self.treeview.show_all() | 146 | self.set_size_request(700, 220) |
4029 | 145 | self.expander.show_all() | 147 | |
4030 | 146 | self.scrolledwindow.show_all() | 148 | def get_command(self): |
4031 | 147 | menu_search_hbox.show_all() | 149 | pagenum = self.get_current_page() |
4032 | 148 | button_box.show_all() | 150 | page = self.get_nth_page(pagenum) |
4033 | 149 | if keyname == "F11": | 151 | return page.get_command() |
4034 | 150 | FULLSCREEN = 1 - FULLSCREEN | 152 | |
4035 | 151 | if FULLSCREEN == 1: | 153 | def run_command(self, cmd=None, ui=None, desc=None): |
4036 | 152 | pwin = button_box.get_window() | 154 | if cmd == None: |
4037 | 153 | pwin.fullscreen() | 155 | cmd, ui, desc = self.get_command() |
4038 | 154 | else: | 156 | if cmd: |
4039 | 155 | pwin = button_box.get_window() | 157 | dbg('running command %s' % cmd) |
4040 | 156 | pwin.unfullscreen() | 158 | self.emit('run_command', cmd, ui, desc) |
4041 | 157 | if keyname == "F4": | 159 | |
4042 | 158 | actions.run_command(self) | 160 | def add_command(self): |
4043 | 159 | if keyname == "F5": | 161 | if self.get_current_page() == 0: |
4044 | 160 | actions.add_command(self) | 162 | self.commandstab.add_command() |
4045 | 161 | if keyname == "F6": | 163 | else: |
4046 | 162 | actions.remove_command(self) | 164 | command = self.get_command() |
4047 | 163 | if keyname == "F7": | 165 | if command[0] != None: |
4048 | 164 | self.tabs.add_tab(self) | 166 | self.commandstab.add_command(*command) |
4049 | 165 | 167 | else: | |
4050 | 166 | def __init__(self): | 168 | self.commandstab.add_command() |
4051 | 167 | #import pdb ##debug | 169 | |
4052 | 168 | #pdb.set_trace() ##debug | 170 | def remove_command(self): |
4053 | 169 | 171 | if self.get_current_page() == 0: | |
4054 | 170 | ##For now TERM is hardcoded to xterm because of a change | 172 | self.commandstab.remove_command() |
4055 | 171 | ##in libvte in Ubuntu Maverick | 173 | |
4056 | 172 | os.putenv('TERM', 'xterm') | 174 | def edit_command(self): |
4057 | 173 | 175 | if self.get_current_page() == 0: | |
4058 | 174 | 176 | self.commandstab.edit_command() | |
4059 | 175 | ## style to reduce padding around tabs | 177 | else: |
4060 | 176 | ## TODO: Find a better place for this? | 178 | command = self.get_command() |
4061 | 177 | gtk.rc_parse_string ("style \"tab-close-button-style\"\n" | 179 | if command[0] != None: |
4062 | 178 | "{\n" | 180 | self.commandstab.add_command(*command) |
4063 | 179 | "GtkWidget::focus-padding = 0\n" | 181 | |
4064 | 180 | "GtkWidget::focus-line-width = 0\n" | 182 | def update(self, config=None, force=None): |
4065 | 181 | "xthickness = 0\n" | 183 | if config: |
4066 | 182 | "ythickness = 0\n" | 184 | self.config = config |
4067 | 183 | "}\n" | 185 | newplugins = self.pluginloader.get_plugins('CommandTab') |
4068 | 184 | "widget \"*.tab-close-button\" style \"tab-close-button-style\""); | 186 | for plugin in self.loaded_plugins.keys(): |
4069 | 185 | 187 | if plugin not in [ name for name, cl in newplugins]: | |
4070 | 186 | ## Create UI widgets | 188 | dbg('Disabling plugin %s' % plugin) |
4071 | 187 | 189 | self.remove_page(self.page_num(self.loaded_plugins[plugin])) | |
4072 | 188 | self.notebook.set_show_tabs(0) | 190 | self.loaded_plugins.pop(plugin) |
4073 | 189 | 191 | for pname, pclass in newplugins: | |
4074 | 190 | ##attach the style to the widget | 192 | if pname not in self.loaded_plugins.keys(): |
4075 | 191 | self.notebook.set_name ("tab-close-button") | 193 | dbg('Adding new selected plugin %s' % pname) |
4076 | 192 | 194 | self.append_tab( | |
4077 | 193 | ## set sizes and borders | 195 | pclass(self.config.get_plugin_conf('LocalCommandList')), |
4078 | 194 | global NETBOOKMODE | 196 | pname) |
4079 | 195 | if NETBOOKMODE == 1: | 197 | for plugin in force: |
4080 | 196 | self.scrolledwindow.set_size_request(700, 200) | 198 | if plugin in self.loaded_plugins: |
4081 | 197 | self.window.set_default_size(700, 500) | 199 | dbg('Reloading plugin %s' % plugin) |
4082 | 198 | else: | 200 | self.loaded_plugins[plugin].reload( |
4083 | 199 | self.scrolledwindow.set_size_request(700, 220) | 201 | self.config.get_plugin_conf(plugin)) |
4084 | 200 | self.window.set_default_size(700, 625) | 202 | self.show_all() |
4085 | 201 | self.window.set_border_width(10) | 203 | |
4086 | 202 | ## Sets the position of the window relative to the screen | 204 | |
4087 | 203 | self.window.set_position(gtk.WIN_POS_CENTER_ALWAYS) | 205 | class MainWindow(gtk.Window): |
4088 | 204 | ## Allow user to resize window | 206 | def __init__(self, config): |
4089 | 205 | self.window.set_resizable(True) | 207 | gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) |
4090 | 206 | 208 | ############### | |
4091 | 207 | ## set Window title and icon | 209 | ### Some state variables |
4092 | 208 | self.window.set_title("CLI Companion") | 210 | self.hiddenui = False |
4093 | 209 | icon = gtk.gdk.pixbuf_new_from_file("/usr/share/pixmaps/clicompanion.16.png") | 211 | self.maximized = False |
4094 | 210 | self.window.set_icon(icon) | 212 | self.filtered = False |
4095 | 211 | 213 | self.fullscr = False | |
4096 | 212 | # sync liststore with commands | 214 | |
4097 | 213 | self.sync_cmnds() | 215 | self.config = config |
4098 | 214 | 216 | ||
4099 | 215 | ## set renderer and colors | 217 | self.load_plugins() |
4100 | 216 | #color2 = gtk.gdk.Color(5000,5000,65000) | 218 | |
4101 | 217 | renderer = gtk.CellRendererText() | 219 | ## two sections, the menus and search box and the expander with the |
4102 | 218 | #renderer.set_property("cell-background-gdk", color) | 220 | ## commands notebook and in the botom, the terminals notebook |
4103 | 219 | #renderer.set_property("foreground-gdk", color2) | 221 | |
4104 | 220 | 222 | ## set various parameters on the main window (size, etc) | |
4105 | 221 | ## create the TreeViewColumns to display the data | 223 | self.init_config() |
4106 | 222 | self.treeview.columns = [None]*3 | 224 | self.term_notebook = cc_tabs.TerminalsNotebook(self.config) |
4107 | 223 | self.treeview.columns[0] = gtk.TreeViewColumn(_('Command'), renderer) | 225 | |
4108 | 224 | self.treeview.columns[1] = gtk.TreeViewColumn(_('User Input'), renderer) | 226 | ########################### |
4109 | 225 | self.treeview.columns[2] = gtk.TreeViewColumn(_('Description'), renderer) | 227 | #### Here we create the commands notebook for the expander |
4110 | 226 | 228 | self.cmd_notebook = CommandsNotebook(config, self.pluginloader) | |
4111 | 227 | for n in range(3): | 229 | |
4112 | 228 | ## add columns to treeview | 230 | ## Create the menus and the searchbox |
4113 | 229 | self.treeview.append_column(self.treeview.columns[n]) | 231 | ## hbox for menu and search Entry |
4114 | 230 | ## create a CellRenderers to render the data | 232 | self.menu_search_hbox = gtk.HBox(False) |
4115 | 231 | self.treeview.columns[n].cell = gtk.CellRendererText() | 233 | |
4116 | 232 | #self.treeview.columns[n].cell.set_property("cell-background-gdk", color) | 234 | #### The menus |
3928 | 233 | #self.treeview.columns[n].cell.set_property("foreground-gdk", color2) | ||
3929 | 234 | ## add the cells to the columns | ||
3930 | 235 | self.treeview.columns[n].pack_start(self.treeview.columns[n].cell, | ||
3931 | 236 | True) | ||
3932 | 237 | ## set the cell attributes to the appropriate liststore column | ||
3933 | 238 | self.treeview.columns[n].set_attributes( | ||
3934 | 239 | self.treeview.columns[n].cell, text=n) | ||
3935 | 240 | self.treeview.columns[n].set_resizable(True) | ||
3936 | 241 | |||
3937 | 242 | ''' set treeview model and put treeview in the scrolled window | ||
3938 | 243 | and the scrolled window in the expander. ''' | ||
3939 | 244 | self.treeview.set_model(self.liststore) | ||
3940 | 245 | self.treeview.set_reorderable(True) | ||
3941 | 246 | self.scrolledwindow.add(self.treeview) | ||
3942 | 247 | |||
3943 | 248 | self.expander.add(self.scrolledwindow) | ||
3944 | 249 | #self.window.show_all() | ||
3945 | 250 | |||
3946 | 251 | ## instantiate tabs | ||
3947 | 252 | self.tabs = clicompanionlib.tabs.Tabs() | ||
3948 | 253 | ## instantiate controller.Actions, where all the button actions are | ||
3949 | 254 | self.actions = clicompanionlib.controller.Actions() | ||
4117 | 255 | ## instantiate 'File' and 'Help' Drop Down Menu [menus_buttons.py] | 235 | ## instantiate 'File' and 'Help' Drop Down Menu [menus_buttons.py] |
4172 | 256 | bar = clicompanionlib.menus_buttons.FileMenu() | 236 | menu_bar = cc_menus_buttons.FileMenu(self.config) |
4173 | 257 | menu_bar = bar.the_menu(self) | 237 | self.menu_search_hbox.pack_start(menu_bar, True) |
4174 | 258 | 238 | ||
4175 | 259 | 239 | #### the expander | |
4176 | 260 | ## get row of a selection | 240 | self.expander = gtk.Expander() |
4177 | 261 | def mark_selected(self, treeselection): | 241 | self.menu_search_hbox.pack_end(self.expander, False, False, 0) |
4124 | 262 | global ROW | ||
4125 | 263 | (model, pathlist) = treeselection.get_selected_rows() | ||
4126 | 264 | ROW = pathlist | ||
4127 | 265 | |||
4128 | 266 | |||
4129 | 267 | ## double click to run a command | ||
4130 | 268 | def treeview_clicked(widget, path, column): | ||
4131 | 269 | self.actions.run_command(self) | ||
4132 | 270 | |||
4133 | 271 | |||
4134 | 272 | ## press enter to run a command | ||
4135 | 273 | def treeview_button(widget, event): | ||
4136 | 274 | keyname = gtk.gdk.keyval_name(event.keyval).upper() | ||
4137 | 275 | dbg('Key %s pressed'%keyname) | ||
4138 | 276 | if event.type == gtk.gdk.KEY_PRESS: | ||
4139 | 277 | if keyname == 'RETURN': | ||
4140 | 278 | self.actions.run_command(self) | ||
4141 | 279 | |||
4142 | 280 | |||
4143 | 281 | |||
4144 | 282 | selection = self.treeview.get_selection() | ||
4145 | 283 | #selection.set_mode(gtk.SELECTION_SINGLE) | ||
4146 | 284 | ## open with top command selected | ||
4147 | 285 | selection.select_path(0) | ||
4148 | 286 | selection.connect("changed", mark_selected, selection) | ||
4149 | 287 | ## double-click | ||
4150 | 288 | self.treeview.connect("row-activated", treeview_clicked) | ||
4151 | 289 | #press enter to run command | ||
4152 | 290 | self.treeview.connect("key-press-event", treeview_button) | ||
4153 | 291 | |||
4154 | 292 | global menu_search_hbox | ||
4155 | 293 | |||
4156 | 294 | ## The search section | ||
4157 | 295 | search_label = gtk.Label(_("Search:")) | ||
4158 | 296 | search_label.set_alignment(xalign=-1, yalign=0) | ||
4159 | 297 | self.search_box = gtk.Entry() | ||
4160 | 298 | self.search_box.connect("changed", self.actions._filter_commands, self.liststore, self.treeview) | ||
4161 | 299 | ## search box tooltip | ||
4162 | 300 | self.search_box.set_tooltip_text(_("Search your list of commands")) | ||
4163 | 301 | ## Set the search box sensitive OFF at program start, because | ||
4164 | 302 | ## expander is not unfolded by default | ||
4165 | 303 | self.search_box.set_sensitive(False) | ||
4166 | 304 | ## hbox for menu and search Entry | ||
4167 | 305 | menu_search_hbox = gtk.HBox(False) | ||
4168 | 306 | menu_search_hbox.pack_end(self.search_box, True) | ||
4169 | 307 | menu_search_hbox.pack_end(search_label, False, False, 10) | ||
4170 | 308 | menu_search_hbox.pack_start(menu_bar, True) | ||
4171 | 309 | |||
4178 | 310 | ## expander title | 242 | ## expander title |
4179 | 311 | expander_hbox = gtk.HBox() | 243 | expander_hbox = gtk.HBox() |
4180 | 312 | image = gtk.Image() | 244 | image = gtk.Image() |
4181 | @@ -314,70 +246,286 @@ | |||
4182 | 314 | label = gtk.Label(_('Command List')) | 246 | label = gtk.Label(_('Command List')) |
4183 | 315 | ## tooltip for the label of the expander | 247 | ## tooltip for the label of the expander |
4184 | 316 | expander_hbox.set_tooltip_text(_("Click to show/hide command list")) | 248 | expander_hbox.set_tooltip_text(_("Click to show/hide command list")) |
4185 | 317 | |||
4186 | 318 | ## add expander widget to hbox | 249 | ## add expander widget to hbox |
4187 | 319 | expander_hbox.pack_start(image, False, False) | 250 | expander_hbox.pack_start(image, False, False) |
4188 | 320 | expander_hbox.pack_start(label, True, False) | 251 | expander_hbox.pack_start(label, True, False) |
4189 | 321 | self.expander.set_label_widget(expander_hbox) | 252 | self.expander.set_label_widget(expander_hbox) |
4190 | 253 | self.expander.set_expanded(True) | ||
4191 | 254 | |||
4192 | 255 | #### The search box | ||
4193 | 256 | self.search_hbox = gtk.HBox() | ||
4194 | 257 | #### the search label | ||
4195 | 258 | search_label = gtk.Label(_("Search:")) | ||
4196 | 259 | search_label.set_alignment(xalign=-1, yalign=0) | ||
4197 | 260 | self.search_box = gtk.Entry() | ||
4198 | 261 | self.search_box.connect("changed", | ||
4199 | 262 | lambda wg, *x: self.cmd_notebook.filter(wg.get_text())) | ||
4200 | 263 | ## search box tooltip | ||
4201 | 264 | self.search_box.set_tooltip_text(_("Search your list of commands")) | ||
4202 | 265 | self.search_hbox.pack_start(search_label, False, False, 8) | ||
4203 | 266 | self.search_hbox.pack_end(self.search_box, True) | ||
4204 | 267 | self.menu_search_hbox.pack_end(self.search_hbox, True) | ||
4205 | 268 | |||
4206 | 269 | ############################ | ||
4207 | 270 | ## and now the terminals notebook | ||
4208 | 271 | self.term_notebook.set_show_tabs(0) | ||
4209 | 272 | ##attach the style to the widget | ||
4210 | 273 | self.term_notebook.set_name("tab-close-button") | ||
4211 | 322 | 274 | ||
4212 | 323 | ## Add the first tab with the Terminal | 275 | ## Add the first tab with the Terminal |
4215 | 324 | self.tabs.add_tab(self.notebook) | 276 | self.term_notebook.add_tab() |
4214 | 325 | self.notebook.set_tab_pos(2) | ||
4216 | 326 | 277 | ||
4217 | 327 | ## The "Add Tab" tab | ||
4218 | 328 | add_tab_button = gtk.Button("+") | ||
4219 | 329 | ## tooltip for "Add Tab" tab | ||
4220 | 330 | add_tab_button.set_tooltip_text(_("Click to add another tab")) | ||
4221 | 331 | ## create first tab | ||
4222 | 332 | self.notebook.append_page(gtk.Label(""), add_tab_button) | ||
4223 | 333 | |||
4224 | 334 | global button_box | ||
4225 | 335 | ## buttons at bottom of main window [menus_buttons.py] | 278 | ## buttons at bottom of main window [menus_buttons.py] |
4227 | 336 | button_box = bar.buttons(self, 10, gtk.BUTTONBOX_END) | 279 | self.button_box = cc_menus_buttons.Buttons(10, gtk.BUTTONBOX_END) |
4228 | 337 | 280 | ||
4229 | 281 | ## pack everything | ||
4230 | 338 | ## vbox for search, notebook, buttonbar | 282 | ## vbox for search, notebook, buttonbar |
4231 | 339 | vbox = gtk.VBox() | ||
4232 | 340 | self.window.add(vbox) | ||
4233 | 341 | ## pack everytyhing in the vbox | 283 | ## pack everytyhing in the vbox |
4240 | 342 | #self.vbox.pack_start(menu_bar, False, False, 0) ##menuBar | 284 | h_vbox = gtk.VBox() |
4241 | 343 | vbox.pack_start(menu_search_hbox, False, False, 5) | 285 | h_vbox.pack_start(self.menu_search_hbox, False, False, 5) |
4242 | 344 | vbox.pack_start(self.expander, False, False, 5) | 286 | h_vbox.pack_start(self.cmd_notebook, True, True, 5) |
4243 | 345 | vbox.pack_start(self.notebook, True, True, 5) | 287 | self.l_vbox = gtk.VBox() |
4244 | 346 | vbox.pack_start(button_box, False, False, 5) | 288 | self.l_vbox.pack_start(self.term_notebook, True, True, 0) |
4245 | 347 | 289 | self.l_vbox.pack_start(self.button_box, False, False, 0) | |
4246 | 290 | |||
4247 | 291 | ## Pack it in a vpant bo allow the user to resize | ||
4248 | 292 | self.vpane = gtk.VPaned() | ||
4249 | 293 | self.vpane.pack1(h_vbox, True, False) | ||
4250 | 294 | self.vpane.pack2(self.l_vbox, True, True) | ||
4251 | 295 | self.add(self.vpane) | ||
4252 | 296 | |||
4253 | 348 | ## signals | 297 | ## signals |
4258 | 349 | self.expander.connect('notify::expanded', self.expanded_cb, self.window, self.search_box) | 298 | self.cmd_notebook.connect('run_command', |
4259 | 350 | self.window.connect("delete_event", self.delete_event) | 299 | lambda wdg, *args: self.term_notebook.run_command(*args)) |
4260 | 351 | self.window.connect("key-press-event", self.key_clicked) | 300 | self.cmd_notebook.connect('show_man', |
4261 | 352 | add_tab_button.connect("clicked", lambda *x: self.tabs.add_tab(self.notebook)) | 301 | lambda wgt, cmd: cc_helpers.ManPage(cmd).run()) |
4262 | 302 | self.cmd_notebook.connect('quit', lambda *x: gtk.main_quit()) | ||
4263 | 303 | self.term_notebook.connect('quit', lambda *x: gtk.main_quit()) | ||
4264 | 304 | self.term_notebook.connect('preferences', lambda *x: self.edit_pref()) | ||
4265 | 305 | self.expander.connect('notify::expanded', | ||
4266 | 306 | lambda *x: self.expanded_cb()) | ||
4267 | 307 | self.connect("delete_event", self.delete_event) | ||
4268 | 308 | self.connect("key-press-event", self.key_clicked) | ||
4269 | 309 | menu_bar.connect('quit', lambda *x: gtk.main_quit()) | ||
4270 | 310 | menu_bar.connect('run_command', | ||
4271 | 311 | lambda *x: self.cmd_notebook.run_command()) | ||
4272 | 312 | menu_bar.connect('add_command', | ||
4273 | 313 | lambda *x: self.cmd_notebook.add_command()) | ||
4274 | 314 | menu_bar.connect('edit_command', | ||
4275 | 315 | lambda *x: self.cmd_notebook.edit_command()) | ||
4276 | 316 | menu_bar.connect('remove_command', | ||
4277 | 317 | lambda *x: self.cmd_notebook.remove_command()) | ||
4278 | 318 | menu_bar.connect('preferences', lambda *x: self.edit_pref()) | ||
4279 | 319 | menu_bar.connect('add_tab', lambda *x: self.term_notebook.add_tab()) | ||
4280 | 320 | menu_bar.connect('close_tab', lambda *x: self.term_notebook.quit_tab()) | ||
4281 | 321 | self.button_box.connect('quit', lambda *x: gtk.main_quit()) | ||
4282 | 322 | self.button_box.connect('run_command', | ||
4283 | 323 | lambda *x: self.cmd_notebook.run_command()) | ||
4284 | 324 | self.button_box.connect('add_command', | ||
4285 | 325 | lambda *x: self.cmd_notebook.add_command()) | ||
4286 | 326 | self.button_box.connect('edit_command', | ||
4287 | 327 | lambda *x: self.cmd_notebook.edit_command()) | ||
4288 | 328 | self.button_box.connect('remove_command', | ||
4289 | 329 | lambda *x: self.cmd_notebook.remove_command()) | ||
4290 | 330 | self.button_box.connect('show_man', | ||
4291 | 331 | lambda *x: cc_helpers.ManPage( | ||
4292 | 332 | self.cmd_notebook.get_command()[0]).run()) | ||
4293 | 333 | self.button_box.connect('add_tab', | ||
4294 | 334 | lambda *x: self.term_notebook.add_tab()) | ||
4295 | 353 | ## right click menu event capture | 335 | ## right click menu event capture |
4296 | 354 | self.treeview.connect("button_press_event", bar.right_click, self) | ||
4297 | 355 | |||
4298 | 356 | # Allow enable drag and drop of rows including row move | 336 | # Allow enable drag and drop of rows including row move |
4315 | 357 | self.treeview.enable_model_drag_source( gtk.gdk.BUTTON1_MASK, | 337 | # self.treeview.enable_model_drag_source( gtk.gdk.BUTTON1_MASK, |
4316 | 358 | TARGETS, | 338 | # TARGETS, |
4317 | 359 | gtk.gdk.ACTION_DEFAULT | | 339 | # gtk.gdk.ACTION_DEFAULT | |
4318 | 360 | gtk.gdk.ACTION_COPY) | 340 | # gtk.gdk.ACTION_COPY) |
4319 | 361 | self.treeview.enable_model_drag_dest(TARGETS, | 341 | # self.treeview.enable_model_drag_dest(TARGETS, |
4320 | 362 | gtk.gdk.ACTION_DEFAULT) | 342 | # gtk.gdk.ACTION_DEFAULT) |
4321 | 363 | 343 | # | |
4322 | 364 | self.treeview.connect ("drag_data_get", self.drag_data_get_event) | 344 | # self.treeview.connect ("drag_data_get", self.drag_data_get_event) |
4323 | 365 | self.treeview.connect ("drag_data_received", self.drag_data_received_event) | 345 | # self.treeview.connect ("drag_data_received", |
4324 | 366 | self.treeview.connect("drag_drop", self.on_drag_drop ) | 346 | # self.drag_data_received_event) |
4325 | 367 | 347 | # self.treeview.connect("drag_drop", self.on_drag_drop ) | |
4326 | 368 | 348 | ||
4327 | 369 | #self.vte.grab_focus() | 349 | ## show everything |
4328 | 370 | self.window.show_all() | 350 | self.show_all() |
4329 | 371 | return | 351 | ## set the focus on the terminal |
4330 | 372 | 352 | self.term_notebook.focus() | |
4331 | 353 | return | ||
4332 | 354 | |||
4333 | 355 | def load_plugins(self): | ||
4334 | 356 | self.pluginloader = cc_plugins.PluginLoader() | ||
4335 | 357 | self.reload_plugins() | ||
4336 | 358 | |||
4337 | 359 | def reload_plugins(self): | ||
4338 | 360 | (head, _tail) = os.path.split(cc_plugins.__file__) | ||
4339 | 361 | pluginspath = os.path.join(head.rsplit(os.sep, 1)[0], 'plugins') | ||
4340 | 362 | allowed = [plg.strip() for plg in self.config.get('general::default', | ||
4341 | 363 | 'plugins').split(',')] | ||
4342 | 364 | dbg('Allowed plugins: %s' % allowed.__repr__()) | ||
4343 | 365 | self.pluginloader.load(pluginspath, allowed) | ||
4344 | 366 | |||
4345 | 367 | def expanded_cb(self): | ||
4346 | 368 | #liststore in a scrolled window in an expander | ||
4347 | 369 | if self.expander.get_expanded(): | ||
4348 | 370 | self.search_hbox.show_all() | ||
4349 | 371 | self.cmd_notebook.show_all() | ||
4350 | 372 | self.cmd_notebook.set_current_page(self.currentpage) | ||
4351 | 373 | self.button_box.show_all() | ||
4352 | 374 | self.vpane.set_position(self.currentpos) | ||
4353 | 375 | else: | ||
4354 | 376 | self.currentpage = self.cmd_notebook.get_current_page() | ||
4355 | 377 | self.currentpos = self.vpane.get_position() | ||
4356 | 378 | self.cmd_notebook.hide_all() | ||
4357 | 379 | self.search_hbox.hide_all() | ||
4358 | 380 | self.button_box.hide_all() | ||
4359 | 381 | self.vpane.set_position(0) | ||
4360 | 382 | return | ||
4361 | 383 | |||
4362 | 384 | def init_config(self): | ||
4363 | 385 | ## Set the netbookmode if needed | ||
4364 | 386 | screen = gtk.gdk.display_get_default().get_default_screen() | ||
4365 | 387 | height = screen.get_height() | ||
4366 | 388 | if height < 750: | ||
4367 | 389 | self.cmd_notebook.set_netbook(True) | ||
4368 | 390 | self.set_default_size(700, 500) | ||
4369 | 391 | else: | ||
4370 | 392 | self.set_default_size(700, 625) | ||
4371 | 393 | self.set_border_width(5) | ||
4372 | 394 | ## Sets the position of the window relative to the screen | ||
4373 | 395 | self.set_position(gtk.WIN_POS_CENTER_ALWAYS) | ||
4374 | 396 | ## Allow user to resize window | ||
4375 | 397 | self.set_resizable(True) | ||
4376 | 398 | |||
4377 | 399 | ## set Window title and icon | ||
4378 | 400 | self.set_title("CLI Companion") | ||
4379 | 401 | icon = gtk.gdk.pixbuf_new_from_file( | ||
4380 | 402 | "/usr/share/pixmaps/clicompanion.16.png") | ||
4381 | 403 | self.set_icon(icon) | ||
4382 | 404 | |||
4383 | 405 | ##For now TERM is hardcoded to xterm because of a change | ||
4384 | 406 | ##in libvte in Ubuntu Maverick | ||
4385 | 407 | os.putenv('TERM', 'xterm') | ||
4386 | 408 | |||
4387 | 409 | ## style to reduce padding around tabs | ||
4388 | 410 | ## TODO: Find a better place for this? | ||
4389 | 411 | gtk.rc_parse_string("style \"tab-close-button-style\"\n" | ||
4390 | 412 | "{\n" | ||
4391 | 413 | "GtkWidget::focus-padding = 0\n" | ||
4392 | 414 | "GtkWidget::focus-line-width = 0\n" | ||
4393 | 415 | "xthickness = 0\n" | ||
4394 | 416 | "ythickness = 0\n" | ||
4395 | 417 | "}\n" | ||
4396 | 418 | "widget \"*.tab-close-button\" style \"tab-close-button-style\"") | ||
4397 | 419 | |||
4398 | 420 | def edit_pref(self): | ||
4399 | 421 | pref_win = cc_pref.PreferencesWindow( | ||
4400 | 422 | self.config.get_config_copy(), self.pluginloader) | ||
4401 | 423 | newconfig, changed_plugins = pref_win.run() | ||
4402 | 424 | if newconfig: | ||
4403 | 425 | self.config = newconfig | ||
4404 | 426 | self.config.save() | ||
4405 | 427 | dbg(', '.join([self.config.get('profile::default', opt) | ||
4406 | 428 | for opt in self.config.options('profile::default')])) | ||
4407 | 429 | self.reload_plugins() | ||
4408 | 430 | self.term_notebook.update_all_term_config(self.config) | ||
4409 | 431 | self.cmd_notebook.update(self.config, changed_plugins) | ||
4410 | 432 | |||
4411 | 433 | def delete_event(self, widget, data=None): | ||
4412 | 434 | # close the window and quit | ||
4413 | 435 | gtk.main_quit() | ||
4414 | 436 | return False | ||
4415 | 437 | |||
4416 | 438 | def key_pressed(self, event): | ||
4417 | 439 | keyname = cc_utils.get_keycomb(event) | ||
4418 | 440 | if keyname in cc_pref.KEY_BINDINGS.keys(): | ||
4419 | 441 | key, func = cc_pref.KEY_BINDINGS[keyname] | ||
4420 | 442 | |||
4421 | 443 | def key_clicked(self, widget, event): | ||
4422 | 444 | if cc_utils.only_modifier(event): | ||
4423 | 445 | return | ||
4424 | 446 | keycomb = cc_utils.get_keycomb(event) | ||
4425 | 447 | for func in cc_config.KEY_BINDINGS.keys(): | ||
4426 | 448 | if keycomb == self.config.get('keybindings', func): | ||
4427 | 449 | getattr(self, func)() | ||
4428 | 450 | ## this is to stop sending the keypress to the widgets | ||
4429 | 451 | return True | ||
4430 | 452 | |||
4431 | 453 | def run_command(self): | ||
4432 | 454 | self.cmd_notebook.run_command() | ||
4433 | 455 | |||
4434 | 456 | def add_command(self): | ||
4435 | 457 | self.cmd_notebook.add_command() | ||
4436 | 458 | |||
4437 | 459 | def remove_command(self): | ||
4438 | 460 | self.cmd_notebook.remove_command() | ||
4439 | 461 | |||
4440 | 462 | def edit_command(self): | ||
4441 | 463 | self.cmd_notebook.edit_command() | ||
4442 | 464 | |||
4443 | 465 | def add_tab(self): | ||
4444 | 466 | self.term_notebook.add_tab() | ||
4445 | 467 | |||
4446 | 468 | def close_tab(self): | ||
4447 | 469 | self.term_notebook.quit_tab() | ||
4448 | 470 | |||
4449 | 471 | def toggle_hide_ui(self): | ||
4450 | 472 | if self.hiddenui: | ||
4451 | 473 | self.show_ui() | ||
4452 | 474 | else: | ||
4453 | 475 | self.hide_ui() | ||
4454 | 476 | |||
4455 | 477 | def hide_ui(self): | ||
4456 | 478 | if self.hiddenui: | ||
4457 | 479 | return | ||
4458 | 480 | dbg('Hide UI') | ||
4459 | 481 | self.l_vbox.remove(self.term_notebook) | ||
4460 | 482 | self.remove(self.vpane) | ||
4461 | 483 | self.add(self.term_notebook) | ||
4462 | 484 | self.hiddenui = True | ||
4463 | 485 | |||
4464 | 486 | def show_ui(self): | ||
4465 | 487 | if not self.hiddenui: | ||
4466 | 488 | return | ||
4467 | 489 | dbg('Show UI') | ||
4468 | 490 | self.remove(self.term_notebook) | ||
4469 | 491 | btns = self.l_vbox.get_children()[0] | ||
4470 | 492 | self.l_vbox.remove(btns) | ||
4471 | 493 | self.l_vbox.pack_start(self.term_notebook, True, True, 0) | ||
4472 | 494 | self.l_vbox.pack_start(btns, False, False, 0) | ||
4473 | 495 | self.add(self.vpane) | ||
4474 | 496 | self.hiddenui = False | ||
4475 | 497 | |||
4476 | 498 | def toggle_maximize(self): | ||
4477 | 499 | if not self.maximized: | ||
4478 | 500 | self.maximize() | ||
4479 | 501 | else: | ||
4480 | 502 | self.unmaximize() | ||
4481 | 503 | self.maximized = not self.maximized | ||
4482 | 504 | |||
4483 | 505 | def toggle_fullscreen(self): | ||
4484 | 506 | ''' | ||
4485 | 507 | Maximize and hide everything, or unmaximize and unhide if it was on | ||
4486 | 508 | fullscren mode | ||
4487 | 509 | ''' | ||
4488 | 510 | if not self.fullscr: | ||
4489 | 511 | self.hide_ui() | ||
4490 | 512 | self.fullscreen() | ||
4491 | 513 | self.set_border_width(0) | ||
4492 | 514 | else: | ||
4493 | 515 | self.show_ui() | ||
4494 | 516 | self.unfullscreen() | ||
4495 | 517 | self.set_border_width(5) | ||
4496 | 518 | self.fullscr = not self.fullscr | ||
4497 | 519 | |||
4498 | 520 | ### TODO: pass these functions to the LocalCommandList class | ||
4499 | 373 | def on_drag_drop(self, treeview, *x): | 521 | def on_drag_drop(self, treeview, *x): |
4500 | 374 | ''' | 522 | ''' |
4501 | 375 | Stop the signal when in search mode | 523 | Stop the signal when in search mode |
4502 | 376 | ''' | 524 | ''' |
4504 | 377 | if FILTER: | 525 | if self.FILTER: |
4505 | 378 | treeview.stop_emission('drag_drop') | 526 | treeview.stop_emission('drag_drop') |
4506 | 379 | 527 | ||
4508 | 380 | def drag_data_get_event(self, treeview, context, selection, target_id, | 528 | def drag_data_get_event(self, treeview, context, selection, target_id, |
4509 | 381 | etime): | 529 | etime): |
4510 | 382 | """ | 530 | """ |
4511 | 383 | Executed on dragging | 531 | Executed on dragging |
4512 | @@ -387,20 +535,19 @@ | |||
4513 | 387 | data = model.get(iter, 0, 1, 2) | 535 | data = model.get(iter, 0, 1, 2) |
4514 | 388 | selection.set(selection.target, 8, '\t'.join(data)) | 536 | selection.set(selection.target, 8, '\t'.join(data)) |
4515 | 389 | 537 | ||
4518 | 390 | def drag_data_received_event(self, treeview, context, x, y, selection, info, | 538 | def drag_data_received_event(self, treeview, context, x, y, selection, |
4519 | 391 | etime): | 539 | info, etime): |
4520 | 392 | """ | 540 | """ |
4521 | 393 | Executed when dropping. | 541 | Executed when dropping. |
4522 | 394 | """ | 542 | """ |
4523 | 395 | global CMNDS | 543 | global CMNDS |
4524 | 396 | global FILTER | ||
4525 | 397 | ## if we are in a search, do nothing | 544 | ## if we are in a search, do nothing |
4527 | 398 | if FILTER == 1: | 545 | if self.FILTER == 1: |
4528 | 399 | return | 546 | return |
4529 | 400 | model = treeview.get_model() | 547 | model = treeview.get_model() |
4530 | 401 | ## get the destination | 548 | ## get the destination |
4531 | 402 | drop_info = treeview.get_dest_row_at_pos(x, y) | 549 | drop_info = treeview.get_dest_row_at_pos(x, y) |
4533 | 403 | if drop_info: | 550 | if drop_info: |
4534 | 404 | path, position = drop_info | 551 | path, position = drop_info |
4535 | 405 | iter = model.get_iter(path) | 552 | iter = model.get_iter(path) |
4536 | 406 | dest = list(model.get(iter, 0, 1, 2)) | 553 | dest = list(model.get(iter, 0, 1, 2)) |
4537 | @@ -408,21 +555,23 @@ | |||
4538 | 408 | ## parse all the incoming commands | 555 | ## parse all the incoming commands |
4539 | 409 | for data in selection.data.split('\n'): | 556 | for data in selection.data.split('\n'): |
4540 | 410 | # if we got an empty line skip it | 557 | # if we got an empty line skip it |
4542 | 411 | if not data.replace('\r',''): continue | 558 | if not data.replace('\r', ''): |
4543 | 559 | continue | ||
4544 | 412 | # format the incoming string | 560 | # format the incoming string |
4547 | 413 | orig = data.replace('\r','').split('\t',2) | 561 | orig = data.replace('\r', '').split('\t', 2) |
4548 | 414 | orig = [ fld.strip() for fld in orig ] | 562 | orig = [fld.strip() for fld in orig] |
4549 | 415 | # fill the empty fields | 563 | # fill the empty fields |
4552 | 416 | if len(orig) < 3: orig = orig + ('',)*(3-len(orig)) | 564 | if len(orig) < 3: |
4553 | 417 | dbg('Got drop of command %s'%'_\t_'.join(orig)) | 565 | orig = orig + ('', ) * (3 - len(orig)) |
4554 | 566 | dbg('Got drop of command %s' % '_\t_'.join(orig)) | ||
4555 | 418 | 567 | ||
4556 | 419 | if drop_info: | 568 | if drop_info: |
4557 | 420 | if (position == gtk.TREE_VIEW_DROP_BEFORE | 569 | if (position == gtk.TREE_VIEW_DROP_BEFORE |
4558 | 421 | or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE): | 570 | or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE): |
4560 | 422 | dbg('\t to before dest %s'%'_\t_'.join(dest)) | 571 | dbg('\t to before dest %s' % '_\t_'.join(dest)) |
4561 | 423 | CMNDS.drag_n_drop(orig, dest, before=True) | 572 | CMNDS.drag_n_drop(orig, dest, before=True) |
4562 | 424 | else: | 573 | else: |
4564 | 425 | dbg('\t to after dest %s'%'_\t_'.join(dest)) | 574 | dbg('\t to after dest %s' % '_\t_'.join(dest)) |
4565 | 426 | CMNDS.drag_n_drop(orig, dest, before=False) | 575 | CMNDS.drag_n_drop(orig, dest, before=False) |
4566 | 427 | else: | 576 | else: |
4567 | 428 | dbg('\t to the end') | 577 | dbg('\t to the end') |
4568 | @@ -431,19 +580,21 @@ | |||
4569 | 431 | context.finish(True, True, etime) | 580 | context.finish(True, True, etime) |
4570 | 432 | self.sync_cmnds() | 581 | self.sync_cmnds() |
4571 | 433 | CMNDS.save() | 582 | CMNDS.save() |
4573 | 434 | 583 | ||
4574 | 435 | def main(self): | 584 | def main(self): |
4575 | 436 | try: | 585 | try: |
4576 | 437 | gtk.main() | 586 | gtk.main() |
4577 | 438 | except KeyboardInterrupt: | 587 | except KeyboardInterrupt: |
4578 | 439 | pass | 588 | pass |
4588 | 440 | 589 | ||
4589 | 441 | def run( options=None ): | 590 | |
4590 | 442 | ##create the config file | 591 | def run(options=None): |
4591 | 443 | config = cc_config.create_config() | 592 | if options and options.conffile: |
4592 | 444 | if config.get('terminal','debug') == 'True': | 593 | config = cc_config.CLIConfig(conffile) |
4593 | 445 | utils.DEBUG = True | 594 | else: |
4594 | 446 | CMNDS.load(options and options.cheatsheet or None) | 595 | config = cc_config.CLIConfig() |
4595 | 447 | dbg('Loaded commands %s'%CMNDS) | 596 | if config.get('general::default', 'debug') == 'True' or options.debug: |
4596 | 448 | main_window = MainWindow() | 597 | cc_utils.DEBUG = True |
4597 | 598 | main_window = MainWindow( | ||
4598 | 599 | config=config) | ||
4599 | 449 | main_window.main() | 600 | main_window.main() |
4600 | 450 | 601 | ||
4601 | === added directory 'plugins' | |||
4602 | === added file 'plugins/CommandLineFU.py' | |||
4603 | --- plugins/CommandLineFU.py 1970-01-01 00:00:00 +0000 | |||
4604 | +++ plugins/CommandLineFU.py 2012-01-08 10:37:24 +0000 | |||
4605 | @@ -0,0 +1,282 @@ | |||
4606 | 1 | #!/usr/bin/env python | ||
4607 | 2 | # -*- coding: utf-8 -*- | ||
4608 | 3 | # | ||
4609 | 4 | # CommandLineFu.py - CommandlineFU commands plugin for CLI Comapnion | ||
4610 | 5 | # | ||
4611 | 6 | # Copyright 2012 David Caro <david.caro.estevez@gmail.com> | ||
4612 | 7 | # | ||
4613 | 8 | # This program is free software: you can redistribute it and/or modify it | ||
4614 | 9 | # under the terms of the GNU General Public License version 3, as published | ||
4615 | 10 | # by the Free Software Foundation. | ||
4616 | 11 | # | ||
4617 | 12 | # This program is distributed in the hope that it will be useful, but | ||
4618 | 13 | # WITHOUT ANY WARRANTY; without even the implied warranties of | ||
4619 | 14 | # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
4620 | 15 | # PURPOSE. See the GNU General Public License for more details. | ||
4621 | 16 | # | ||
4622 | 17 | # You should have received a copy of the GNU General Public License along | ||
4623 | 18 | # with this program. If not, see <http://www.gnu.org/licenses/>. | ||
4624 | 19 | # | ||
4625 | 20 | |||
4626 | 21 | |||
4627 | 22 | import os | ||
4628 | 23 | import pygtk | ||
4629 | 24 | pygtk.require('2.0') | ||
4630 | 25 | import gobject | ||
4631 | 26 | import webbrowser | ||
4632 | 27 | |||
4633 | 28 | try: | ||
4634 | 29 | import gtk | ||
4635 | 30 | except: | ||
4636 | 31 | ## do not use gtk, just print | ||
4637 | 32 | print _("You need to install the python gtk bindings package" | ||
4638 | 33 | "'python-gtk2'") | ||
4639 | 34 | sys.exit(1) | ||
4640 | 35 | |||
4641 | 36 | from clicompanionlib.utils import dbg | ||
4642 | 37 | import clfu as cc_clf | ||
4643 | 38 | import clicompanionlib.plugins as plugins | ||
4644 | 39 | |||
4645 | 40 | |||
4646 | 41 | class CommandLineFU(plugins.TabPlugin): | ||
4647 | 42 | ''' | ||
4648 | 43 | Tab with all the commandlinefu commands and search options | ||
4649 | 44 | ''' | ||
4650 | 45 | __authors__ = 'David Caro <david.caro.estevez@gmail.com>' | ||
4651 | 46 | __info__ = ('This plugin crates a tab on the commands list that allows you' | ||
4652 | 47 | ' to search commands on the CommandlineFU website ' | ||
4653 | 48 | '(www.commandlinefu.com).') | ||
4654 | 49 | __title__ = 'CommandlineFU Commands' | ||
4655 | 50 | |||
4656 | 51 | def __init__(self, config): | ||
4657 | 52 | plugins.TabPlugin.__init__(self) | ||
4658 | 53 | self.config = config | ||
4659 | 54 | self.clfu = cc_clf.CLFu() | ||
4660 | 55 | self.pages = [] | ||
4661 | 56 | ## las_search will store the params of the last search | ||
4662 | 57 | self.lastsearch = [] | ||
4663 | 58 | |||
4664 | 59 | ## box for commands tags and timerange comboboxes, and refresh | ||
4665 | 60 | hbox = gtk.HBox() | ||
4666 | 61 | self.pack_start(hbox, False, False, 0) | ||
4667 | 62 | |||
4668 | 63 | ## combobox for selecting command tag | ||
4669 | 64 | self.tags_box = gtk.combo_box_new_text() | ||
4670 | 65 | self.tags_box.append_text('Update') | ||
4671 | 66 | self.tags_box.append_text('None') | ||
4672 | 67 | self.tags_box.set_active(1) | ||
4673 | 68 | self.tags_box.connect('changed', lambda *x: self.populate_tags()) | ||
4674 | 69 | hbox.pack_start(gtk.Label(_('Tag:')), False, False, 0) | ||
4675 | 70 | hbox.pack_start(self.tags_box, False, False, 0) | ||
4676 | 71 | |||
4677 | 72 | ## time range combobox | ||
4678 | 73 | self.trange_box = gtk.combo_box_new_text() | ||
4679 | 74 | ## populate time ranges, no http call needed | ||
4680 | 75 | for timerange in self.clfu.get_timeranges(): | ||
4681 | 76 | self.trange_box.append_text(timerange) | ||
4682 | 77 | self.trange_box.set_active(0) | ||
4683 | 78 | hbox.pack_start(gtk.Label(_('From:')), False, False, 0) | ||
4684 | 79 | hbox.pack_start(self.trange_box, False, False, 0) | ||
4685 | 80 | |||
4686 | 81 | ## refresh button | ||
4687 | 82 | refresh_btn = gtk.Button("Search") | ||
4688 | 83 | refresh_btn.connect("clicked", lambda *x: self.populate()) | ||
4689 | 84 | hbox.pack_start(refresh_btn, False, False, 0) | ||
4690 | 85 | |||
4691 | 86 | ## add the commands list | ||
4692 | 87 | sw = gtk.ScrolledWindow() | ||
4693 | 88 | self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, gtk.gdk.Pixbuf, | ||
4694 | 89 | str, str, int, str) | ||
4695 | 90 | self.treeview = gtk.TreeView(gtk.TreeModelSort(self.liststore)) | ||
4696 | 91 | sw.add(self.treeview) | ||
4697 | 92 | #self.treeview.set_reorderable(True) | ||
4698 | 93 | self.treeview.connect('row-activated', self.clicked) | ||
4699 | 94 | self.treeview.connect("button_press_event", self.right_click) | ||
4700 | 95 | self.init_cols() | ||
4701 | 96 | self.pack_start(sw, True, True, 0) | ||
4702 | 97 | |||
4703 | 98 | ## Get more button | ||
4704 | 99 | self.more_btn = gtk.Button("Get more!") | ||
4705 | 100 | self.more_btn.connect("clicked", | ||
4706 | 101 | lambda *x: self.populate(next_page=True)) | ||
4707 | 102 | hbox.pack_start(self.more_btn, False, False, 0) | ||
4708 | 103 | self.more_btn.set_sensitive(False) | ||
4709 | 104 | |||
4710 | 105 | ## Show everything | ||
4711 | 106 | self.show_all() | ||
4712 | 107 | |||
4713 | 108 | def add_pixbuf_col(self, colname, n=0): | ||
4714 | 109 | col = gtk.TreeViewColumn() | ||
4715 | 110 | col.set_title(colname) | ||
4716 | 111 | render_pixbuf = gtk.CellRendererPixbuf() | ||
4717 | 112 | col.pack_start(render_pixbuf, expand=True) | ||
4718 | 113 | col.add_attribute(render_pixbuf, 'pixbuf', n) | ||
4719 | 114 | col.set_resizable(True) | ||
4720 | 115 | self.treeview.append_column(col) | ||
4721 | 116 | |||
4722 | 117 | def add_text_col(self, colname, n=0): | ||
4723 | 118 | col = gtk.TreeViewColumn() | ||
4724 | 119 | col.set_title(_(colname)) | ||
4725 | 120 | render = gtk.CellRendererText() | ||
4726 | 121 | col.pack_start(render, expand=True) | ||
4727 | 122 | col.add_attribute(render, 'text', n) | ||
4728 | 123 | col.set_resizable(True) | ||
4729 | 124 | col.set_sort_column_id(n) | ||
4730 | 125 | self.treeview.append_column(col) | ||
4731 | 126 | |||
4732 | 127 | def init_cols(self): | ||
4733 | 128 | self.add_pixbuf_col('', 0) | ||
4734 | 129 | self.add_pixbuf_col('', 1) | ||
4735 | 130 | self.add_text_col('Command', 2) | ||
4736 | 131 | self.add_text_col('Summary', 3) | ||
4737 | 132 | self.add_text_col('Votes', 4) | ||
4738 | 133 | |||
4739 | 134 | def populate_tags(self): | ||
4740 | 135 | dbg('Poulating clf tags') | ||
4741 | 136 | tag = self.tags_box.get_active_text() | ||
4742 | 137 | if tag == "Update": | ||
4743 | 138 | self.tags_box.set_model(gtk.ListStore(str)) | ||
4744 | 139 | self.tags_box.append_text('Update') | ||
4745 | 140 | self.tags_box.append_text('None') | ||
4746 | 141 | ## populate commands tags | ||
4747 | 142 | for tag, tagid in self.clfu.get_tags(): | ||
4748 | 143 | self.tags_box.append_text(tag) | ||
4749 | 144 | self.tags_box.set_active(1) | ||
4750 | 145 | |||
4751 | 146 | def populate(self, next_page=False, filter_str=''): | ||
4752 | 147 | ''' | ||
4753 | 148 | populate the widgets with the info from the commandlinefu website | ||
4754 | 149 | ''' | ||
4755 | 150 | ## get the filter params, the saved ones if it's a next page request or | ||
4756 | 151 | ## the new ones if it's anew search | ||
4757 | 152 | self.liststore.clear() | ||
4758 | 153 | if next_page: | ||
4759 | 154 | tag, timerange, filter_str = self.lastsearch | ||
4760 | 155 | page = len(self.pages) | ||
4761 | 156 | else: | ||
4762 | 157 | tag = self.tags_box.get_active_text() | ||
4763 | 158 | timerange = self.trange_box.get_active_text() | ||
4764 | 159 | self.lastsearch = tag, timerange, filter_str | ||
4765 | 160 | page = 0 | ||
4766 | 161 | self.pages = [] | ||
4767 | 162 | ## get new commands page | ||
4768 | 163 | if tag != 'None': | ||
4769 | 164 | commands = self.clfu.tag(tag=tag, timerange=timerange, page=page) | ||
4770 | 165 | elif filter_str != '': | ||
4771 | 166 | commands = self.clfu.search(filter_str, timerange=timerange, | ||
4772 | 167 | page=page) | ||
4773 | 168 | else: | ||
4774 | 169 | commands = self.clfu.browse(timerange=timerange, page=page) | ||
4775 | 170 | ## if there were no results, do avoid requesting more pages, show the | ||
4776 | 171 | ## user that there are no more pages | ||
4777 | 172 | self.more_btn.set_sensitive(True) | ||
4778 | 173 | if not commands or len(commands) < 25: | ||
4779 | 174 | self.more_btn.set_sensitive(False) | ||
4780 | 175 | ## append it to the revious pages if any | ||
4781 | 176 | self.pages.append(commands) | ||
4782 | 177 | ## filter and show all the commands | ||
4783 | 178 | for page in self.pages: | ||
4784 | 179 | for command in page: | ||
4785 | 180 | if filter_str != '': | ||
4786 | 181 | if filter_str not in command['command'] \ | ||
4787 | 182 | and filter_str not in command['summary']: | ||
4788 | 183 | continue | ||
4789 | 184 | add_btn = self.treeview.render_icon(stock_id=gtk.STOCK_ADD, | ||
4790 | 185 | size=gtk.ICON_SIZE_SMALL_TOOLBAR) | ||
4791 | 186 | link_btn = self.treeview.render_icon(stock_id=gtk.STOCK_INFO, | ||
4792 | 187 | size=gtk.ICON_SIZE_SMALL_TOOLBAR) | ||
4793 | 188 | self.liststore.append((add_btn, link_btn, | ||
4794 | 189 | command['command'], command['summary'], | ||
4795 | 190 | int(command['votes']), command['url'])) | ||
4796 | 191 | |||
4797 | 192 | def filter(self, search_term): | ||
4798 | 193 | """ | ||
4799 | 194 | Show commands matching a given search term. | ||
4800 | 195 | The user should enter a term in the search box and the treeview should | ||
4801 | 196 | only display the rows which contain the search term. | ||
4802 | 197 | No reordering allowed when filtering. | ||
4803 | 198 | """ | ||
4804 | 199 | ## If the search term is empty, and we change filtering state | ||
4805 | 200 | ## restore the liststore, else do nothing | ||
4806 | 201 | if search_term == "": | ||
4807 | 202 | if self.filtering: | ||
4808 | 203 | dbg("Uniltering...") | ||
4809 | 204 | self.filtering = False | ||
4810 | 205 | self.treeview.set_model(gtk.TreeModelSort(self.liststore)) | ||
4811 | 206 | return | ||
4812 | 207 | dbg("Filtering...") | ||
4813 | 208 | self.filtering = True | ||
4814 | 209 | modelfilter = self.liststore.filter_new() | ||
4815 | 210 | |||
4816 | 211 | def search(model, iter, search_term): | ||
4817 | 212 | cmd, desc = model.get(iter, 2, 3) | ||
4818 | 213 | if search_term in ('%s %s' % (cmd, desc)).lower(): | ||
4819 | 214 | return True | ||
4820 | 215 | modelfilter.set_visible_func(search, search_term) | ||
4821 | 216 | self.treeview.set_model(gtk.TreeModelSort(modelfilter)) | ||
4822 | 217 | |||
4823 | 218 | def clicked(self, treeview, path, column): | ||
4824 | 219 | treeselection = treeview.get_selection() | ||
4825 | 220 | model, iter = treeselection.get_selected() | ||
4826 | 221 | data = model.get(iter, 2, 3, 4, 5) | ||
4827 | 222 | if column == treeview.get_column(0): | ||
4828 | 223 | dbg('Adding command %s, %s, %s' % (data[0], '', data[1])) | ||
4829 | 224 | self.emit('add_command', data[0], '', data[1]) | ||
4830 | 225 | elif column == treeview.get_column(1): | ||
4831 | 226 | webbrowser.open(data[3]) | ||
4832 | 227 | else: | ||
4833 | 228 | self.emit('run_command', data[0], '', data[1]) | ||
4834 | 229 | |||
4835 | 230 | def get_command(self, withurl=False): | ||
4836 | 231 | treeselection = self.treeview.get_selection() | ||
4837 | 232 | model, iter = treeselection.get_selected() | ||
4838 | 233 | if not iter: | ||
4839 | 234 | return None, None, None | ||
4840 | 235 | cmd, desc, url = model.get(iter, 2, 3, 5) | ||
4841 | 236 | if not withurl: | ||
4842 | 237 | return cmd, '', desc | ||
4843 | 238 | return cmd, '', desc, url | ||
4844 | 239 | |||
4845 | 240 | |||
4846 | 241 | #right-click popup menu for the Liststore(command list) | ||
4847 | 242 | def right_click(self, widget, event): | ||
4848 | 243 | if event.button == 3: | ||
4849 | 244 | x = int(event.x) | ||
4850 | 245 | y = int(event.y) | ||
4851 | 246 | time = event.time | ||
4852 | 247 | row = self.treeview.get_path_at_pos(x, y) | ||
4853 | 248 | if row: | ||
4854 | 249 | path, col, x, y = row | ||
4855 | 250 | if row: | ||
4856 | 251 | popupMenu = gtk.Menu() | ||
4857 | 252 | self.treeview.grab_focus() | ||
4858 | 253 | self.treeview.set_cursor(path, col, 0) | ||
4859 | 254 | data = self.get_command(withurl=True) | ||
4860 | 255 | if data[0] == None: | ||
4861 | 256 | return | ||
4862 | 257 | # right-click popup menu Apply(run) | ||
4863 | 258 | menuPopup1 = gtk.ImageMenuItem(gtk.STOCK_APPLY) | ||
4864 | 259 | popupMenu.add(menuPopup1) | ||
4865 | 260 | menuPopup1.connect("activate", | ||
4866 | 261 | lambda *x: self.emit('run_command', *data[:-1])) | ||
4867 | 262 | # right-click popup menu AddToLocal | ||
4868 | 263 | menuPopup2 = gtk.ImageMenuItem(gtk.STOCK_ADD) | ||
4869 | 264 | popupMenu.add(menuPopup2) | ||
4870 | 265 | menuPopup2.connect("activate", | ||
4871 | 266 | lambda *x: self.emit('add_command', *data[:-1])) | ||
4872 | 267 | # right-click popup menu Help | ||
4873 | 268 | menuPopup4 = gtk.ImageMenuItem(gtk.STOCK_HELP) | ||
4874 | 269 | popupMenu.add(menuPopup4) | ||
4875 | 270 | menuPopup4.connect("activate", | ||
4876 | 271 | lambda *x: self.emit('show_man', data[0])) | ||
4877 | 272 | # right-click popup menu Online Help | ||
4878 | 273 | menuPopup4 = gtk.ImageMenuItem(gtk.STOCK_INFO) | ||
4879 | 274 | box = menuPopup4.get_children()[0] | ||
4880 | 275 | box.set_label(_('Show online info')) | ||
4881 | 276 | popupMenu.add(menuPopup4) | ||
4882 | 277 | menuPopup4.connect("activate", | ||
4883 | 278 | lambda wg, url: webbrowser.open(url), data[3]) | ||
4884 | 279 | # Show popup menu | ||
4885 | 280 | popupMenu.show_all() | ||
4886 | 281 | popupMenu.popup(None, None, None, event.button, time) | ||
4887 | 282 | return True | ||
4888 | 0 | 283 | ||
4889 | === added file 'plugins/LocalCommandList.py' | |||
4890 | --- plugins/LocalCommandList.py 1970-01-01 00:00:00 +0000 | |||
4891 | +++ plugins/LocalCommandList.py 2012-01-08 10:37:24 +0000 | |||
4892 | @@ -0,0 +1,710 @@ | |||
4893 | 1 | #!/usr/bin/env python | ||
4894 | 2 | # -*- coding: utf-8 -*- | ||
4895 | 3 | # | ||
4896 | 4 | # LocalCommandList.py - Plugin for handling locally stored commands | ||
4897 | 5 | # | ||
4898 | 6 | # Copyright 2010 Duane Hinnen, Kenny Meyer, David Caro | ||
4899 | 7 | # <david.caro.estevez@gmail.com> | ||
4900 | 8 | # | ||
4901 | 9 | # This program is free software: you can redistribute it and/or modify it | ||
4902 | 10 | # under the terms of the GNU General Public License version 3, as published | ||
4903 | 11 | # by the Free Software Foundation. | ||
4904 | 12 | # | ||
4905 | 13 | # This program is distributed in the hope that it will be useful, but | ||
4906 | 14 | # WITHOUT ANY WARRANTY; without even the implied warranties of | ||
4907 | 15 | # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
4908 | 16 | # PURPOSE. See the GNU General Public License for more details. | ||
4909 | 17 | # | ||
4910 | 18 | # You should have received a copy of the GNU General Public License along | ||
4911 | 19 | # with this program. If not, see <http://www.gnu.org/licenses/>. | ||
4912 | 20 | # | ||
4913 | 21 | |||
4914 | 22 | |||
4915 | 23 | import os | ||
4916 | 24 | import pygtk | ||
4917 | 25 | pygtk.require('2.0') | ||
4918 | 26 | import gobject | ||
4919 | 27 | |||
4920 | 28 | try: | ||
4921 | 29 | import gtk | ||
4922 | 30 | except: | ||
4923 | 31 | ## do not use gtk, just print | ||
4924 | 32 | print _("You need to install the python gtk bindings package" | ||
4925 | 33 | "'python-gtk2'") | ||
4926 | 34 | sys.exit(1) | ||
4927 | 35 | |||
4928 | 36 | ## we should try to absolutely detach it from the clicompanion libs someday | ||
4929 | 37 | from clicompanionlib.utils import dbg | ||
4930 | 38 | import clicompanionlib.plugins as plugins | ||
4931 | 39 | |||
4932 | 40 | ## Targets for the Drag and Drop | ||
4933 | 41 | TARGETS = [ | ||
4934 | 42 | ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0), | ||
4935 | 43 | ('text/plain', 0, 1), | ||
4936 | 44 | ('TEXT', 0, 2), | ||
4937 | 45 | ('STRING', 0, 3), | ||
4938 | 46 | ] | ||
4939 | 47 | |||
4940 | 48 | |||
4941 | 49 | class LocalCommandList(plugins.TabPlugin): | ||
4942 | 50 | ''' | ||
4943 | 51 | Tab with the list of local stored commands, the ponly signals that should | ||
4944 | 52 | get emited (right now) are the run_command and show_man. The other can be | ||
4945 | 53 | processed inhouse | ||
4946 | 54 | ''' | ||
4947 | 55 | __authors__ = ('Duane Hinnen\n' | ||
4948 | 56 | 'Kenny Meyer\n' | ||
4949 | 57 | 'Marcos Vanettai\n' | ||
4950 | 58 | 'Marek Bardoński\n' | ||
4951 | 59 | 'David Caro <david.caro.estevez@gmail.com>\n') | ||
4952 | 60 | __info__ = ('This is the main plugin for the CLI Companion, the one that ' | ||
4953 | 61 | 'handles the locally stored commands.') | ||
4954 | 62 | __title__ = 'Local Commands' | ||
4955 | 63 | |||
4956 | 64 | def __init__(self, config): | ||
4957 | 65 | self.config = config | ||
4958 | 66 | plugins.TabPlugin.__init__(self) | ||
4959 | 67 | self.treeview = gtk.TreeView() | ||
4960 | 68 | self.filtering = False | ||
4961 | 69 | ## command, user input, description, and index in cmnds array | ||
4962 | 70 | self.liststore = gtk.ListStore(str, str, str, int) | ||
4963 | 71 | ## Load the given commands file | ||
4964 | 72 | self.cmnds = Cheatsheet( | ||
4965 | 73 | self.config.get('default', 'cheatsheet')) | ||
4966 | 74 | ## will hold the commands. Actually the first three columns | ||
4967 | 75 | ## note that this commands list will not change with searchers nor | ||
4968 | 76 | ## filters, instead, when adding a command to the liststore, we will | ||
4969 | 77 | ## add also the index of the command in the cmnds array as another | ||
4970 | 78 | ## field | ||
4971 | 79 | self.sync_cmnds(rld=True) | ||
4972 | 80 | self.treeview.set_model(DummySort(self.liststore)) | ||
4973 | 81 | |||
4974 | 82 | ### Only show the three firs columns | ||
4975 | 83 | ## create the TreeViewColumns to display the data | ||
4976 | 84 | self.add_text_col('Command', 0) | ||
4977 | 85 | self.add_text_col('User Input', 1) | ||
4978 | 86 | self.add_text_col('Description', 2) | ||
4979 | 87 | self.treeview.set_tooltip_column(2) | ||
4980 | 88 | self.treeview.set_reorderable(True) | ||
4981 | 89 | ## open with top command selected | ||
4982 | 90 | selection = self.treeview.get_selection() | ||
4983 | 91 | selection.select_path(0) | ||
4984 | 92 | selection.set_mode(gtk.SELECTION_SINGLE) | ||
4985 | 93 | ### double-click | ||
4986 | 94 | self.treeview.connect("row-activated", self.event_clicked) | ||
4987 | 95 | ##press enter to run command | ||
4988 | 96 | self.treeview.connect("key-press-event", | ||
4989 | 97 | lambda wg, event: self.event_key_pressed(event)) | ||
4990 | 98 | ## Right click event | ||
4991 | 99 | self.treeview.connect("button_press_event", self.right_click) | ||
4992 | 100 | # Allow enable drag and drop of rows including row move | ||
4993 | 101 | self.treeview.enable_model_drag_source( gtk.gdk.BUTTON1_MASK, | ||
4994 | 102 | TARGETS, | ||
4995 | 103 | gtk.gdk.ACTION_DEFAULT | | ||
4996 | 104 | gtk.gdk.ACTION_COPY) | ||
4997 | 105 | self.treeview.enable_model_drag_dest(TARGETS, | ||
4998 | 106 | gtk.gdk.ACTION_DEFAULT) | ||
4999 | 107 | |||
5000 | 108 | self.treeview.connect ("drag_data_get", self.drag_data_get_event) |
Also added the -d command line option to enable the debug mode (very handy).