Merge lp:~dcaro/clicompanion/fix-608608 into lp:clicompanion

Proposed by David Caro on 2012-01-08
Status: Merged
Approved by: Marek Bardoński on 2012-01-08
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
Reviewer Review Type Date Requested Status
Marek Bardoński 2012-01-08 Approve on 2012-01-08
David Caro Needs Information on 2012-01-08
Review via email: mp+87868@code.launchpad.net

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!

To post a comment you must log in.
David Caro (dcaro) wrote :

Also added the -d command line option to enable the debug mode (very handy).

lp:~dcaro/clicompanion/fix-608608 updated on 2012-01-08
99. By David Caro "<email address hidden>" on 2012-01-08

Missed to try to install from the setup.py file... fixed.

100. By David Caro "<email address hidden>" on 2012-01-08

Added new files to the manifest

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/lib/python2.7/dist-packages/clicompanionlib/view.py:586: GtkWarning: Failed to set text from markup due to error parsing markup ///- on application startup
2. Don't remeber exactly how to reproduce :

"""
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/clicompanionlib/view.py", line 332, in <lambda>
    self.cmd_notebook.get_command()[0]).run())
  File "/usr/local/lib/python2.7/dist-packages/clicompanionlib/helpers.py", line 40, in __init__
    choose_row_error()
NameError: global name 'choose_row_error' is not defined
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/clicompanionlib/view.py", line 332, in <lambda>
    self.cmd_notebook.get_command()[0]).run())
  File "/usr/local/lib/python2.7/dist-packages/clicompanionlib/helpers.py", line 40, in __init__
    choose_row_error()
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!

review: Needs Information
lp:~dcaro/clicompanion/fix-608608 updated on 2012-01-08
101. By David Caro "<email address hidden>" on 2012-01-08

fixed a problem with the man page when no command selected

102. By David Caro "<email address hidden>" on 2012-01-08

fixed a little bug with detecting if the current font is a valid one when parsing the config

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_get_default()
value = gconf_cli.get('/desktop/gnome/interface/monospace_font_name')
value.get_string()

----- Show available fonts -----------
import gtk
families = gtk.TextView().create_pango_context().list_families()
[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://launchpad.net/clicompanion')

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)

review: Needs Information
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!

review: Approve

Preview Diff

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

Subscribers

People subscribed via source and target branches