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

Proposed by David Caro
Status: Merged
Approved by: Marek Bardoński
Approved revision: 97
Merged at revision: 101
Proposed branch: lp:~dcaro/clicompanion/fix-910355
Merge into: lp:clicompanion
Diff against target: 2007 lines (+779/-533)
7 files modified
clicompanion (+6/-4)
clicompanionlib/config.py (+237/-26)
clicompanionlib/controller.py (+206/-252)
clicompanionlib/menus_buttons.py (+27/-25)
clicompanionlib/tabs.py (+77/-42)
clicompanionlib/utils.py (+142/-3)
clicompanionlib/view.py (+84/-181)
To merge this branch: bzr merge lp:~dcaro/clicompanion/fix-910355
Reviewer Review Type Date Requested Status
Marek Bardoński Approve
Review via email: mp+87218@code.launchpad.net

Description of the change

Done a lot of improvements and code refactoring.

Also solved a lot of bugs.

The problem was that the bugs that I resolved one by one, where easily resolved together (also I resolved some other bugs that seemed easy).

Please do a full testing of the program, because a lot of things changed, and is possible that new bug may have been created. I tested myself and I will be testing it from now on, but two (or more than one) heads are better for doing so.

Also, taken back all the conflicting merge proposals, to avoid double merging of fixes.

Thanks,
David

To post a comment you must log in.
Revision history for this message
Marek Bardoński (bdfhjk) wrote :

I checked Clicompanion both statically and dynamically. No errors in code observed.

    Bug #611141: It is not obvious that "q" must be pressed to exit help (man pages) [OK]

    Bug #801906: clicompanion crashes on start with ValueError in _get() [Look good]

    Bug #909894: Double click execution with user input drags command [OK]

    Bug #910249: Warning window (when wrong params issued) not working properly [OK]

    Bug #910355: Properties are not applied to the current terminal [OK]

    Bug #910360: Incorrect .clicompanion2 file causes the program to crash [OK]

    Bug #910370: Drag and drop when searching breaks the drag and drop [OK]

    Bug #910531: Allowed empty user input [OK]

    Bug #910533: In a fresh start, when executting the first command, the second gets executed instead [OK]

Regression bugs:
1. Low importance. Everytime, when I open new instance of Clicompanion, I got a message "INFO: Created config file at /home/user/.config/clicompanion/config", but that isn't true - my configuration is stored correctly and no new file was created.

Thank You for your work.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'clicompanion'
2--- clicompanion 2011-12-31 12:33:51 +0000
3+++ clicompanion 2012-01-02 01:28:26 +0000
4@@ -6,7 +6,6 @@
5 import sys
6 from optparse import OptionParser
7
8-from clicompanionlib.view import run
9
10 share_dirs = os.environ.get('XDG_BASE_PDATA',
11 '/usr/local/share:/usr/share').split(os.pathsep)
12@@ -27,10 +26,12 @@
13
14 parser = OptionParser(usage="%prog [-f] [-q]", version="%prog 1.1")
15 parser.add_option("-f", "--file", dest="filename",
16- help="write report to FILE", metavar="FILE")
17+ help="Write report to FILE", metavar="FILE")
18+parser.add_option("-c", "--cheatsheet", dest="cheatsheet",
19+ help="Read cheatsheet from FILE", metavar="FILE")
20 parser.add_option("-q", "--quiet",
21 action="store_false", dest="verbose", default=True,
22- help="don't print status messages to stdout")
23+ help="Don't print status messages to stdout")
24
25 (options, args) = parser.parse_args()
26
27@@ -62,4 +63,5 @@
28
29
30 if __name__ == "__main__":
31- run()
32+ from clicompanionlib.view import run
33+ run( options )
34
35=== modified file 'clicompanionlib/config.py'
36--- clicompanionlib/config.py 2011-03-12 16:08:24 +0000
37+++ clicompanionlib/config.py 2012-01-02 01:28:26 +0000
38@@ -20,33 +20,244 @@
39 #
40 import os
41 import ConfigParser
42+import clicompanionlib.utils as utils
43+from clicompanionlib.utils import dbg
44+import collections
45
46+CHEATSHEET = os.path.expanduser("~/.clicompanion2")
47 CONFIGDIR = os.path.expanduser("~/.config/clicompanion/")
48 CONFIGFILE = os.path.expanduser("~/.config/clicompanion/config")
49-
50-class Config(object):
51-
52- '''
53- create configuration file
54- '''
55-
56- def create_config(self):
57-
58- if not os.path.exists(CONFIGFILE):
59- os.makedirs(CONFIGDIR)
60- config = ConfigParser.ConfigParser()
61- # set a number of parameters
62+CONFIG_ORIG = "/etc/clicompanion.d/clicompanion2.config"
63+DEFAULTS = { "scrollb": '500',
64+ "colorf": '#FFFFFF',
65+ "colorb": '#000000',
66+ "encoding": 'UTF-8',
67+ "debug": 'False'}
68+
69+## To avoid parsing the config file each time, we store the loaded config here
70+CONFIG = None
71+
72+def create_config(conffile=CONFIGFILE):
73+ global CONFIG
74+ config = CONFIG
75+ configdir = conffile.rsplit(os.sep,1)[0]
76+ if not os.path.exists(configdir):
77+ try:
78+ os.makedirs(configdir)
79+ except Exception, e:
80+ print _('Unable to create config at dir %s (%s)')%(configdir,e)
81+ return False
82+ # reuse the config if able
83+ if not config:
84+ config = ConfigParser.SafeConfigParser(DEFAULTS)
85+ # set a number of parameters
86+ if os.path.isfile(conffile):
87+ config.read([conffile])
88+ else:
89 config.add_section("terminal")
90- config.set("terminal", "scrollb", 500)
91- config.set("terminal", "colorf", '#FFFFFF')
92- config.set("terminal", "colorb", '#000000')
93- config.set("terminal", "encoding", 'utf-8')
94- # Writing our configuration file
95- with open(CONFIGFILE, 'wb') as f:
96- config.write(f)
97-
98- else:
99- pass
100-
101-
102-
103+ for option, value in DEFAULTS.items():
104+ config.set("terminal", option, value)
105+ CONFIG = config
106+ # Writing our configuration file
107+ save_config(config, conffile)
108+ print _("INFO: Created config file at %s.")%conffile
109+ return config
110+
111+
112+def get_config_copy(config=None):
113+ global CONFIG
114+ if not config:
115+ config = CONFIG
116+ new_cfg = ConfigParser.SafeConfigParser(DEFAULTS)
117+ for section in config.sections():
118+ new_cfg.add_section(section)
119+ for option in config.options(section):
120+ new_cfg.set(section, option, config.get(section, option))
121+ return new_cfg
122+
123+
124+def get_config(conffile=CONFIGFILE, confdir=CONFIGDIR):
125+ global CONFIG
126+ config = CONFIG
127+ if not config:
128+ dbg('Loading new config')
129+ if not os.path.isfile(conffile):
130+ config = create_config(conffile)
131+ config = ConfigParser.SafeConfigParser(DEFAULTS)
132+ config.add_section("terminal")
133+ config.read([conffile])
134+ CONFIG = config
135+ else:
136+ dbg('Reusing already loaded config')
137+ return config
138+
139+
140+def save_config(config, conffile=CONFIGFILE):
141+ global CONFIG
142+ dbg('Saving conffile at %s'%conffile)
143+ with open(CONFIGFILE, 'wb') as f:
144+ config.write(f)
145+ CONFIG = config
146+
147+class Cheatsheet:
148+ '''
149+ comtainer class for the cheatsheet
150+
151+ Example of usage:
152+ >>> c = config.Cheatsheet()
153+ >>> c.load('/home/cascara/.clicompanion2')
154+ >>> c[3]
155+ ['uname -a', '', 'What kernel am I running\n']
156+ >>> c.file
157+ '/home/cascara/.clicompanion2'
158+ >>> c[2]=[ 'mycmd', 'userui', 'desc' ]
159+ >>> c[2]
160+ ['mycmd', 'userui', 'desc']
161+ >>> del c[2]
162+ >>> c[2]
163+ ['ps aux | grep ?', 'search string', 'Search active processes for search string\n']
164+ >>> c.insert('cmd2','ui2','desc2',2)
165+ >>> c[2]
166+ ['cmd2', 'ui2', 'desc2']
167+
168+ '''
169+ def __init__(self):
170+ self.file = CHEATSHEET
171+ self.commands = []
172+
173+ def __repr__(self):
174+ return 'Config: %s - %s'%(self.file, self.commands)
175+
176+ def load(self, cheatfile=None):
177+ if not cheatfile:
178+ self.file = CHEATSHEET
179+ if not os.path.exists(CHEATSHEET):
180+ if os.path.exists(CONFIG_ORIG):
181+ os.system ("cp %s %s" % (CONFIG_ORIG, CHEATSHEET))
182+ else:
183+ # Oops! Looks like there's no default cheatsheet.
184+ # Then, create an empty cheatsheet.
185+ open(CHEATSHEET, 'w').close()
186+ else:
187+ self.file = cheatfile
188+ try:
189+ dbg('Reading cheatsheet from file %s'%self.file)
190+ with open(self.file, 'r') as ch_fd:
191+ ## try to detect if the line is a old fashines config line
192+ ## (separated by ':'), when saved will rewrite it
193+ no_tabs = True
194+ some_colon = False
195+ for line in ch_fd:
196+ line = line.strip()
197+ if not line:
198+ continue
199+ cmd, ui, desc = [ l.strip() for l in line.split('\t',2)] \
200+ + ['',]*(3-len(line.split('\t',2)))
201+ if ':' in cmd:
202+ some_colon = True
203+ if ui or desc:
204+ no_tabs = False
205+ if cmd and [ cmd, ui, desc ] not in self.commands:
206+ self.commands.append([cmd, ui, desc])
207+ dbg('Adding command %s'%[cmd, ui, desc])
208+ if no_tabs and some_colon:
209+ ## None of the commands had tabs, and all had ':' in the
210+ ## cmd... most probably old config style
211+ print _("Detected old cheatsheet style at")\
212+ +" %s"%self.file+_(", parsing to new one.")
213+ for i in range(len(self.commands)):
214+ cmd, ui, desc = self.commands[i]
215+ cmd, ui, desc = [ l.strip() for l in cmd.split(':',2)] \
216+ + ['',]*(3-len(cmd.split(':',2)))
217+ self.commands[i] = [cmd, ui, desc]
218+ self.save()
219+ except IOError, e:
220+ print _("Error while loading cheatfile")+" %s: %s"%(self.file, e)
221+
222+ def save(self, cheatfile=None):
223+ '''
224+ Saves the current config to the file cheatfile, or the file that was
225+ loaded.
226+ NOTE: It does not overwrite the value self.file, that points to the file
227+ that was loaded
228+ '''
229+ if not cheatfile and self.file:
230+ cheatfile = self.file
231+ elif not cheatfile:
232+ return False
233+ try:
234+ with open(cheatfile, 'wb') as ch_fd:
235+ for command in self.commands:
236+ ch_fd.write('\t'.join(command)+'\n')
237+ except IOError, e:
238+ print _("Error writing cheatfile")+" %s: %s"%(cheatfile, e)
239+ return False
240+ return True
241+
242+ def __len__(self):
243+ return len(self.commands)
244+
245+ def __getitem__(self, key):
246+ return self.commands[key]
247+
248+ def __setitem__(self, key, value):
249+ if not isinstance(value, collections.Iterable) or len(value) < 3:
250+ raise ValueError('Value must be a container with three items, but got %s'%value)
251+ if key < len(self.commands):
252+ self.commands[key]=list(value)
253+ else:
254+ try:
255+ self.insert(*value, pos=key)
256+ except ValueError, e:
257+ raise ValueError('Value must be a container with three items, but got %s'%value)
258+
259+ def __iter__(self):
260+ for command in self.commands:
261+ yield command
262+
263+ def insert(self, cmd, ui, desc, pos=None):
264+ if not [cmd, ui, desc] in self.commands:
265+ if not pos:
266+ self.commands.append([cmd, ui, desc])
267+ else:
268+ self.commands.insert(pos, [cmd, ui, desc])
269+
270+ def append(self, cmd, ui, desc):
271+ self.insert(cmd, ui, desc)
272+
273+ def index(self, cmd, ui, value):
274+ return self.commands.index([cmd, ui, desc])
275+
276+ def __delitem__(self, key):
277+ del self.commands[key]
278+
279+ def pop(self, key):
280+ return self.commands.pop(key)
281+
282+ def del_by_value(self, cmd, ui, desc):
283+ if [cmd, ui, desc] in self.commands:
284+ return self.commands.pop(self.commands.index([cmd, ui, desc]))
285+
286+ def drag_n_drop(self, cmd1, cmd2, before=True):
287+ if cmd1 in self.commands:
288+ dbg('Dropping command from inside %s'%'_\t_'.join(cmd1))
289+ i1 = self.commands.index(cmd1)
290+ del self.commands[i1]
291+ if cmd2:
292+ i2 = self.commands.index(cmd2)
293+ if before:
294+ self.commands.insert(i2, cmd1)
295+ else:
296+ self.commands.insert(i2+1, cmd1)
297+ else:
298+ self.commands.append(cmd1)
299+ else:
300+ dbg('Dropping command from outside %s'%'_\t_'.join(cmd1))
301+ if cmd2:
302+ i2 = self.commands.index(cmd2)
303+ if before:
304+ self.commands.insert(i2, cmd1)
305+ else:
306+ self.commands.insert(i2+1, cmd1)
307+ else:
308+ self.commands.append(cmd1)
309
310=== modified file 'clicompanionlib/controller.py'
311--- clicompanionlib/controller.py 2011-12-31 14:49:46 +0000
312+++ clicompanionlib/controller.py 2012-01-02 01:28:26 +0000
313@@ -19,17 +19,21 @@
314 #
315 #
316
317-from clicompanionlib.utils import get_user_shell
318-
319-import clicompanionlib.tabs
320-import view
321-
322+import os
323 import pygtk
324 pygtk.require('2.0')
325 import re
326 import webbrowser
327-import ConfigParser
328-import os
329+import view
330+import copy
331+import clicompanionlib.tabs
332+import clicompanionlib.config as cc_config
333+import clicompanionlib.utils as utils
334+from clicompanionlib.utils import get_user_shell, dbg
335+
336+#if cc_config.get_config().get('terminal','debug') == 'True':
337+# utils.DEBUG = True
338+
339 # import vte and gtk or print error
340 try:
341 import gtk
342@@ -49,27 +53,17 @@
343
344
345
346-CHEATSHEET = os.path.expanduser("~/.clicompanion2")
347-CONFIG_ORIG = "/etc/clicompanion.d/clicompanion2.config"
348-CONFIGFILE = os.path.expanduser("~/.config/clicompanion/config")
349-
350 class Actions(object):
351- #make instances of the Classes we are going to use
352- #main_window = view.MainWindow
353 ## Info Dialog Box
354 ## if a command needs more info EX: a package name, a path
355- def get_info(self, widget, liststore):
356-
357- if not view.ROW:
358- return
359- row_int = int(view.ROW[0][0])
360-
361+ def get_info(self, cmd, ui, desc):
362+ dbg('Got command with user input')
363 ## Create Dialog object
364 dialog = gtk.MessageDialog(
365 None,
366 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
367 gtk.MESSAGE_QUESTION,
368- gtk.BUTTONS_OK,
369+ gtk.BUTTONS_OK_CANCEL,
370 None)
371
372 # Primary text
373@@ -82,16 +76,16 @@
374
375 ## create a horizontal box to pack the entry and a label
376 hbox = gtk.HBox()
377- hbox.pack_start(gtk.Label(liststore[row_int][1]+":"), False, 5, 5)
378+ hbox.pack_start(gtk.Label(ui+":"), False, 5, 5)
379 hbox.pack_end(entry)
380 ## some secondary text
381- dialog.format_secondary_markup(_("Please provide a "+liststore[row_int][1]))
382+ dialog.format_secondary_markup(_("Please provide a "+ui))
383 ## add it and show it
384 dialog.vbox.pack_end(hbox, True, True, 0)
385 dialog.show_all()
386
387 ## Show the dialog
388- dialog.run()
389+ response = dialog.run()
390
391 ## user text assigned to a variable
392 text = entry.get_text()
393@@ -100,13 +94,15 @@
394 ## The destroy method must be called otherwise the 'Close' button will
395 ## not work.
396 dialog.destroy()
397+ if response != gtk.RESPONSE_OK:
398+ user_input = None
399 return user_input
400
401 def responseToDialog(self, text, dialog, response):
402 dialog.response(response)
403
404 ## Add command dialog box
405- def add_command(self, widget, liststore):
406+ def add_command(self, mw):
407
408 ## Create Dialog object
409 dialog = gtk.MessageDialog(
410@@ -141,15 +137,16 @@
411 hbox2.pack_start(entry3, True, 5, 5)
412
413 ## cancel button
414- dialog.add_button('Cancel', gtk.RESPONSE_DELETE_EVENT)
415+ dialog.add_button(_('Cancel'), gtk.RESPONSE_DELETE_EVENT)
416 ## some secondary text
417- dialog.format_secondary_markup(_('When entering a command use question '
418- 'marks(?) as placeholders if user input is required when the '
419- 'command runs. Example: ls /any/directory would be entered as, '
420- 'ls ? .For each question mark(?) in your command, if any, use '
421- 'the User Input field to provide a hint for each variable. '
422- 'Using our example ls ? you could put directory as the User '
423- 'Input. Lastly provide a brief Description.'))
424+ dialog.format_secondary_markup(
425+ _("When entering a command use question marks(?) as placeholders if"
426+ " user input is required when the command runs. Example: ls "
427+ "/any/directory would be entered as, ls ? .For each question "
428+ "mark(?) in your command, if any, use the User Input field to "
429+ "provide a hint for each variable. Using our example ls ? you "
430+ "could put directory as the User Input. Lastly provide a brief "
431+ "Description."))
432
433 ## add it and show it
434 dialog.vbox.pack_end(hbox2, True, True, 0)
435@@ -163,24 +160,10 @@
436 text1 = entry1.get_text()
437 text2 = entry2.get_text()
438 text3 = entry3.get_text()
439- '''open flat file, add the new command, update CMNDS variable
440- ## update commands in liststore (on screen) '''
441- if text1 != "":
442- with open(CHEATSHEET, "r") as cheatfile:
443- cheatlines = cheatfile.readlines()
444- cheatlines.append(text1+"\t"+text2+"\t"+text3+'\n')
445- cheatfile.close()
446- with open(CHEATSHEET, "w") as cheatfile2:
447- cheatfile2.writelines(cheatlines)
448- cheatfile2.close()
449- l = str(text1+"\t"+text2+"\t"+text3)
450- #ls = l.split(':',2)
451- ## update view.CMNDS variable
452- filteredcommandplus = text1, text2, text3
453- view.CMNDS.append(filteredcommandplus)
454- ## update the command list on screen
455- liststore.append([text1,text2,text3])
456-
457+ ## update commandsand sync with screen '''
458+ view.CMNDS.append(text1, text2, text3)
459+ mw.sync_cmnds()
460+ view.CMNDS.save()
461
462 ## The destroy method must be called otherwise the 'Close' button will
463 ## not work.
464@@ -188,32 +171,14 @@
465 #return text
466
467 ## This the edit function
468- def edit_command(self, widget , liststore):
469-
470+ def edit_command(self, mw):
471 if not view.ROW:
472 return
473- row_int_x = int(view.ROW[0][0])
474- row_int = 0
475- ## TODO: Not implemented with filted yet
476- if view.FILTER == 1:
477- with open(CHEATSHEET, "r") as cheatfile:
478- cheatlines = cheatfile.readlines()
479- for i in range(len(cheatlines)):
480- if view.CMNDS[row_int_x][0] in cheatlines[i] and view.CMNDS[row_int_x][1] in cheatlines[i] :
481- row_int = i
482- cheatfile.close()
483- else:
484- row_int = row_int_x
485-
486-
487- row_obj1 = view.MainWindow.liststore[row_int][0]
488- text1 = "".join(row_obj1)
489-
490- row_obj2 = view.MainWindow.liststore[row_int][1]
491- text2 = "".join(row_obj2)
492-
493- row_obj3 = view.MainWindow.liststore[row_int][2]
494- text3 = "".join(row_obj3)
495+ lst_index = int(view.ROW[0][0])
496+ model = mw.treeview.get_model()
497+ cmd = ''.join(model[lst_index][0])
498+ ui = ''.join(model[lst_index][1])
499+ desc = ''.join(model[lst_index][2])
500
501 ## Create Dialog object
502 dialog = gtk.MessageDialog(
503@@ -228,11 +193,11 @@
504
505 ## create the text input fields
506 entry1 = gtk.Entry()
507- entry1.set_text(text1)
508+ entry1.set_text(cmd)
509 entry2 = gtk.Entry()
510- entry2.set_text(text2)
511+ entry2.set_text(ui)
512 entry3 = gtk.Entry()
513- entry3.set_text(text3)
514+ entry3.set_text(desc)
515 ## allow the user to press enter to do ok
516 entry1.connect("activate", self.responseToDialog, dialog, gtk.RESPONSE_OK)
517
518@@ -249,7 +214,7 @@
519 hbox2.pack_start(entry3, True, 5, 5)
520
521 ## cancel button
522- dialog.add_button('Cancel', gtk.RESPONSE_DELETE_EVENT)
523+ dialog.add_button(_('Cancel'), gtk.RESPONSE_DELETE_EVENT)
524 ## some secondary text
525 dialog.format_secondary_markup(_("Please provide a command, description, and what type of user variable, if any, is required."))
526
527@@ -262,64 +227,35 @@
528
529 if result == gtk.RESPONSE_OK:
530 ## user text assigned to a variable
531- text1 = entry1.get_text()
532- text2 = entry2.get_text()
533- text3 = entry3.get_text()
534+ cmd = entry1.get_text()
535+ ui = entry2.get_text()
536+ desc = entry3.get_text()
537
538- if text1 != "":
539- self.remove_command(widget, liststore)
540- '''open flat file, add the new command, update CMNDS variable
541- ## update commands in liststore (on screen) '''
542-
543- with open(CHEATSHEET, "r") as cheatfile:
544- cheatlines = cheatfile.readlines()
545- cheatlines.append(text1+":"+text2+":"+text3+'\n')
546- cheatfile.close()
547- with open(CHEATSHEET, "w") as cheatfile2:
548- cheatfile2.writelines(cheatlines)
549- cheatfile2.close()
550- l = str(text1+":"+text2+":"+text3)
551- #ls = l.split(':',2)
552- ## update view.CMNDS variable
553- filteredcommandplus = text1, text2, text3
554- view.CMNDS.append(filteredcommandplus)
555- ## update the command list on screen
556- liststore.append([text1,text2,text3])
557-
558+ if cmd != "":
559+ cmd_index = model[lst_index][3]
560+ dbg('Got index %d for command at pos %d'%(cmd_index, lst_index))
561+ view.CMNDS[cmd_index] = [cmd, ui, desc]
562+ mw.sync_cmnds()
563+ view.CMNDS.save()
564 ## The destroy method must be called otherwise the 'Close' button will
565 ## not work.
566 dialog.destroy()
567
568
569 ## Remove command from command file and GUI
570- def remove_command(self, widget, liststore):
571-
572+ def remove_command(self, mw):
573 if not view.ROW:
574 return
575- row_int_x = int(view.ROW[0][0])
576- row_int = 0
577- ## TODO: Not implemented with filted yet
578- if view.FILTER == 1:
579- with open(CHEATSHEET, "r") as cheatfile:
580- cheatlines = cheatfile.readlines()
581- for i in range(len(cheatlines)):
582- if view.CMNDS[row_int_x][0] in cheatlines[i] and view.CMNDS[row_int_x][1] in cheatlines[i]:
583- row_int = i
584- cheatfile.close()
585- else:
586- row_int = row_int_x
587-
588- del view.CMNDS[row_int_x]
589- del liststore[row_int]
590-
591- ## open command file and delete line so the change is persistent
592- with open(CHEATSHEET, "r") as cheatfile:
593- cheatlines = cheatfile.readlines()
594- del cheatlines[row_int]
595- cheatfile.close()
596- with open(CHEATSHEET, "w") as cheatfile2:
597- cheatfile2.writelines(cheatlines)
598- cheatfile2.close()
599+ ## get selected row
600+ lst_index = int(view.ROW[0][0])
601+ ## get selected element index, even from search filter
602+ model = mw.treeview.get_model()
603+ cmd_index = model[lst_index][3]
604+ ## delete element from liststore and CMNDS
605+ del view.CMNDS[cmd_index]
606+ mw.sync_cmnds()
607+ ## save changes
608+ view.CMNDS.save()
609
610
611 def _filter_commands(self, widget, liststore, treeview):
612@@ -330,11 +266,13 @@
613 Pretty straight-forward.
614 """
615 search_term = widget.get_text().lower()
616+ ## If the search term is empty, restore the liststore
617 if search_term == "":
618- view.FILTER = 0
619- else:
620- view.FILTER = 1
621-
622+ view.FILTER = 0
623+ treeview.set_model(liststore)
624+ return
625+
626+ view.FILTER = 1
627 ## Create a TreeModelFilter object which provides auxiliary functions for
628 ## filtering data.
629 ## http://www.pygtk.org/pygtk2tutorial/sec-TreeModelSortAndTreeModelFilter.html
630@@ -356,39 +294,31 @@
631 ## Python raises a AttributeError if row data was modified . Catch
632 ## that and fail silently.
633 pass
634-
635-
636 modelfilter.set_visible_func(search, search_term)
637+ ## save the old liststore and cmnds
638 treeview.set_model(modelfilter)
639-
640-
641- #clear CMNDS list then populate it with the filteredlist of commands
642- view.CMNDS = []
643- for line in modelfilter:
644- filteredcommandplus = tuple(modelfilter.get(line.iter, 0, 1, 2))
645- view.CMNDS.append(filteredcommandplus)
646-
647-
648
649 ## send the command to the terminal
650- def run_command(self, widget, notebook, liststore):
651+ def run_command(self, mw):
652
653 ## if called without selecting a command from the list return
654 if not view.ROW:
655 return
656 text = ""
657- row_int = int(view.ROW[0][0]) ## removes everything but number from [5,]
658+ lst_index = int(view.ROW[0][0]) ## removes everything but number from [5,]
659
660 ## get the current notebook page so the function knows which terminal to run the command in.
661- pagenum = notebook.get_current_page()
662- widget = notebook.get_nth_page(pagenum)
663+ pagenum = mw.notebook.get_current_page()
664+ widget = mw.notebook.get_nth_page(pagenum)
665 page_widget = widget.get_child()
666
667- ## view.CMNDS is where commands are stored
668- cmnd = view.CMNDS[row_int][0]
669+ model = mw.treeview.get_model()
670+ cmd = ''.join(model[lst_index][0])
671+ ui = ''.join(model[lst_index][1])
672+ desc = ''.join(model[lst_index][2])
673
674 ## find how many ?(user arguments) are in command
675- match = re.findall('\?', cmnd)
676+ match = re.findall('\?', cmd)
677 '''
678 Make sure user arguments were found. Replace ? with something
679 .format can read. This is done so the user can just enter ?, when
680@@ -400,23 +330,33 @@
681 else:
682 num = len(match)
683 ran = 0
684- new_cmnd = self.replace(cmnd, num, ran)
685-
686-
687- if not view.CMNDS[row_int][1] == "": # command with user input
688- c = ""
689- try:
690- text = self.get_info(self, liststore)
691- c = new_cmnd.format(text)
692- except:
693- error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
694- _("You need to enter full input. Click on [x] to continue."))
695- error.run()
696- page_widget.feed_child(c+"\n") #send command w/ input
697+ new_cmnd = self.replace(cmd, num, ran)
698+
699+ if len(match) > 0: # command with user input
700+ dbg('command with ui')
701+ f_cmd = ""
702+ while True:
703+ try:
704+ ui_text = self.get_info(cmd, ui, desc)
705+ if ui_text == None:
706+ return
707+ dbg('Got ui "%s"'%' '.join(ui_text))
708+ if ''.join(ui_text) == '':
709+ raise IndexError
710+ f_cmd = new_cmnd.format(ui_text)
711+ except IndexError, e:
712+ error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, \
713+ gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
714+ _("You need to enter full input. Space separated."))
715+ error.connect('response', lambda err, *x: err.destroy())
716+ error.run()
717+ continue
718+ break
719+ page_widget.feed_child(f_cmd+"\n") #send command w/ input
720 page_widget.show()
721 page_widget.grab_focus()
722 else: ## command that has no user input
723- page_widget.feed_child(cmnd+"\n") #send command
724+ page_widget.feed_child(cmd+"\n") #send command
725 page_widget.show()
726 page_widget.grab_focus()
727
728@@ -432,7 +372,9 @@
729 return cmnd
730
731 ## open the man page for selected command
732- def man_page(self, widget, notebook):
733+ def man_page(self, notebook):
734+ import subprocess as sp
735+ import shlex
736 try:
737 row_int = int(view.ROW[0][0]) # removes everything but number from EX: [5,]
738 except IndexError:
739@@ -443,21 +385,67 @@
740 gtk.MESSAGE_QUESTION,
741 gtk.BUTTONS_OK,
742 None)
743- dialog.set_markup('You must choose row to view help')
744+ dialog.set_markup(_('You must choose a row to view the help'))
745 dialog.show_all()
746 dialog.run()
747 dialog.destroy()
748 return
749+ ## get the manpage for the command
750 cmnd = view.CMNDS[row_int][0] #CMNDS is where commands are store
751- splitcommand = self._filter_sudo_from(cmnd.split(" "))
752- ## get current notebook tab to use in function
753- pagenum = notebook.get_current_page()
754- widget = notebook.get_nth_page(pagenum)
755- page_widget = widget.get_child()
756- #send command to Terminal
757- page_widget.feed_child("man "+splitcommand[0]+"| most \n")
758- page_widget.grab_focus()
759- page_widget.show()
760+ ## get each command for each pipe, It's not 100 accurate, but good
761+ ## enough (by now)
762+ commands = []
763+ next_part = True
764+ found_sudo = False
765+ for part in shlex.split(cmnd):
766+ if next_part:
767+ if part == 'sudo' and not found_sudo:
768+ found_sudo = True
769+ commands.append('sudo')
770+ else:
771+ if part not in commands:
772+ commands.append(part)
773+ next_part = False
774+ else:
775+ if part in [ '||', '&&', '&', '|']:
776+ next_part = True
777+
778+ notebook = gtk.Notebook()
779+ notebook.set_scrollable(True)
780+ notebook.popup_enable()
781+ notebook.set_properties(group_id=0, tab_vborder=0, tab_hborder=1, tab_pos=gtk.POS_TOP)
782+ ## create a tab for each command
783+ for command in commands:
784+ scrolled_page = gtk.ScrolledWindow()
785+ scrolled_page.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
786+ tab = gtk.HBox()
787+ tab_label = gtk.Label(command)
788+ tab_label.show()
789+ tab.pack_start(tab_label)
790+ page = gtk.TextView()
791+ page.set_wrap_mode(gtk.WRAP_WORD)
792+ page.set_editable(False)
793+ page.set_cursor_visible(False)
794+ try:
795+ manpage = sp.check_output(["man",command])
796+ except sp.CalledProcessError, e:
797+ manpage = _('Failed to get manpage for command "%s"\nReason:\n%s')%(
798+ command, e)
799+ textbuffer = page.get_buffer()
800+ textbuffer.set_text(manpage)
801+ scrolled_page.add(page)
802+ notebook.append_page(scrolled_page, tab)
803+
804+ help_win = gtk.Dialog()
805+ help_win.set_title(_("Man page for %s")%cmnd)
806+ help_win.vbox.pack_start(notebook, True, True, 0)
807+ button = gtk.Button("close")
808+ button.connect_object("clicked", lambda self: self.destroy(), help_win)
809+ button.set_flags(gtk.CAN_DEFAULT)
810+ help_win.action_area.pack_start( button, True, True, 0)
811+ button.grab_default()
812+ help_win.set_default_size(500,600)
813+ help_win.show_all()
814
815
816 @staticmethod
817@@ -471,7 +459,6 @@
818 return command
819
820
821-
822 #TODO: Move to menus_buttons
823 def copy_paste(self, vte, event, data=None):
824 if event.button == 3:
825@@ -517,11 +504,11 @@
826
827 # Add a short comment about the application, this appears below the application
828 # name in the dialog
829- dialog.set_comments('This is a CLI Companion program.')
830+ dialog.set_comments(_('This is a CLI Companion program.'))
831
832 # Add license information, this is connected to the 'License' button
833 # and is displayed in a new window.
834- dialog.set_license('Distributed under the GNU license. You can see it at <http://www.gnu.org/licenses/>.')
835+ dialog.set_license(_('Distributed under the GNU license. You can see it at <http://www.gnu.org/licenses/>.'))
836
837 # Show the dialog
838 dialog.run()
839@@ -529,11 +516,13 @@
840 # The destroy method must be called otherwise the 'Close' button will
841 # not work.
842 dialog.destroy()
843+
844+
845 def help_event(self, widget, data=None):
846- webbrowser.open("http://okiebuntu.homelinux.com/okwiki/clicompanion")
847+ webbrowser.open("http://launchpad.net/clicompanion")
848
849+
850 def usage_event(self, widget, data=None):
851- mw = view.MainWindow
852 dialog = gtk.Dialog("Usage",
853 None,
854 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
855@@ -573,139 +562,104 @@
856
857 ## File --> Preferences
858 def changed_cb(self, combobox, config):
859- config.read(CONFIGFILE)
860+ dbg('Changed encoding')
861 model = combobox.get_model()
862 index = combobox.get_active()
863- if index:
864+ if index>=0:
865 text_e = model[index][0]
866- config.set("terminal", "encoding", text_e)
867- # Writing our configuration file
868- with open(CONFIGFILE, 'wb') as f:
869- config.write(f)
870+ encoding = text_e.split(':',1)[0].strip()
871+ dbg('Setting encoding to "%s"'%encoding)
872+ config.set("terminal", "encoding", encoding)
873
874
875- def color_set_fg_cb(self, colorbutton_fg, config):
876- config.read(CONFIGFILE)
877- #colorf16 = colorbutton_fg.get_color()
878+ def color_set_fg_cb(self, colorbutton_fg, config, tabs):
879+ dbg('Changing fg color')
880 colorf = self.color2hex(colorbutton_fg)
881 config.set("terminal", "colorf", str(colorf))
882- # Writing our configuration file
883- with open(CONFIGFILE, 'wb') as f:
884- config.write(f)
885-
886-
887-
888- def color_set_bg_cb(self, colorbutton_bg, config):
889- config.read(CONFIGFILE)
890- #colorb16 = colorbutton_bg.get_color()
891+ tabs.update_all_term_config(config)
892+
893+
894+ def color_set_bg_cb(self, colorbutton_bg, config, tabs):
895+ dbg('Changing bg color')
896 colorb = self.color2hex(colorbutton_bg)
897 config.set("terminal", "colorb", str(colorb))
898- # Writing our configuration file
899- with open(CONFIGFILE, 'wb') as f:
900- config.write(f)
901-
902+ tabs.update_all_term_config(config)
903
904
905 def color2hex(self, widget):
906 """Pull the colour values out of a Gtk ColorPicker widget and return them
907 as 8bit hex values, sinces its default behaviour is to give 16bit values"""
908 widcol = widget.get_color()
909- print widcol
910 return('#%02x%02x%02x' % (widcol.red>>8, widcol.green>>8, widcol.blue>>8))
911
912- def preferences(self, widget, data=None):
913+ def preferences(self, tabs, data=None):
914 '''
915 Preferences window
916 '''
917- mw = view.MainWindow
918- dialog = gtk.Dialog("User Preferences",
919+ dialog = gtk.Dialog(_("User Preferences"),
920 None,
921 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
922 (gtk.STOCK_CANCEL, gtk.RESPONSE_CLOSE,
923 gtk.STOCK_OK, gtk.RESPONSE_OK))
924
925- config = ConfigParser.RawConfigParser()
926- config.read(CONFIGFILE)
927-
928+ config = cc_config.get_config_copy()
929+
930 ##create the text input fields
931 entry1 = gtk.Entry()
932 entry1.set_text(config.get('terminal', 'scrollb'))
933-
934
935 ##combobox for selecting encoding
936 combobox = gtk.combo_box_new_text()
937- combobox.append_text('Select encoding:')
938- combobox.append_text('UTF-8')
939- combobox.append_text('ISO-8859-1')
940- combobox.append_text('ISO-8859-15')
941-
942+ i=0
943+ for encoding, desc in utils.encodings:
944+ combobox.append_text(encoding + ': '+desc)
945+ if encoding.strip().upper() == config.get('terminal','encoding').upper():
946+ active = i
947+ i=i+1
948+ combobox.set_active(active)
949 combobox.connect('changed', self.changed_cb, config)
950- combobox.set_active(0)
951
952 ##colorbox for selecting text and background color
953- colorbutton_fg = gtk.ColorButton(gtk.gdk.color_parse('white'))
954- colorbutton_bg = gtk.ColorButton(gtk.gdk.color_parse('black'))
955-
956- colorbutton_fg.connect('color-set', self.color_set_fg_cb, config)
957- colorbutton_bg.connect('color-set', self.color_set_bg_cb, config)
958-
959- #dialog.show_all()
960+ colorbutton_fg = gtk.ColorButton(
961+ gtk.gdk.color_parse(config.get('terminal','colorf')))
962+ colorbutton_bg = gtk.ColorButton(
963+ gtk.gdk.color_parse(config.get('terminal','colorb')))
964+
965+ colorbutton_fg.connect('color-set', self.color_set_fg_cb, config, tabs)
966+ colorbutton_bg.connect('color-set', self.color_set_bg_cb, config, tabs)
967
968 ## allow the user to press enter to do ok
969 entry1.connect("activate", self.responseToDialog, dialog, gtk.RESPONSE_OK)
970
971-
972-
973- ## create three labels
974+ ## create the labels
975 hbox1 = gtk.HBox()
976 hbox1.pack_start(gtk.Label(_("Scrollback")), False, 5, 5)
977 hbox1.pack_start(entry1, False, 5, 5)
978
979- hbox1.pack_start(gtk.Label(_("encoding")), False, 5, 5)
980+ hbox1.pack_start(gtk.Label(_("Encoding")), False, 5, 5)
981 hbox1.pack_start(combobox, False, 5, 5)
982
983-
984 hbox2 = gtk.HBox()
985- hbox2.pack_start(gtk.Label(_("font color")), False, 5, 5)
986+ hbox2.pack_start(gtk.Label(_("Font color")), False, 5, 5)
987 hbox2.pack_start(colorbutton_fg, True, 5, 5)
988
989- hbox2.pack_start(gtk.Label(_("background color")), False, 5, 5)
990+ hbox2.pack_start(gtk.Label(_("Background color")), False, 5, 5)
991 hbox2.pack_start(colorbutton_bg, True, 5, 5)
992
993-
994 ## add it and show it
995 dialog.vbox.pack_end(hbox2, True, True, 0)
996 dialog.vbox.pack_end(hbox1, True, True, 0)
997 dialog.show_all()
998-
999+
1000 result = dialog.run()
1001-
1002 if result == gtk.RESPONSE_OK:
1003-
1004 ## user text assigned to a variable
1005 text_sb = entry1.get_text()
1006-
1007- config.read(CONFIGFILE)
1008 config.set("terminal", "scrollb", text_sb)
1009-
1010-
1011-
1012- # Writing our configuration file
1013- with open(CONFIGFILE, 'wb') as f:
1014- config.write(f)
1015-
1016-
1017- ## instantiate tabs
1018- tabs = clicompanionlib.tabs.Tabs()
1019- tabs.update_term_config
1020+ cc_config.save_config(config)
1021+ tabs.update_all_term_config()
1022
1023 ## The destroy method must be called otherwise the 'Close' button will
1024 ## not work.
1025 dialog.destroy()
1026
1027- def save_cmnds(self):
1028- with open(CHEATSHEET, "w") as cheatfile:
1029- for command in view.CMNDS:
1030- cheatfile.write('\t'.join(command)+'\n')
1031-
1032-
1033
1034=== modified file 'clicompanionlib/menus_buttons.py'
1035--- clicompanionlib/menus_buttons.py 2011-11-16 10:48:50 +0000
1036+++ clicompanionlib/menus_buttons.py 2012-01-02 01:28:26 +0000
1037@@ -26,7 +26,11 @@
1038
1039 class FileMenu(object):
1040
1041- def the_menu(self, actions, notebook, liststore):
1042+ def the_menu(self, mw):
1043+ actions = mw.actions
1044+ liststore = mw.liststore
1045+ tabs = mw.tabs
1046+ notebook = mw.notebook
1047 menu = gtk.Menu()
1048 #color = gtk.gdk.Color(65555, 62000, 65555)
1049 #menu.modify_bg(gtk.STATE_NORMAL, color)
1050@@ -43,33 +47,31 @@
1051 ## Make 'Run' menu entry
1052 menu_item1 = gtk.MenuItem(_("Run Command [F4]"))
1053 menu.append(menu_item1)
1054- menu_item1.connect("activate", actions.run_command, notebook, liststore)
1055+ menu_item1.connect("activate", lambda *x: actions.run_command(mw))
1056 menu_item1.show()
1057
1058 ## Make 'Add' file menu entry
1059 menu_item2 = gtk.MenuItem(_("Add Command [F5]"))
1060 menu.append(menu_item2)
1061- menu_item2.connect("activate", actions.add_command, liststore)
1062+ menu_item2.connect("activate", lambda *x: actions.add_command(mw))
1063 menu_item2.show()
1064
1065 ## Make 'Remove' file menu entry
1066 menu_item3 = gtk.MenuItem(_("Remove Command [F6]"))
1067 menu.append(menu_item3)
1068- menu_item3.connect("activate", actions.remove_command, liststore)
1069+ menu_item3.connect("activate", lambda *x: actions.remove_command(mw))
1070 menu_item3.show()
1071
1072 ## Make 'Add Tab' file menu entry
1073- tab = tabs.Tabs()
1074 menu_item4 = gtk.MenuItem(_("Add Tab [F7]"))
1075 menu.append(menu_item4)
1076- menu_item4.connect("activate", tab.add_tab, notebook)
1077+ menu_item4.connect("activate", lambda *x: tabs.add_tab(notebook))
1078 menu_item4.show()
1079
1080 ## Make 'User Preferences' file menu entry
1081- #tab = tabs.Tabs()
1082 menu_item5 = gtk.MenuItem(_("Preferences"))
1083 menu.append(menu_item5)
1084- menu_item5.connect("activate", actions.preferences)
1085+ menu_item5.connect("activate", lambda *x: actions.preferences(tabs))
1086 menu_item5.show()
1087
1088 ## Make 'Quit' file menu entry
1089@@ -113,7 +115,7 @@
1090
1091
1092
1093- def buttons(self, actions, spacing, layout, notebook, liststore):
1094+ def buttons(self, mw, spacing, layout):
1095 #button box at bottom of main window
1096 frame = gtk.Frame()
1097 bbox = gtk.HButtonBox()
1098@@ -125,9 +127,9 @@
1099 bbox.set_layout(layout)
1100 bbox.set_spacing(spacing)
1101 # Run button
1102- buttonRun = gtk.Button(_("Run"))
1103+ buttonRun = gtk.Button('_'+_("Run"))
1104 bbox.add(buttonRun)
1105- buttonRun.connect("clicked", actions.run_command, notebook, liststore)
1106+ buttonRun.connect("clicked", lambda *x: mw.actions.run_command(mw))
1107 buttonRun.set_tooltip_text(_("Click to run a highlighted command"))
1108 #buttonRun.modify_bg(gtk.STATE_NORMAL, color)
1109 #buttonRun.modify_bg(gtk.STATE_PRELIGHT, color)
1110@@ -135,15 +137,15 @@
1111 # Add button
1112 buttonAdd = gtk.Button(stock=gtk.STOCK_ADD)
1113 bbox.add(buttonAdd)
1114- buttonAdd.connect("clicked", actions.add_command, liststore)
1115+ buttonAdd.connect("clicked", lambda *x: mw.actions.add_command(mw))
1116 buttonAdd.set_tooltip_text(_("Click to add a command to your command list"))
1117 #buttonAdd.modify_bg(gtk.STATE_NORMAL, color)
1118 #buttonAdd.modify_bg(gtk.STATE_PRELIGHT, color)
1119 #buttonAdd.modify_bg(gtk.STATE_INSENSITIVE, color)
1120 # Edit button
1121- buttonEdit = gtk.Button(_("Edit"))
1122+ buttonEdit = gtk.Button('_'+_("Edit"))
1123 bbox.add(buttonEdit)
1124- buttonEdit.connect("clicked", actions.edit_command, liststore)
1125+ buttonEdit.connect("clicked", lambda *x: mw.actions.edit_command(mw))
1126 buttonEdit.set_tooltip_text(_("Click to edit a command in your command list"))
1127 #buttonEdit.modify_bg(gtk.STATE_NORMAL, color)
1128 #buttonEdit.modify_bg(gtk.STATE_PRELIGHT, color)
1129@@ -151,7 +153,7 @@
1130 # Delete button
1131 buttonDelete = gtk.Button(stock=gtk.STOCK_DELETE)
1132 bbox.add(buttonDelete)
1133- buttonDelete.connect("clicked", actions.remove_command, liststore)
1134+ buttonDelete.connect("clicked", lambda *x: mw.actions.remove_command(mw))
1135 buttonDelete.set_tooltip_text(_("Click to delete a command in your command list"))
1136 #buttonDelete.modify_bg(gtk.STATE_NORMAL, color)
1137 #buttonDelete.modify_bg(gtk.STATE_PRELIGHT, color)
1138@@ -159,7 +161,7 @@
1139 #Help Button
1140 buttonHelp = gtk.Button(stock=gtk.STOCK_HELP)
1141 bbox.add(buttonHelp)
1142- buttonHelp.connect("clicked", actions.man_page, notebook)
1143+ buttonHelp.connect("clicked", lambda *x: mw.actions.man_page(mw.notebook))
1144 buttonHelp.set_tooltip_text(_("Click to get help with a command in your command list"))
1145 #buttonHelp.modify_bg(gtk.STATE_NORMAL, color)
1146 #buttonHelp.modify_bg(gtk.STATE_PRELIGHT, color)
1147@@ -167,7 +169,7 @@
1148 # Cancel button
1149 buttonCancel = gtk.Button(stock=gtk.STOCK_QUIT)
1150 bbox.add(buttonCancel)
1151- buttonCancel.connect("clicked", actions.delete_event)
1152+ buttonCancel.connect("clicked", mw.actions.delete_event)
1153 buttonCancel.set_tooltip_text(_("Click to quit CLI Companion"))
1154 #buttonCancel.modify_bg(gtk.STATE_NORMAL, color)
1155 #buttonCancel.modify_bg(gtk.STATE_PRELIGHT, color)
1156@@ -176,34 +178,34 @@
1157
1158
1159 #right-click popup menu for the Liststore(command list)
1160- def right_click(self, widget, event, actions, treeview, notebook, liststore):
1161+ def right_click(self, widget, event, mw):
1162 if event.button == 3:
1163 x = int(event.x)
1164 y = int(event.y)
1165 time = event.time
1166- pthinfo = treeview.get_path_at_pos(x, y)
1167+ pthinfo = mw.treeview.get_path_at_pos(x, y)
1168 if pthinfo is not None:
1169 path, col, cellx, celly = pthinfo
1170- treeview.grab_focus()
1171- treeview.set_cursor( path, col, 0)
1172+ mw.treeview.grab_focus()
1173+ mw.treeview.set_cursor( path, col, 0)
1174
1175 # right-click popup menu Apply(run)
1176 popupMenu = gtk.Menu()
1177 menuPopup1 = gtk.ImageMenuItem (gtk.STOCK_APPLY)
1178 popupMenu.add(menuPopup1)
1179- menuPopup1.connect("activate", actions.run_command, notebook, liststore)
1180+ menuPopup1.connect("activate", lambda self, *x: mw.actions.run_command(mw))
1181 # right-click popup menu Edit
1182 menuPopup2 = gtk.ImageMenuItem (gtk.STOCK_EDIT)
1183 popupMenu.add(menuPopup2)
1184- menuPopup2.connect("activate", actions.edit_command, liststore)
1185+ menuPopup2.connect("activate", lambda self, *x: mw.actions.edit_command(mw))
1186 # right-click popup menu Delete
1187 menuPopup3 = gtk.ImageMenuItem (gtk.STOCK_DELETE)
1188 popupMenu.add(menuPopup3)
1189- menuPopup3.connect("activate", actions.remove_command, liststore)
1190+ menuPopup3.connect("activate", lambda self, *x: mw.actions.remove_command(mw))
1191 # right-click popup menu Help
1192 menuPopup4 = gtk.ImageMenuItem (gtk.STOCK_HELP)
1193 popupMenu.add(menuPopup4)
1194- menuPopup4.connect("activate", actions.man_page, notebook)
1195+ menuPopup4.connect("activate", lambda self, *x: mw.actions.man_page(mw.notebook))
1196 # Show popup menu
1197 popupMenu.show_all()
1198 popupMenu.popup( None, None, None, event.button, time)
1199
1200=== modified file 'clicompanionlib/tabs.py'
1201--- clicompanionlib/tabs.py 2011-03-29 17:31:11 +0000
1202+++ clicompanionlib/tabs.py 2012-01-02 01:28:26 +0000
1203@@ -22,33 +22,33 @@
1204 pygtk.require('2.0')
1205 import gtk
1206 import vte
1207-import ConfigParser
1208+import clicompanionlib.config as cc_config
1209
1210-from clicompanionlib.utils import get_user_shell
1211+from clicompanionlib.utils import get_user_shell, dbg
1212 import clicompanionlib.controller
1213+import clicompanionlib.utils as utils
1214 import view
1215
1216-CONFIGFILE = os.path.expanduser("~/.config/clicompanion/config")
1217-
1218-
1219-#definition gcp - how many pages is visible
1220-gcp=0;
1221-
1222-#definition nop - (no of pages) reflects no of terminal tabs left (some may be closed by the user)
1223-nop=0;
1224
1225 class Tabs(object):
1226 '''
1227 add a new terminal in a tab above the current terminal
1228 '''
1229- def add_tab(self,widget, notebook):
1230+ def __init__(self):
1231+ #definition nop - (no of pages) reflects no of terminal tabs left (some may be closed by the user)
1232+ self.nop = 0
1233+ #definition gcp - how many pages is visible
1234+ self.gcp = 0
1235+
1236+ def add_tab(self, notebook):
1237+ dbg('Adding a new tab')
1238 _vte = vte.Terminal()
1239 if view.NETBOOKMODE == 1:
1240 _vte.set_size_request(700, 120)
1241 else:
1242 _vte.set_size_request(700, 220)
1243
1244- _vte.connect ("child-exited", lambda term: gtk.main_quit())
1245+ _vte.connect("child-exited", lambda term: gtk.main_quit())
1246 _vte.fork_command(get_user_shell()) # Get the user's default shell
1247
1248 self.update_term_config(_vte)
1249@@ -58,19 +58,16 @@
1250 #notebook.set_show_tabs(True)
1251 #notebook.set_show_border(True)
1252
1253- global gcp
1254- global nop
1255- nop += 1
1256- gcp += 1
1257- pagenum = ('Tab %d') % gcp
1258- if nop > 1:
1259+ self.nop += 1
1260+ self.gcp += 1
1261+ pagenum = ('Tab %d') % self.gcp
1262+ if self.nop > 1:
1263+ dbg('More than one tab, showing them.')
1264 view.MainWindow.notebook.set_show_tabs(True)
1265 box = gtk.HBox()
1266 label = gtk.Label(pagenum)
1267 box.pack_start(label, True, True)
1268
1269-
1270-
1271
1272 ## x image for tab close button
1273 close_image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
1274@@ -87,10 +84,10 @@
1275 view.MainWindow.notebook.prepend_page(vte_tab, box) # add tab
1276 view.MainWindow.notebook.set_scrollable(True)
1277 actions = clicompanionlib.controller.Actions()
1278- _vte.connect ("button_press_event", actions.copy_paste, None)
1279+ _vte.connect("button_press_event", actions.copy_paste, None)
1280 vte_tab.grab_focus()
1281 # signal handler for tab
1282- closebtn.connect("clicked", self.close_tab, vte_tab, notebook)
1283+ closebtn.connect("clicked", lambda *x: self.close_tab(vte_tab, notebook))
1284
1285 vte_tab.show_all()
1286
1287@@ -98,29 +95,42 @@
1288
1289
1290 ## Remove a page from the notebook
1291- def close_tab(self, sender, widget, notebook):
1292+ def close_tab(self, vte_tab, notebook):
1293 ## get the page number of the tab we wanted to close
1294- pagenum = view.MainWindow.notebook.page_num(widget)
1295+ pagenum = view.MainWindow.notebook.page_num(vte_tab)
1296 ## and close it
1297 view.MainWindow.notebook.remove_page(pagenum)
1298- global nop
1299- nop -= 1
1300- if nop <= 1:
1301+ self.nop -= 1
1302+ if self.nop <= 1:
1303 view.MainWindow.notebook.set_show_tabs(False)
1304
1305 # check if the focus does not go to the last page (ie with only a + sign)
1306- if view.MainWindow.notebook.get_current_page() == nop:
1307+ if view.MainWindow.notebook.get_current_page() == self.nop:
1308 view.MainWindow.notebook.prev_page()
1309
1310-
1311-
1312- def update_term_config(self, _vte):
1313- ##read config file
1314- config = ConfigParser.RawConfigParser()
1315- config.read(CONFIGFILE)
1316-
1317+ def update_all_term_config(self, config=None):
1318+ for pagenum in range(view.MainWindow.notebook.get_n_pages()):
1319+ page = view.MainWindow.notebook.get_nth_page(pagenum)
1320+ dbg(page)
1321+ if isinstance(page, gtk.ScrolledWindow):
1322+ for grandson in page.get_children():
1323+ dbg(grandson)
1324+ if isinstance(grandson,vte.Terminal):
1325+ self.update_term_config(grandson, config)
1326+
1327+ def update_term_config(self, _vte, config=None):
1328 ##set terminal preferences from conig file data
1329- config_scrollback = config.getint('terminal', 'scrollb')
1330+ if not config:
1331+ config = cc_config.get_config()
1332+ try:
1333+ config_scrollback = config.getint('terminal', 'scrollb')
1334+ except ValueError:
1335+ print _("WARNING: Invalid value for property 'terminal', int expected:"
1336+ " got '%s', using default '%s'")%(
1337+ config.get('terminal', 'scrollb'),
1338+ config.get('DEFAULT', 'scrollb'))
1339+ config.set('terminal','scrollb',config.get('DEFAULT', 'scrollb'))
1340+ config_scrollback = config.getint('DEFAULT', 'scrollb')
1341 _vte.set_scrollback_lines(config_scrollback)
1342
1343 color = '#2e3436:#cc0000:#4e9a06:#c4a000:#3465a4:#75507b:#06989a:#d3d7cf:#555753:#ef2929:#8ae234:#fce94f:#729fcf:#ad7fa8:#34e2e2:#eeeeec'
1344@@ -130,14 +140,39 @@
1345 if color:
1346 palette.append(gtk.gdk.color_parse(color))
1347
1348- config_color_fore = gtk.gdk.color_parse(config.get('terminal', 'colorf'))
1349- #_vte.set_color_foreground(config_color_fore)
1350-
1351- config_color_back = gtk.gdk.color_parse(config.get('terminal', 'colorb'))
1352- #_vte.set_color_background( config_color_back)
1353-
1354+ try:
1355+ config_color_fore = gtk.gdk.color_parse(config.get('terminal', 'colorf'))
1356+ except ValueError, e:
1357+ print _("WARNING: Invalid value for property '%s':"
1358+ " got '%s', using default '%s'.")%(
1359+ 'colorf',
1360+ config.get('terminal', 'colorf'),
1361+ config.get('DEFAULT', 'colorf'))
1362+ config.set('terminal','colorf',config.get('DEFAULT', 'colorf'))
1363+ config_color_fore = gtk.gdk.color_parse(config.get('DEFAULT', 'colorf'))
1364+
1365+ try:
1366+ config_color_back = gtk.gdk.color_parse(config.get('terminal', 'colorb'))
1367+ except ValueError, e:
1368+ print _("WARNING: Invalid value for property '%s':"
1369+ " got '%s', using default '%s'.")%(
1370+ 'colorb',
1371+ config.get('terminal', 'colorb'),
1372+ config.get('DEFAULT', 'colorb'))
1373+ config.set('terminal','colorb',config.get('DEFAULT', 'colorb'))
1374+ config_color_back = gtk.gdk.color_parse(config.get('DEFAULT', 'colorb'))
1375 _vte.set_colors(config_color_fore, config_color_back, palette)
1376
1377 config_encoding = config.get('terminal', 'encoding')
1378+ if config_encoding.upper() not in [ enc.upper() for enc, desc in utils.encodings]:
1379+ print _("WARNING: Invalid value for property '%s':"
1380+ " got '%s', using default '%s'")%(
1381+ 'encoding',
1382+ config_encoding,
1383+ config.get('DEFAULT', 'encoding'))
1384+ config.set('terminal','encoding',config.get('DEFAULT', 'encoding'))
1385+ config_encoding = config.get('DEFAULT', 'encoding')
1386 _vte.set_encoding(config_encoding)
1387+
1388+
1389
1390
1391=== modified file 'clicompanionlib/utils.py'
1392--- clicompanionlib/utils.py 2011-03-29 17:31:11 +0000
1393+++ clicompanionlib/utils.py 2012-01-02 01:28:26 +0000
1394@@ -25,9 +25,148 @@
1395
1396 import getpass
1397 import os
1398-
1399-CHEATSHEET = os.path.expanduser("~/.clicompanion2")
1400-#CONFIG_ORIG = "/etc/clicompanion.d/clicompanion2.config"
1401+import sys
1402+import gtk
1403+import pwd
1404+import inspect
1405+
1406+
1407+## set to True if you want to see more logs
1408+DEBUG = False
1409+DEBUGFILES = False
1410+DEBUGCLASSES = []
1411+DEBUGMETHODS = []
1412+
1413+## list gotten from terminator (https://launchpad.net/terminator)
1414+encodings = [
1415+ ["ISO-8859-1", _("Western")],
1416+ ["ISO-8859-2", _("Central European")],
1417+ ["ISO-8859-3", _("South European") ],
1418+ ["ISO-8859-4", _("Baltic") ],
1419+ ["ISO-8859-5", _("Cyrillic") ],
1420+ ["ISO-8859-6", _("Arabic") ],
1421+ ["ISO-8859-7", _("Greek") ],
1422+ ["ISO-8859-8", _("Hebrew Visual") ],
1423+ ["ISO-8859-8-I", _("Hebrew") ],
1424+ ["ISO-8859-9", _("Turkish") ],
1425+ ["ISO-8859-10", _("Nordic") ],
1426+ ["ISO-8859-13", _("Baltic") ],
1427+ ["ISO-8859-14", _("Celtic") ],
1428+ ["ISO-8859-15", _("Western") ],
1429+ ["ISO-8859-16", _("Romanian") ],
1430+ # ["UTF-7", _("Unicode") ],
1431+ ["UTF-8", _("Unicode") ],
1432+ # ["UTF-16", _("Unicode") ],
1433+ # ["UCS-2", _("Unicode") ],
1434+ # ["UCS-4", _("Unicode") ],
1435+ ["ARMSCII-8", _("Armenian") ],
1436+ ["BIG5", _("Chinese Traditional") ],
1437+ ["BIG5-HKSCS", _("Chinese Traditional") ],
1438+ ["CP866", _("Cyrillic/Russian") ],
1439+ ["EUC-JP", _("Japanese") ],
1440+ ["EUC-KR", _("Korean") ],
1441+ ["EUC-TW", _("Chinese Traditional") ],
1442+ ["GB18030", _("Chinese Simplified") ],
1443+ ["GB2312", _("Chinese Simplified") ],
1444+ ["GBK", _("Chinese Simplified") ],
1445+ ["GEORGIAN-PS", _("Georgian") ],
1446+ ["HZ", _("Chinese Simplified") ],
1447+ ["IBM850", _("Western") ],
1448+ ["IBM852", _("Central European") ],
1449+ ["IBM855", _("Cyrillic") ],
1450+ ["IBM857", _("Turkish") ],
1451+ ["IBM862", _("Hebrew") ],
1452+ ["IBM864", _("Arabic") ],
1453+ ["ISO-2022-JP", _("Japanese") ],
1454+ ["ISO-2022-KR", _("Korean") ],
1455+ ["EUC-TW", _("Chinese Traditional") ],
1456+ ["GB18030", _("Chinese Simplified") ],
1457+ ["GB2312", _("Chinese Simplified") ],
1458+ ["GBK", _("Chinese Simplified") ],
1459+ ["GEORGIAN-PS", _("Georgian") ],
1460+ ["HZ", _("Chinese Simplified") ],
1461+ ["IBM850", _("Western") ],
1462+ ["IBM852", _("Central European") ],
1463+ ["IBM855", _("Cyrillic") ],
1464+ ["IBM857", _("Turkish") ],
1465+ ["IBM862", _("Hebrew") ],
1466+ ["IBM864", _("Arabic") ],
1467+ ["ISO-2022-JP", _("Japanese") ],
1468+ ["ISO-2022-KR", _("Korean") ],
1469+ ["ISO-IR-111", _("Cyrillic") ],
1470+ # ["JOHAB", _("Korean") ],
1471+ ["KOI8-R", _("Cyrillic") ],
1472+ ["KOI8-U", _("Cyrillic/Ukrainian") ],
1473+ ["MAC_ARABIC", _("Arabic") ],
1474+ ["MAC_CE", _("Central European") ],
1475+ ["MAC_CROATIAN", _("Croatian") ],
1476+ ["MAC-CYRILLIC", _("Cyrillic") ],
1477+ ["MAC_DEVANAGARI", _("Hindi") ],
1478+ ["MAC_FARSI", _("Persian") ],
1479+ ["MAC_GREEK", _("Greek") ],
1480+ ["MAC_GUJARATI", _("Gujarati") ],
1481+ ["MAC_GURMUKHI", _("Gurmukhi") ],
1482+ ["MAC_HEBREW", _("Hebrew") ],
1483+ ["MAC_ICELANDIC", _("Icelandic") ],
1484+ ["MAC_ROMAN", _("Western") ],
1485+ ["MAC_ROMANIAN", _("Romanian") ],
1486+ ["MAC_TURKISH", _("Turkish") ],
1487+ ["MAC_UKRAINIAN", _("Cyrillic/Ukrainian") ],
1488+ ["SHIFT-JIS", _("Japanese") ],
1489+ ["TCVN", _("Vietnamese") ],
1490+ ["TIS-620", _("Thai") ],
1491+ ["UHC", _("Korean") ],
1492+ ["VISCII", _("Vietnamese") ],
1493+ ["WINDOWS-1250", _("Central European") ],
1494+ ["WINDOWS-1251", _("Cyrillic") ],
1495+ ["WINDOWS-1252", _("Western") ],
1496+ ["WINDOWS-1253", _("Greek") ],
1497+ ["WINDOWS-1254", _("Turkish") ],
1498+ ["WINDOWS-1255", _("Hebrew") ],
1499+ ["WINDOWS-1256", _("Arabic") ],
1500+ ["WINDOWS-1257", _("Baltic") ],
1501+ ["WINDOWS-1258", _("Vietnamese") ]
1502+ ]
1503+
1504+
1505+def dbg(log):
1506+ if DEBUG:
1507+ stack = inspect.stack()
1508+ method = None
1509+ for stackitem in stack:
1510+ parent_frame = stackitem[0]
1511+ names, varargs, keywords, local_vars = inspect.getargvalues(parent_frame)
1512+ ## little trick to get the second stackline method, in case we do
1513+ ## not find self
1514+ if not method and method != None:
1515+ method = stackitem[3]
1516+ elif not method:
1517+ method = ''
1518+ try:
1519+ self_name = names[0]
1520+ if self_name != 'self':
1521+ continue
1522+ classname = local_vars[self_name].__class__.__name__
1523+ except IndexError:
1524+ classname = "noclass"
1525+ ## in case self is found, get the method
1526+ method = stackitem[3]
1527+ break
1528+ if DEBUGFILES:
1529+ line = stackitem[2]
1530+ filename = parent_frame.f_code.co_filename
1531+ extra = " (%s:%s)" % (filename, line)
1532+ else:
1533+ extra = ""
1534+ if DEBUGCLASSES != [] and classname not in DEBUGCLASSES:
1535+ return
1536+ if DEBUGMETHODS != [] and method not in DEBUGMETHODS:
1537+ return
1538+ try:
1539+ print >> sys.stderr, "%s::%s: %s%s" % (classname, method, log, extra)
1540+ except IOError:
1541+ pass
1542+
1543
1544 #TODO: Move this to controller.py
1545 def get_user_shell():
1546
1547=== modified file 'clicompanionlib/view.py'
1548--- clicompanionlib/view.py 2011-12-30 12:22:17 +0000
1549+++ clicompanionlib/view.py 2012-01-02 01:28:26 +0000
1550@@ -22,7 +22,6 @@
1551 import pygtk
1552 pygtk.require('2.0')
1553 import os
1554-import ConfigParser
1555
1556 # import vte and gtk or print error
1557 try:
1558@@ -43,18 +42,20 @@
1559
1560 import clicompanionlib.menus_buttons
1561 import clicompanionlib.controller
1562-from clicompanionlib.utils import get_user_shell , Borg
1563+from clicompanionlib.utils import get_user_shell , Borg, dbg
1564 import clicompanionlib.tabs
1565-from clicompanionlib.config import Config
1566-
1567-
1568-CONFIGFILE = os.path.expanduser("~/.config/clicompanion/config")
1569-CHEATSHEET = os.path.expanduser("~/.clicompanion2")
1570-CONFIG_ORIG = "/etc/clicompanion.d/clicompanion2.config"
1571+import clicompanionlib.utils as utils
1572+import clicompanionlib.config as cc_config
1573+
1574
1575 ## Changed two->three columns
1576-CMNDS = [] ## will hold the commands. Actually the first three columns
1577-ROW = '1' ## holds the currently selected row
1578+CMNDS = cc_config.Cheatsheet()
1579+## will hold the commands. Actually the first three columns
1580+## note that this commands list will not change with searchers and filters,
1581+## instead, when adding a command to the liststore, we will add also the index
1582+## of the command in the CMND list
1583+
1584+ROW = '0' ## holds the currently selected row
1585 TARGETS = [
1586 ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0),
1587 ('text/plain', 0, 1),
1588@@ -67,7 +68,6 @@
1589 HIDEUI = 0
1590 FULLSCREEN = 0
1591
1592-
1593 menu_search_hbox = ''
1594 button_box = ''
1595
1596@@ -76,90 +76,32 @@
1597 window = gtk.Window(gtk.WINDOW_TOPLEVEL)
1598 #color = gtk.gdk.Color(60000, 65533, 60000)
1599 #window.modify_bg(gtk.STATE_NORMAL, color)
1600- liststore = gtk.ListStore(str, str, str)
1601+ liststore = gtk.ListStore(str, str, str, int)
1602 treeview = gtk.TreeView()
1603 expander = gtk.Expander()
1604 scrolledwindow = gtk.ScrolledWindow()
1605 notebook = gtk.Notebook()
1606
1607-
1608 screen = gtk.gdk.display_get_default().get_default_screen()
1609 screen_size = screen.get_monitor_geometry(0)
1610 height = screen.get_height() ## screen height ##
1611 global NETBOOKMODE
1612 if height < 750:
1613 NETBOOKMODE = 1
1614- ## open file containing command list and put it in a variable
1615- def update(self, liststore):
1616- try:
1617- with open(CHEATSHEET, "r") as cheatfile:
1618- bugdata=cheatfile.read()
1619- cheatfile.close()
1620- except IOError:
1621- ## CHEATSHEET is not there. Oh, no!
1622- ## So, run self.setup() again.
1623- self.setup()
1624- ## Then, run me again.
1625- self.update(self.liststore)
1626-
1627- ## add bug data from .clicompanion --> bugdata --> to the liststore
1628- for line in bugdata.splitlines():
1629- l = line.split('\t',2)
1630- if len(l) < 2:
1631- """
1632- If for any reason we have a old file, we must
1633- replace it by new one
1634- """
1635- print "PLEASE RESTART APPLICATION TO FINISH UPDATE"
1636- self.setup()
1637- return
1638- commandplus = l[0], l[1], l[2]
1639- CMNDS.append(commandplus)
1640- self.liststore.append([l[0],l[1],l[2]])
1641-
1642-
1643- #copy config file to user $HOME if does not exist
1644- def setup(self):
1645- """
1646- Check if ~/.clicompanion2 exists. If not check for original
1647- installed in /etc/clicompanion.d/. If origianl exists copy to $HOME.
1648- if not create a new, blank ~/.clicompanion2 so program will not crash
1649- """
1650-
1651- if not os.path.exists(CHEATSHEET):
1652- if os.path.exists(CONFIG_ORIG):
1653- os.system ("cp %s %s" % (CONFIG_ORIG, CHEATSHEET))
1654- else:
1655- # Oops! Looks like there's no cheatsheet in CHEATSHEET.
1656- # Then, create an empty cheatsheet.
1657- open(CHEATSHEET, 'w').close()
1658- """
1659- If we have old file, we must replace it by fresh list
1660- """
1661- cheatlines = []
1662- try:
1663- with open(CHEATSHEET, "r") as cheatfile:
1664- bugdata=cheatfile.read()
1665- cheatfile.close()
1666- for line in bugdata.splitlines():
1667- l = line.split('\t', 2)
1668- if len(l) < 2:
1669- l = line.split(':', 2)
1670- p = str(l[0] + "\t"+ l[1] +"\t"+ l[2] + "\n")
1671- cheatlines.append(p)
1672- else:
1673- cheatlines.append(str(l[0] + "\t"+ l[1] +"\t"+ l[2] + "\n"))
1674-
1675- with open(CHEATSHEET, "w") as cheatfile2:
1676- cheatfile2.writelines(cheatlines)
1677- cheatfile2.close()
1678-
1679- except IOError:
1680- ## CHEATSHEET is not there. Oh, no!
1681- ## So, run self.setup() again.
1682- self.setup()
1683- ## Then, run me again.
1684- self.update(self.liststore)
1685+
1686+
1687+ def sync_cmnds(self, rld=False):
1688+ global CMNDS
1689+ dbg('syncing commands')
1690+ if rld:
1691+ ## reload the commands list from the file
1692+ CMNDS.load()
1693+ self.liststore.clear()
1694+ ## Store also the index of the command in the CMNDS list
1695+ i = 0
1696+ for cmd, ui, desc in CMNDS:
1697+ self.liststore.append((cmd, ui, desc, i))
1698+ i = i +1
1699
1700
1701 #liststore in a scrolled window in an expander
1702@@ -185,13 +127,11 @@
1703
1704 def key_clicked(self, widget, event):
1705 actions = clicompanionlib.controller.Actions()
1706- tabs = clicompanionlib.tabs.Tabs()
1707 global HIDEUI
1708 global FULLSCREEN
1709 global menu_search_hbox
1710 global button_box
1711 keyname = gtk.gdk.keyval_name(event.keyval).upper()
1712- #print keyname ##debug
1713 if keyname == "F12":
1714 HIDEUI = 1 - HIDEUI
1715 if HIDEUI == 1:
1716@@ -215,13 +155,13 @@
1717 pwin = button_box.get_window()
1718 pwin.unfullscreen()
1719 if keyname == "F4":
1720- actions.run_command(self, self.notebook, self.liststore)
1721+ actions.run_command(self)
1722 if keyname == "F5":
1723- actions.add_command(self, self.liststore)
1724+ actions.add_command(self)
1725 if keyname == "F6":
1726- actions.remove_command(self, self.liststore)
1727+ actions.remove_command(self)
1728 if keyname == "F7":
1729- tabs.add_tab(self, self.notebook)
1730+ self.tabs.add_tab(self)
1731
1732 def __init__(self):
1733 #import pdb ##debug
1734@@ -231,14 +171,7 @@
1735 ##in libvte in Ubuntu Maverick
1736 os.putenv('TERM', 'xterm')
1737
1738- ## copy command list to user $HOME if does not exist
1739- self.setup()
1740
1741- ##create the config file
1742- conf_mod = Config()
1743- conf_mod.create_config()
1744-
1745-
1746 ## style to reduce padding around tabs
1747 ## TODO: Find a better place for this?
1748 gtk.rc_parse_string ("style \"tab-close-button-style\"\n"
1749@@ -257,7 +190,6 @@
1750 ##attach the style to the widget
1751 self.notebook.set_name ("tab-close-button")
1752
1753-
1754 ## set sizes and borders
1755 global NETBOOKMODE
1756 if NETBOOKMODE == 1:
1757@@ -272,19 +204,15 @@
1758 ## Allow user to resize window
1759 self.window.set_resizable(True)
1760
1761-
1762 ## set Window title and icon
1763 self.window.set_title("CLI Companion")
1764 icon = gtk.gdk.pixbuf_new_from_file("/usr/share/pixmaps/clicompanion.16.png")
1765 self.window.set_icon(icon)
1766-
1767-
1768
1769- # get commands and put in liststore
1770- self.update(self.liststore)
1771+ # sync liststore with commands
1772+ self.sync_cmnds()
1773
1774 ## set renderer and colors
1775-
1776 #color2 = gtk.gdk.Color(5000,5000,65000)
1777 renderer = gtk.CellRendererText()
1778 #renderer.set_property("cell-background-gdk", color)
1779@@ -308,7 +236,7 @@
1780 True)
1781 ## set the cell attributes to the appropriate liststore column
1782 self.treeview.columns[n].set_attributes(
1783- self.treeview.columns[n].cell, text=n)
1784+ self.treeview.columns[n].cell, text=n)
1785 self.treeview.columns[n].set_resizable(True)
1786
1787 ''' set treeview model and put treeview in the scrolled window
1788@@ -321,12 +249,12 @@
1789 #self.window.show_all()
1790
1791 ## instantiate tabs
1792- tabs = clicompanionlib.tabs.Tabs()
1793+ self.tabs = clicompanionlib.tabs.Tabs()
1794 ## instantiate controller.Actions, where all the button actions are
1795 self.actions = clicompanionlib.controller.Actions()
1796 ## instantiate 'File' and 'Help' Drop Down Menu [menus_buttons.py]
1797 bar = clicompanionlib.menus_buttons.FileMenu()
1798- menu_bar = bar.the_menu(self.actions, self.notebook, self.liststore)
1799+ menu_bar = bar.the_menu(self)
1800
1801
1802 ## get row of a selection
1803@@ -337,17 +265,17 @@
1804
1805
1806 ## double click to run a command
1807- def treeview_clicked(widget, event):
1808- if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:
1809- self.actions.run_command(self, self.notebook, self.liststore)
1810+ def treeview_clicked(widget, path, column):
1811+ self.actions.run_command(self)
1812+
1813
1814 ## press enter to run a command
1815 def treeview_button(widget, event):
1816 keyname = gtk.gdk.keyval_name(event.keyval).upper()
1817- #print keyname ##debug
1818+ dbg('Key %s pressed'%keyname)
1819 if event.type == gtk.gdk.KEY_PRESS:
1820 if keyname == 'RETURN':
1821- self.actions.run_command(self, self.notebook, self.liststore)
1822+ self.actions.run_command(self)
1823
1824
1825
1826@@ -357,7 +285,7 @@
1827 selection.select_path(0)
1828 selection.connect("changed", mark_selected, selection)
1829 ## double-click
1830- self.treeview.connect("button-press-event", treeview_clicked)
1831+ self.treeview.connect("row-activated", treeview_clicked)
1832 #press enter to run command
1833 self.treeview.connect("key-press-event", treeview_button)
1834
1835@@ -393,7 +321,7 @@
1836 self.expander.set_label_widget(expander_hbox)
1837
1838 ## Add the first tab with the Terminal
1839- tabs.add_tab(self, self.notebook)
1840+ self.tabs.add_tab(self.notebook)
1841 self.notebook.set_tab_pos(2)
1842
1843 ## The "Add Tab" tab
1844@@ -405,7 +333,7 @@
1845
1846 global button_box
1847 ## buttons at bottom of main window [menus_buttons.py]
1848- button_box = bar.buttons(self.actions, 10, gtk.BUTTONBOX_END, self.notebook, self.liststore)
1849+ button_box = bar.buttons(self, 10, gtk.BUTTONBOX_END)
1850
1851 ## vbox for search, notebook, buttonbar
1852 vbox = gtk.VBox()
1853@@ -421,9 +349,9 @@
1854 self.expander.connect('notify::expanded', self.expanded_cb, self.window, self.search_box)
1855 self.window.connect("delete_event", self.delete_event)
1856 self.window.connect("key-press-event", self.key_clicked)
1857- add_tab_button.connect("clicked", tabs.add_tab, self.notebook)
1858+ add_tab_button.connect("clicked", lambda *x: self.tabs.add_tab(self.notebook))
1859 ## right click menu event capture
1860- self.treeview.connect ("button_press_event", bar.right_click, self.actions, self.treeview, self.notebook, self.liststore)
1861+ self.treeview.connect("button_press_event", bar.right_click, self)
1862
1863 # Allow enable drag and drop of rows including row move
1864 self.treeview.enable_model_drag_source( gtk.gdk.BUTTON1_MASK,
1865@@ -435,12 +363,20 @@
1866
1867 self.treeview.connect ("drag_data_get", self.drag_data_get_event)
1868 self.treeview.connect ("drag_data_received", self.drag_data_received_event)
1869+ self.treeview.connect("drag_drop", self.on_drag_drop )
1870
1871
1872 #self.vte.grab_focus()
1873 self.window.show_all()
1874 return
1875
1876+ def on_drag_drop(self, treeview, *x):
1877+ '''
1878+ Stop the signal when in search mode
1879+ '''
1880+ if FILTER:
1881+ treeview.stop_emission('drag_drop')
1882+
1883 def drag_data_get_event(self, treeview, context, selection, target_id,
1884 etime):
1885 """
1886@@ -457,90 +393,57 @@
1887 Executed when dropping.
1888 """
1889 global CMNDS
1890+ global FILTER
1891+ ## if we are in a search, do nothing
1892+ if FILTER == 1:
1893+ return
1894 model = treeview.get_model()
1895+ ## get the destination
1896+ drop_info = treeview.get_dest_row_at_pos(x, y)
1897+ if drop_info:
1898+ path, position = drop_info
1899+ iter = model.get_iter(path)
1900+ dest = list(model.get(iter, 0, 1, 2))
1901+
1902+ ## parse all the incoming commands
1903 for data in selection.data.split('\n'):
1904 # if we got an empty line skip it
1905 if not data.replace('\r',''): continue
1906 # format the incoming string
1907 orig = data.replace('\r','').split('\t',2)
1908- orig = tuple([ fld.strip() for fld in orig ])
1909+ orig = [ fld.strip() for fld in orig ]
1910 # fill the empty fields
1911 if len(orig) < 3: orig = orig + ('',)*(3-len(orig))
1912- # if the element already exists delete it (dragged from clicompanion)
1913- olditer = self.find_iter_by_tuple(orig, model)
1914- if olditer: del model[olditer]
1915+ dbg('Got drop of command %s'%'_\t_'.join(orig))
1916
1917- drop_info = treeview.get_dest_row_at_pos(x, y)
1918 if drop_info:
1919- path, position = drop_info
1920- iter = model.get_iter(path)
1921- dest = tuple(model.get(iter, 0, 1, 2))
1922 if (position == gtk.TREE_VIEW_DROP_BEFORE
1923 or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
1924- model.insert_before(iter, orig)
1925- self.drag_cmnd(orig, dest, before=True)
1926+ dbg('\t to before dest %s'%'_\t_'.join(dest))
1927+ CMNDS.drag_n_drop(orig, dest, before=True)
1928 else:
1929- model.insert_after(iter, orig)
1930- self.drag_cmnd(orig, dest, before=False)
1931+ dbg('\t to after dest %s'%'_\t_'.join(dest))
1932+ CMNDS.drag_n_drop(orig, dest, before=False)
1933 else:
1934- if len(model) > 0:
1935- iter = model[-1].iter
1936- model.insert_after(iter, orig)
1937- else:
1938- model.insert(0, orig)
1939- return
1940- dest = tuple(model.get(iter, 0, 1, 2))
1941- self.drag_cmnd(orig, dest, before=False)
1942- if context.action == gtk.gdk.ACTION_MOVE:
1943- context.finish(True, True, etime)
1944- self.actions.save_cmnds()
1945+ dbg('\t to the end')
1946+ CMNDS[len(CMNDS)] = orig
1947+ if context.action == gtk.gdk.ACTION_MOVE:
1948+ context.finish(True, True, etime)
1949+ self.sync_cmnds()
1950+ CMNDS.save()
1951
1952- def find_iter_by_tuple(self, data, model):
1953- for row in model:
1954- if tuple(model.get(row.iter, 0, 1, 2)) == data:
1955- return row.iter
1956- return None
1957-
1958- def drag_cmnd(self, orig, dest, before=True):
1959- """
1960- Sync the CMNDS array with the drag and drop of the treeview.
1961- """
1962- global CMNDS
1963- i = j = None
1964- pos = 0
1965- for cmnd in CMNDS:
1966- if cmnd == orig:
1967- i = pos
1968- elif cmnd == dest:
1969- j = pos
1970- pos += 1
1971- ## both from clicompanion
1972- if i != None and j != None:
1973- cmnd = CMNDS.pop(i)
1974- if before and i<=j:
1975- CMNDS.insert(j-1, cmnd)
1976- elif before and i>j:
1977- CMNDS.insert(j, cmnd)
1978- elif i<=j:
1979- CMNDS.insert(j, cmnd)
1980- else:
1981- CMNDS.insert(j+1, cmnd)
1982- ## origin unknown
1983- elif j != None:
1984- cmnd = orig
1985- if before:
1986- CMNDS.insert(j, cmnd)
1987- else:
1988- CMNDS.insert(j+1, cmnd)
1989-
1990-
1991 def main(self):
1992 try:
1993 gtk.main()
1994 except KeyboardInterrupt:
1995 pass
1996
1997-def run():
1998-
1999+def run( options=None ):
2000+ ##create the config file
2001+ config = cc_config.create_config()
2002+ if config.get('terminal','debug') == 'True':
2003+ utils.DEBUG = True
2004+ CMNDS.load(options and options.cheatsheet or None)
2005+ dbg('Loaded commands %s'%CMNDS)
2006 main_window = MainWindow()
2007 main_window.main()

Subscribers

People subscribed via source and target branches