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

Proposed by David Caro
Status: Merged
Approved by: Marek Bardoński
Approved revision: 102
Merged at revision: 102
Proposed branch: lp:~dcaro/clicompanion/fix-608608
Merge into: lp:clicompanion
Prerequisite: lp:~dcaro/clicompanion/fix-910355
Diff against target: 5891 lines (+3850/-1637)
18 files modified
.bzrignore (+3/-0)
MANIFEST (+9/-1)
clicompanion (+4/-5)
clicompanionlib/__init__.py (+1/-1)
clicompanionlib/config.py (+314/-237)
clicompanionlib/controller.py (+0/-665)
clicompanionlib/helpers.py (+186/-0)
clicompanionlib/menus_buttons.py (+115/-132)
clicompanionlib/plugins.py (+207/-0)
clicompanionlib/preferences.py (+708/-0)
clicompanionlib/tabs.py (+375/-133)
clicompanionlib/utils.py (+181/-125)
clicompanionlib/view.py (+488/-337)
plugins/CommandLineFU.py (+282/-0)
plugins/LocalCommandList.py (+710/-0)
plugins/__init__.py (+23/-0)
plugins/clfu.py (+243/-0)
setup.py (+1/-1)
To merge this branch: bzr merge lp:~dcaro/clicompanion/fix-608608
Reviewer Review Type Date Requested Status
Marek Bardoński Approve
David Caro Needs Information
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.
Revision history for this message
David Caro (dcaro) wrote :

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

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

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

100. By David Caro "<email address hidden>"

Added new files to the manifest

Revision history for this message
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
101. By David Caro "<email address hidden>"

fixed a problem with the man page when no command selected

102. By David Caro "<email address hidden>"

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

Revision history for this message
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
Revision history for this message
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

Revision history for this message
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
=== modified file '.bzrignore'
--- .bzrignore 2010-08-22 00:14:56 +0000
+++ .bzrignore 2012-01-08 10:37:24 +0000
@@ -3,3 +3,6 @@
3.ropeproject/globalnames3.ropeproject/globalnames
4.ropeproject/history4.ropeproject/history
5.ropeproject/objectdb5.ropeproject/objectdb
6./tags
7./build
8*~
69
=== modified file 'MANIFEST'
--- MANIFEST 2011-11-18 08:58:05 +0000
+++ MANIFEST 2012-01-08 10:37:24 +0000
@@ -1,8 +1,11 @@
1setup.py1setup.py
2clicompanion2clicompanion
3clicompanionlib/__init__.py3clicompanionlib/__init__.py
4clicompanionlib/controller.py4clicompanionlib/config.py
5clicompanionlib/helpers.py
5clicompanionlib/menus_buttons.py6clicompanionlib/menus_buttons.py
7clicompanionlib/plugins.py
8clicompanionlib/preferences.py
6clicompanionlib/tabs.py9clicompanionlib/tabs.py
7clicompanionlib/utils.py10clicompanionlib/utils.py
8clicompanionlib/view.py11clicompanionlib/view.py
@@ -10,3 +13,8 @@
10data/clicompanion2.config13data/clicompanion2.config
11data/clicompanion.16.png14data/clicompanion.16.png
12data/clicompanion.64.png15data/clicompanion.64.png
16plugins/clfu.py
17plugins/CommandLineFU.py
18plugins/__init__.py
19plugins/LocalCommandList.py
20
1321
=== modified file 'clicompanion'
--- clicompanion 2012-01-02 00:23:11 +0000
+++ clicompanion 2012-01-08 10:37:24 +0000
@@ -25,13 +25,12 @@
2525
2626
27parser = OptionParser(usage="%prog [-f] [-q]", version="%prog 1.1")27parser = OptionParser(usage="%prog [-f] [-q]", version="%prog 1.1")
28parser.add_option("-f", "--file", dest="filename",28parser.add_option("-f", "--file", dest="conffile",
29 help="Write report to FILE", metavar="FILE")29 help="Configuration file to use.", metavar="FILE")
30parser.add_option("-c", "--cheatsheet", dest="cheatsheet",30parser.add_option("-c", "--cheatsheet", dest="cheatsheet",
31 help="Read cheatsheet from FILE", metavar="FILE")31 help="Read cheatsheet from FILE", metavar="FILE")
32parser.add_option("-q", "--quiet",32parser.add_option("-d", "--debug", dest="debug", action='store_true',
33 action="store_false", dest="verbose", default=True,33 default=False, help="Print debug messages",)
34 help="Don't print status messages to stdout")
3534
36(options, args) = parser.parse_args()35(options, args) = parser.parse_args()
3736
3837
=== modified file 'clicompanionlib/__init__.py'
--- clicompanionlib/__init__.py 2010-11-30 16:03:59 +0000
+++ clicompanionlib/__init__.py 2012-01-08 10:37:24 +0000
@@ -1,7 +1,7 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2# -*- coding: utf-8 -*-2# -*- coding: utf-8 -*-
3#3#
4# clicompanion.py - commandline tool.4# __init__.py
5#5#
6# Copyright 2010 Duane Hinnen, Kenny Meyer6# Copyright 2010 Duane Hinnen, Kenny Meyer
7#7#
88
=== modified file 'clicompanionlib/config.py'
--- clicompanionlib/config.py 2012-01-02 00:23:11 +0000
+++ clicompanionlib/config.py 2012-01-08 10:37:24 +0000
@@ -1,9 +1,9 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2# -*- coding: utf-8 -*-2# -*- coding: utf-8 -*-
3#3#
4# clicompanion.py - commandline tool.4# config.py - Configuration classes for the clicompanion
5#5#
6# Copyright 2010 Duane Hinnen6# Copyright 2012 David Caro <david.caro.estevez@gmail.com>
7#7#
8# This program is free software: you can redistribute it and/or modify it8# This program is free software: you can redistribute it and/or modify it
9# under the terms of the GNU General Public License version 3, as published9# under the terms of the GNU General Public License version 3, as published
@@ -18,246 +18,323 @@
18# with this program. If not, see <http://www.gnu.org/licenses/>.18# with this program. If not, see <http://www.gnu.org/licenses/>.
19#19#
20#20#
21# This file has the CLIConfig class definition, and the CLIConfigView, the
22# first is the main configuration model of the progran, where all the config
23# is stored and processed to be correct, also sets up all the required
24# configuration (default sections and keybindings) if they are not set.
25#
26# The CLIConfigViewer, is something similar to a view in MySQL, is an object
27# that has the same (almos all) methods than the normal CLIConfig, but only
28# shows a part of it, used to allow the plugins to handle their own
29# configurations (a prefix will be added, like the 'profiles::' prefix or the
30# name of the plugin that stores the config).
31
32
21import os33import os
22import ConfigParser34import ConfigParser
23import clicompanionlib.utils as utils35import collections
36import gtk
37import pango
38import clicompanionlib.utils as cc_utils
24from clicompanionlib.utils import dbg39from clicompanionlib.utils import dbg
25import collections
2640
27CHEATSHEET = os.path.expanduser("~/.clicompanion2")
28CONFIGDIR = os.path.expanduser("~/.config/clicompanion/")41CONFIGDIR = os.path.expanduser("~/.config/clicompanion/")
29CONFIGFILE = os.path.expanduser("~/.config/clicompanion/config")42CONFIGFILE = os.path.expanduser("~/.config/clicompanion/config")
30CONFIG_ORIG = "/etc/clicompanion.d/clicompanion2.config"43CONFIG_ORIG = "/etc/clicompanion.d/clicompanion2.config"
31DEFAULTS = { "scrollb": '500',44
32 "colorf": '#FFFFFF',45
33 "colorb": '#000000',46## All the options (except keybindings) passed as name: (default, test), where
34 "encoding": 'UTF-8',47## test can be one of 'bool', 'str', 'encoding', 'font', or a function to test
35 "debug": 'False'}48## the value (the function must throw an exception on fail)
3649DEFAULTS = {'profile': {"scrollb": ('500', 'int'),
37## To avoid parsing the config file each time, we store the loaded config here50 "color_scheme": ("Custom", 'str'),
38CONFIG = None51 "colorf": ('#FFFFFF', gtk.gdk.color_parse),
3952 "colorb": ('#000000', gtk.gdk.color_parse),
40def create_config(conffile=CONFIGFILE):53 "use_system_colors": ("False", 'bool'),
41 global CONFIG54 "encoding": ('UTF-8', 'encoding'),
42 config = CONFIG55 "font": (cc_utils.get_system_font(), 'font'),
43 configdir = conffile.rsplit(os.sep,1)[0]56 "use_system_font": ("False", 'bool'),
44 if not os.path.exists(configdir):57 "use_system_colors": ("False", 'bool'),
45 try:58 "bold_text": ("False", 'bool'),
46 os.makedirs(configdir)59 "antialias": ("True", 'bool'),
47 except Exception, e:60 "sel_word": (u"-A-Za-z0-9,./?%&#:_", 'str'),
48 print _('Unable to create config at dir %s (%s)')%(configdir,e)61 "update_login_records": ("True", 'bool'),
49 return False62 },
50 # reuse the config if able63 'general': {"debug": ('False', 'bool'),
51 if not config:64 "plugins": ('LocalCommandList, CommandLineFU', 'str')
52 config = ConfigParser.SafeConfigParser(DEFAULTS)65 },
53 # set a number of parameters66 'LocalCommandList': {"cheatsheet":
54 if os.path.isfile(conffile):67 (os.path.expanduser("~/.clicompanion2"), 'str'),
55 config.read([conffile])68 },
56 else:69 }
57 config.add_section("terminal")70
58 for option, value in DEFAULTS.items():71## Note that the modifiers must be specified as 'mod1+mod2+key', where the
59 config.set("terminal", option, value)72## modifiers are 'shift', 'alt','ctrl', in that order (shift+alt+ctrl+key), and
60 CONFIG = config73## that the key pressed is the key affecteed by the modifiers, for example,
61 # Writing our configuration file74## shift+ctrl+D (not shift+ctrl+d). And the function keys go uppercase (F10).
62 save_config(config, conffile)75DEFAULT_KEY_BINDINGS = {
63 print _("INFO: Created config file at %s.")%conffile76 'run_command': 'F4',
64 return config77 'add_command': 'F5',
65 78 'remove_command': 'F6',
6679 'edit_command': 'unused',
67def get_config_copy(config=None):80 'add_tab': 'F7',
68 global CONFIG81 'close_tab': 'unused',
69 if not config:82 'toggle_fullscreen': 'F12',
70 config = CONFIG83 'toggle_maximize': 'F11',
71 new_cfg = ConfigParser.SafeConfigParser(DEFAULTS)84 'toggle_hide_ui': 'F9',
72 for section in config.sections():85 }
73 new_cfg.add_section(section)86
74 for option in config.options(section):87### funcname : labelname
75 new_cfg.set(section, option, config.get(section, option))88## a function with the name funcname and signature void(void) must exist in the
76 return new_cfg89## main window class, and is the one that will be called when the keybinding is
77 90## actibated
7891KEY_BINDINGS = {
79def get_config(conffile=CONFIGFILE, confdir=CONFIGDIR):92 'run_command': 'Run command',
80 global CONFIG93 'add_command': 'Add command',
81 config = CONFIG94 'remove_command': 'Remove command',
82 if not config:95 'edit_command': 'Edit command',
83 dbg('Loading new config')96 'add_tab': 'Add tab',
84 if not os.path.isfile(conffile):97 'close_tab': 'Close tab',
85 config = create_config(conffile)98 'toggle_fullscreen': 'Toggle fullscreen',
86 config = ConfigParser.SafeConfigParser(DEFAULTS)99 'toggle_maximize': 'Maximize',
87 config.add_section("terminal")100 'toggle_hide_ui': 'Hide UI',
88 config.read([conffile])101 }
89 CONFIG = config102
90 else:103
91 dbg('Reusing already loaded config')104class CLIConfig(ConfigParser.RawConfigParser):
92 return config105 def __init__(self, defaults=DEFAULTS, conffile=CONFIGFILE):
93106 ConfigParser.RawConfigParser.__init__(self)
94107 self.conffile = os.path.abspath(conffile)
95def save_config(config, conffile=CONFIGFILE):108 configdir = self.conffile.rsplit(os.sep, 1)[0]
96 global CONFIG109 if not os.path.exists(configdir):
97 dbg('Saving conffile at %s'%conffile)
98 with open(CONFIGFILE, 'wb') as f:
99 config.write(f)
100 CONFIG = config
101
102class Cheatsheet:
103 '''
104 comtainer class for the cheatsheet
105
106 Example of usage:
107 >>> c = config.Cheatsheet()
108 >>> c.load('/home/cascara/.clicompanion2')
109 >>> c[3]
110 ['uname -a', '', 'What kernel am I running\n']
111 >>> c.file
112 '/home/cascara/.clicompanion2'
113 >>> c[2]=[ 'mycmd', 'userui', 'desc' ]
114 >>> c[2]
115 ['mycmd', 'userui', 'desc']
116 >>> del c[2]
117 >>> c[2]
118 ['ps aux | grep ?', 'search string', 'Search active processes for search string\n']
119 >>> c.insert('cmd2','ui2','desc2',2)
120 >>> c[2]
121 ['cmd2', 'ui2', 'desc2']
122
123 '''
124 def __init__(self):
125 self.file = CHEATSHEET
126 self.commands = []
127
128 def __repr__(self):
129 return 'Config: %s - %s'%(self.file, self.commands)
130
131 def load(self, cheatfile=None):
132 if not cheatfile:
133 self.file = CHEATSHEET
134 if not os.path.exists(CHEATSHEET):
135 if os.path.exists(CONFIG_ORIG):
136 os.system ("cp %s %s" % (CONFIG_ORIG, CHEATSHEET))
137 else:
138 # Oops! Looks like there's no default cheatsheet.
139 # Then, create an empty cheatsheet.
140 open(CHEATSHEET, 'w').close()
141 else:
142 self.file = cheatfile
143 try:
144 dbg('Reading cheatsheet from file %s'%self.file)
145 with open(self.file, 'r') as ch_fd:
146 ## try to detect if the line is a old fashines config line
147 ## (separated by ':'), when saved will rewrite it
148 no_tabs = True
149 some_colon = False
150 for line in ch_fd:
151 line = line.strip()
152 if not line:
153 continue
154 cmd, ui, desc = [ l.strip() for l in line.split('\t',2)] \
155 + ['',]*(3-len(line.split('\t',2)))
156 if ':' in cmd:
157 some_colon = True
158 if ui or desc:
159 no_tabs = False
160 if cmd and [ cmd, ui, desc ] not in self.commands:
161 self.commands.append([cmd, ui, desc])
162 dbg('Adding command %s'%[cmd, ui, desc])
163 if no_tabs and some_colon:
164 ## None of the commands had tabs, and all had ':' in the
165 ## cmd... most probably old config style
166 print _("Detected old cheatsheet style at")\
167 +" %s"%self.file+_(", parsing to new one.")
168 for i in range(len(self.commands)):
169 cmd, ui, desc = self.commands[i]
170 cmd, ui, desc = [ l.strip() for l in cmd.split(':',2)] \
171 + ['',]*(3-len(cmd.split(':',2)))
172 self.commands[i] = [cmd, ui, desc]
173 self.save()
174 except IOError, e:
175 print _("Error while loading cheatfile")+" %s: %s"%(self.file, e)
176
177 def save(self, cheatfile=None):
178 '''
179 Saves the current config to the file cheatfile, or the file that was
180 loaded.
181 NOTE: It does not overwrite the value self.file, that points to the file
182 that was loaded
183 '''
184 if not cheatfile and self.file:
185 cheatfile = self.file
186 elif not cheatfile:
187 return False
188 try:
189 with open(cheatfile, 'wb') as ch_fd:
190 for command in self.commands:
191 ch_fd.write('\t'.join(command)+'\n')
192 except IOError, e:
193 print _("Error writing cheatfile")+" %s: %s"%(cheatfile, e)
194 return False
195 return True
196
197 def __len__(self):
198 return len(self.commands)
199
200 def __getitem__(self, key):
201 return self.commands[key]
202
203 def __setitem__(self, key, value):
204 if not isinstance(value, collections.Iterable) or len(value) < 3:
205 raise ValueError('Value must be a container with three items, but got %s'%value)
206 if key < len(self.commands):
207 self.commands[key]=list(value)
208 else:
209 try:110 try:
210 self.insert(*value, pos=key)111 os.makedirs(configdir)
211 except ValueError, e:112 except Exception, e:
212 raise ValueError('Value must be a container with three items, but got %s'%value)113 print _('Unable to create config at dir %s (%s)') \
213114 % (configdir, e)
214 def __iter__(self):115 return False
215 for command in self.commands:116 # set a number of default parameters, and fill the missing ones
216 yield command117 if os.path.isfile(self.conffile):
217118 self.read([self.conffile])
218 def insert(self, cmd, ui, desc, pos=None):119 print _("INFO: Reading config file at %s.") % self.conffile
219 if not [cmd, ui, desc] in self.commands:120 else:
220 if not pos:121 print _("INFO: Creating config file at %s.") % self.conffile
221 self.commands.append([cmd, ui, desc])122
222 else:123 for section in DEFAULTS.keys():
223 self.commands.insert(pos, [cmd, ui, desc])124 fullsection = section + '::default'
224125 ## Set default profile options
225 def append(self, cmd, ui, desc):126 if fullsection not in self.sections():
226 self.insert(cmd, ui, desc)127 self.add_section(fullsection)
227128 for option, optdesc in DEFAULTS[section].items():
228 def index(self, cmd, ui, value):129 value, test = optdesc
229 return self.commands.index([cmd, ui, desc])130 self.set(fullsection, option, value)
230 131 ## Set default keybindings
231 def __delitem__(self, key):132 if 'keybindings' not in self.sections():
232 del self.commands[key]133 self.add_section("keybindings")
233 134 for option, value in DEFAULT_KEY_BINDINGS.items():
234 def pop(self, key):135 if not self.has_option('keybindings', option):
235 return self.commands.pop(key)136 self.set('keybindings', option, value)
236137 self.parse()
237 def del_by_value(self, cmd, ui, desc):138 # Writing our configuration file
238 if [cmd, ui, desc] in self.commands:139 self.save()
239 return self.commands.pop(self.commands.index([cmd, ui, desc]))140
240141 def parse(self):
241 def drag_n_drop(self, cmd1, cmd2, before=True):142 ## clean the default options to avoid seeing options where they are not
242 if cmd1 in self.commands:143 for option in self.defaults().keys():
243 dbg('Dropping command from inside %s'%'_\t_'.join(cmd1))144 self.remove_option('DEFAULT', option)
244 i1 = self.commands.index(cmd1)145 ## now parse the rest of sections
245 del self.commands[i1]146 for section in self.sections():
246 if cmd2:147 for option in self.options(section):
247 i2 = self.commands.index(cmd2)148 if section == 'keybindings':
248 if before:149 if option not in KEY_BINDINGS.keys():
249 self.commands.insert(i2, cmd1)150 print _("Option %s:%s not recognised, deleting." \
250 else:151 % (section, option))
251 self.commands.insert(i2+1, cmd1)152 self.remove_option(section, option)
252 else:153 else:
253 self.commands.append(cmd1)154 if not '::' in section:
254 else:155 print _("Deleting unrecognzed section %s." % section)
255 dbg('Dropping command from outside %s'%'_\t_'.join(cmd1))156 self.remove_section(section)
256 if cmd2:157 break
257 i2 = self.commands.index(cmd2)158 secttype = section.split('::')[0]
258 if before:159 if secttype not in DEFAULTS:
259 self.commands.insert(i2, cmd1)160 print _("Deleting unrecognized section %s." % section)
260 else:161 self.remove_section(section)
261 self.commands.insert(i2+1, cmd1)162 break
262 else:163 if option not in DEFAULTS[secttype].keys():
263 self.commands.append(cmd1)164 print _("Option %s:%s not recognised, deleting." \
165 % (section, option))
166 self.remove_option(section, option)
167 else:
168 val = self.get(section, option)
169 defval, test = DEFAULTS[secttype][option]
170 try:
171 if test == 'str':
172 continue
173 elif test == 'int':
174 res = self.getint(section, option)
175 elif test == 'bool':
176 res = self.getboolean(section, option)
177 elif test == 'encoding':
178 if val.lower() not in [enc.lower()
179 for enc, desc
180 in cc_utils.encodings]:
181 raise ValueError(
182 _('Option %s is not valid.') % test)
183 elif test == 'font':
184 fname, fsize = val.rsplit(' ', 1)
185 fsize = int(fsize)
186 cont = gtk.TextView().create_pango_context()
187 avail_fonts = cont.list_families()
188 found = False
189 for font in avail_fonts:
190 if fname == font.get_name():
191 found = True
192 break
193 if not found:
194 raise ValueError(
195 _('Option %s is not valid.') % type)
196 elif callable(test):
197 res = test(val)
198 if not res:
199 raise Exception
200 else:
201 print _("Wrong specification for "
202 "option %s in file %s") \
203 % (option, __file__)
204 except Exception, e:
205 print (_('ERROR: Wrong config value for %s: %s ') \
206 % (option, val) +
207 _(',using default one %s.') % defval)
208 self.set(section, option, defval)
209
210 def set(self, section, option, value):
211 if section == 'DEFAULT':
212 raise ConfigParser.NoSectionError(
213 'Section "DEFAULT" is not allowed. Use section '
214 '"TYPE::default instead"')
215 else:
216 return ConfigParser.RawConfigParser.set(self, section,
217 option, value)
218
219 def get(self, section, option):
220 if '::' in section:
221 sectiontag = section.split('::')[0]
222 if not self.has_option(section, option):
223 if not self.has_option(sectiontag + '::default', option):
224 raise ConfigParser.NoOptionError(option, section)
225 return ConfigParser.RawConfigParser.get(self,
226 sectiontag + '::default', option)
227 elif not self.has_option(section, option):
228 raise ConfigParser.NoOptionError(option, section)
229 return ConfigParser.RawConfigParser.get(self, section, option)
230
231 def get_config_copy(self):
232 new_cfg = CLIConfig(DEFAULTS)
233 for section in self.sections():
234 if section not in new_cfg.sections():
235 new_cfg.add_section(section)
236 for option in self.options(section):
237 new_cfg.set(section, option, self.get(section, option))
238 return new_cfg
239
240 def save(self, conffile=None):
241 if not conffile:
242 conffile = self.conffile
243 dbg('Saving conffile at %s' % conffile)
244 with open(conffile, 'wb') as f:
245 self.write(f)
246
247 def get_plugin_conf(self, plugin):
248 return CLIConfigView(plugin, self)
249
250
251class CLIConfigView():
252 '''
253 This class implements an editable view (hiding unwanted options) of the
254 CLIConfig class, for example, to avoid the plugins editing other options
255 but their own, some methods of the configRaw Parser are not implemented.
256 '''
257 def __init__(self, sectionkey, config):
258 self.key = sectionkey + '::'
259 self._config = config
260
261 def get(self, section, option):
262 if section == 'DEFAULT':
263 section = 'default'
264 if self.key + section not in self._config.sections():
265 raise ConfigParser.NoSectionError(
266 'The section %s does not exist.' % section)
267 return self._config.get(self.key + section, option)
268
269 def getint(self, section, option):
270 return self._config.getint(self.key + section, option)
271
272 def getfloat(self, section, option):
273 return self._config.getfloat(self.key + section, option)
274
275 def getboolean(self, section, option):
276 return self._config.getboolean(self.key + section, option)
277
278 def items(self, section):
279 return self._config.items(self.key + section)
280
281 def write(self, fileobject):
282 pass
283
284 def remove_option(self, section, option):
285 return self._config.remove_option(self.key + section, option)
286
287 def optionxform(self, option):
288 pass
289
290 def set(self, section, option, value):
291 if section == 'DEFAULT':
292 section = 'default'
293 if self.key + section not in self._config.sections():
294 raise ConfigParser.NoSectionError(
295 'The section %s does not exist.' % section)
296 return self._config.set(self.key + section, option, value)
297
298 def add_section(self, section):
299 return self._config.add_section(self.key + section)
300
301 def remove_section(self, section):
302 return self._config.remove_section(self.key + section)
303
304 def sections(self):
305 sections = []
306 for section in self._config.sections():
307 if section.startswith(self.key):
308 sections.append(section.split('::', 1)[1])
309 return sections
310
311 def options(self, section):
312 return self._config.options(self.key + section)
313
314 def defaults(self):
315 return self._config.options(self.key + 'default')
316
317 def has_section(self, section):
318 return self._config.has_section(self.key + section)
319
320 def has_option(self, section, option):
321 return self._config.has_option(self.key + section, option)
322
323 def readfp(self, filedesc, name='<???>'):
324 tempconf = ConfigParser.RawConfigParser()
325 tempconf.readfp(filedesc, name)
326 for option in tempconf.defaults():
327 self.set('DEFAULT', option, tempconf.get('DEFAULT', option))
328 for section in tempconf.sections():
329 if not self.has_section(section):
330 self.add_section(section)
331 for option in tempconf.options():
332 self.set(section, option)
333
334 def read(self, files):
335 for file in files:
336 with open(file, 'r') as fd:
337 self.readfp(fd)
338
339 def save(self):
340 self._config.save()
264341
=== removed file 'clicompanionlib/controller.py'
--- clicompanionlib/controller.py 2012-01-02 00:23:11 +0000
+++ clicompanionlib/controller.py 1970-01-01 00:00:00 +0000
@@ -1,665 +0,0 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# clicompanion - commandline tool.
5#
6# Copyright 2010 Duane Hinnen, Kenny Meyer
7#
8# This program is free software: you can redistribute it and/or modify it
9# under the terms of the GNU General Public License version 3, as published
10# by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful, but
13# WITHOUT ANY WARRANTY; without even the implied warranties of
14# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
15# PURPOSE. See the GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20#
21
22import os
23import pygtk
24pygtk.require('2.0')
25import re
26import webbrowser
27import view
28import copy
29import clicompanionlib.tabs
30import clicompanionlib.config as cc_config
31import clicompanionlib.utils as utils
32from clicompanionlib.utils import get_user_shell, dbg
33
34#if cc_config.get_config().get('terminal','debug') == 'True':
35# utils.DEBUG = True
36
37# import vte and gtk or print error
38try:
39 import gtk
40except:
41 error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
42 _("You need to install the python gtk bindings package 'python-gtk2'"))
43 error.run()
44 sys.exit (1)
45
46try:
47 import vte
48except:
49 error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
50 _("You need to install 'python-vte' the python bindings for libvte."))
51 error.run()
52 sys.exit (1)
53
54
55
56class Actions(object):
57 ## Info Dialog Box
58 ## if a command needs more info EX: a package name, a path
59 def get_info(self, cmd, ui, desc):
60 dbg('Got command with user input')
61 ## Create Dialog object
62 dialog = gtk.MessageDialog(
63 None,
64 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
65 gtk.MESSAGE_QUESTION,
66 gtk.BUTTONS_OK_CANCEL,
67 None)
68
69 # Primary text
70 dialog.set_markup(_("This command requires more information."))
71
72 ## create the text input field
73 entry = gtk.Entry()
74 ## allow the user to press enter to do ok
75 entry.connect("activate", self.responseToDialog, dialog, gtk.RESPONSE_OK)
76
77 ## create a horizontal box to pack the entry and a label
78 hbox = gtk.HBox()
79 hbox.pack_start(gtk.Label(ui+":"), False, 5, 5)
80 hbox.pack_end(entry)
81 ## some secondary text
82 dialog.format_secondary_markup(_("Please provide a "+ui))
83 ## add it and show it
84 dialog.vbox.pack_end(hbox, True, True, 0)
85 dialog.show_all()
86
87 ## Show the dialog
88 response = dialog.run()
89
90 ## user text assigned to a variable
91 text = entry.get_text()
92 user_input = text.split(' ')
93
94 ## The destroy method must be called otherwise the 'Close' button will
95 ## not work.
96 dialog.destroy()
97 if response != gtk.RESPONSE_OK:
98 user_input = None
99 return user_input
100
101 def responseToDialog(self, text, dialog, response):
102 dialog.response(response)
103
104 ## Add command dialog box
105 def add_command(self, mw):
106
107 ## Create Dialog object
108 dialog = gtk.MessageDialog(
109 None,
110 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
111 gtk.MESSAGE_QUESTION,
112 gtk.BUTTONS_OK,
113 None)
114
115 ## primaary text
116 dialog.set_markup(_("Add a command to your command list"))
117
118 #create the text input field
119 entry1 = gtk.Entry()
120 entry2 = gtk.Entry()
121 entry3 = gtk.Entry()
122 ## allow the user to press enter to do ok
123 entry1.connect("activate", self.responseToDialog, dialog, gtk.RESPONSE_OK)
124 entry2.connect("activate", self.responseToDialog, dialog, gtk.RESPONSE_OK)
125 entry3.connect("activate", self.responseToDialog, dialog, gtk.RESPONSE_OK)
126
127 ## create three labels
128 hbox1 = gtk.HBox()
129 hbox1.pack_start(gtk.Label(_("Command")), False, 5, 5)
130 hbox1.pack_start(entry1, False, 5, 5)
131
132 hbox1.pack_start(gtk.Label(_("User Input")), False, 5, 5)
133 hbox1.pack_start(entry2, False, 5, 5)
134
135 hbox2 = gtk.HBox()
136 hbox2.pack_start(gtk.Label(_("Description")), False, 5, 5)
137 hbox2.pack_start(entry3, True, 5, 5)
138
139 ## cancel button
140 dialog.add_button(_('Cancel'), gtk.RESPONSE_DELETE_EVENT)
141 ## some secondary text
142 dialog.format_secondary_markup(
143 _("When entering a command use question marks(?) as placeholders if"
144 " user input is required when the command runs. Example: ls "
145 "/any/directory would be entered as, ls ? .For each question "
146 "mark(?) in your command, if any, use the User Input field to "
147 "provide a hint for each variable. Using our example ls ? you "
148 "could put directory as the User Input. Lastly provide a brief "
149 "Description."))
150
151 ## add it and show it
152 dialog.vbox.pack_end(hbox2, True, True, 0)
153 dialog.vbox.pack_end(hbox1, True, True, 0)
154 dialog.show_all()
155 ## Show the dialog
156 result = dialog.run()
157
158 if result == gtk.RESPONSE_OK:
159 ## user text assigned to a variable
160 text1 = entry1.get_text()
161 text2 = entry2.get_text()
162 text3 = entry3.get_text()
163 ## update commandsand sync with screen '''
164 view.CMNDS.append(text1, text2, text3)
165 mw.sync_cmnds()
166 view.CMNDS.save()
167
168 ## The destroy method must be called otherwise the 'Close' button will
169 ## not work.
170 dialog.destroy()
171 #return text
172
173 ## This the edit function
174 def edit_command(self, mw):
175 if not view.ROW:
176 return
177 lst_index = int(view.ROW[0][0])
178 model = mw.treeview.get_model()
179 cmd = ''.join(model[lst_index][0])
180 ui = ''.join(model[lst_index][1])
181 desc = ''.join(model[lst_index][2])
182
183 ## Create Dialog object
184 dialog = gtk.MessageDialog(
185 None,
186 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
187 gtk.MESSAGE_QUESTION,
188 gtk.BUTTONS_OK,
189 None)
190
191 # primary text
192 dialog.set_markup(_("Edit a command in your command list"))
193
194 ## create the text input fields
195 entry1 = gtk.Entry()
196 entry1.set_text(cmd)
197 entry2 = gtk.Entry()
198 entry2.set_text(ui)
199 entry3 = gtk.Entry()
200 entry3.set_text(desc)
201 ## allow the user to press enter to do ok
202 entry1.connect("activate", self.responseToDialog, dialog, gtk.RESPONSE_OK)
203
204 ## create three labels
205 hbox1 = gtk.HBox()
206 hbox1.pack_start(gtk.Label(_("Command")), False, 5, 5)
207 hbox1.pack_start(entry1, False, 5, 5)
208
209 hbox1.pack_start(gtk.Label(_("User Input")), False, 5, 5)
210 hbox1.pack_start(entry2, False, 5, 5)
211
212 hbox2 = gtk.HBox()
213 hbox2.pack_start(gtk.Label(_("Description")), False, 5, 5)
214 hbox2.pack_start(entry3, True, 5, 5)
215
216 ## cancel button
217 dialog.add_button(_('Cancel'), gtk.RESPONSE_DELETE_EVENT)
218 ## some secondary text
219 dialog.format_secondary_markup(_("Please provide a command, description, and what type of user variable, if any, is required."))
220
221 ## add it and show it
222 dialog.vbox.pack_end(hbox2, True, True, 0)
223 dialog.vbox.pack_end(hbox1, True, True, 0)
224 dialog.show_all()
225 ## Show the dialog
226 result = dialog.run()
227
228 if result == gtk.RESPONSE_OK:
229 ## user text assigned to a variable
230 cmd = entry1.get_text()
231 ui = entry2.get_text()
232 desc = entry3.get_text()
233
234 if cmd != "":
235 cmd_index = model[lst_index][3]
236 dbg('Got index %d for command at pos %d'%(cmd_index, lst_index))
237 view.CMNDS[cmd_index] = [cmd, ui, desc]
238 mw.sync_cmnds()
239 view.CMNDS.save()
240 ## The destroy method must be called otherwise the 'Close' button will
241 ## not work.
242 dialog.destroy()
243
244
245 ## Remove command from command file and GUI
246 def remove_command(self, mw):
247 if not view.ROW:
248 return
249 ## get selected row
250 lst_index = int(view.ROW[0][0])
251 ## get selected element index, even from search filter
252 model = mw.treeview.get_model()
253 cmd_index = model[lst_index][3]
254 ## delete element from liststore and CMNDS
255 del view.CMNDS[cmd_index]
256 mw.sync_cmnds()
257 ## save changes
258 view.CMNDS.save()
259
260
261 def _filter_commands(self, widget, liststore, treeview):
262 """
263 Show commands matching a given search term.
264 The user should enter a term in the search box and the treeview should
265 only display the rows which contain the search term.
266 Pretty straight-forward.
267 """
268 search_term = widget.get_text().lower()
269 ## If the search term is empty, restore the liststore
270 if search_term == "":
271 view.FILTER = 0
272 treeview.set_model(liststore)
273 return
274
275 view.FILTER = 1
276 ## Create a TreeModelFilter object which provides auxiliary functions for
277 ## filtering data.
278 ## http://www.pygtk.org/pygtk2tutorial/sec-TreeModelSortAndTreeModelFilter.html
279 modelfilter = liststore.filter_new()
280 def search(modelfilter, iter, search_term):
281 try:
282 ## Iterate through every column and row and check if the search
283 ## term is there:
284 if search_term in modelfilter.get_value(iter, 0).lower() or \
285 search_term in modelfilter.get_value(iter, 1).lower() or \
286 search_term in modelfilter.get_value(iter, 2).lower() :
287 return True
288
289 except TypeError:
290 ## Python raises a TypeError if row data doesn't exist. Catch
291 ## that and fail silently.
292 pass
293 except AttributeError:
294 ## Python raises a AttributeError if row data was modified . Catch
295 ## that and fail silently.
296 pass
297 modelfilter.set_visible_func(search, search_term)
298 ## save the old liststore and cmnds
299 treeview.set_model(modelfilter)
300
301 ## send the command to the terminal
302 def run_command(self, mw):
303
304 ## if called without selecting a command from the list return
305 if not view.ROW:
306 return
307 text = ""
308 lst_index = int(view.ROW[0][0]) ## removes everything but number from [5,]
309
310 ## get the current notebook page so the function knows which terminal to run the command in.
311 pagenum = mw.notebook.get_current_page()
312 widget = mw.notebook.get_nth_page(pagenum)
313 page_widget = widget.get_child()
314
315 model = mw.treeview.get_model()
316 cmd = ''.join(model[lst_index][0])
317 ui = ''.join(model[lst_index][1])
318 desc = ''.join(model[lst_index][2])
319
320 ## find how many ?(user arguments) are in command
321 match = re.findall('\?', cmd)
322 '''
323 Make sure user arguments were found. Replace ? with something
324 .format can read. This is done so the user can just enter ?, when
325 adding a command where arguments are needed, instead
326 of {0[1]}, {0[1]}, {0[2]}
327 '''
328 if match == False:
329 pass
330 else:
331 num = len(match)
332 ran = 0
333 new_cmnd = self.replace(cmd, num, ran)
334
335 if len(match) > 0: # command with user input
336 dbg('command with ui')
337 f_cmd = ""
338 while True:
339 try:
340 ui_text = self.get_info(cmd, ui, desc)
341 if ui_text == None:
342 return
343 dbg('Got ui "%s"'%' '.join(ui_text))
344 if ''.join(ui_text) == '':
345 raise IndexError
346 f_cmd = new_cmnd.format(ui_text)
347 except IndexError, e:
348 error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, \
349 gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
350 _("You need to enter full input. Space separated."))
351 error.connect('response', lambda err, *x: err.destroy())
352 error.run()
353 continue
354 break
355 page_widget.feed_child(f_cmd+"\n") #send command w/ input
356 page_widget.show()
357 page_widget.grab_focus()
358 else: ## command that has no user input
359 page_widget.feed_child(cmd+"\n") #send command
360 page_widget.show()
361 page_widget.grab_focus()
362
363 ## replace ? with {0[n]}
364 def replace(self, cmnd, num, ran):
365 replace_cmnd=re.sub('\?', '{0['+str(ran)+']}', cmnd, count=1)
366 cmnd = replace_cmnd
367 ran += 1
368 if ran < num:
369 return self.replace(cmnd, num, ran)
370 else:
371 pass
372 return cmnd
373
374 ## open the man page for selected command
375 def man_page(self, notebook):
376 import subprocess as sp
377 import shlex
378 try:
379 row_int = int(view.ROW[0][0]) # removes everything but number from EX: [5,]
380 except IndexError:
381 ## When user not choose row, when is in filter mode
382 dialog = gtk.MessageDialog(
383 None,
384 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
385 gtk.MESSAGE_QUESTION,
386 gtk.BUTTONS_OK,
387 None)
388 dialog.set_markup(_('You must choose a row to view the help'))
389 dialog.show_all()
390 dialog.run()
391 dialog.destroy()
392 return
393 ## get the manpage for the command
394 cmnd = view.CMNDS[row_int][0] #CMNDS is where commands are store
395 ## get each command for each pipe, It's not 100 accurate, but good
396 ## enough (by now)
397 commands = []
398 next_part = True
399 found_sudo = False
400 for part in shlex.split(cmnd):
401 if next_part:
402 if part == 'sudo' and not found_sudo:
403 found_sudo = True
404 commands.append('sudo')
405 else:
406 if part not in commands:
407 commands.append(part)
408 next_part = False
409 else:
410 if part in [ '||', '&&', '&', '|']:
411 next_part = True
412
413 notebook = gtk.Notebook()
414 notebook.set_scrollable(True)
415 notebook.popup_enable()
416 notebook.set_properties(group_id=0, tab_vborder=0, tab_hborder=1, tab_pos=gtk.POS_TOP)
417 ## create a tab for each command
418 for command in commands:
419 scrolled_page = gtk.ScrolledWindow()
420 scrolled_page.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
421 tab = gtk.HBox()
422 tab_label = gtk.Label(command)
423 tab_label.show()
424 tab.pack_start(tab_label)
425 page = gtk.TextView()
426 page.set_wrap_mode(gtk.WRAP_WORD)
427 page.set_editable(False)
428 page.set_cursor_visible(False)
429 try:
430 manpage = sp.check_output(["man",command])
431 except sp.CalledProcessError, e:
432 manpage = _('Failed to get manpage for command "%s"\nReason:\n%s')%(
433 command, e)
434 textbuffer = page.get_buffer()
435 textbuffer.set_text(manpage)
436 scrolled_page.add(page)
437 notebook.append_page(scrolled_page, tab)
438
439 help_win = gtk.Dialog()
440 help_win.set_title(_("Man page for %s")%cmnd)
441 help_win.vbox.pack_start(notebook, True, True, 0)
442 button = gtk.Button("close")
443 button.connect_object("clicked", lambda self: self.destroy(), help_win)
444 button.set_flags(gtk.CAN_DEFAULT)
445 help_win.action_area.pack_start( button, True, True, 0)
446 button.grab_default()
447 help_win.set_default_size(500,600)
448 help_win.show_all()
449
450
451 @staticmethod
452 def _filter_sudo_from(command):
453 """Filter the sudo from `command`, where `command` is a list.
454 Return the command list with the "sudo" filtered out.
455 """
456 if command[0].startswith("sudo"):
457 del command[0]
458 return command
459 return command
460
461
462 #TODO: Move to menus_buttons
463 def copy_paste(self, vte, event, data=None):
464 if event.button == 3:
465
466 time = event.time
467 ## right-click popup menu Copy
468 popupMenu = gtk.Menu()
469 menuPopup1 = gtk.ImageMenuItem (gtk.STOCK_COPY)
470 popupMenu.add(menuPopup1)
471 menuPopup1.connect('activate', lambda x: vte.copy_clipboard())
472 ## right-click popup menu Paste
473 menuPopup2 = gtk.ImageMenuItem (gtk.STOCK_PASTE)
474 popupMenu.add(menuPopup2)
475 menuPopup2.connect('activate', lambda x: vte.paste_clipboard())
476
477 ## Show popup menu
478 popupMenu.show_all()
479 popupMenu.popup( None, None, None, event.button, time)
480 return True
481 else:
482 pass
483
484 ## close the window and quit
485 def delete_event(self, widget, data=None):
486 gtk.main_quit()
487 return False
488
489 ## Help --> About and Help --> Help menus
490 def about_event(self, widget, data=None):
491 # Create AboutDialog object
492 dialog = gtk.AboutDialog()
493
494 # Add the application name to the dialog
495 dialog.set_name('CLI Companion ')
496
497 # Set the application version
498 dialog.set_version('1.1')
499
500 # Pass a list of authors. This is then connected to the 'Credits'
501 # button. When clicked the buttons opens a new window showing
502 # each author on their own line.
503 dialog.set_authors(['Duane Hinnen', 'Kenny Meyer', 'Marcos Vanettai', 'Marek Bardoński'])
504
505 # Add a short comment about the application, this appears below the application
506 # name in the dialog
507 dialog.set_comments(_('This is a CLI Companion program.'))
508
509 # Add license information, this is connected to the 'License' button
510 # and is displayed in a new window.
511 dialog.set_license(_('Distributed under the GNU license. You can see it at <http://www.gnu.org/licenses/>.'))
512
513 # Show the dialog
514 dialog.run()
515
516 # The destroy method must be called otherwise the 'Close' button will
517 # not work.
518 dialog.destroy()
519
520
521 def help_event(self, widget, data=None):
522 webbrowser.open("http://launchpad.net/clicompanion")
523
524
525 def usage_event(self, widget, data=None):
526 dialog = gtk.Dialog("Usage",
527 None,
528 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
529 (gtk.STOCK_CANCEL, gtk.RESPONSE_CLOSE))
530
531 hbox1 = gtk.HBox()
532 hbox2 = gtk.HBox()
533 hbox21 = gtk.HBox()
534 hbox3 = gtk.HBox()
535 hbox4 = gtk.HBox()
536 hbox5 = gtk.HBox()
537 hbox6 = gtk.HBox()
538
539 hbox1.pack_start(gtk.Label(_("To maximize window, press F11")), False, 5, 5)
540 hbox2.pack_start(gtk.Label(_("To hide UI, press F12")), False, 5, 5)
541 hbox21.pack_start(gtk.Label(_("--------------------")), False, 5, 5)
542 hbox3.pack_start(gtk.Label(_("Run command - F4")), False, 5, 5)
543 hbox4.pack_start(gtk.Label(_("Add command - F5")), False, 5, 5)
544 hbox5.pack_start(gtk.Label(_("Remove command - F6")), False, 5, 5)
545 hbox6.pack_start(gtk.Label(_("Add tab - F7")), False, 5, 5)
546
547 dialog.vbox.pack_end(hbox1, True, True, 0)
548 dialog.vbox.pack_end(hbox2, True, True, 0)
549 dialog.vbox.pack_end(hbox21, True, True, 0)
550 dialog.vbox.pack_end(hbox3, True, True, 0)
551 dialog.vbox.pack_end(hbox4, True, True, 0)
552 dialog.vbox.pack_end(hbox5, True, True, 0)
553 dialog.vbox.pack_end(hbox6, True, True, 0)
554
555 dialog.show_all()
556
557 result = dialog.run()
558 ## The destroy method must be called otherwise the 'Close' button will
559 ## not work.
560 dialog.destroy()
561
562
563 ## File --> Preferences
564 def changed_cb(self, combobox, config):
565 dbg('Changed encoding')
566 model = combobox.get_model()
567 index = combobox.get_active()
568 if index>=0:
569 text_e = model[index][0]
570 encoding = text_e.split(':',1)[0].strip()
571 dbg('Setting encoding to "%s"'%encoding)
572 config.set("terminal", "encoding", encoding)
573
574
575 def color_set_fg_cb(self, colorbutton_fg, config, tabs):
576 dbg('Changing fg color')
577 colorf = self.color2hex(colorbutton_fg)
578 config.set("terminal", "colorf", str(colorf))
579 tabs.update_all_term_config(config)
580
581
582 def color_set_bg_cb(self, colorbutton_bg, config, tabs):
583 dbg('Changing bg color')
584 colorb = self.color2hex(colorbutton_bg)
585 config.set("terminal", "colorb", str(colorb))
586 tabs.update_all_term_config(config)
587
588
589 def color2hex(self, widget):
590 """Pull the colour values out of a Gtk ColorPicker widget and return them
591 as 8bit hex values, sinces its default behaviour is to give 16bit values"""
592 widcol = widget.get_color()
593 return('#%02x%02x%02x' % (widcol.red>>8, widcol.green>>8, widcol.blue>>8))
594
595 def preferences(self, tabs, data=None):
596 '''
597 Preferences window
598 '''
599 dialog = gtk.Dialog(_("User Preferences"),
600 None,
601 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
602 (gtk.STOCK_CANCEL, gtk.RESPONSE_CLOSE,
603 gtk.STOCK_OK, gtk.RESPONSE_OK))
604
605 config = cc_config.get_config_copy()
606
607 ##create the text input fields
608 entry1 = gtk.Entry()
609 entry1.set_text(config.get('terminal', 'scrollb'))
610
611 ##combobox for selecting encoding
612 combobox = gtk.combo_box_new_text()
613 i=0
614 for encoding, desc in utils.encodings:
615 combobox.append_text(encoding + ': '+desc)
616 if encoding.strip().upper() == config.get('terminal','encoding').upper():
617 active = i
618 i=i+1
619 combobox.set_active(active)
620 combobox.connect('changed', self.changed_cb, config)
621
622 ##colorbox for selecting text and background color
623 colorbutton_fg = gtk.ColorButton(
624 gtk.gdk.color_parse(config.get('terminal','colorf')))
625 colorbutton_bg = gtk.ColorButton(
626 gtk.gdk.color_parse(config.get('terminal','colorb')))
627
628 colorbutton_fg.connect('color-set', self.color_set_fg_cb, config, tabs)
629 colorbutton_bg.connect('color-set', self.color_set_bg_cb, config, tabs)
630
631 ## allow the user to press enter to do ok
632 entry1.connect("activate", self.responseToDialog, dialog, gtk.RESPONSE_OK)
633
634 ## create the labels
635 hbox1 = gtk.HBox()
636 hbox1.pack_start(gtk.Label(_("Scrollback")), False, 5, 5)
637 hbox1.pack_start(entry1, False, 5, 5)
638
639 hbox1.pack_start(gtk.Label(_("Encoding")), False, 5, 5)
640 hbox1.pack_start(combobox, False, 5, 5)
641
642 hbox2 = gtk.HBox()
643 hbox2.pack_start(gtk.Label(_("Font color")), False, 5, 5)
644 hbox2.pack_start(colorbutton_fg, True, 5, 5)
645
646 hbox2.pack_start(gtk.Label(_("Background color")), False, 5, 5)
647 hbox2.pack_start(colorbutton_bg, True, 5, 5)
648
649 ## add it and show it
650 dialog.vbox.pack_end(hbox2, True, True, 0)
651 dialog.vbox.pack_end(hbox1, True, True, 0)
652 dialog.show_all()
653
654 result = dialog.run()
655 if result == gtk.RESPONSE_OK:
656 ## user text assigned to a variable
657 text_sb = entry1.get_text()
658 config.set("terminal", "scrollb", text_sb)
659 cc_config.save_config(config)
660 tabs.update_all_term_config()
661
662 ## The destroy method must be called otherwise the 'Close' button will
663 ## not work.
664 dialog.destroy()
665
6660
=== added file 'clicompanionlib/helpers.py'
--- clicompanionlib/helpers.py 1970-01-01 00:00:00 +0000
+++ clicompanionlib/helpers.py 2012-01-08 10:37:24 +0000
@@ -0,0 +1,186 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# helpers.py - Helper dialogs for clicompanion
5#
6# Copyright 2012 Duane Hinnen, Kenny Meyer, Marcos Vanetta, Marek Bardoński,
7# David Caro
8#
9# This program is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License version 3, as published
11# by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful, but
14# WITHOUT ANY WARRANTY; without even the implied warranties of
15# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16# PURPOSE. See the GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21#
22####
23## This file keeps some popups that are shown across the program execution,
24## that aren't directly related with a class, like the edit comand popup or the
25## about popup, but are too small to be kept in a separate file
26
27import os
28import re
29import pygtk
30pygtk.require('2.0')
31import gtk
32import subprocess as sp
33import shlex
34from clicompanionlib.utils import dbg
35
36
37class ManPage(gtk.Dialog):
38 def __init__(self, cmd):
39 if not cmd:
40 choose_row_error()
41 gtk.Dialog.__init__(self)
42 self.cmd = None
43 return
44 self.cmd = cmd
45 notebook = gtk.Notebook()
46 notebook.set_scrollable(True)
47 notebook.popup_enable()
48 notebook.set_properties(group_id=0, tab_vborder=0,
49 tab_hborder=1, tab_pos=gtk.POS_TOP)
50 ## create a tab for each command
51 for command in self.get_commands():
52 scrolled_page = gtk.ScrolledWindow()
53 scrolled_page.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
54 tab = gtk.HBox()
55 tab_label = gtk.Label(command)
56 tab_label.show()
57 tab.pack_start(tab_label)
58 page = gtk.TextView()
59 page.set_wrap_mode(gtk.WRAP_WORD)
60 page.set_editable(False)
61 page.set_cursor_visible(False)
62 try:
63 manpage = sp.check_output(["man", command])
64 except sp.CalledProcessError, e:
65 manpage = _('Failed to get manpage for command '
66 '"%s"\nReason:\n%s') % (command, e)
67 textbuffer = page.get_buffer()
68 textbuffer.set_text(manpage)
69 scrolled_page.add(page)
70 notebook.append_page(scrolled_page, tab)
71 self.set_title(_("Man page for %s") % cmd)
72 self.vbox.pack_start(notebook, True, True, 0)
73 button = gtk.Button("close")
74 button.connect_object("clicked", lambda *x: self.destroy(), self)
75 button.set_flags(gtk.CAN_DEFAULT)
76 self.action_area.pack_start(button, True, True, 0)
77 button.grab_default()
78 self.set_default_size(500, 600)
79 self.show_all()
80
81 def run(self):
82 if not self.cmd:
83 return
84 gtk.Dialog.run(self)
85
86 def get_commands(self):
87 commands = []
88 next_part = True
89 found_sudo = False
90 try:
91 for part in shlex.split(self.cmd):
92 if next_part:
93 if part == 'sudo' and not found_sudo:
94 found_sudo = True
95 commands.append('sudo')
96 else:
97 if part not in commands:
98 commands.append(part)
99 next_part = False
100 else:
101 if part in ['||', '&&', '&', '|']:
102 next_part = True
103 except Exception, e:
104 return [self.cmd]
105 return commands
106
107
108def show_about():
109 dialog = gtk.AboutDialog()
110 dialog.set_name('CLI Companion')
111 dialog.set_version('1.1')
112 dialog.set_authors([u'Duane Hinnen', u'Kenny Meyer', u'Marcos Vanettai',
113 u'Marek Bardoński', u'David Caro'])
114 dialog.set_comments(_('This is a CLI Companion program.'))
115 dialog.set_license(_('Distributed under the GNU license. You can see it at'
116 '<http://www.gnu.org/licenses/>.'))
117 dialog.run()
118 dialog.destroy()
119
120
121## Some hlper popus like edit command and so
122class CommandInfoWindow(gtk.MessageDialog):
123 def __init__(self, cmd, ui, desc):
124 self.cmd, self.ui, self.desc = cmd, ui, desc
125 gtk.MessageDialog.__init__(self,
126 None,
127 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
128 gtk.MESSAGE_QUESTION,
129 gtk.BUTTONS_OK_CANCEL,
130 None)
131 self.set_markup(_("This command requires more information."))
132 ## create the text input field
133 self.entry = gtk.Entry()
134 ## allow the user to press enter to do ok
135 self.entry.connect("activate", lambda *x: self.response(
136 gtk.RESPONSE_OK))
137 ## create a horizontal box to pack the entry and a label
138 hbox = gtk.HBox()
139 hbox.pack_start(gtk.Label(self.ui + ":"), False, 5, 5)
140 hbox.pack_end(self.entry)
141 ## some secondary text
142 self.format_secondary_markup(_("Please provide a " + self.ui))
143 ## add it and show it
144 self.vbox.pack_end(hbox, True, True, 0)
145 self.show_all()
146 ## The destroy method must be called otherwise the 'Close' button will
147 ## not work.
148
149 def run(self):
150 result = False
151 while not result:
152 result = gtk.MessageDialog.run(self)
153 if result == gtk.RESPONSE_OK:
154 ui = self.entry.get_text().strip()
155 dbg('Got ui "%s"' % ui)
156 if not ui:
157 self.show_error()
158 result = None
159 try:
160 cmd = self.cmd.format(ui.split(' '))
161 except:
162 result = None
163 else:
164 cmd = None
165 self.destroy()
166 return cmd
167
168 def show_error(self):
169 error = gtk.MessageDialog(None, gtk.DIALOG_MODAL, \
170 gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
171 _("You need to enter full input. Space separated."))
172 error.connect('response', lambda *x: error.destroy())
173 error.run()
174
175
176def choose_row_error():
177 dialog = gtk.MessageDialog(
178 None,
179 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
180 gtk.MESSAGE_QUESTION,
181 gtk.BUTTONS_OK,
182 None)
183 dialog.set_markup(_('You must choose a row to view the help'))
184 dialog.show_all()
185 dialog.run()
186 dialog.destroy()
0187
=== modified file 'clicompanionlib/menus_buttons.py'
--- clicompanionlib/menus_buttons.py 2012-01-02 00:23:11 +0000
+++ clicompanionlib/menus_buttons.py 2012-01-08 10:37:24 +0000
@@ -1,7 +1,9 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2# -*- coding: utf-8 -*-2# -*- coding: utf-8 -*-
3#3#
4# Copyright 2010 Duane Hinnen, Kenny Meyer, Marcos Vanetta4# menus_buttons.py - Menus and Buttons for the clicompanion
5#
6# Copyright 2010 Duane Hinnen, Kenny Meyer, Marcos Vanetta, David Caro
5#7#
6# This program is free software: you can redistribute it and/or modify it8# This program is free software: you can redistribute it and/or modify it
7# under the terms of the GNU General Public License version 3, as published9# under the terms of the GNU General Public License version 3, as published
@@ -16,199 +18,180 @@
16# with this program. If not, see <http://www.gnu.org/licenses/>.18# with this program. If not, see <http://www.gnu.org/licenses/>.
17#19#
18#20#
19#21# This file contains the upper menus (class FileMenu), and the lower buttons
20# This file contains the menus, buttons, and right clicks22# (class Buttons) used in the CLI Companion main window
21#
2223
23import gtk24import gtk
24import tabs25import gobject
2526import webbrowser
2627import clicompanionlib.helpers as cc_helpers
27class FileMenu(object):28
2829
29 def the_menu(self, mw):30class FileMenu(gtk.MenuBar):
30 actions = mw.actions31 __gsignals__ = {
31 liststore = mw.liststore32 'run_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
32 tabs = mw.tabs33 ()),
33 notebook = mw.notebook34 'add_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
35 ()),
36 'remove_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
37 ()),
38 'edit_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
39 ()),
40 'add_tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
41 ()),
42 'close_tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
43 ()),
44 'preferences': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
45 ()),
46 'quit': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
47 ()),
48 }
49
50 def __init__(self, config):
51 gtk.MenuBar.__init__(self)
34 menu = gtk.Menu()52 menu = gtk.Menu()
35 #color = gtk.gdk.Color(65555, 62000, 65555)53 #color = gtk.gdk.Color(65555, 62000, 65555)
36 #menu.modify_bg(gtk.STATE_NORMAL, color)54 #menu.modify_bg(gtk.STATE_NORMAL, color)
37 root_menu = gtk.MenuItem(_("File")) 55 root_menu = gtk.MenuItem(_("File"))
38 root_menu.set_submenu(menu)56 root_menu.set_submenu(menu)
39 57
40 menu2 = gtk.Menu() 58 menu2 = gtk.Menu()
41 #color = gtk.gdk.Color(65555, 62000, 60000)59 #color = gtk.gdk.Color(65555, 62000, 60000)
42 #menu2.modify_bg(gtk.STATE_NORMAL, color)60 #menu2.modify_bg(gtk.STATE_NORMAL, color)
43 root_menu2 = gtk.MenuItem(_("Help"))61 root_menu2 = gtk.MenuItem(_("Help"))
44 root_menu2.set_submenu(menu2)62 root_menu2.set_submenu(menu2)
4563
46 ##FILE MENU ## 64 ##FILE MENU ##
47 ## Make 'Run' menu entry65 ## Make 'Run' menu entry
48 menu_item1 = gtk.MenuItem(_("Run Command [F4]"))66 menu_item1 = gtk.MenuItem(_("Run Command"))
49 menu.append(menu_item1)67 menu.append(menu_item1)
50 menu_item1.connect("activate", lambda *x: actions.run_command(mw))68 menu_item1.connect("activate", lambda *x: self.emit('run_command'))
51 menu_item1.show()69 menu_item1.show()
5270
53 ## Make 'Add' file menu entry71 ## Make 'Add' file menu entry
54 menu_item2 = gtk.MenuItem(_("Add Command [F5]"))72 menu_item2 = gtk.MenuItem(_("Add Command"))
55 menu.append(menu_item2)73 menu.append(menu_item2)
56 menu_item2.connect("activate", lambda *x: actions.add_command(mw))74 menu_item2.connect("activate", lambda *x: self.emit('add_command'))
57 menu_item2.show()75 menu_item2.show()
58 76
59 ## Make 'Remove' file menu entry77 ## Make 'Remove' file menu entry
60 menu_item3 = gtk.MenuItem(_("Remove Command [F6]"))78 menu_item3 = gtk.MenuItem(_("Remove Command"))
61 menu.append(menu_item3)79 menu.append(menu_item3)
62 menu_item3.connect("activate", lambda *x: actions.remove_command(mw))80 menu_item3.connect("activate", lambda *x: self.emit('remove_command'))
63 menu_item3.show()81 menu_item3.show()
64 82
65 ## Make 'Add Tab' file menu entry83 ## Make 'Add Tab' file menu entry
66 menu_item4 = gtk.MenuItem(_("Add Tab [F7]"))84 menu_item4 = gtk.MenuItem(_("Add Tab"))
67 menu.append(menu_item4)85 menu.append(menu_item4)
68 menu_item4.connect("activate", lambda *x: tabs.add_tab(notebook))86 menu_item4.connect("activate", lambda *x: self.emit('add_tab'))
69 menu_item4.show()87 menu_item4.show()
70 88
89 ## Make 'Close Tab' file menu entry
90 menu_item4 = gtk.MenuItem(_("Close Tab"))
91 menu.append(menu_item4)
92 menu_item4.connect("activate", lambda *x: self.emit('close_tab'))
93 menu_item4.show()
94
71 ## Make 'User Preferences' file menu entry95 ## Make 'User Preferences' file menu entry
72 menu_item5 = gtk.MenuItem(_("Preferences"))96 menu_item5 = gtk.MenuItem(_("Preferences"))
73 menu.append(menu_item5)97 menu.append(menu_item5)
74 menu_item5.connect("activate", lambda *x: actions.preferences(tabs))98 menu_item5.connect("activate", lambda *x: self.emit('preferences'))
75 menu_item5.show()99 menu_item5.show()
76100
77 ## Make 'Quit' file menu entry101 ## Make 'Quit' file menu entry
78 menu_item6 = gtk.MenuItem(_("Quit"))102 menu_item6 = gtk.MenuItem(_("Quit"))
79 menu.append(menu_item6)103 menu.append(menu_item6)
80 menu_item6.connect("activate", actions.delete_event)104 menu_item6.connect("activate", lambda *x: self.emit('quit'))
81 menu_item6.show()105 menu_item6.show()
82 106
83
84 ## HELP MENU ##107 ## HELP MENU ##
85 ## Make 'About' file menu entry108 ## Make 'About' file menu entry
86 menu_item11 = gtk.MenuItem(_("About"))109 menu_item11 = gtk.MenuItem(_("About"))
87 menu2.append(menu_item11)110 menu2.append(menu_item11)
88 menu_item11.connect("activate", actions.about_event)111 menu_item11.connect("activate", lambda *x: cc_helpers.show_about())
89 menu_item11.show()112 menu_item11.show()
90113
91 ## Make 'Usage' file menu entry
92 menu_item22 = gtk.MenuItem(_("Usage"))
93 menu2.append(menu_item22)
94 menu_item22.connect("activate", actions.usage_event)
95 menu_item22.show()
96
97 ## Make 'Help' file menu entry114 ## Make 'Help' file menu entry
98 menu_item22 = gtk.MenuItem(_("Help-online"))115 menu_item22 = gtk.MenuItem(_("Help-online"))
99 menu2.append(menu_item22)116 menu2.append(menu_item22)
100 menu_item22.connect("activate", actions.help_event)117 menu_item22.connect("activate", lambda *x: webbrowser.open(
118 "http://launchpad.net/clicompanion"))
101 menu_item22.show()119 menu_item22.show()
102 120
103 121 self.append(root_menu) # Menu bar(file)
104122 self.append(root_menu2) # Menu bar(help)
105 menu_bar = gtk.MenuBar()123 self.show_all()
106 #color = gtk.gdk.Color(60000, 65533, 60000) 124
107 #menu_bar.modify_bg(gtk.STATE_NORMAL, color)125
108 126class Buttons(gtk.Frame):
109 menu_bar.append (root_menu) ##Menu bar(file)127 __gsignals__ = {
110 menu_bar.append (root_menu2) ##Menu bar(help) 128 'run_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
111 #menu_bar.show() ##show File Menu # Menu Bar129 ()),
112 ##Show 'File' Menu130 'add_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
113 #root_menu.show()131 ()),
114 return menu_bar132 'remove_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
115 133 ()),
116 134 'edit_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
117 135 ()),
118 def buttons(self, mw, spacing, layout):136 'add_tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
119 #button box at bottom of main window137 ()),
120 frame = gtk.Frame()138 'show_man': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
139 ()),
140 'quit': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
141 ()),
142 }
143
144 #button box at bottom of main window
145 def __init__(self, spacing, layout):
146 gtk.Frame.__init__(self)
121 bbox = gtk.HButtonBox()147 bbox = gtk.HButtonBox()
122 bbox.set_border_width(5)148 bbox.set_border_width(5)
123 frame.add(bbox)149 self.add(bbox)
124150
125 # Set the appearance of the Button Box151 # Set the appearance of the Button Box
126 #color = gtk.gdk.Color(65000, 61000, 61000)
127 bbox.set_layout(layout)152 bbox.set_layout(layout)
128 bbox.set_spacing(spacing)153 bbox.set_spacing(spacing)
129 # Run button154 # Run button
130 buttonRun = gtk.Button('_'+_("Run"))155 buttonRun = gtk.Button(stock=gtk.STOCK_EXECUTE)
131 bbox.add(buttonRun)156 bbox.add(buttonRun)
132 buttonRun.connect("clicked", lambda *x: mw.actions.run_command(mw))157 buttonRun.connect("clicked", lambda *x: self.emit('run_command'))
133 buttonRun.set_tooltip_text(_("Click to run a highlighted command"))158 buttonRun.set_tooltip_text(_("Click to run a highlighted command"))
134 #buttonRun.modify_bg(gtk.STATE_NORMAL, color)
135 #buttonRun.modify_bg(gtk.STATE_PRELIGHT, color)
136 #buttonRun.modify_bg(gtk.STATE_INSENSITIVE, color)
137 # Add button159 # Add button
138 buttonAdd = gtk.Button(stock=gtk.STOCK_ADD)160 buttonAdd = gtk.Button(stock=gtk.STOCK_ADD)
139 bbox.add(buttonAdd)161 bbox.add(buttonAdd)
140 buttonAdd.connect("clicked", lambda *x: mw.actions.add_command(mw))162 buttonAdd.connect("clicked", lambda *x: self.emit('add_command'))
141 buttonAdd.set_tooltip_text(_("Click to add a command to your command list"))163 buttonAdd.set_tooltip_text(_("Click to add a command to your"
142 #buttonAdd.modify_bg(gtk.STATE_NORMAL, color) 164 "command list"))
143 #buttonAdd.modify_bg(gtk.STATE_PRELIGHT, color)
144 #buttonAdd.modify_bg(gtk.STATE_INSENSITIVE, color)
145 # Edit button165 # Edit button
146 buttonEdit = gtk.Button('_'+_("Edit"))166 buttonEdit = gtk.Button(stock=gtk.STOCK_EDIT)
147 bbox.add(buttonEdit)167 bbox.add(buttonEdit)
148 buttonEdit.connect("clicked", lambda *x: mw.actions.edit_command(mw))168 buttonEdit.connect("clicked", lambda *x: self.emit('edit_command'))
149 buttonEdit.set_tooltip_text(_("Click to edit a command in your command list"))169 buttonEdit.set_tooltip_text(_("Click to edit a command in your "
150 #buttonEdit.modify_bg(gtk.STATE_NORMAL, color) 170 "command list"))
151 #buttonEdit.modify_bg(gtk.STATE_PRELIGHT, color)
152 #buttonEdit.modify_bg(gtk.STATE_INSENSITIVE, color)
153 # Delete button171 # Delete button
154 buttonDelete = gtk.Button(stock=gtk.STOCK_DELETE)172 buttonDelete = gtk.Button(stock=gtk.STOCK_DELETE)
155 bbox.add(buttonDelete)173 bbox.add(buttonDelete)
156 buttonDelete.connect("clicked", lambda *x: mw.actions.remove_command(mw))174 buttonDelete.connect("clicked", lambda *x: self.emit('remove_command'))
157 buttonDelete.set_tooltip_text(_("Click to delete a command in your command list"))175 buttonDelete.set_tooltip_text(_("Click to delete a command in your "
158 #buttonDelete.modify_bg(gtk.STATE_NORMAL, color) 176 "command list"))
159 #buttonDelete.modify_bg(gtk.STATE_PRELIGHT, color)
160 #buttonDelete.modify_bg(gtk.STATE_INSENSITIVE, color)
161 #Help Button177 #Help Button
162 buttonHelp = gtk.Button(stock=gtk.STOCK_HELP)178 buttonHelp = gtk.Button(stock=gtk.STOCK_HELP)
163 bbox.add(buttonHelp)179 bbox.add(buttonHelp)
164 buttonHelp.connect("clicked", lambda *x: mw.actions.man_page(mw.notebook))180 buttonHelp.connect("clicked", lambda *x: self.emit('show_man'))
165 buttonHelp.set_tooltip_text(_("Click to get help with a command in your command list"))181 buttonHelp.set_tooltip_text(_("Click to get help with a command in "
166 #buttonHelp.modify_bg(gtk.STATE_NORMAL, color) 182 "your command list"))
167 #buttonHelp.modify_bg(gtk.STATE_PRELIGHT, color) 183 #AddTab Button
168 #buttonHelp.modify_bg(gtk.STATE_INSENSITIVE, color)184 button_addtab = gtk.Button(stock=gtk.STOCK_NEW)
185 bbox.add(button_addtab)
186 # Very ugly and nasty hack...
187 box = button_addtab.get_children()[0].get_children()[0]
188 lbl = box.get_children()[1]
189 lbl.set_text(_('Add tab'))
190 button_addtab.connect("clicked", lambda *x: self.emit('add_tab'))
191 button_addtab.set_tooltip_text(_("Click to add a terminal tab"))
169 # Cancel button192 # Cancel button
170 buttonCancel = gtk.Button(stock=gtk.STOCK_QUIT)193 buttonCancel = gtk.Button(stock=gtk.STOCK_QUIT)
171 bbox.add(buttonCancel)194 bbox.add(buttonCancel)
172 buttonCancel.connect("clicked", mw.actions.delete_event)195 buttonCancel.connect("clicked", lambda *x: self.emit('quit'))
173 buttonCancel.set_tooltip_text(_("Click to quit CLI Companion"))196 buttonCancel.set_tooltip_text(_("Click to quit CLI Companion"))
174 #buttonCancel.modify_bg(gtk.STATE_NORMAL, color) 197 self.show_all()
175 #buttonCancel.modify_bg(gtk.STATE_PRELIGHT, color)
176 #buttonCancel.modify_bg(gtk.STATE_INSENSITIVE, color)
177 return frame
178
179
180 #right-click popup menu for the Liststore(command list)
181 def right_click(self, widget, event, mw):
182 if event.button == 3:
183 x = int(event.x)
184 y = int(event.y)
185 time = event.time
186 pthinfo = mw.treeview.get_path_at_pos(x, y)
187 if pthinfo is not None:
188 path, col, cellx, celly = pthinfo
189 mw.treeview.grab_focus()
190 mw.treeview.set_cursor( path, col, 0)
191
192 # right-click popup menu Apply(run)
193 popupMenu = gtk.Menu()
194 menuPopup1 = gtk.ImageMenuItem (gtk.STOCK_APPLY)
195 popupMenu.add(menuPopup1)
196 menuPopup1.connect("activate", lambda self, *x: mw.actions.run_command(mw))
197 # right-click popup menu Edit
198 menuPopup2 = gtk.ImageMenuItem (gtk.STOCK_EDIT)
199 popupMenu.add(menuPopup2)
200 menuPopup2.connect("activate", lambda self, *x: mw.actions.edit_command(mw))
201 # right-click popup menu Delete
202 menuPopup3 = gtk.ImageMenuItem (gtk.STOCK_DELETE)
203 popupMenu.add(menuPopup3)
204 menuPopup3.connect("activate", lambda self, *x: mw.actions.remove_command(mw))
205 # right-click popup menu Help
206 menuPopup4 = gtk.ImageMenuItem (gtk.STOCK_HELP)
207 popupMenu.add(menuPopup4)
208 menuPopup4.connect("activate", lambda self, *x: mw.actions.man_page(mw.notebook))
209 # Show popup menu
210 popupMenu.show_all()
211 popupMenu.popup( None, None, None, event.button, time)
212 return True
213
214
215198
=== added file 'clicompanionlib/plugins.py'
--- clicompanionlib/plugins.py 1970-01-01 00:00:00 +0000
+++ clicompanionlib/plugins.py 2012-01-08 10:37:24 +0000
@@ -0,0 +1,207 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# plugins.py - Plugin related clases for the clicompanion
5#
6# Copyright 2012 David Caro <david.caro.estevez@gmail.com>
7#
8# This program is free software: you can redistribute it and/or modify it
9# under the terms of the GNU General Public License version 3, as published
10# by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful, but
13# WITHOUT ANY WARRANTY; without even the implied warranties of
14# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
15# PURPOSE. See the GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20#################################################
21## The plugins
22##
23## Here are defined the PluginLoader class and all the plugin base classes.
24##
25## The PluginLoader class
26## This class handles the loading and handpling of the plugins, it get a
27## directory and a list of the allowed plugins and loads all the allowed
28## plugins that it found on the dir *.py files (a plugin is a class that
29## inherits from the base class Plugin defined in this file).
30##
31## The Plugin class is the base class for all the plugins, if the plugin does
32## not inherit from that class, it will not be loaded.
33##
34## TabPlugin: This is a plugin that will be used as a tab in the upper notebook
35## of the application, like the LocalCommandList o CommandLineFU plugins
36##
37## PluginConfig: this will be the config tab in the preferences for the plugin.
38##
39## About the plugins:
40## The plugins have some attributes that should be set, like the __authors__,
41## and the __info__ attributes, that will be shown in the info page for the
42## plugin, and the __title__ that will be the plugin name shown in the plugins
43## list.
44
45import gobject
46import gtk
47import sys
48import os
49import inspect
50from clicompanionlib.utils import dbg
51
52
53class PluginLoader:
54 def __init__(self):
55 self.plugins = {}
56 self.allowed = []
57
58 def load(self, pluginsdir, allowed):
59 self.allowed = allowed
60 dbg('Allowing only the plugins %s' % allowed.__repr__())
61 sys.path.insert(0, pluginsdir)
62 try:
63 files = os.listdir(pluginsdir)
64 except OSError:
65 sys.path.remove(pluginsdir)
66 return False
67 for plugin in files:
68 pluginpath = os.path.join(pluginsdir, plugin)
69 if not os.path.isfile(pluginpath) or not plugin[-3:] == '.py':
70 continue
71 dbg('Searching plugin file %s for plugins...' % plugin)
72 try:
73 module = __import__(plugin[:-3], globals(), locals(), [''])
74 for cname, mclass \
75 in inspect.getmembers(module, inspect.isclass):
76 dbg(' Checking if class %s is a plugin.' % cname)
77 if issubclass(mclass, Plugin):
78 if cname not in self.plugins.keys():
79 dbg(' Found plugin %s' % cname)
80 self.plugins[cname] = mclass
81 continue
82 except Exception, ex:
83 print 'Error searching plugin file %s: %s' % (plugin, ex)
84
85 def enable(self, plugins):
86 for plugin in plugins:
87 if plugin not in self.allowed:
88 self.allowed.append(plugin)
89
90 def get_plugins(self, capabilities=None):
91 plugins = []
92 if capabilities == None:
93 return [(pg, cs) for pg, cs in self.plugins.items()
94 if pg in self.allowed()]
95 for plugin, pclass in self.plugins.items():
96 for capability in pclass.__capabilities__:
97 if capability in capabilities \
98 and plugin in self.allowed:
99 plugins.append((plugin, pclass))
100 dbg('Matching plugin %s for %s' % (plugin, capability))
101 return plugins
102
103 def get_plugin_conf(self, plugin):
104 if plugin + 'Config' in self.plugins:
105 return self.plugins[plugin + 'Config']
106
107 def is_enabled(self, plugin):
108 return plugin in self.allowed
109
110 def get_allowed(self):
111 return self.allowed
112
113 def get_disallowed(self):
114 disallowed = []
115 for plugin, pclass in self.plugins.items():
116 if plugin not in self.allowed \
117 and pclass.__capabilities__ != ['Config']:
118 disallowed.append(plugin)
119 return disallowed
120
121 def get_available_plugins(self):
122 return [pg for pg, cl
123 in self.plugins.items()
124 if ['Config'] == cl.__capabilities__]
125
126 def get_info(self, plugin):
127 if plugin in self.plugins.keys():
128 return self.plugins[plugin].__info__
129
130 def get_authors(self, plugin):
131 if plugin in self.plugins.keys():
132 return self.plugins[plugin].__authors__
133
134
135## To make all the classe sinherit from this one
136class Plugin(gtk.VBox):
137 def __init__(self):
138 gtk.VBox.__init__(self)
139
140
141class TabPlugin(Plugin):
142 '''
143 Generic Tab plugin that implements all the possible signals.
144 The *command signals are used to interact mainly with the LocalCommandList
145 plugin that handles the locally stored commands.
146 The add_tab is used to add anew terminal tab.
147 '''
148 __gsignals__ = {
149 'run_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
150 (str, str, str)),
151 'add_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
152 (str, str, str)),
153 'remove_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
154 (str, str, str)),
155 'edit_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
156 (str, str, str)),
157 'add_tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
158 ()),
159 'preferences': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
160 ()),
161 'show_man': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
162 (str,)),
163 'quit': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
164 ()),
165 }
166 __capabilities__ = ['CommandTab']
167 __title__ = ''
168 __authors__ = ''
169 __info__ = ''
170
171 def reload(self):
172 '''
173 This method is called when a signal 'reload' is sent from it's
174 configurator
175 '''
176 pass
177
178 def get_command(self):
179 '''
180 This method is uset to retrieve a command, not sure if it's needed yet
181 '''
182 return None, None, None
183
184 def filter(self, string):
185 '''
186 This function is used to filter the commandslist, usually by the
187 search box
188 '''
189 pass
190
191
192class PluginConfig(Plugin):
193 '''
194 Generic plugin configuration window, to be used in the preferences plugins
195 tab
196 '''
197 __gsignals__ = {
198 ## when emited, this signal forces the reload of it's associated plugin
199 ## to reload the plugin config without having to restart the program
200 'reload': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
201 ())
202 }
203 __capabilities__ = ['Config']
204
205 def __init__(self, config):
206 Plugin.__init__(self)
207 self.config = config
0208
=== added file 'clicompanionlib/preferences.py'
--- clicompanionlib/preferences.py 1970-01-01 00:00:00 +0000
+++ clicompanionlib/preferences.py 2012-01-08 10:37:24 +0000
@@ -0,0 +1,708 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# preferences.py - Preferences dialogs for clicompanion
5#
6# Copyright 2012 Duane Hinnen, Kenny Meyer, Marcos Vanettai, Marek Bardoński,
7# David Caro <david.caro.estevez@gmail.com>
8#
9# This program is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License version 3, as published
11# by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful, but
14# WITHOUT ANY WARRANTY; without even the implied warranties of
15# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16# PURPOSE. See the GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21############################################
22## The preferences window is a popup that shows all the configuration options
23## allowing the user to change them, also handles the profiles creatin and
24## delete.
25##
26## The main class is the PreferencesWindow, that has all the other packed. Each
27## as a tab inside the preferences window.
28## The other classes are:
29## - PluginsTab: handles the plugin activation and configuration
30## - KeybindingsTab: handles the keybindings
31## - ProfilesTab: The main tab for the profiles setting, this class is a
32## notebook with three classes (tabs) packed
33## - ProfScrollingTab: the scrolling options tab inside the profiles tab
34## - ProfColorsTab: the colors tab
35## - ProfGeneralTab: the general setting tab
36##
37## About the profiles: each profile is a configuration section in the config
38## file with the name 'profile::profilename', having always the profile
39## 'profile::default', the will have all the default options (if it is not
40## found, it will be created with the harcoded options inside the code.
41
42import pygtk
43pygtk.require('2.0')
44import gtk
45import gobject
46import clicompanionlib.config as cc_config
47import clicompanionlib.utils as cc_utils
48from clicompanionlib.utils import dbg
49
50COLOR_SCHEMES = {
51 'Grey on Black': ['#aaaaaa', '#000000'],
52 'Black on Yellow': ['#000000', '#ffffdd'],
53 'Black on White': ['#000000', '#ffffff'],
54 'White on Black': ['#ffffff', '#000000'],
55 'Green on Black': ['#00ff00', '#000000'],
56 'Orange on Black': ['#e53c00', '#000000'],
57 'Custom': []
58 }
59
60
61def color2hex(color_16b):
62 """
63 Pull the colour values out of a Gtk ColorPicker widget and return them
64 as 8bit hex values, sinces its default behaviour is to give 16bit values
65 """
66 return('#%02x%02x%02x' % (color_16b.red >> 8,
67 color_16b.green >> 8,
68 color_16b.blue >> 8))
69
70
71class ProfGeneralTab(gtk.VBox):
72 def __init__(self, config, profile='default'):
73 gtk.VBox.__init__(self)
74 self.config = config
75 self.profile = profile
76 self.draw_all()
77
78 def draw_all(self):
79 ## 'use_system_font'
80 self.systemfont = gtk.CheckButton(label=_('Use system fixed'
81 'width font'))
82 self.pack_start(self.systemfont, False, False, 8)
83 self.systemfont.set_active(
84 self.config.getboolean('profile::' + self.profile,
85 'use_system_font'))
86 self.systemfont.connect('toggled', lambda *x: self.update_font_btn())
87
88 ## 'font'
89 font_box = gtk.HBox()
90 font_box.pack_start(gtk.Label('Font:'), False, False, 8)
91 self.fontbtn = gtk.FontButton(self.config.get('profile::'
92 + self.profile, 'font'))
93 font_box.pack_start(self.fontbtn, False, False, 8)
94 self.pack_start(font_box, False, False, 8)
95 ## 'bold_text'
96 self.bold_text = gtk.CheckButton(label=_('Allow bold text'))
97 self.pack_start(self.bold_text, False, False, 8)
98 self.bold_text.set_active(self.config.getboolean('profile::'
99 + self.profile, 'bold_text'))
100 ## 'antialias'
101 self.antialias = gtk.CheckButton(label=_('Anti-alias text'))
102 self.pack_start(self.antialias, False, False, 8)
103 self.antialias.set_active(self.config.getboolean('profile::'
104 + self.profile, 'antialias'))
105 ## 'sel_word'
106 sel_word_box = gtk.HBox()
107 sel_word_box.pack_start(gtk.Label('Select-by-word characters:'))
108 self.sel_word_text = gtk.Entry()
109 self.sel_word_text.set_text(self.config.get('profile::'
110 + self.profile, 'sel_word'))
111 sel_word_box.pack_start(self.sel_word_text, False, False, 0)
112 self.pack_start(sel_word_box, False, False, 8)
113 ## System subsection
114 sys_lbl = gtk.Label()
115 sys_lbl.set_markup('<b>System Configuration</b>')
116 self.pack_start(sys_lbl, False, False, 4)
117 ## 'update_login_records'
118 self.update_login_records = gtk.CheckButton(
119 label=_('Update login records'))
120 self.pack_start(self.update_login_records, False, False, 8)
121 self.update_login_records.set_active(
122 self.config.getboolean('profile::' + self.profile,
123 'update_login_records'))
124 self.update_font_btn()
125
126 def update_font_btn(self):
127 if self.systemfont.get_active():
128 self.fontbtn.set_sensitive(False)
129 else:
130 self.fontbtn.set_sensitive(True)
131
132 def save_changes(self):
133 if 'profile::' + self.profile in self.config.sections():
134 self.config.set('profile::' + self.profile,
135 'use_system_font', '%s' % self.systemfont.get_active())
136 self.config.set('profile::' + self.profile,
137 'font', '%s' % self.fontbtn.get_font_name())
138 self.config.set('profile::' + self.profile,
139 'bold_text', '%s' % self.bold_text.get_active())
140 self.config.set('profile::' + self.profile,
141 'antialias', '%s' % self.antialias.get_active())
142 self.config.set('profile::' + self.profile,
143 'sel_word', self.sel_word_text.get_text())
144 self.config.set('profile::' + self.profile,
145 'update_login_records',
146 '%s' % self.update_login_records.get_active())
147
148 def set_profile(self, profile='default'):
149 self.save_changes()
150 if profile != self.profile:
151 self.profile = profile
152 self.update()
153 self.show_all()
154
155 def update(self):
156 for child in self.get_children():
157 self.remove(child)
158 self.draw_all()
159
160
161class ProfColorsTab(gtk.VBox):
162 def __init__(self, config, profile='default'):
163 gtk.VBox.__init__(self)
164 self.config = config
165 self.profile = profile
166 self.draw_all()
167
168 def draw_all(self):
169 ## 'use_system_colors'
170 self.systemcols = gtk.CheckButton(
171 label=_('Use colors from system theme'))
172 self.pack_start(self.systemcols, False, False, 8)
173 self.systemcols.set_active(
174 self.config.getboolean('profile::' + self.profile,
175 'use_system_colors'))
176 self.systemcols.connect('toggled', lambda *x: self.update_sys_colors())
177
178 ## 'color_scheme'
179 hbox = gtk.HBox()
180 hbox.pack_start(gtk.Label('Color scheme:'), False, False, 8)
181 self.colsch_combo = gtk.combo_box_new_text()
182 color_scheme = self.config.get('profile::' + self.profile,
183 'color_scheme')
184 self.colsch_combo.append_text('Custom')
185 if color_scheme == 'Custom':
186 self.colsch_combo.set_active(0)
187 hbox.pack_start(self.colsch_combo, False, False, 8)
188 i = 0
189 for cs, colors in COLOR_SCHEMES.items():
190 i = i + 1
191 self.colsch_combo.append_text(cs)
192 if color_scheme == cs:
193 self.colsch_combo.set_active(i)
194 self.pack_start(hbox, False, False, 8)
195 self.colsch_combo.connect('changed',
196 lambda *x: self.update_color_btns())
197
198 ## 'colorf'
199 hbox = gtk.HBox()
200 hbox.pack_start(gtk.Label('Font color:'), False, False, 8)
201 self.colorf = gtk.ColorButton()
202 hbox.pack_start(self.colorf, False, False, 8)
203 self.pack_start(hbox, False, False, 8)
204 self.colorf.connect('color-set', lambda *x: self.update_custom_color())
205
206 ## 'colorb'
207 hbox = gtk.HBox()
208 hbox.pack_start(gtk.Label('Background color:'), False, False, 8)
209 self.colorb = gtk.ColorButton()
210 hbox.pack_start(self.colorb, False, False, 8)
211 self.pack_start(hbox, False, False, 8)
212 self.colorb.connect('color-set', lambda *x: self.update_custom_color())
213
214 self.update_sys_colors()
215
216 def update_sys_colors(self):
217 if not self.systemcols.get_active():
218 self.colsch_combo.set_sensitive(True)
219 self.update_color_btns()
220 else:
221 self.colsch_combo.set_sensitive(False)
222 self.update_color_btns()
223 self.colorb.set_sensitive(False)
224 self.colorf.set_sensitive(False)
225
226 def update_color_btns(self):
227 color_scheme = self.colsch_combo.get_active_text()
228 if color_scheme != 'Custom':
229 self.colorb.set_sensitive(False)
230 self.colorf.set_sensitive(False)
231 self.colorf.set_color(gtk.gdk.color_parse(
232 COLOR_SCHEMES[color_scheme][0]))
233 self.colorb.set_color(gtk.gdk.color_parse(
234 COLOR_SCHEMES[color_scheme][1]))
235 else:
236 self.colorb.set_sensitive(True)
237 self.colorf.set_sensitive(True)
238 self.colorf.set_color(gtk.gdk.color_parse(
239 self.config.get('profile::' + self.profile, 'colorf')))
240 self.colorb.set_color(gtk.gdk.color_parse(
241 self.config.get('profile::' + self.profile, 'colorb')))
242
243 def update_custom_color(self):
244 color_scheme = self.colsch_combo.get_active_text()
245 if color_scheme == 'Custom':
246 self.config.set('profile::' + self.profile, 'colorf',
247 color2hex(self.colorf.get_color()))
248 self.config.set('profile::' + self.profile, 'colorb',
249 color2hex(self.colorb.get_color()))
250
251 def save_changes(self):
252 if 'profile::' + self.profile in self.config.sections():
253 self.config.set('profile::' + self.profile, 'use_system_colors',
254 self.systemcols.get_active().__repr__())
255 self.config.set('profile::' + self.profile, 'color_scheme',
256 self.colsch_combo.get_active_text())
257 if self.colsch_combo.get_active_text() == 'Custom':
258 self.config.set('profile::' + self.profile, 'colorf',
259 color2hex(self.colorf.get_color()))
260 self.config.set('profile::' + self.profile, 'colorb',
261 color2hex(self.colorb.get_color()))
262
263 def set_profile(self, profile='default'):
264 self.save_changes()
265 if profile != self.profile:
266 self.profile = profile
267 self.update()
268 self.show_all()
269
270 def update(self):
271 for child in self.get_children():
272 self.remove(child)
273 self.draw_all()
274
275
276class ProfScrollingTab(gtk.VBox):
277 def __init__(self, config, profile='default'):
278 gtk.VBox.__init__(self)
279 self.config = config
280 self.profile = 'profile::' + profile
281 self.draw_all()
282
283 def draw_all(self):
284 hbox = gtk.HBox()
285 hbox.pack_start(gtk.Label('Number of history lines:'), False, False, 8)
286 self.scrollb_sb = gtk.SpinButton(
287 adjustment=gtk.Adjustment(upper=9999, step_incr=1))
288 self.scrollb_sb.set_wrap(True)
289 self.scrollb_sb.set_numeric(True)
290 self.scrollb_sb.set_value(self.config.getint(self.profile, 'scrollb'))
291 hbox.pack_start(self.scrollb_sb, False, False, 8)
292 self.pack_start(hbox, False, False, 8)
293
294 def set_profile(self, profile='default'):
295 if 'profile::' + profile != self.profile:
296 self.profile = 'profile::' + profile
297 self.update()
298 self.show_all()
299
300 def update(self):
301 for child in self.get_children():
302 self.remove(child)
303 self.draw_all()
304
305 def save_changes(self):
306 if self.profile in self.config.sections():
307 dbg('Setting scrollb to %d' % int(self.scrollb_sb.get_value()))
308 self.config.set(self.profile,
309 'scrollb',
310 str(int(self.scrollb_sb.get_value())))
311
312
313class ProfilesTab(gtk.HBox):
314 def __init__(self, config):
315 gtk.HBox.__init__(self)
316 self.config = config
317 self.tabs = []
318 self.gprofs = 0
319
320 vbox = gtk.VBox()
321 self.proflist = gtk.TreeView(gtk.ListStore(str))
322 self.init_proflist()
323 hbox = gtk.HBox()
324 add_btn = gtk.Button()
325 add_btn.add(self.get_img_box('Add', gtk.STOCK_ADD))
326 add_btn.connect('clicked', lambda *x: self.add_profile())
327 del_btn = gtk.Button()
328 del_btn.add(self.get_img_box('Remove', gtk.STOCK_DELETE))
329 del_btn.connect('clicked', lambda *x: self.del_profile())
330 hbox.pack_start(add_btn, False, False, 0)
331 hbox.pack_start(del_btn, False, False, 0)
332
333 vbox.pack_start(self.proflist, True, True, 0)
334 vbox.pack_start(hbox, False, False, 0)
335 self.pack_start(vbox)
336
337 self.tabs.append(('General', ProfGeneralTab(config)))
338 self.tabs.append(('Colors', ProfColorsTab(config)))
339 self.tabs.append(('Scrolling', ProfScrollingTab(config)))
340
341 self.options = gtk.Notebook()
342 for name, tab in self.tabs:
343 self.options.append_page(tab, gtk.Label(_(name)))
344
345 self.proflist.connect('cursor-changed', lambda *x: self.update_tabs())
346 self.pack_start(self.options)
347
348 def get_img_box(self, text, img):
349 box = gtk.HBox()
350 image = gtk.Image()
351 image.set_from_stock(img, gtk.ICON_SIZE_BUTTON)
352 label = gtk.Label(text)
353 box.pack_start(image, False, False, 0)
354 box.pack_start(label, False, False, 0)
355 return box
356
357 def add_text_col(self, colname, n=0):
358 col = gtk.TreeViewColumn()
359 col.set_title(_(colname))
360 self.render = gtk.CellRendererText()
361 self.render.connect('edited',
362 lambda cell, path, text: self.added_profile(path, text))
363 col.pack_start(self.render, expand=True)
364 col.add_attribute(self.render, 'text', n)
365 col.set_resizable(True)
366 col.set_sort_column_id(n)
367 self.proflist.append_column(col)
368
369 def init_proflist(self):
370 self.add_text_col('Profile')
371 model = self.proflist.get_model()
372 for section in self.config.sections():
373 if section.startswith('profile::'):
374 last = model.append((section[9:],))
375 if section == 'profile::default':
376 self.proflist.set_cursor_on_cell(
377 model.get_path(last),
378 self.proflist.get_column(0))
379 self.gprofs += 1
380
381 def update_tabs(self):
382 selection = self.proflist.get_selection()
383 model, iterator = selection.get_selected()
384 if not iterator:
385 return
386 profile = model.get(iterator, 0)[0]
387 for name, tab in self.tabs:
388 if 'profile::' + profile in self.config.sections():
389 tab.set_profile(profile)
390
391 def save_all(self):
392 for name, tab in self.tabs:
393 tab.save_changes()
394
395 def add_profile(self):
396 self.gprofs += 1
397 model = self.proflist.get_model()
398 iterator = model.append(('New Profile %d' % self.gprofs,))
399 self.render.set_property('editable', True)
400 self.proflist.grab_focus()
401 self.proflist.set_cursor_on_cell(model.get_path(iterator),
402 self.proflist.get_column(0),
403 start_editing=True)
404
405 def added_profile(self, path, text):
406 dbg('Added profile %s' % text)
407 if 'profile::' + text in self.config.sections():
408 return
409 model = self.proflist.get_model()
410 model[path][0] = text
411 self.render.set_property('editable', False)
412 self.config.add_section('profile::' + text)
413 self.update_tabs()
414
415 def del_profile(self):
416 selection = self.proflist.get_selection()
417 model, iterator = selection.get_selected()
418 profile = model.get(iterator, 0)[0]
419 if profile != 'default':
420 self.config.remove_section('profile::' + profile)
421 model.remove(iterator)
422
423
424class KeybindingsTab(gtk.VBox):
425 def __init__(self, config):
426 gtk.VBox.__init__(self)
427 self.config = config
428 self.draw_all()
429
430 def draw_all(self):
431 self.labels = []
432 for kb_func, kb_name in cc_config.KEY_BINDINGS.items():
433 hbox = gtk.HBox()
434 lbl = gtk.Label(_(kb_name))
435 self.labels.append(lbl)
436 btn = gtk.Button(self.config.get('keybindings', kb_func))
437 btn.connect('clicked',
438 lambda wg, func: self.get_key(func), kb_func)
439 btn.set_size_request(100, -1)
440 hbox.pack_start(btn, False, False, 8)
441 del_btn = gtk.Button()
442 del_img = gtk.Image()
443 del_img.set_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_BUTTON)
444 del_btn.add(del_img)
445 del_btn.connect('clicked',
446 lambda wg, func: self.get_key(func, 'not used'), kb_func)
447 hbox.pack_start(del_btn, False, False, 8)
448 hbox.pack_start(lbl, True, True, 8)
449 self.pack_start(hbox)
450
451 def update(self):
452 for child in self.children():
453 self.remove(child)
454 self.draw_all()
455 self.show_all()
456
457 def get_key(self, func, key=None):
458 if key != None:
459 self.config.set('keybindings', func, key)
460 self.update()
461 return
462 self.md = gtk.Dialog("Press the new key for '%s'" % func,
463 None,
464 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
465 lbl = gtk.Label('Press a key')
466 self.md.get_content_area().pack_start((lbl), True, True, 0)
467 lbl.set_size_request(100, 100)
468 self.md.connect('key-press-event',
469 lambda w, event: self.key_pressed(func, event, lbl))
470 self.md.connect('key-release-event',
471 lambda w, event: self.key_released(event, lbl))
472 self.md.show_all()
473 self.md.run()
474
475 def key_released(self, event, lbl):
476 keycomb = cc_utils.get_keycomb(event)
477 combname = ''
478 activemods = keycomb.split('+')[:-1]
479 released = keycomb.rsplit('+', 1)[-1]
480 mods = {'shift': 'shift',
481 'control': 'ctrl',
482 'alt': 'alt',
483 'super': 'super'}
484 for mod in mods.keys():
485 if mod in released.lower():
486 if mods[mod] in activemods:
487 activemods.pop(activemods.index(mods[mod]))
488 combname = '+'.join(activemods) + '+'
489 if combname == '+':
490 combname = 'Press a key'
491 lbl.set_text(combname)
492
493 def key_pressed(self, func, event, lbl):
494 keycomb = cc_utils.get_keycomb(event)
495 if not cc_utils.only_modifier(event):
496 self.md.destroy()
497 self.config.set('keybindings', func, keycomb)
498 self.update()
499 else:
500 combname = ''
501 activemods = keycomb.split('+')[:-1]
502 pressed = keycomb.rsplit('+', 1)[-1]
503 mods = {'shift': 'shift',
504 'control': 'ctrl',
505 'alt': 'alt',
506 'super': 'super'}
507 for mod in mods.keys():
508 if mod in pressed.lower():
509 if mods[mod] not in activemods:
510 activemods.append(mods[mod])
511 combname = '+'.join(activemods) + '+'
512 if combname == '+':
513 combname = 'Press a key'
514 lbl.set_text(combname)
515
516 def save_all(self):
517 pass
518
519
520class PluginsTab(gtk.HBox):
521 __gsignals__ = {
522 'changed-plugin': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
523 (str, ))
524 }
525
526 def __init__(self, config, plugins):
527 gtk.HBox.__init__(self)
528 self.config = config
529 self.plugins = plugins
530
531 self.pluginlist = gtk.TreeView(gtk.ListStore(bool, str))
532 first = self.init_pluginlist()
533 self.pack_start(self.pluginlist, False, False, 8)
534
535 self.infonb = gtk.Notebook()
536 self.generate_infotabs(first)
537 self.pack_start(self.infonb, True, True, 8)
538
539 self.pluginlist.connect('row_activated',
540 lambda *x: self.toggle_plugin())
541 self.pluginlist.connect('cursor-changed',
542 lambda *x: self.update_info())
543
544 def init_pluginlist(self):
545 self.add_cbox_col('Enabled', 0)
546 self.add_text_col('Plugin Name', 1)
547 model = self.pluginlist.get_model()
548 first = None
549 for plugin in self.plugins.get_allowed():
550 if not first:
551 first = plugin
552 model.append((True, plugin))
553 for plugin in self.plugins.get_disallowed():
554 if not first:
555 first = plugin
556 model.append((False, plugin))
557 self.pluginlist.set_cursor((0,))
558 return first
559
560 def add_cbox_col(self, colname, n=0):
561 col = gtk.TreeViewColumn()
562 col.set_title(_(colname))
563 render = gtk.CellRendererToggle()
564 col.pack_start(render, expand=True)
565 col.add_attribute(render, 'active', n)
566 col.set_resizable(True)
567 col.set_sort_column_id(n)
568 self.pluginlist.append_column(col)
569
570 def add_text_col(self, colname, n=0):
571 col = gtk.TreeViewColumn()
572 col.set_title(_(colname))
573 render = gtk.CellRendererText()
574 col.pack_start(render, expand=True)
575 col.add_attribute(render, 'text', n)
576 col.set_resizable(True)
577 col.set_sort_column_id(n)
578 self.pluginlist.append_column(col)
579
580 def toggle_plugin(self):
581 selection = self.pluginlist.get_selection()
582 model, iterator = selection.get_selected()
583 oldvalue, plugin = model.get(iterator, 0, 1)
584 if plugin == 'LocalCommandList':
585 self.show_warning()
586 return
587 model.set(iterator, 0, not oldvalue)
588
589 def generate_infotabs(self, plugin):
590 '''
591 Adds the plugins info and config page (if any) to the plugins info
592 notebook
593 '''
594 confplg = self.plugins.get_plugin_conf(plugin)
595
596 if confplg:
597 conf_tab = confplg(self.config.get_plugin_conf(plugin))
598 self.infonb.append_page(
599 conf_tab,
600 gtk.Label(_('Configutarion')))
601 conf_tab.connect('reload',
602 lambda wg, pl: self.emit('changed-plugin', plugin),
603 plugin)
604
605 self.infonb.append_page(self.generate_infopage(plugin),
606 gtk.Label(_('About')))
607
608 def generate_infopage(self, plugin):
609 '''
610 Generates the plugins info page
611 '''
612 info = self.plugins.get_info(plugin)
613 authors = self.plugins.get_authors(plugin)
614 page = gtk.TextView()
615 page.set_wrap_mode(gtk.WRAP_WORD)
616 page.set_editable(False)
617 buffer = page.get_buffer()
618 scrolled_window = gtk.ScrolledWindow()
619 scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
620 scrolled_window.add(page)
621 iter = buffer.get_iter_at_offset(0)
622 buffer.insert(iter, info + '\n\nAuthors:\n' + authors)
623 return scrolled_window
624
625 def update_info(self):
626 selection = self.pluginlist.get_selection()
627 model, iterator = selection.get_selected()
628 enabled, plugin = model.get(iterator, 0, 1)
629 for child in self.infonb.get_children():
630 self.infonb.remove(child)
631 self.generate_infotabs(plugin)
632 self.show_all()
633
634 def save_all(self):
635 newenabled = []
636 model = self.pluginlist.get_model()
637 elem = model.get_iter_first()
638 while elem:
639 enabled, plugin = model.get(elem, 0, 1)
640 if enabled:
641 newenabled.append(plugin)
642 elem = model.iter_next(elem)
643 self.config.set('general::default', 'plugins', ', '.join(newenabled))
644
645 def show_warning(self):
646 dlg = gtk.MessageDialog(
647 None,
648 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
649 gtk.MESSAGE_ERROR,
650 gtk.BUTTONS_CLOSE,
651 message_format=_('Can\'t disable "LocalCommandList'))
652 dlg.format_secondary_text(_('The plugin "LocalCommandList is the main'
653 ' plugin for CLI Companion, and can\'t be disalbed, sorry.'))
654 dlg.run()
655 dlg.destroy()
656
657
658class PreferencesWindow(gtk.Dialog):
659 '''
660 Preferences window, the tabs are needed for the preview
661 '''
662 __gsignals__ = {
663 'preview': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
664 (str, str))
665 }
666
667 def __init__(self, config, plugins):
668 gtk.Dialog.__init__(self, _("User Preferences"),
669 None,
670 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
671 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
672 gtk.STOCK_OK, gtk.RESPONSE_OK))
673 self.config = config
674 self.plugins = plugins
675 self.config_bkp = self.config.get_config_copy()
676 self.changed_plugins = []
677
678 mainwdg = self.get_content_area()
679 self.tabs = gtk.Notebook()
680 mainwdg.pack_start(self.tabs, True, True, 0)
681
682 ## profiles
683 proftab = ProfilesTab(config)
684 self.tabs.append_page(proftab, gtk.Label('Profiles'))
685 ## keybindings
686 keybind_tab = KeybindingsTab(config)
687 self.tabs.append_page(keybind_tab, gtk.Label('Keybindings'))
688 ## plugins
689 plug_tab = PluginsTab(config, plugins)
690 plug_tab.connect('changed-plugin',
691 lambda wg, pl: self.mark_changed_plugins(pl))
692 self.tabs.append_page(plug_tab, gtk.Label('Plugins'))
693
694 def mark_changed_plugins(self, plugin):
695 if plugin not in self.changed_plugins:
696 self.changed_plugins.append(plugin)
697
698 def run(self):
699 self.show_all()
700 response = gtk.Dialog.run(self)
701 if response == gtk.RESPONSE_OK:
702 for i in range(self.tabs.get_n_pages()):
703 self.tabs.get_nth_page(i).save_all()
704 config = self.config
705 else:
706 config = None
707 self.destroy()
708 return config, self.changed_plugins
0709
=== modified file 'clicompanionlib/tabs.py'
--- clicompanionlib/tabs.py 2012-01-02 00:23:11 +0000
+++ clicompanionlib/tabs.py 2012-01-08 10:37:24 +0000
@@ -1,6 +1,9 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2#2# -*- coding: utf-8 -*-
3# Copyright 2010 Duane Hinnen, Kenny Meyer, Marcos Vanetta3#
4# tabs.py - Terminal tab handling classes for clicompanion
5#
6# Copyright 2010 Duane Hinnen, Kenny Meyer, Marcos Vanetta, David Caro
4#7#
5# This program is free software: you can redistribute it and/or modify it8# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published9# under the terms of the GNU General Public License version 3, as published
@@ -15,6 +18,14 @@
15# with this program. If not, see <http://www.gnu.org/licenses/>.18# with this program. If not, see <http://www.gnu.org/licenses/>.
16#19#
17#20#
21# This file contains the classes used to provide the terminals secion of the
22# main window.
23#
24# TerminalTab: This class implements one tab of the terminals notebook, with
25# it's own profile and vte associated.
26#
27# TerminalsNotebook: This class implements the notebook, where the terminals
28# will be added.
18#29#
1930
20import os31import os
@@ -22,157 +33,388 @@
22pygtk.require('2.0')33pygtk.require('2.0')
23import gtk34import gtk
24import vte35import vte
25import clicompanionlib.config as cc_config36import re
26
27from clicompanionlib.utils import get_user_shell, dbg
28import clicompanionlib.controller
29import clicompanionlib.utils as utils
30import view37import view
3138import gobject
3239import pango
33class Tabs(object):40import gconf
34 '''41from clicompanionlib.utils import dbg
35 add a new terminal in a tab above the current terminal42import clicompanionlib.utils as cc_utils
36 '''43import clicompanionlib.helpers as cc_helpers
37 def __init__(self):44import clicompanionlib.preferences as cc_pref
38 #definition nop - (no of pages) reflects no of terminal tabs left (some may be closed by the user)45
39 self.nop = 046
40 #definition gcp - how many pages is visible47class TerminalTab(gtk.ScrolledWindow):
48 __gsignals__ = {
49 'quit': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
50 ()),
51 'add_tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
52 ()),
53 'rename': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
54 (str, )),
55 'preferences': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
56 ()),
57 }
58
59 def __init__(self, title, config, profile='default'):
60 gtk.ScrolledWindow.__init__(self)
61 self.config = config
62 self.title = title
63 self.profile = 'profile::' + profile
64 self.vte = vte.Terminal()
65 self.add(self.vte)
66 self.vte.connect("child-exited", lambda *x: self.emit('quit'))
67 self.update_records = self.config.getboolean(self.profile,
68 'update_login_records')
69 dbg('Updating login records: ' + self.update_records.__repr__())
70 self.vte.fork_command(cc_utils.shell_lookup(),
71 logutmp=self.update_records,
72 logwtmp=self.update_records,
73 loglastlog=self.update_records)
74 self.vte.connect("button_press_event", self.copy_paste_menu)
75 self.update_config()
76 self.show_all()
77
78 def update_config(self, config=None, preview=False):
79 if not config:
80 config = self.config
81 elif not preview:
82 self.config = config
83 if self.profile not in config.sections():
84 self.profile = 'profile::default'
85 if self.profile not in config.sections():
86 config.add_section(self.profile)
87 dbg(self.profile)
88 dbg(','.join([config.get(self.profile, option)
89 for option in config.options(self.profile)]))
90
91 ## Scrollback
92 try:
93 config_scrollback = config.getint(self.profile, 'scrollb')
94 except ValueError:
95 print _("WARNING: Invalid value for property '%s', int expected:"
96 " got '%s', using default '%s'") % (
97 'scrollb',
98 config.get(self.profile, 'scrollb'),
99 config.get('DEFAULT', 'scrollb'))
100 config.set(self.profile, 'scrollb',
101 config.get('DEFAULT', 'scrollb'))
102 config_scrollback = config.getint('DEFAULT', 'scrollb')
103 self.vte.set_scrollback_lines(config_scrollback)
104
105 color = ('#2e3436:#cc0000:#4e9a06:#c4a000:#3465a4:#75507b:#06989a:'
106 '#d3d7cf:#555753:#ef2929:#8ae234:#fce94f:#729fcf:#ad7fa8:'
107 '#34e2e2:#eeeeec')
108 colors = color.split(':')
109 palette = []
110 for color in colors:
111 if color:
112 palette.append(gtk.gdk.color_parse(color))
113
114 #### Colors
115 if config.getboolean(self.profile, 'use_system_colors'):
116 config_color_fore = self.vte.get_style().text[gtk.STATE_NORMAL]
117 config_color_back = self.vte.get_style().base[gtk.STATE_NORMAL]
118 else:
119 color_scheme = config.get(self.profile, 'color_scheme')
120 if color_scheme != 'Custom':
121 fgcolor, bgcolor = cc_pref.COLOR_SCHEMES[color_scheme]
122 else:
123 fgcolor = config.get(self.profile, 'colorf')
124 bgcolor = config.get(self.profile, 'colorb')
125
126 try:
127 config_color_fore = gtk.gdk.color_parse(fgcolor)
128 except ValueError, e:
129 print _("WARNING: Invalid value for property '%s':"
130 " got '%s', using default '%s'.") % (
131 'colorf',
132 fgcolor,
133 config.get('DEFAULT', 'colorf'))
134 config.set(self.profile, 'colorf',
135 config.get('DEFAULT', 'colorf'))
136 config_color_fore = gtk.gdk.color_parse(config.get('DEFAULT',
137 'colorf'))
138
139 try:
140 config_color_back = gtk.gdk.color_parse(bgcolor)
141 except ValueError, e:
142 print _("WARNING: Invalid value for property '%s':"
143 " got '%s', using default '%s'.") % (
144 'colorb',
145 bgcolor,
146 config.get('DEFAULT', 'colorb'))
147 config.set(self.profile, 'colorb',
148 config.get('DEFAULT', 'colorb'))
149 config_color_back = gtk.gdk.color_parse(config.get('DEFAULT',
150 'colorb'))
151 self.vte.set_colors(config_color_fore, config_color_back, palette)
152
153 ### Encoding
154 config_encoding = config.get(self.profile, 'encoding')
155 if config_encoding.upper() not in [enc.upper()
156 for enc, desc
157 in cc_utils.encodings]:
158 print _("WARNING: Invalid value for property '%s':"
159 " got '%s', using default '%s'") \
160 % ('encoding', config_encoding,
161 config.get('DEFAULT', 'encoding'))
162 config.set(self.profile, 'encoding',
163 config.get('DEFAULT', 'encoding'))
164 config_encoding = config.get('DEFAULT', 'encoding')
165 self.vte.set_encoding(config_encoding)
166
167 ## Font
168 if config.getboolean(self.profile, 'use_system_font'):
169 fontname = cc_utils.get_system_font(
170 lambda *x: self.update_config())
171 else:
172 fontname = config.get(self.profile, 'font')
173 font = pango.FontDescription(fontname)
174 if not font or not fontname:
175 print _("WARNING: Invalid value for property '%s':"
176 " got '%s', using default '%s'") % (
177 'font',
178 fontname,
179 cc_utils.get_system_font())
180 config.set('DEFAULT', 'font', c_utils.get_system_font())
181 fontname = config.get('DEFAULT', 'font')
182 font = pango.FontDescription(fontname)
183 if font:
184 self.vte.set_font_full(font,
185 config.getboolean(self.profile, 'antialias'))
186
187 update_records = config.getboolean(self.profile,
188 'update_login_records')
189 if update_records != self.update_records:
190 if not preview:
191 self.update_records = update_records
192 dbg('Updating login records: ' + update_records.__repr__())
193 self.vte.feed('\n\r')
194 self.vte.fork_command(cc_utils.shell_lookup(),
195 logutmp=update_records,
196 logwtmp=update_records,
197 loglastlog=update_records)
198
199 self.vte.set_allow_bold(config.getboolean(self.profile, 'bold_text'))
200 self.vte.set_word_chars(config.get(self.profile, 'sel_word'))
201
202 def copy_paste_menu(self, vte, event):
203 if event.button == 3:
204 time = event.time
205 ## right-click popup menu Copy
206 popupMenu = gtk.Menu()
207 menuPopup1 = gtk.ImageMenuItem(gtk.STOCK_COPY)
208 popupMenu.add(menuPopup1)
209 menuPopup1.connect('activate', lambda x: vte.copy_clipboard())
210 ## right-click popup menu Paste
211 menuPopup2 = gtk.ImageMenuItem(gtk.STOCK_PASTE)
212 popupMenu.add(menuPopup2)
213 menuPopup2.connect('activate', lambda x: vte.paste_clipboard())
214 ## right-click popup menu Rename
215 menuPopup3 = gtk.ImageMenuItem(gtk.STOCK_EDIT)
216 menuPopup3.set_label(_('Rename'))
217 popupMenu.add(menuPopup3)
218 menuPopup3.connect('activate', lambda x: self.rename())
219 ## right-click popup menu Add Tab
220 menuPopup3 = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
221 menuPopup3.set_label(_('Configure'))
222 popupMenu.add(menuPopup3)
223 menuPopup3.connect('activate', lambda x: self.emit('preferences'))
224 ## right-click popup menu Add Tab
225 menuPopup3 = gtk.ImageMenuItem(gtk.STOCK_ADD)
226 menuPopup3.set_label(_('Add tab'))
227 popupMenu.add(menuPopup3)
228 menuPopup3.connect('activate', lambda x: self.emit('add_tab'))
229 ## right-click popup menu Profiles
230 menuit_prof = gtk.MenuItem()
231 menuit_prof.set_label(_('Profiles'))
232 submenu_prof = gtk.Menu()
233 menuit_prof.set_submenu(submenu_prof)
234 popupMenu.add(menuit_prof)
235 for section in self.config.sections():
236 if section.startswith('profile::'):
237 subitem = gtk.MenuItem()
238 subitem.set_label(_(section[9:]))
239 submenu_prof.add(subitem)
240 subitem.connect('activate',
241 lambda wg, *x: self.change_profile(
242 wg.get_label()))
243 ## right-click popup menu Close Tab
244 menuPopup3 = gtk.ImageMenuItem(gtk.STOCK_CLOSE)
245 menuPopup3.set_label(_('Close tab'))
246 popupMenu.add(menuPopup3)
247 menuPopup3.connect('activate', lambda x: self.emit('quit'))
248 ## Show popup menu
249 popupMenu.show_all()
250 popupMenu.popup(None, None, None, event.button, time)
251 return True
252
253 def rename(self):
254 dlg = gtk.Dialog("Enter the new name",
255 None,
256 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
257 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
258 gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
259 entry = gtk.Entry()
260 entry.connect("activate", lambda *x: dlg.response(gtk.RESPONSE_ACCEPT))
261 entry.set_text(self.title)
262 dlg.vbox.pack_start(entry)
263 dlg.show_all()
264 response = dlg.run()
265 if response == gtk.RESPONSE_ACCEPT:
266 text = entry.get_text()
267 self.emit('rename', text)
268 dlg.destroy()
269
270 def run_command(self, cmd, ui, desc):
271 if not cmd:
272 dbg('Empty command... doing nothing')
273 return
274 '''
275 Make sure user arguments were found. Replace ? with something
276 .format can read. This is done so the user can just enter ?, when
277 adding a command where arguments are needed, instead
278 of {0[1]}, {0[1]}, {0[2]}
279 '''
280 ## find how many ?(user arguments) are in command
281 match = re.findall('\?', cmd)
282 if match:
283 num = len(match)
284 ran = 0
285 new_cmd = cc_utils.replace(cmd, num, ran)
286
287 if len(match) > 0: # command with user input
288 dbg('command with ui')
289 cmd_info_win = cc_helpers.CommandInfoWindow(new_cmd, ui, desc)
290 cmd = cmd_info_win.run()
291 if cmd == None:
292 return
293 self.vte.feed_child(cmd + "\n") # send command
294 self.show()
295 self.grab_focus()
296
297 def change_profile(self, profile):
298 dbg(profile)
299 self.profile = 'profile::' + profile
300 self.update_config()
301
302
303class TerminalsNotebook(gtk.Notebook):
304 __gsignals__ = {
305 'quit': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
306 ()),
307 'preferences': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
308 ()),
309 }
310
311 def __init__(self, config):
312 gtk.Notebook.__init__(self)
313 #definition gcp - global page count, how many pages have been created
41 self.gcp = 0314 self.gcp = 0
42315 self.global_config = config
43 def add_tab(self, notebook):316 ## The "Add Tab" tab
317 add_tab_button = gtk.Button("+")
318 ## tooltip for "Add Tab" tab
319 add_tab_button.set_tooltip_text(_("Click to add another tab"))
320 ## create first tab
321 self.append_page(gtk.Label(""), add_tab_button)
322 add_tab_button.connect("clicked", lambda *x: self.add_tab())
323 self.set_size_request(700, 120)
324
325 def focus(self):
326 num = self.get_current_page()
327 self.get_nth_page(num).vte.grab_focus()
328
329 def add_tab(self, title=None):
44 dbg('Adding a new tab')330 dbg('Adding a new tab')
45 _vte = vte.Terminal()
46 if view.NETBOOKMODE == 1:
47 _vte.set_size_request(700, 120)
48 else:
49 _vte.set_size_request(700, 220)
50
51 _vte.connect("child-exited", lambda term: gtk.main_quit())
52 _vte.fork_command(get_user_shell()) # Get the user's default shell
53
54 self.update_term_config(_vte)
55
56 vte_tab = gtk.ScrolledWindow()
57 vte_tab.add(_vte)
58 #notebook.set_show_tabs(True)
59 #notebook.set_show_border(True)
60
61 self.nop += 1
62 self.gcp += 1331 self.gcp += 1
63 pagenum = ('Tab %d') % self.gcp332 if title == None:
64 if self.nop > 1:333 title = 'Tab %d' % self.gcp
334
335 newtab = TerminalTab(title, self.global_config)
336
337 if self.get_n_pages() > 1:
65 dbg('More than one tab, showing them.')338 dbg('More than one tab, showing them.')
66 view.MainWindow.notebook.set_show_tabs(True)339 self.set_show_tabs(True)
340 label = self.create_tab_label(title, newtab)
341 self.insert_page(newtab, label, self.get_n_pages() - 1)
342 self.set_current_page(self.get_n_pages() - 2)
343 self.set_scrollable(True)
344 # signal handler for tab
345 newtab.connect("quit", lambda *x: self.quit_tab(newtab))
346 newtab.connect("add_tab", lambda *x: self.add_tab())
347 newtab.connect("preferences", lambda *x: self.emit('preferences'))
348 newtab.connect("rename",
349 lambda wg, text: self.rename_tab(newtab, text))
350 self.focus()
351 return newtab
352
353 def create_tab_label(self, title, tab):
354 ## Create the tab's labe with button
67 box = gtk.HBox()355 box = gtk.HBox()
68 label = gtk.Label(pagenum)356 label = gtk.Label(title)
69 box.pack_start(label, True, True)357 box.pack_start(label, True, True)
70
71
72 ## x image for tab close button358 ## x image for tab close button
73 close_image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)359 close_image = gtk.image_new_from_stock(gtk.STOCK_CLOSE,
360 gtk.ICON_SIZE_MENU)
74 ## close button361 ## close button
75 closebtn = gtk.Button()362 closebtn = gtk.Button()
76 closebtn.set_relief(gtk.RELIEF_NONE)363 closebtn.set_relief(gtk.RELIEF_NONE)
77 closebtn.set_focus_on_click(True)364 closebtn.set_focus_on_click(True)
78
79 closebtn.add(close_image)365 closebtn.add(close_image)
80 ## put button in a box and show box366 ## put button in a box and show box
81 box.pack_end(closebtn, False, False)367 box.pack_end(closebtn, False, False)
82 box.show_all()368 box.show_all()
83369 closebtn.connect("clicked", lambda *x: self.close_tab(tab))
84 view.MainWindow.notebook.prepend_page(vte_tab, box) # add tab370 return box
85 view.MainWindow.notebook.set_scrollable(True)371
86 actions = clicompanionlib.controller.Actions()372 def rename_tab(self, tab, newname):
87 _vte.connect("button_press_event", actions.copy_paste, None)373 dbg('Renaming tab to %s' % newname)
88 vte_tab.grab_focus()374 label = self.create_tab_label(newname, tab)
89 # signal handler for tab375 self.set_tab_label(tab, label)
90 closebtn.connect("clicked", lambda *x: self.close_tab(vte_tab, notebook))376 self.focus()
91
92 vte_tab.show_all()
93
94 return vte_tab
95
96377
97 ## Remove a page from the notebook378 ## Remove a page from the notebook
98 def close_tab(self, vte_tab, notebook):379 def close_tab(self, tab):
99 ## get the page number of the tab we wanted to close380 ## get the page number of the tab we wanted to close
100 pagenum = view.MainWindow.notebook.page_num(vte_tab)381 pagenum = self.page_num(tab)
101 ## and close it382 ## and close it
102 view.MainWindow.notebook.remove_page(pagenum)383 self.remove_page(pagenum)
103 self.nop -= 1384 if self.get_n_pages() < 3:
104 if self.nop <= 1:385 self.set_show_tabs(False)
105 view.MainWindow.notebook.set_show_tabs(False)386
106 387 # check if the focus does not go to the last page (ie with only a +
107 # check if the focus does not go to the last page (ie with only a + sign)388 # sign)
108 if view.MainWindow.notebook.get_current_page() == self.nop:389 if self.get_current_page() == self.get_n_pages() - 1:
109 view.MainWindow.notebook.prev_page()390 self.prev_page()
110 391 if self.get_n_pages() != 1:
392 self.focus()
393
394 def quit_tab(self, tab=None):
395 if not tab:
396 tab = self.get_nth_page(self.get_current_page())
397 self.close_tab(tab)
398 if self.get_n_pages() == 1:
399 self.emit('quit')
400
401 def run_command(self, cmd, ui, desc):
402 ## get the current notebook page so the function knows which terminal
403 ## to run the command in.
404 pagenum = self.get_current_page()
405 page = self.get_nth_page(pagenum)
406 page.run_command(cmd, ui, desc)
407 self.focus()
408
111 def update_all_term_config(self, config=None):409 def update_all_term_config(self, config=None):
112 for pagenum in range(view.MainWindow.notebook.get_n_pages()):410 self.global_config = config
113 page = view.MainWindow.notebook.get_nth_page(pagenum)411 for pagenum in range(self.get_n_pages()):
114 dbg(page)412 page = self.get_nth_page(pagenum)
115 if isinstance(page, gtk.ScrolledWindow):413 if isinstance(page, TerminalTab):
116 for grandson in page.get_children():414 self.update_term_config(page, config)
117 dbg(grandson)415
118 if isinstance(grandson,vte.Terminal):416 def update_term_config(self, tab, config=None):
119 self.update_term_config(grandson, config)
120
121 def update_term_config(self, _vte, config=None):
122 ##set terminal preferences from conig file data417 ##set terminal preferences from conig file data
123 if not config:418 if not config:
124 config = cc_config.get_config()419 config = self.global_config
125 try:420 tab.update_config(config)
126 config_scrollback = config.getint('terminal', 'scrollb')
127 except ValueError:
128 print _("WARNING: Invalid value for property 'terminal', int expected:"
129 " got '%s', using default '%s'")%(
130 config.get('terminal', 'scrollb'),
131 config.get('DEFAULT', 'scrollb'))
132 config.set('terminal','scrollb',config.get('DEFAULT', 'scrollb'))
133 config_scrollback = config.getint('DEFAULT', 'scrollb')
134 _vte.set_scrollback_lines(config_scrollback)
135
136 color = '#2e3436:#cc0000:#4e9a06:#c4a000:#3465a4:#75507b:#06989a:#d3d7cf:#555753:#ef2929:#8ae234:#fce94f:#729fcf:#ad7fa8:#34e2e2:#eeeeec'
137 colors = color.split(':')
138 palette = []
139 for color in colors:
140 if color:
141 palette.append(gtk.gdk.color_parse(color))
142
143 try:
144 config_color_fore = gtk.gdk.color_parse(config.get('terminal', 'colorf'))
145 except ValueError, e:
146 print _("WARNING: Invalid value for property '%s':"
147 " got '%s', using default '%s'.")%(
148 'colorf',
149 config.get('terminal', 'colorf'),
150 config.get('DEFAULT', 'colorf'))
151 config.set('terminal','colorf',config.get('DEFAULT', 'colorf'))
152 config_color_fore = gtk.gdk.color_parse(config.get('DEFAULT', 'colorf'))
153
154 try:
155 config_color_back = gtk.gdk.color_parse(config.get('terminal', 'colorb'))
156 except ValueError, e:
157 print _("WARNING: Invalid value for property '%s':"
158 " got '%s', using default '%s'.")%(
159 'colorb',
160 config.get('terminal', 'colorb'),
161 config.get('DEFAULT', 'colorb'))
162 config.set('terminal','colorb',config.get('DEFAULT', 'colorb'))
163 config_color_back = gtk.gdk.color_parse(config.get('DEFAULT', 'colorb'))
164 _vte.set_colors(config_color_fore, config_color_back, palette)
165
166 config_encoding = config.get('terminal', 'encoding')
167 if config_encoding.upper() not in [ enc.upper() for enc, desc in utils.encodings]:
168 print _("WARNING: Invalid value for property '%s':"
169 " got '%s', using default '%s'")%(
170 'encoding',
171 config_encoding,
172 config.get('DEFAULT', 'encoding'))
173 config.set('terminal','encoding',config.get('DEFAULT', 'encoding'))
174 config_encoding = config.get('DEFAULT', 'encoding')
175 _vte.set_encoding(config_encoding)
176
177
178
179421
=== modified file 'clicompanionlib/utils.py'
--- clicompanionlib/utils.py 2012-01-02 00:23:11 +0000
+++ clicompanionlib/utils.py 2012-01-08 10:37:24 +0000
@@ -1,9 +1,9 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2# -*- coding: utf-8 -*-2# -*- coding: utf-8 -*-
3#3#
4# clicompanion.py - commandline tool.4# utils.py - Some helpful functions for clicompanion
5#5#
6# Copyright 2010 Duane Hinnen, Kenny Meyer, Marcos Vanetta6# Copyright 2010 Duane Hinnen, Kenny Meyer, Marcos Vanetta, David Caro
7#7#
8# This program is free software: you can redistribute it and/or modify it8# This program is free software: you can redistribute it and/or modify it
9# under the terms of the GNU General Public License version 3, as published9# under the terms of the GNU General Public License version 3, as published
@@ -18,17 +18,19 @@
18# with this program. If not, see <http://www.gnu.org/licenses/>.18# with this program. If not, see <http://www.gnu.org/licenses/>.
19#19#
20#20#
2121# In this file are implemented some functions that do not need any clicompanion
22"""22# class and can somehow be useful in more than one module.
23A collection of useful functions.23
24"""
25
26import getpass
27import os24import os
28import sys 25import sys
29import gtk 26import gtk
30import pwd 27import pwd
31import inspect28import inspect
29import re
30try:
31 import gconf
32except ImportError:
33 pass
3234
3335
34## set to True if you want to see more logs36## set to True if you want to see more logs
@@ -37,95 +39,97 @@
37DEBUGCLASSES = []39DEBUGCLASSES = []
38DEBUGMETHODS = []40DEBUGMETHODS = []
3941
42gconf_cli = None
43
40## list gotten from terminator (https://launchpad.net/terminator)44## list gotten from terminator (https://launchpad.net/terminator)
41encodings = [45encodings = [
42 ["ISO-8859-1", _("Western")],46 ["ISO-8859-1", _("Western")],
43 ["ISO-8859-2", _("Central European")],47 ["ISO-8859-2", _("Central European")],
44 ["ISO-8859-3", _("South European") ],48 ["ISO-8859-3", _("South European")],
45 ["ISO-8859-4", _("Baltic") ],49 ["ISO-8859-4", _("Baltic")],
46 ["ISO-8859-5", _("Cyrillic") ],50 ["ISO-8859-5", _("Cyrillic")],
47 ["ISO-8859-6", _("Arabic") ],51 ["ISO-8859-6", _("Arabic")],
48 ["ISO-8859-7", _("Greek") ],52 ["ISO-8859-7", _("Greek")],
49 ["ISO-8859-8", _("Hebrew Visual") ],53 ["ISO-8859-8", _("Hebrew Visual")],
50 ["ISO-8859-8-I", _("Hebrew") ],54 ["ISO-8859-8-I", _("Hebrew")],
51 ["ISO-8859-9", _("Turkish") ],55 ["ISO-8859-9", _("Turkish")],
52 ["ISO-8859-10", _("Nordic") ],56 ["ISO-8859-10", _("Nordic")],
53 ["ISO-8859-13", _("Baltic") ],57 ["ISO-8859-13", _("Baltic")],
54 ["ISO-8859-14", _("Celtic") ],58 ["ISO-8859-14", _("Celtic")],
55 ["ISO-8859-15", _("Western") ],59 ["ISO-8859-15", _("Western")],
56 ["ISO-8859-16", _("Romanian") ],60 ["ISO-8859-16", _("Romanian")],
57 # ["UTF-7", _("Unicode") ],61 # ["UTF-7", _("Unicode")],
58 ["UTF-8", _("Unicode") ],62 ["UTF-8", _("Unicode")],
59 # ["UTF-16", _("Unicode") ],63 # ["UTF-16", _("Unicode")],
60 # ["UCS-2", _("Unicode") ],64 # ["UCS-2", _("Unicode")],
61 # ["UCS-4", _("Unicode") ],65 # ["UCS-4", _("Unicode")],
62 ["ARMSCII-8", _("Armenian") ],66 ["ARMSCII-8", _("Armenian")],
63 ["BIG5", _("Chinese Traditional") ],67 ["BIG5", _("Chinese Traditional")],
64 ["BIG5-HKSCS", _("Chinese Traditional") ],68 ["BIG5-HKSCS", _("Chinese Traditional")],
65 ["CP866", _("Cyrillic/Russian") ],69 ["CP866", _("Cyrillic/Russian")],
66 ["EUC-JP", _("Japanese") ],70 ["EUC-JP", _("Japanese")],
67 ["EUC-KR", _("Korean") ],71 ["EUC-KR", _("Korean")],
68 ["EUC-TW", _("Chinese Traditional") ],72 ["EUC-TW", _("Chinese Traditional")],
69 ["GB18030", _("Chinese Simplified") ],73 ["GB18030", _("Chinese Simplified")],
70 ["GB2312", _("Chinese Simplified") ],74 ["GB2312", _("Chinese Simplified")],
71 ["GBK", _("Chinese Simplified") ],75 ["GBK", _("Chinese Simplified")],
72 ["GEORGIAN-PS", _("Georgian") ],76 ["GEORGIAN-PS", _("Georgian")],
73 ["HZ", _("Chinese Simplified") ],77 ["HZ", _("Chinese Simplified")],
74 ["IBM850", _("Western") ],78 ["IBM850", _("Western")],
75 ["IBM852", _("Central European") ],79 ["IBM852", _("Central European")],
76 ["IBM855", _("Cyrillic") ],80 ["IBM855", _("Cyrillic")],
77 ["IBM857", _("Turkish") ],81 ["IBM857", _("Turkish")],
78 ["IBM862", _("Hebrew") ],82 ["IBM862", _("Hebrew")],
79 ["IBM864", _("Arabic") ],83 ["IBM864", _("Arabic")],
80 ["ISO-2022-JP", _("Japanese") ],84 ["ISO-2022-JP", _("Japanese")],
81 ["ISO-2022-KR", _("Korean") ],85 ["ISO-2022-KR", _("Korean")],
82 ["EUC-TW", _("Chinese Traditional") ],86 ["EUC-TW", _("Chinese Traditional")],
83 ["GB18030", _("Chinese Simplified") ],87 ["GB18030", _("Chinese Simplified")],
84 ["GB2312", _("Chinese Simplified") ],88 ["GB2312", _("Chinese Simplified")],
85 ["GBK", _("Chinese Simplified") ],89 ["GBK", _("Chinese Simplified")],
86 ["GEORGIAN-PS", _("Georgian") ],90 ["GEORGIAN-PS", _("Georgian")],
87 ["HZ", _("Chinese Simplified") ],91 ["HZ", _("Chinese Simplified")],
88 ["IBM850", _("Western") ],92 ["IBM850", _("Western")],
89 ["IBM852", _("Central European") ],93 ["IBM852", _("Central European")],
90 ["IBM855", _("Cyrillic") ],94 ["IBM855", _("Cyrillic")],
91 ["IBM857", _("Turkish") ],95 ["IBM857", _("Turkish")],
92 ["IBM862", _("Hebrew") ],96 ["IBM862", _("Hebrew")],
93 ["IBM864", _("Arabic") ],97 ["IBM864", _("Arabic")],
94 ["ISO-2022-JP", _("Japanese") ],98 ["ISO-2022-JP", _("Japanese")],
95 ["ISO-2022-KR", _("Korean") ],99 ["ISO-2022-KR", _("Korean")],
96 ["ISO-IR-111", _("Cyrillic") ],100 ["ISO-IR-111", _("Cyrillic")],
97 # ["JOHAB", _("Korean") ],101 # ["JOHAB", _("Korean")],
98 ["KOI8-R", _("Cyrillic") ],102 ["KOI8-R", _("Cyrillic")],
99 ["KOI8-U", _("Cyrillic/Ukrainian") ],103 ["KOI8-U", _("Cyrillic/Ukrainian")],
100 ["MAC_ARABIC", _("Arabic") ],104 ["MAC_ARABIC", _("Arabic")],
101 ["MAC_CE", _("Central European") ],105 ["MAC_CE", _("Central European")],
102 ["MAC_CROATIAN", _("Croatian") ],106 ["MAC_CROATIAN", _("Croatian")],
103 ["MAC-CYRILLIC", _("Cyrillic") ],107 ["MAC-CYRILLIC", _("Cyrillic")],
104 ["MAC_DEVANAGARI", _("Hindi") ],108 ["MAC_DEVANAGARI", _("Hindi")],
105 ["MAC_FARSI", _("Persian") ],109 ["MAC_FARSI", _("Persian")],
106 ["MAC_GREEK", _("Greek") ],110 ["MAC_GREEK", _("Greek")],
107 ["MAC_GUJARATI", _("Gujarati") ],111 ["MAC_GUJARATI", _("Gujarati")],
108 ["MAC_GURMUKHI", _("Gurmukhi") ],112 ["MAC_GURMUKHI", _("Gurmukhi")],
109 ["MAC_HEBREW", _("Hebrew") ],113 ["MAC_HEBREW", _("Hebrew")],
110 ["MAC_ICELANDIC", _("Icelandic") ],114 ["MAC_ICELANDIC", _("Icelandic")],
111 ["MAC_ROMAN", _("Western") ],115 ["MAC_ROMAN", _("Western")],
112 ["MAC_ROMANIAN", _("Romanian") ],116 ["MAC_ROMANIAN", _("Romanian")],
113 ["MAC_TURKISH", _("Turkish") ],117 ["MAC_TURKISH", _("Turkish")],
114 ["MAC_UKRAINIAN", _("Cyrillic/Ukrainian") ],118 ["MAC_UKRAINIAN", _("Cyrillic/Ukrainian")],
115 ["SHIFT-JIS", _("Japanese") ],119 ["SHIFT-JIS", _("Japanese")],
116 ["TCVN", _("Vietnamese") ],120 ["TCVN", _("Vietnamese")],
117 ["TIS-620", _("Thai") ],121 ["TIS-620", _("Thai")],
118 ["UHC", _("Korean") ],122 ["UHC", _("Korean")],
119 ["VISCII", _("Vietnamese") ],123 ["VISCII", _("Vietnamese")],
120 ["WINDOWS-1250", _("Central European") ],124 ["WINDOWS-1250", _("Central European")],
121 ["WINDOWS-1251", _("Cyrillic") ],125 ["WINDOWS-1251", _("Cyrillic")],
122 ["WINDOWS-1252", _("Western") ],126 ["WINDOWS-1252", _("Western")],
123 ["WINDOWS-1253", _("Greek") ],127 ["WINDOWS-1253", _("Greek")],
124 ["WINDOWS-1254", _("Turkish") ],128 ["WINDOWS-1254", _("Turkish")],
125 ["WINDOWS-1255", _("Hebrew") ],129 ["WINDOWS-1255", _("Hebrew")],
126 ["WINDOWS-1256", _("Arabic") ],130 ["WINDOWS-1256", _("Arabic")],
127 ["WINDOWS-1257", _("Baltic") ],131 ["WINDOWS-1257", _("Baltic")],
128 ["WINDOWS-1258", _("Vietnamese") ]132 ["WINDOWS-1258", _("Vietnamese")]
129 ]133 ]
130134
131135
@@ -135,8 +139,9 @@
135 method = None139 method = None
136 for stackitem in stack:140 for stackitem in stack:
137 parent_frame = stackitem[0]141 parent_frame = stackitem[0]
138 names, varargs, keywords, local_vars = inspect.getargvalues(parent_frame)142 names, varargs, keywords, local_vars = \
139 ## little trick to get the second stackline method, in case we do 143 inspect.getargvalues(parent_frame)
144 ## little trick to get the second stackline method, in case we do
140 ## not find self145 ## not find self
141 if not method and method != None:146 if not method and method != None:
142 method = stackitem[3]147 method = stackitem[3]
@@ -163,37 +168,88 @@
163 if DEBUGMETHODS != [] and method not in DEBUGMETHODS:168 if DEBUGMETHODS != [] and method not in DEBUGMETHODS:
164 return169 return
165 try:170 try:
166 print >> sys.stderr, "%s::%s: %s%s" % (classname, method, log, extra)171 print >> sys.stderr, "%s::%s: %s%s" % (classname, method,
172 log, extra)
167 except IOError:173 except IOError:
168 pass174 pass
169 175
170176
171#TODO: Move this to controller.py177def shell_lookup():
172def get_user_shell():178 """Find an appropriate shell for the user
173 """Get the user's shell defined in /etc/passwd ."""179 Function copied from the terminator project source code
174 data = None180 www.launchpad.net/terminator"""
175 try:181 try:
176 # Read out the data in /etc/passwd182 usershell = pwd.getpwuid(os.getuid())[6]
177 with open('/etc/passwd') as f:183 except KeyError:
178 data = f.readlines()184 usershell = None
179 except e:185 shells = [os.getenv('SHELL'), usershell, 'bash',
180 print "Something unexpected happened!"186 'zsh', 'tcsh', 'ksh', 'csh', 'sh']
181 raise e187
182188 for shell in shells:
183 for i in data:189 if shell is None:
184 tmp = i.split(":")190 continue
185 # Check for the entry of the currently logged in user191 elif os.path.isfile(shell):
186 if tmp[0] == getpass.getuser(): 192 dbg('Found shell %s' % (shell))
187 # Columns are separated by colons, so split each column.193 return shell
188 # Sample /etc/passwd entry for a user:194 else:
189 # 195 rshell = path_lookup(shell)
190 # jorge:x:1001:1002:,,,:/home/jorge:/bin/bash196 if rshell is not None:
191 #197 dbg('Found shell %s at %s' % (shell, rshell))
192 # The last column is relevant for us.198 return rshell
193 # Don't forget to strip the newline at the end of the string!199 dbg('Unable to locate a shell')
194 return i.split(":")[-1:][0].strip('\n')200
195201
202## replace ? with {0[n]}
203def replace(cmnd, num, ran):
204 while ran < num:
205 replace_cmnd = re.sub('\?', '{0[' + str(ran) + ']}', cmnd, count=1)
206 cmnd = replace_cmnd
207 ran += 1
208 return cmnd
209
210
211def get_system_font(callback=None):
212 """Look up the system font"""
213 global gconf_cli
214 if 'gconf' not in globals():
215 return 'Monospace 10'
216 else:
217 if not gconf_cli:
218 gconf_cli = gconf.client_get_default()
219 value = gconf_cli.get(
220 '/desktop/gnome/interface/monospace_font_name')
221 system_font = value.get_string()
222 if callback:
223 gconf_cli.notify_add(
224 '/desktop/gnome/interface/monospace_font_name',
225 callback)
226 return system_font
227
228
229## WARNING: the altgr key is detected as a normal key
230def get_keycomb(event):
231 keyname = gtk.gdk.keyval_name(event.keyval)
232 if event.state & gtk.gdk.CONTROL_MASK:
233 keyname = 'ctrl+' + keyname
234 if event.state & gtk.gdk.MOD1_MASK:
235 keyname = 'alt+' + keyname
236 if event.state & gtk.gdk.SHIFT_MASK:
237 keyname = 'shift+' + keyname
238 return keyname
239
240
241## WARNING: the altgr key is detected as shift
242def only_modifier(event):
243 key = gtk.gdk.keyval_name(event.keyval)
244 return 'shift' in key.lower() \
245 or 'control' in key.lower() \
246 or 'super' in key.lower() \
247 or 'alt' in key.lower()
248
249
250### Singleton implementation (kind of)
196class Borg:251class Borg:
197 __shared_state = {}252 __shared_state = {}
253
198 def __init__(self):254 def __init__(self):
199 self.__dict__ = self.__shared_state255 self.__dict__ = self.__shared_state
200256
=== modified file 'clicompanionlib/view.py'
--- clicompanionlib/view.py 2012-01-02 00:23:11 +0000
+++ clicompanionlib/view.py 2012-01-08 10:37:24 +0000
@@ -1,9 +1,9 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2# -*- coding: utf-8 -*-2# -*- coding: utf-8 -*-
3#3#
4# clicompanion.py - commandline tool.4# view.py - Main window for the clicompanon
5#5#
6# Copyright 2010 Duane Hinnen, Kenny Meyer6# Copyright 2010 Duane Hinnen, Kenny Meyer, David Caro
7#7#
8# This program is free software: you can redistribute it and/or modify it8# This program is free software: you can redistribute it and/or modify it
9# under the terms of the GNU General Public License version 3, as published9# under the terms of the GNU General Public License version 3, as published
@@ -17,45 +17,47 @@
17# You should have received a copy of the GNU General Public License along17# You should have received a copy of the GNU General Public License along
18# with this program. If not, see <http://www.gnu.org/licenses/>.18# with this program. If not, see <http://www.gnu.org/licenses/>.
19#19#
2020# This file is where the main window is defined, depends on all the modules of
2121# the clicompanion libraries.
22#
23# Also holds the class that implements the commands notebook (the upper
24# notebook), where all the tab plugins will be added (like LocalCommandList and
25# CommandLineFU)
26
27
28import os
22import pygtk29import pygtk
23pygtk.require('2.0')30pygtk.require('2.0')
24import os31import gobject
32import webbrowser
2533
26# import vte and gtk or print error34# import vte and gtk or print error
27try:35try:
28 import gtk36 import gtk
29except:37except:
30 error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,38 ## do not use gtk, just print
31 _("You need to install the python gtk bindings package 'python-gtk2'"))39 print _("You need to install the python gtk bindings package"
32 error.run()40 "'python-gtk2'")
33 sys.exit (1)41 sys.exit(1)
34 42
35try:43try:
36 import vte44 import vte
37except:45except:
38 error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,46 error = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR,
47 gtk.BUTTONS_OK,
39 _("You need to install 'python-vte' the python bindings for libvte."))48 _("You need to install 'python-vte' the python bindings for libvte."))
40 error.run()49 error.run()
41 sys.exit (1)50 sys.exit(1)
42 51
43import clicompanionlib.menus_buttons52import clicompanionlib.menus_buttons as cc_menus_buttons
44import clicompanionlib.controller53import clicompanionlib.tabs as cc_tabs
45from clicompanionlib.utils import get_user_shell , Borg, dbg54import clicompanionlib.utils as cc_utils
46import clicompanionlib.tabs55from clicompanionlib.utils import dbg
47import clicompanionlib.utils as utils
48import clicompanionlib.config as cc_config56import clicompanionlib.config as cc_config
4957import clicompanionlib.helpers as cc_helpers
5058import clicompanionlib.plugins as cc_plugins
51## Changed two->three columns59import clicompanionlib.preferences as cc_pref
52CMNDS = cc_config.Cheatsheet() 60
53## will hold the commands. Actually the first three columns
54## note that this commands list will not change with searchers and filters,
55## instead, when adding a command to the liststore, we will add also the index
56## of the command in the CMND list
57
58ROW = '0' ## holds the currently selected row
59TARGETS = [61TARGETS = [
60 ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0),62 ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0),
61 ('text/plain', 0, 1),63 ('text/plain', 0, 1),
@@ -63,250 +65,180 @@
63 ('STRING', 0, 3),65 ('STRING', 0, 3),
64 ]66 ]
6567
66FILTER = 068
67NETBOOKMODE = 069class CommandsNotebook(gtk.Notebook):
68HIDEUI = 070 '''
69FULLSCREEN = 071 This is the notebook where the commands list and the commandlinefu commands
7072 are displayed
71menu_search_hbox = ''73 '''
72button_box = ''74 ### We need a way to tell the main window that a command must be runned on
7375 ## the selected terminal tab
7476 __gsignals__ = {
75class MainWindow(Borg):77 'run_command': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
76 window = gtk.Window(gtk.WINDOW_TOPLEVEL) 78 (str, str, str)),
77 #color = gtk.gdk.Color(60000, 65533, 60000)79 'add_tab': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
78 #window.modify_bg(gtk.STATE_NORMAL, color)80 ()),
79 liststore = gtk.ListStore(str, str, str, int) 81 'preferences': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
80 treeview = gtk.TreeView()82 ()),
81 expander = gtk.Expander()83 'show_man': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
82 scrolledwindow = gtk.ScrolledWindow()84 (str, )),
83 notebook = gtk.Notebook()85 'quit': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
8486 ()),
85 screen = gtk.gdk.display_get_default().get_default_screen()87 }
86 screen_size = screen.get_monitor_geometry(0)88
87 height = screen.get_height() ## screen height ##89 def __init__(self, config, pluginloader):
88 global NETBOOKMODE90 self.config = config
89 if height < 750:91 self.pluginloader = pluginloader
90 NETBOOKMODE = 192 gtk.Notebook.__init__(self)
9193 self.loaded_plugins = {}
9294 self.draw_all()
93 def sync_cmnds(self, rld=False):95
94 global CMNDS96 def draw_all(self):
95 dbg('syncing commands')97 ## Load the needed LocalCommandList plugin
96 if rld:98 if 'LocalCommandList' not in self.pluginloader.plugins.keys():
97 ## reload the commands list from the file99 print _("ERROR: LocalCommandList plugin is needed for the "
98 CMNDS.load()100 "execution of CLI Companion, please retore the plugin or "
99 self.liststore.clear()101 "reinstall.")
100 ## Store also the index of the command in the CMNDS list102 self.emit('quit')
101 i = 0103 else:
102 for cmd, ui, desc in CMNDS:104 self.commandstab = self.pluginloader.plugins['LocalCommandList'](
103 self.liststore.append((cmd, ui, desc, i))105 self.config.get_plugin_conf('LocalCommandList'))
104 i = i +1106 for pname, pclass in self.pluginloader.get_plugins('CommandTab'):
105107 if pname == 'LocalCommandList':
106 108 tab = self.commandstab
107 #liststore in a scrolled window in an expander109 else:
108 def expanded_cb(self, expander, params, window, search_box):110 tab = pclass(self.config.get_plugin_conf('LocalCommandList'))
109 if expander.get_expanded():111 self.append_tab(tab, pname)
110112
111 # Activate the search box when expanded113 def append_tab(self, tab, pname):
112 search_box.set_sensitive(True)114 ## save the tab related to the plugin name
113 else:115 self.loaded_plugins[pname] = tab
114 # De-activate the search box when not expanded116 self.append_page(tab, gtk.Label(tab.__title__ or pname))
115 self.search_box.set_sensitive(False)117 ##All the available signals for the plugins
116 expander.set_expanded(False)118 tab.connect('run_command',
117 #expander.remove(expander.child)119 lambda wg, *args: self.run_command(*args))
118 ##reset the size of the window to its original one120 tab.connect('add_command',
119 window.resize(1, 1)121 lambda wg, *args: self.commandstab.add_command(*args))
120 return 122 tab.connect('remove_command',
121 123 lambda wg, *args: self.remove_command(*args))
122124 tab.connect('edit_command',
123 # close the window and quit125 lambda wg, *args: self.edit_command(*args))
124 def delete_event(self, widget, data=None):126 tab.connect('add_tab',
125 gtk.main_quit()127 lambda wg, *args: self.emit('add_tab', *args))
126 return False128 tab.connect('preferences',
127 129 lambda wg, *args: self.emit('preferences', *args))
128 def key_clicked(self, widget, event):130 tab.connect('show_man',
129 actions = clicompanionlib.controller.Actions()131 lambda wg, *args: self.emit('show_man', *args))
130 global HIDEUI132 tab.connect('quit',
131 global FULLSCREEN133 lambda wg, *args: self.emit('quit', *args))
132 global menu_search_hbox134
133 global button_box135 def filter(self, filter_str, pagenum=None):
134 keyname = gtk.gdk.keyval_name(event.keyval).upper()136 if pagenum == None:
135 if keyname == "F12":137 pagenum = self.get_current_page()
136 HIDEUI = 1 - HIDEUI138 page = self.get_nth_page(pagenum)
137 if HIDEUI == 1:139 dbg('filtering by %s' % filter_str)
138 self.treeview.hide_all()140 page.filter(filter_str)
139 self.expander.hide_all()141
140 self.scrolledwindow.hide_all()142 def set_netbook(netbookmode=False):
141 menu_search_hbox.hide_all()143 if netbookmode:
142 button_box.hide_all()144 self.set_size_request(700, 200)
143 else:145 else:
144 self.treeview.show_all()146 self.set_size_request(700, 220)
145 self.expander.show_all()147
146 self.scrolledwindow.show_all()148 def get_command(self):
147 menu_search_hbox.show_all()149 pagenum = self.get_current_page()
148 button_box.show_all()150 page = self.get_nth_page(pagenum)
149 if keyname == "F11":151 return page.get_command()
150 FULLSCREEN = 1 - FULLSCREEN152
151 if FULLSCREEN == 1:153 def run_command(self, cmd=None, ui=None, desc=None):
152 pwin = button_box.get_window()154 if cmd == None:
153 pwin.fullscreen()155 cmd, ui, desc = self.get_command()
154 else: 156 if cmd:
155 pwin = button_box.get_window()157 dbg('running command %s' % cmd)
156 pwin.unfullscreen()158 self.emit('run_command', cmd, ui, desc)
157 if keyname == "F4":159
158 actions.run_command(self)160 def add_command(self):
159 if keyname == "F5":161 if self.get_current_page() == 0:
160 actions.add_command(self)162 self.commandstab.add_command()
161 if keyname == "F6":163 else:
162 actions.remove_command(self)164 command = self.get_command()
163 if keyname == "F7":165 if command[0] != None:
164 self.tabs.add_tab(self)166 self.commandstab.add_command(*command)
165 167 else:
166 def __init__(self):168 self.commandstab.add_command()
167 #import pdb ##debug169
168 #pdb.set_trace() ##debug170 def remove_command(self):
169 171 if self.get_current_page() == 0:
170 ##For now TERM is hardcoded to xterm because of a change172 self.commandstab.remove_command()
171 ##in libvte in Ubuntu Maverick173
172 os.putenv('TERM', 'xterm')174 def edit_command(self):
173175 if self.get_current_page() == 0:
174176 self.commandstab.edit_command()
175 ## style to reduce padding around tabs177 else:
176 ## TODO: Find a better place for this? 178 command = self.get_command()
177 gtk.rc_parse_string ("style \"tab-close-button-style\"\n"179 if command[0] != None:
178 "{\n"180 self.commandstab.add_command(*command)
179 "GtkWidget::focus-padding = 0\n"181
180 "GtkWidget::focus-line-width = 0\n"182 def update(self, config=None, force=None):
181 "xthickness = 0\n"183 if config:
182 "ythickness = 0\n"184 self.config = config
183 "}\n"185 newplugins = self.pluginloader.get_plugins('CommandTab')
184 "widget \"*.tab-close-button\" style \"tab-close-button-style\"");186 for plugin in self.loaded_plugins.keys():
185 187 if plugin not in [ name for name, cl in newplugins]:
186 ## Create UI widgets188 dbg('Disabling plugin %s' % plugin)
187189 self.remove_page(self.page_num(self.loaded_plugins[plugin]))
188 self.notebook.set_show_tabs(0)190 self.loaded_plugins.pop(plugin)
189191 for pname, pclass in newplugins:
190 ##attach the style to the widget192 if pname not in self.loaded_plugins.keys():
191 self.notebook.set_name ("tab-close-button")193 dbg('Adding new selected plugin %s' % pname)
192194 self.append_tab(
193 ## set sizes and borders195 pclass(self.config.get_plugin_conf('LocalCommandList')),
194 global NETBOOKMODE196 pname)
195 if NETBOOKMODE == 1:197 for plugin in force:
196 self.scrolledwindow.set_size_request(700, 200)198 if plugin in self.loaded_plugins:
197 self.window.set_default_size(700, 500)199 dbg('Reloading plugin %s' % plugin)
198 else:200 self.loaded_plugins[plugin].reload(
199 self.scrolledwindow.set_size_request(700, 220)201 self.config.get_plugin_conf(plugin))
200 self.window.set_default_size(700, 625)202 self.show_all()
201 self.window.set_border_width(10)203
202 ## Sets the position of the window relative to the screen204
203 self.window.set_position(gtk.WIN_POS_CENTER_ALWAYS)205class MainWindow(gtk.Window):
204 ## Allow user to resize window206 def __init__(self, config):
205 self.window.set_resizable(True)207 gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
206 208 ###############
207 ## set Window title and icon209 ### Some state variables
208 self.window.set_title("CLI Companion")210 self.hiddenui = False
209 icon = gtk.gdk.pixbuf_new_from_file("/usr/share/pixmaps/clicompanion.16.png")211 self.maximized = False
210 self.window.set_icon(icon)212 self.filtered = False
211 213 self.fullscr = False
212 # sync liststore with commands214
213 self.sync_cmnds()215 self.config = config
214 216
215 ## set renderer and colors217 self.load_plugins()
216 #color2 = gtk.gdk.Color(5000,5000,65000)218
217 renderer = gtk.CellRendererText()219 ## two sections, the menus and search box and the expander with the
218 #renderer.set_property("cell-background-gdk", color)220 ## commands notebook and in the botom, the terminals notebook
219 #renderer.set_property("foreground-gdk", color2)221
220 222 ## set various parameters on the main window (size, etc)
221 ## create the TreeViewColumns to display the data223 self.init_config()
222 self.treeview.columns = [None]*3224 self.term_notebook = cc_tabs.TerminalsNotebook(self.config)
223 self.treeview.columns[0] = gtk.TreeViewColumn(_('Command'), renderer)225
224 self.treeview.columns[1] = gtk.TreeViewColumn(_('User Input'), renderer)226 ###########################
225 self.treeview.columns[2] = gtk.TreeViewColumn(_('Description'), renderer) 227 #### Here we create the commands notebook for the expander
226 228 self.cmd_notebook = CommandsNotebook(config, self.pluginloader)
227 for n in range(3):229
228 ## add columns to treeview230 ## Create the menus and the searchbox
229 self.treeview.append_column(self.treeview.columns[n])231 ## hbox for menu and search Entry
230 ## create a CellRenderers to render the data232 self.menu_search_hbox = gtk.HBox(False)
231 self.treeview.columns[n].cell = gtk.CellRendererText()233
232 #self.treeview.columns[n].cell.set_property("cell-background-gdk", color)234 #### The menus
233 #self.treeview.columns[n].cell.set_property("foreground-gdk", color2)
234 ## add the cells to the columns
235 self.treeview.columns[n].pack_start(self.treeview.columns[n].cell,
236 True)
237 ## set the cell attributes to the appropriate liststore column
238 self.treeview.columns[n].set_attributes(
239 self.treeview.columns[n].cell, text=n)
240 self.treeview.columns[n].set_resizable(True)
241
242 ''' set treeview model and put treeview in the scrolled window
243 and the scrolled window in the expander. '''
244 self.treeview.set_model(self.liststore)
245 self.treeview.set_reorderable(True)
246 self.scrolledwindow.add(self.treeview)
247
248 self.expander.add(self.scrolledwindow)
249 #self.window.show_all()
250
251 ## instantiate tabs
252 self.tabs = clicompanionlib.tabs.Tabs()
253 ## instantiate controller.Actions, where all the button actions are
254 self.actions = clicompanionlib.controller.Actions()
255 ## instantiate 'File' and 'Help' Drop Down Menu [menus_buttons.py]235 ## instantiate 'File' and 'Help' Drop Down Menu [menus_buttons.py]
256 bar = clicompanionlib.menus_buttons.FileMenu()236 menu_bar = cc_menus_buttons.FileMenu(self.config)
257 menu_bar = bar.the_menu(self)237 self.menu_search_hbox.pack_start(menu_bar, True)
258 238
259239 #### the expander
260 ## get row of a selection240 self.expander = gtk.Expander()
261 def mark_selected(self, treeselection):241 self.menu_search_hbox.pack_end(self.expander, False, False, 0)
262 global ROW
263 (model, pathlist) = treeselection.get_selected_rows()
264 ROW = pathlist
265
266
267 ## double click to run a command
268 def treeview_clicked(widget, path, column):
269 self.actions.run_command(self)
270
271
272 ## press enter to run a command
273 def treeview_button(widget, event):
274 keyname = gtk.gdk.keyval_name(event.keyval).upper()
275 dbg('Key %s pressed'%keyname)
276 if event.type == gtk.gdk.KEY_PRESS:
277 if keyname == 'RETURN':
278 self.actions.run_command(self)
279
280
281
282 selection = self.treeview.get_selection()
283 #selection.set_mode(gtk.SELECTION_SINGLE)
284 ## open with top command selected
285 selection.select_path(0)
286 selection.connect("changed", mark_selected, selection)
287 ## double-click
288 self.treeview.connect("row-activated", treeview_clicked)
289 #press enter to run command
290 self.treeview.connect("key-press-event", treeview_button)
291
292 global menu_search_hbox
293
294 ## The search section
295 search_label = gtk.Label(_("Search:"))
296 search_label.set_alignment(xalign=-1, yalign=0)
297 self.search_box = gtk.Entry()
298 self.search_box.connect("changed", self.actions._filter_commands, self.liststore, self.treeview)
299 ## search box tooltip
300 self.search_box.set_tooltip_text(_("Search your list of commands"))
301 ## Set the search box sensitive OFF at program start, because
302 ## expander is not unfolded by default
303 self.search_box.set_sensitive(False)
304 ## hbox for menu and search Entry
305 menu_search_hbox = gtk.HBox(False)
306 menu_search_hbox.pack_end(self.search_box, True)
307 menu_search_hbox.pack_end(search_label, False, False, 10)
308 menu_search_hbox.pack_start(menu_bar, True)
309
310 ## expander title242 ## expander title
311 expander_hbox = gtk.HBox()243 expander_hbox = gtk.HBox()
312 image = gtk.Image()244 image = gtk.Image()
@@ -314,70 +246,286 @@
314 label = gtk.Label(_('Command List'))246 label = gtk.Label(_('Command List'))
315 ## tooltip for the label of the expander247 ## tooltip for the label of the expander
316 expander_hbox.set_tooltip_text(_("Click to show/hide command list"))248 expander_hbox.set_tooltip_text(_("Click to show/hide command list"))
317
318 ## add expander widget to hbox249 ## add expander widget to hbox
319 expander_hbox.pack_start(image, False, False)250 expander_hbox.pack_start(image, False, False)
320 expander_hbox.pack_start(label, True, False)251 expander_hbox.pack_start(label, True, False)
321 self.expander.set_label_widget(expander_hbox)252 self.expander.set_label_widget(expander_hbox)
253 self.expander.set_expanded(True)
254
255 #### The search box
256 self.search_hbox = gtk.HBox()
257 #### the search label
258 search_label = gtk.Label(_("Search:"))
259 search_label.set_alignment(xalign=-1, yalign=0)
260 self.search_box = gtk.Entry()
261 self.search_box.connect("changed",
262 lambda wg, *x: self.cmd_notebook.filter(wg.get_text()))
263 ## search box tooltip
264 self.search_box.set_tooltip_text(_("Search your list of commands"))
265 self.search_hbox.pack_start(search_label, False, False, 8)
266 self.search_hbox.pack_end(self.search_box, True)
267 self.menu_search_hbox.pack_end(self.search_hbox, True)
268
269 ############################
270 ## and now the terminals notebook
271 self.term_notebook.set_show_tabs(0)
272 ##attach the style to the widget
273 self.term_notebook.set_name("tab-close-button")
322274
323 ## Add the first tab with the Terminal275 ## Add the first tab with the Terminal
324 self.tabs.add_tab(self.notebook)276 self.term_notebook.add_tab()
325 self.notebook.set_tab_pos(2)
326277
327 ## The "Add Tab" tab
328 add_tab_button = gtk.Button("+")
329 ## tooltip for "Add Tab" tab
330 add_tab_button.set_tooltip_text(_("Click to add another tab"))
331 ## create first tab
332 self.notebook.append_page(gtk.Label(""), add_tab_button)
333
334 global button_box
335 ## buttons at bottom of main window [menus_buttons.py]278 ## buttons at bottom of main window [menus_buttons.py]
336 button_box = bar.buttons(self, 10, gtk.BUTTONBOX_END)279 self.button_box = cc_menus_buttons.Buttons(10, gtk.BUTTONBOX_END)
337280
281 ## pack everything
338 ## vbox for search, notebook, buttonbar282 ## vbox for search, notebook, buttonbar
339 vbox = gtk.VBox()
340 self.window.add(vbox)
341 ## pack everytyhing in the vbox283 ## pack everytyhing in the vbox
342 #self.vbox.pack_start(menu_bar, False, False, 0) ##menuBar284 h_vbox = gtk.VBox()
343 vbox.pack_start(menu_search_hbox, False, False, 5)285 h_vbox.pack_start(self.menu_search_hbox, False, False, 5)
344 vbox.pack_start(self.expander, False, False, 5)286 h_vbox.pack_start(self.cmd_notebook, True, True, 5)
345 vbox.pack_start(self.notebook, True, True, 5)287 self.l_vbox = gtk.VBox()
346 vbox.pack_start(button_box, False, False, 5)288 self.l_vbox.pack_start(self.term_notebook, True, True, 0)
347 289 self.l_vbox.pack_start(self.button_box, False, False, 0)
290
291 ## Pack it in a vpant bo allow the user to resize
292 self.vpane = gtk.VPaned()
293 self.vpane.pack1(h_vbox, True, False)
294 self.vpane.pack2(self.l_vbox, True, True)
295 self.add(self.vpane)
296
348 ## signals297 ## signals
349 self.expander.connect('notify::expanded', self.expanded_cb, self.window, self.search_box)298 self.cmd_notebook.connect('run_command',
350 self.window.connect("delete_event", self.delete_event)299 lambda wdg, *args: self.term_notebook.run_command(*args))
351 self.window.connect("key-press-event", self.key_clicked)300 self.cmd_notebook.connect('show_man',
352 add_tab_button.connect("clicked", lambda *x: self.tabs.add_tab(self.notebook))301 lambda wgt, cmd: cc_helpers.ManPage(cmd).run())
302 self.cmd_notebook.connect('quit', lambda *x: gtk.main_quit())
303 self.term_notebook.connect('quit', lambda *x: gtk.main_quit())
304 self.term_notebook.connect('preferences', lambda *x: self.edit_pref())
305 self.expander.connect('notify::expanded',
306 lambda *x: self.expanded_cb())
307 self.connect("delete_event", self.delete_event)
308 self.connect("key-press-event", self.key_clicked)
309 menu_bar.connect('quit', lambda *x: gtk.main_quit())
310 menu_bar.connect('run_command',
311 lambda *x: self.cmd_notebook.run_command())
312 menu_bar.connect('add_command',
313 lambda *x: self.cmd_notebook.add_command())
314 menu_bar.connect('edit_command',
315 lambda *x: self.cmd_notebook.edit_command())
316 menu_bar.connect('remove_command',
317 lambda *x: self.cmd_notebook.remove_command())
318 menu_bar.connect('preferences', lambda *x: self.edit_pref())
319 menu_bar.connect('add_tab', lambda *x: self.term_notebook.add_tab())
320 menu_bar.connect('close_tab', lambda *x: self.term_notebook.quit_tab())
321 self.button_box.connect('quit', lambda *x: gtk.main_quit())
322 self.button_box.connect('run_command',
323 lambda *x: self.cmd_notebook.run_command())
324 self.button_box.connect('add_command',
325 lambda *x: self.cmd_notebook.add_command())
326 self.button_box.connect('edit_command',
327 lambda *x: self.cmd_notebook.edit_command())
328 self.button_box.connect('remove_command',
329 lambda *x: self.cmd_notebook.remove_command())
330 self.button_box.connect('show_man',
331 lambda *x: cc_helpers.ManPage(
332 self.cmd_notebook.get_command()[0]).run())
333 self.button_box.connect('add_tab',
334 lambda *x: self.term_notebook.add_tab())
353 ## right click menu event capture335 ## right click menu event capture
354 self.treeview.connect("button_press_event", bar.right_click, self)
355
356 # Allow enable drag and drop of rows including row move336 # Allow enable drag and drop of rows including row move
357 self.treeview.enable_model_drag_source( gtk.gdk.BUTTON1_MASK,337# self.treeview.enable_model_drag_source( gtk.gdk.BUTTON1_MASK,
358 TARGETS,338# TARGETS,
359 gtk.gdk.ACTION_DEFAULT |339# gtk.gdk.ACTION_DEFAULT |
360 gtk.gdk.ACTION_COPY)340# gtk.gdk.ACTION_COPY)
361 self.treeview.enable_model_drag_dest(TARGETS,341# self.treeview.enable_model_drag_dest(TARGETS,
362 gtk.gdk.ACTION_DEFAULT)342# gtk.gdk.ACTION_DEFAULT)
363343#
364 self.treeview.connect ("drag_data_get", self.drag_data_get_event)344# self.treeview.connect ("drag_data_get", self.drag_data_get_event)
365 self.treeview.connect ("drag_data_received", self.drag_data_received_event)345# self.treeview.connect ("drag_data_received",
366 self.treeview.connect("drag_drop", self.on_drag_drop )346# self.drag_data_received_event)
367347# self.treeview.connect("drag_drop", self.on_drag_drop )
368348
369 #self.vte.grab_focus()349 ## show everything
370 self.window.show_all()350 self.show_all()
371 return351 ## set the focus on the terminal
372352 self.term_notebook.focus()
353 return
354
355 def load_plugins(self):
356 self.pluginloader = cc_plugins.PluginLoader()
357 self.reload_plugins()
358
359 def reload_plugins(self):
360 (head, _tail) = os.path.split(cc_plugins.__file__)
361 pluginspath = os.path.join(head.rsplit(os.sep, 1)[0], 'plugins')
362 allowed = [plg.strip() for plg in self.config.get('general::default',
363 'plugins').split(',')]
364 dbg('Allowed plugins: %s' % allowed.__repr__())
365 self.pluginloader.load(pluginspath, allowed)
366
367 def expanded_cb(self):
368 #liststore in a scrolled window in an expander
369 if self.expander.get_expanded():
370 self.search_hbox.show_all()
371 self.cmd_notebook.show_all()
372 self.cmd_notebook.set_current_page(self.currentpage)
373 self.button_box.show_all()
374 self.vpane.set_position(self.currentpos)
375 else:
376 self.currentpage = self.cmd_notebook.get_current_page()
377 self.currentpos = self.vpane.get_position()
378 self.cmd_notebook.hide_all()
379 self.search_hbox.hide_all()
380 self.button_box.hide_all()
381 self.vpane.set_position(0)
382 return
383
384 def init_config(self):
385 ## Set the netbookmode if needed
386 screen = gtk.gdk.display_get_default().get_default_screen()
387 height = screen.get_height()
388 if height < 750:
389 self.cmd_notebook.set_netbook(True)
390 self.set_default_size(700, 500)
391 else:
392 self.set_default_size(700, 625)
393 self.set_border_width(5)
394 ## Sets the position of the window relative to the screen
395 self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
396 ## Allow user to resize window
397 self.set_resizable(True)
398
399 ## set Window title and icon
400 self.set_title("CLI Companion")
401 icon = gtk.gdk.pixbuf_new_from_file(
402 "/usr/share/pixmaps/clicompanion.16.png")
403 self.set_icon(icon)
404
405 ##For now TERM is hardcoded to xterm because of a change
406 ##in libvte in Ubuntu Maverick
407 os.putenv('TERM', 'xterm')
408
409 ## style to reduce padding around tabs
410 ## TODO: Find a better place for this?
411 gtk.rc_parse_string("style \"tab-close-button-style\"\n"
412 "{\n"
413 "GtkWidget::focus-padding = 0\n"
414 "GtkWidget::focus-line-width = 0\n"
415 "xthickness = 0\n"
416 "ythickness = 0\n"
417 "}\n"
418 "widget \"*.tab-close-button\" style \"tab-close-button-style\"")
419
420 def edit_pref(self):
421 pref_win = cc_pref.PreferencesWindow(
422 self.config.get_config_copy(), self.pluginloader)
423 newconfig, changed_plugins = pref_win.run()
424 if newconfig:
425 self.config = newconfig
426 self.config.save()
427 dbg(', '.join([self.config.get('profile::default', opt)
428 for opt in self.config.options('profile::default')]))
429 self.reload_plugins()
430 self.term_notebook.update_all_term_config(self.config)
431 self.cmd_notebook.update(self.config, changed_plugins)
432
433 def delete_event(self, widget, data=None):
434 # close the window and quit
435 gtk.main_quit()
436 return False
437
438 def key_pressed(self, event):
439 keyname = cc_utils.get_keycomb(event)
440 if keyname in cc_pref.KEY_BINDINGS.keys():
441 key, func = cc_pref.KEY_BINDINGS[keyname]
442
443 def key_clicked(self, widget, event):
444 if cc_utils.only_modifier(event):
445 return
446 keycomb = cc_utils.get_keycomb(event)
447 for func in cc_config.KEY_BINDINGS.keys():
448 if keycomb == self.config.get('keybindings', func):
449 getattr(self, func)()
450 ## this is to stop sending the keypress to the widgets
451 return True
452
453 def run_command(self):
454 self.cmd_notebook.run_command()
455
456 def add_command(self):
457 self.cmd_notebook.add_command()
458
459 def remove_command(self):
460 self.cmd_notebook.remove_command()
461
462 def edit_command(self):
463 self.cmd_notebook.edit_command()
464
465 def add_tab(self):
466 self.term_notebook.add_tab()
467
468 def close_tab(self):
469 self.term_notebook.quit_tab()
470
471 def toggle_hide_ui(self):
472 if self.hiddenui:
473 self.show_ui()
474 else:
475 self.hide_ui()
476
477 def hide_ui(self):
478 if self.hiddenui:
479 return
480 dbg('Hide UI')
481 self.l_vbox.remove(self.term_notebook)
482 self.remove(self.vpane)
483 self.add(self.term_notebook)
484 self.hiddenui = True
485
486 def show_ui(self):
487 if not self.hiddenui:
488 return
489 dbg('Show UI')
490 self.remove(self.term_notebook)
491 btns = self.l_vbox.get_children()[0]
492 self.l_vbox.remove(btns)
493 self.l_vbox.pack_start(self.term_notebook, True, True, 0)
494 self.l_vbox.pack_start(btns, False, False, 0)
495 self.add(self.vpane)
496 self.hiddenui = False
497
498 def toggle_maximize(self):
499 if not self.maximized:
500 self.maximize()
501 else:
502 self.unmaximize()
503 self.maximized = not self.maximized
504
505 def toggle_fullscreen(self):
506 '''
507 Maximize and hide everything, or unmaximize and unhide if it was on
508 fullscren mode
509 '''
510 if not self.fullscr:
511 self.hide_ui()
512 self.fullscreen()
513 self.set_border_width(0)
514 else:
515 self.show_ui()
516 self.unfullscreen()
517 self.set_border_width(5)
518 self.fullscr = not self.fullscr
519
520 ### TODO: pass these functions to the LocalCommandList class
373 def on_drag_drop(self, treeview, *x):521 def on_drag_drop(self, treeview, *x):
374 '''522 '''
375 Stop the signal when in search mode523 Stop the signal when in search mode
376 '''524 '''
377 if FILTER:525 if self.FILTER:
378 treeview.stop_emission('drag_drop')526 treeview.stop_emission('drag_drop')
379527
380 def drag_data_get_event(self, treeview, context, selection, target_id, 528 def drag_data_get_event(self, treeview, context, selection, target_id,
381 etime):529 etime):
382 """530 """
383 Executed on dragging531 Executed on dragging
@@ -387,20 +535,19 @@
387 data = model.get(iter, 0, 1, 2)535 data = model.get(iter, 0, 1, 2)
388 selection.set(selection.target, 8, '\t'.join(data))536 selection.set(selection.target, 8, '\t'.join(data))
389537
390 def drag_data_received_event(self, treeview, context, x, y, selection, info,538 def drag_data_received_event(self, treeview, context, x, y, selection,
391 etime):539 info, etime):
392 """540 """
393 Executed when dropping.541 Executed when dropping.
394 """542 """
395 global CMNDS543 global CMNDS
396 global FILTER
397 ## if we are in a search, do nothing544 ## if we are in a search, do nothing
398 if FILTER == 1:545 if self.FILTER == 1:
399 return546 return
400 model = treeview.get_model()547 model = treeview.get_model()
401 ## get the destination548 ## get the destination
402 drop_info = treeview.get_dest_row_at_pos(x, y)549 drop_info = treeview.get_dest_row_at_pos(x, y)
403 if drop_info: 550 if drop_info:
404 path, position = drop_info551 path, position = drop_info
405 iter = model.get_iter(path)552 iter = model.get_iter(path)
406 dest = list(model.get(iter, 0, 1, 2))553 dest = list(model.get(iter, 0, 1, 2))
@@ -408,21 +555,23 @@
408 ## parse all the incoming commands555 ## parse all the incoming commands
409 for data in selection.data.split('\n'):556 for data in selection.data.split('\n'):
410 # if we got an empty line skip it557 # if we got an empty line skip it
411 if not data.replace('\r',''): continue558 if not data.replace('\r', ''):
559 continue
412 # format the incoming string560 # format the incoming string
413 orig = data.replace('\r','').split('\t',2)561 orig = data.replace('\r', '').split('\t', 2)
414 orig = [ fld.strip() for fld in orig ]562 orig = [fld.strip() for fld in orig]
415 # fill the empty fields563 # fill the empty fields
416 if len(orig) < 3: orig = orig + ('',)*(3-len(orig))564 if len(orig) < 3:
417 dbg('Got drop of command %s'%'_\t_'.join(orig))565 orig = orig + ('', ) * (3 - len(orig))
566 dbg('Got drop of command %s' % '_\t_'.join(orig))
418567
419 if drop_info:568 if drop_info:
420 if (position == gtk.TREE_VIEW_DROP_BEFORE569 if (position == gtk.TREE_VIEW_DROP_BEFORE
421 or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):570 or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
422 dbg('\t to before dest %s'%'_\t_'.join(dest))571 dbg('\t to before dest %s' % '_\t_'.join(dest))
423 CMNDS.drag_n_drop(orig, dest, before=True)572 CMNDS.drag_n_drop(orig, dest, before=True)
424 else:573 else:
425 dbg('\t to after dest %s'%'_\t_'.join(dest))574 dbg('\t to after dest %s' % '_\t_'.join(dest))
426 CMNDS.drag_n_drop(orig, dest, before=False)575 CMNDS.drag_n_drop(orig, dest, before=False)
427 else:576 else:
428 dbg('\t to the end')577 dbg('\t to the end')
@@ -431,19 +580,21 @@
431 context.finish(True, True, etime)580 context.finish(True, True, etime)
432 self.sync_cmnds()581 self.sync_cmnds()
433 CMNDS.save()582 CMNDS.save()
434 583
435 def main(self):584 def main(self):
436 try:585 try:
437 gtk.main()586 gtk.main()
438 except KeyboardInterrupt:587 except KeyboardInterrupt:
439 pass588 pass
440 589
441def run( options=None ):590
442 ##create the config file591def run(options=None):
443 config = cc_config.create_config()592 if options and options.conffile:
444 if config.get('terminal','debug') == 'True':593 config = cc_config.CLIConfig(conffile)
445 utils.DEBUG = True594 else:
446 CMNDS.load(options and options.cheatsheet or None)595 config = cc_config.CLIConfig()
447 dbg('Loaded commands %s'%CMNDS)596 if config.get('general::default', 'debug') == 'True' or options.debug:
448 main_window = MainWindow()597 cc_utils.DEBUG = True
598 main_window = MainWindow(
599 config=config)
449 main_window.main()600 main_window.main()
450601
=== added directory 'plugins'
=== added file 'plugins/CommandLineFU.py'
--- plugins/CommandLineFU.py 1970-01-01 00:00:00 +0000
+++ plugins/CommandLineFU.py 2012-01-08 10:37:24 +0000
@@ -0,0 +1,282 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# CommandLineFu.py - CommandlineFU commands plugin for CLI Comapnion
5#
6# Copyright 2012 David Caro <david.caro.estevez@gmail.com>
7#
8# This program is free software: you can redistribute it and/or modify it
9# under the terms of the GNU General Public License version 3, as published
10# by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful, but
13# WITHOUT ANY WARRANTY; without even the implied warranties of
14# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
15# PURPOSE. See the GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20
21
22import os
23import pygtk
24pygtk.require('2.0')
25import gobject
26import webbrowser
27
28try:
29 import gtk
30except:
31 ## do not use gtk, just print
32 print _("You need to install the python gtk bindings package"
33 "'python-gtk2'")
34 sys.exit(1)
35
36from clicompanionlib.utils import dbg
37import clfu as cc_clf
38import clicompanionlib.plugins as plugins
39
40
41class CommandLineFU(plugins.TabPlugin):
42 '''
43 Tab with all the commandlinefu commands and search options
44 '''
45 __authors__ = 'David Caro <david.caro.estevez@gmail.com>'
46 __info__ = ('This plugin crates a tab on the commands list that allows you'
47 ' to search commands on the CommandlineFU website '
48 '(www.commandlinefu.com).')
49 __title__ = 'CommandlineFU Commands'
50
51 def __init__(self, config):
52 plugins.TabPlugin.__init__(self)
53 self.config = config
54 self.clfu = cc_clf.CLFu()
55 self.pages = []
56 ## las_search will store the params of the last search
57 self.lastsearch = []
58
59 ## box for commands tags and timerange comboboxes, and refresh
60 hbox = gtk.HBox()
61 self.pack_start(hbox, False, False, 0)
62
63 ## combobox for selecting command tag
64 self.tags_box = gtk.combo_box_new_text()
65 self.tags_box.append_text('Update')
66 self.tags_box.append_text('None')
67 self.tags_box.set_active(1)
68 self.tags_box.connect('changed', lambda *x: self.populate_tags())
69 hbox.pack_start(gtk.Label(_('Tag:')), False, False, 0)
70 hbox.pack_start(self.tags_box, False, False, 0)
71
72 ## time range combobox
73 self.trange_box = gtk.combo_box_new_text()
74 ## populate time ranges, no http call needed
75 for timerange in self.clfu.get_timeranges():
76 self.trange_box.append_text(timerange)
77 self.trange_box.set_active(0)
78 hbox.pack_start(gtk.Label(_('From:')), False, False, 0)
79 hbox.pack_start(self.trange_box, False, False, 0)
80
81 ## refresh button
82 refresh_btn = gtk.Button("Search")
83 refresh_btn.connect("clicked", lambda *x: self.populate())
84 hbox.pack_start(refresh_btn, False, False, 0)
85
86 ## add the commands list
87 sw = gtk.ScrolledWindow()
88 self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, gtk.gdk.Pixbuf,
89 str, str, int, str)
90 self.treeview = gtk.TreeView(gtk.TreeModelSort(self.liststore))
91 sw.add(self.treeview)
92 #self.treeview.set_reorderable(True)
93 self.treeview.connect('row-activated', self.clicked)
94 self.treeview.connect("button_press_event", self.right_click)
95 self.init_cols()
96 self.pack_start(sw, True, True, 0)
97
98 ## Get more button
99 self.more_btn = gtk.Button("Get more!")
100 self.more_btn.connect("clicked",
101 lambda *x: self.populate(next_page=True))
102 hbox.pack_start(self.more_btn, False, False, 0)
103 self.more_btn.set_sensitive(False)
104
105 ## Show everything
106 self.show_all()
107
108 def add_pixbuf_col(self, colname, n=0):
109 col = gtk.TreeViewColumn()
110 col.set_title(colname)
111 render_pixbuf = gtk.CellRendererPixbuf()
112 col.pack_start(render_pixbuf, expand=True)
113 col.add_attribute(render_pixbuf, 'pixbuf', n)
114 col.set_resizable(True)
115 self.treeview.append_column(col)
116
117 def add_text_col(self, colname, n=0):
118 col = gtk.TreeViewColumn()
119 col.set_title(_(colname))
120 render = gtk.CellRendererText()
121 col.pack_start(render, expand=True)
122 col.add_attribute(render, 'text', n)
123 col.set_resizable(True)
124 col.set_sort_column_id(n)
125 self.treeview.append_column(col)
126
127 def init_cols(self):
128 self.add_pixbuf_col('', 0)
129 self.add_pixbuf_col('', 1)
130 self.add_text_col('Command', 2)
131 self.add_text_col('Summary', 3)
132 self.add_text_col('Votes', 4)
133
134 def populate_tags(self):
135 dbg('Poulating clf tags')
136 tag = self.tags_box.get_active_text()
137 if tag == "Update":
138 self.tags_box.set_model(gtk.ListStore(str))
139 self.tags_box.append_text('Update')
140 self.tags_box.append_text('None')
141 ## populate commands tags
142 for tag, tagid in self.clfu.get_tags():
143 self.tags_box.append_text(tag)
144 self.tags_box.set_active(1)
145
146 def populate(self, next_page=False, filter_str=''):
147 '''
148 populate the widgets with the info from the commandlinefu website
149 '''
150 ## get the filter params, the saved ones if it's a next page request or
151 ## the new ones if it's anew search
152 self.liststore.clear()
153 if next_page:
154 tag, timerange, filter_str = self.lastsearch
155 page = len(self.pages)
156 else:
157 tag = self.tags_box.get_active_text()
158 timerange = self.trange_box.get_active_text()
159 self.lastsearch = tag, timerange, filter_str
160 page = 0
161 self.pages = []
162 ## get new commands page
163 if tag != 'None':
164 commands = self.clfu.tag(tag=tag, timerange=timerange, page=page)
165 elif filter_str != '':
166 commands = self.clfu.search(filter_str, timerange=timerange,
167 page=page)
168 else:
169 commands = self.clfu.browse(timerange=timerange, page=page)
170 ## if there were no results, do avoid requesting more pages, show the
171 ## user that there are no more pages
172 self.more_btn.set_sensitive(True)
173 if not commands or len(commands) < 25:
174 self.more_btn.set_sensitive(False)
175 ## append it to the revious pages if any
176 self.pages.append(commands)
177 ## filter and show all the commands
178 for page in self.pages:
179 for command in page:
180 if filter_str != '':
181 if filter_str not in command['command'] \
182 and filter_str not in command['summary']:
183 continue
184 add_btn = self.treeview.render_icon(stock_id=gtk.STOCK_ADD,
185 size=gtk.ICON_SIZE_SMALL_TOOLBAR)
186 link_btn = self.treeview.render_icon(stock_id=gtk.STOCK_INFO,
187 size=gtk.ICON_SIZE_SMALL_TOOLBAR)
188 self.liststore.append((add_btn, link_btn,
189 command['command'], command['summary'],
190 int(command['votes']), command['url']))
191
192 def filter(self, search_term):
193 """
194 Show commands matching a given search term.
195 The user should enter a term in the search box and the treeview should
196 only display the rows which contain the search term.
197 No reordering allowed when filtering.
198 """
199 ## If the search term is empty, and we change filtering state
200 ## restore the liststore, else do nothing
201 if search_term == "":
202 if self.filtering:
203 dbg("Uniltering...")
204 self.filtering = False
205 self.treeview.set_model(gtk.TreeModelSort(self.liststore))
206 return
207 dbg("Filtering...")
208 self.filtering = True
209 modelfilter = self.liststore.filter_new()
210
211 def search(model, iter, search_term):
212 cmd, desc = model.get(iter, 2, 3)
213 if search_term in ('%s %s' % (cmd, desc)).lower():
214 return True
215 modelfilter.set_visible_func(search, search_term)
216 self.treeview.set_model(gtk.TreeModelSort(modelfilter))
217
218 def clicked(self, treeview, path, column):
219 treeselection = treeview.get_selection()
220 model, iter = treeselection.get_selected()
221 data = model.get(iter, 2, 3, 4, 5)
222 if column == treeview.get_column(0):
223 dbg('Adding command %s, %s, %s' % (data[0], '', data[1]))
224 self.emit('add_command', data[0], '', data[1])
225 elif column == treeview.get_column(1):
226 webbrowser.open(data[3])
227 else:
228 self.emit('run_command', data[0], '', data[1])
229
230 def get_command(self, withurl=False):
231 treeselection = self.treeview.get_selection()
232 model, iter = treeselection.get_selected()
233 if not iter:
234 return None, None, None
235 cmd, desc, url = model.get(iter, 2, 3, 5)
236 if not withurl:
237 return cmd, '', desc
238 return cmd, '', desc, url
239
240
241 #right-click popup menu for the Liststore(command list)
242 def right_click(self, widget, event):
243 if event.button == 3:
244 x = int(event.x)
245 y = int(event.y)
246 time = event.time
247 row = self.treeview.get_path_at_pos(x, y)
248 if row:
249 path, col, x, y = row
250 if row:
251 popupMenu = gtk.Menu()
252 self.treeview.grab_focus()
253 self.treeview.set_cursor(path, col, 0)
254 data = self.get_command(withurl=True)
255 if data[0] == None:
256 return
257 # right-click popup menu Apply(run)
258 menuPopup1 = gtk.ImageMenuItem(gtk.STOCK_APPLY)
259 popupMenu.add(menuPopup1)
260 menuPopup1.connect("activate",
261 lambda *x: self.emit('run_command', *data[:-1]))
262 # right-click popup menu AddToLocal
263 menuPopup2 = gtk.ImageMenuItem(gtk.STOCK_ADD)
264 popupMenu.add(menuPopup2)
265 menuPopup2.connect("activate",
266 lambda *x: self.emit('add_command', *data[:-1]))
267 # right-click popup menu Help
268 menuPopup4 = gtk.ImageMenuItem(gtk.STOCK_HELP)
269 popupMenu.add(menuPopup4)
270 menuPopup4.connect("activate",
271 lambda *x: self.emit('show_man', data[0]))
272 # right-click popup menu Online Help
273 menuPopup4 = gtk.ImageMenuItem(gtk.STOCK_INFO)
274 box = menuPopup4.get_children()[0]
275 box.set_label(_('Show online info'))
276 popupMenu.add(menuPopup4)
277 menuPopup4.connect("activate",
278 lambda wg, url: webbrowser.open(url), data[3])
279 # Show popup menu
280 popupMenu.show_all()
281 popupMenu.popup(None, None, None, event.button, time)
282 return True
0283
=== added file 'plugins/LocalCommandList.py'
--- plugins/LocalCommandList.py 1970-01-01 00:00:00 +0000
+++ plugins/LocalCommandList.py 2012-01-08 10:37:24 +0000
@@ -0,0 +1,710 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# LocalCommandList.py - Plugin for handling locally stored commands
5#
6# Copyright 2010 Duane Hinnen, Kenny Meyer, David Caro
7# <david.caro.estevez@gmail.com>
8#
9# This program is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License version 3, as published
11# by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful, but
14# WITHOUT ANY WARRANTY; without even the implied warranties of
15# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16# PURPOSE. See the GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21
22
23import os
24import pygtk
25pygtk.require('2.0')
26import gobject
27
28try:
29 import gtk
30except:
31 ## do not use gtk, just print
32 print _("You need to install the python gtk bindings package"
33 "'python-gtk2'")
34 sys.exit(1)
35
36## we should try to absolutely detach it from the clicompanion libs someday
37from clicompanionlib.utils import dbg
38import clicompanionlib.plugins as plugins
39
40## Targets for the Drag and Drop
41TARGETS = [
42 ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0),
43 ('text/plain', 0, 1),
44 ('TEXT', 0, 2),
45 ('STRING', 0, 3),
46 ]
47
48
49class LocalCommandList(plugins.TabPlugin):
50 '''
51 Tab with the list of local stored commands, the ponly signals that should
52 get emited (right now) are the run_command and show_man. The other can be
53 processed inhouse
54 '''
55 __authors__ = ('Duane Hinnen\n'
56 'Kenny Meyer\n'
57 'Marcos Vanettai\n'
58 'Marek Bardoński\n'
59 'David Caro <david.caro.estevez@gmail.com>\n')
60 __info__ = ('This is the main plugin for the CLI Companion, the one that '
61 'handles the locally stored commands.')
62 __title__ = 'Local Commands'
63
64 def __init__(self, config):
65 self.config = config
66 plugins.TabPlugin.__init__(self)
67 self.treeview = gtk.TreeView()
68 self.filtering = False
69 ## command, user input, description, and index in cmnds array
70 self.liststore = gtk.ListStore(str, str, str, int)
71 ## Load the given commands file
72 self.cmnds = Cheatsheet(
73 self.config.get('default', 'cheatsheet'))
74 ## will hold the commands. Actually the first three columns
75 ## note that this commands list will not change with searchers nor
76 ## filters, instead, when adding a command to the liststore, we will
77 ## add also the index of the command in the cmnds array as another
78 ## field
79 self.sync_cmnds(rld=True)
80 self.treeview.set_model(DummySort(self.liststore))
81
82 ### Only show the three firs columns
83 ## create the TreeViewColumns to display the data
84 self.add_text_col('Command', 0)
85 self.add_text_col('User Input', 1)
86 self.add_text_col('Description', 2)
87 self.treeview.set_tooltip_column(2)
88 self.treeview.set_reorderable(True)
89 ## open with top command selected
90 selection = self.treeview.get_selection()
91 selection.select_path(0)
92 selection.set_mode(gtk.SELECTION_SINGLE)
93 ### double-click
94 self.treeview.connect("row-activated", self.event_clicked)
95 ##press enter to run command
96 self.treeview.connect("key-press-event",
97 lambda wg, event: self.event_key_pressed(event))
98 ## Right click event
99 self.treeview.connect("button_press_event", self.right_click)
100 # Allow enable drag and drop of rows including row move
101 self.treeview.enable_model_drag_source( gtk.gdk.BUTTON1_MASK,
102 TARGETS,
103 gtk.gdk.ACTION_DEFAULT |
104 gtk.gdk.ACTION_COPY)
105 self.treeview.enable_model_drag_dest(TARGETS,
106 gtk.gdk.ACTION_DEFAULT)
107
108 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