Merge lp:clicompanion into lp:clicompanion/1.0

Proposed by Duane Hinnen
Status: Approved
Approved by: Duane Hinnen
Approved revision: 116
Proposed branch: lp:clicompanion
Merge into: lp:clicompanion/1.0
Diff against target: 5659 lines (+5448/-23) (has conflicts)
25 files modified
.bzrignore (+8/-0)
.clicompanion (+48/-23)
MANIFEST (+23/-0)
clicompanion (+66/-0)
clicompanion.1 (+61/-0)
clicompanionlib/__init__.py (+23/-0)
clicompanionlib/config.py (+353/-0)
clicompanionlib/helpers.py (+186/-0)
clicompanionlib/menus_buttons.py (+253/-0)
clicompanionlib/plugins.py (+249/-0)
clicompanionlib/preferences.py (+708/-0)
clicompanionlib/tabs.py (+598/-0)
clicompanionlib/utils.py (+269/-0)
clicompanionlib/view.py (+575/-0)
data/clicompanion.desktop (+7/-0)
data/clicompanion2.config.debian (+150/-0)
data/clicompanion2.config.ubuntu (+150/-0)
locale/clicompanion.pot (+155/-0)
plugins/CommandLineFU.py (+282/-0)
plugins/LaunchpadURL.py (+62/-0)
plugins/LocalCommandList.py (+808/-0)
plugins/StandardURLs.py (+91/-0)
plugins/__init__.py (+23/-0)
plugins/clfu.py (+243/-0)
setup.py (+57/-0)
Conflict adding file MANIFEST.  Moved existing file to MANIFEST.moved.
Conflict adding file clicompanion.  Moved existing file to clicompanion.moved.
Conflict adding file clicompanionlib.  Moved existing file to clicompanionlib.moved.
Conflict adding file data.  Moved existing file to data.moved.
Conflict adding file locale.  Moved existing file to locale.moved.
Conflict adding file setup.py.  Moved existing file to setup.py.moved.
To merge this branch: bzr merge lp:clicompanion
Reviewer Review Type Date Requested Status
David Caro (community) Needs Information
Duane Hinnen Needs Resubmitting
Review via email: mp+80925@code.launchpad.net

Description of the change

Update from trunk revision 83

To post a comment you must log in.
Revision history for this message
Duane Hinnen (duanedesign) wrote :

clicompanion 1.0 branch not ready for merge. Still has Debian branch, etc

review: Needs Resubmitting
Revision history for this message
stlsaint (stlsaint) wrote :

> clicompanion 1.0 branch not ready for merge. Still has Debian branch, etc

I say we delete the branch all together and start new series for 1.1

lp:clicompanion updated
84. By bdfhjk

Added file for man to use

85. By bdfhjk

Changed version to 1.1

86. By bdfhjk

Actually change the version in setup.py

87. By bdfhjk

removed colors

88. By bdfhjk

Color removed

89. By Duane Hinnen

Edited two lines in MANIFEST file to reflect the actual filenames in the source tree

90. By Duane Hinnen

Edited data/clicompanion.desktop to use the correct name for the icon file (added .16 to end of the line)

91. By Duane Hinnen

fixed commands with errors in clicompanion2.config file

92. By Marek Bardoński

[MERGE] lp:~clicompanion-devs/clicompanion/fix-819038 solving lp:819038

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

Merge from lp:~dcaro/clicompanion/fix-908240.

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

Merged from lp:~dcaro/clicompanion/fix-908235

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

Merged patch for bug lp:802298

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

Merged with patch from bug report lp:802303

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

added more index out of bound controls

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

Merged from p:~dcaro/clicompanion/fix-909893, for bugs: lp:748888, lp:909893

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

Merged from lp:~dcaro/clicompanion/fix-909545 to resolve bugs: lp:909545

Revision history for this message
David Caro (dcaro) wrote :

Not sure if this is applicable anymore. Should we just start from the trunk on the 1.0 series? The bug is fixed in the trunk version (and a lot of thing have changed, like command format and so).

review: Needs Information
lp:clicompanion updated
100. By David Caro "<email address hidden>"

fixed a problem with the searches

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

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

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

Merging from lp:~dcaro/clicompanion/fix-608608

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

Merged from lp:~dcaro/clicompanion/fix-913436

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

Merged from lp:~dcaro/clicompanion/fix-614789

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

Merged from lp:~dcaro/clicompanion/fix-915606

106. By Duane Hinnen

merge from lp:~bdfhjk/clicompanion/fix-622063 fixes lp:622063

107. By Duane Hinnen

merge from lp:~dcaro/clicompanion/fix-623475 fixes lp:623475

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

Merged from lp:~dcaro/clicompanion/fix-923535

109. By Marek Bardoński

Merged with lp:~benny/clicompanion/fix-for-946817

110. By Duane Hinnen

Merged from lp:~dcaro/clicompanion/fix-1035073.2 fixes lp:1035073

111. By Duane Hinnen

Merged from lp:~pablorubianes-uy/clicompanion/dev fixes mispelling in LocalCommandList.py

112. By stlsaint

Merge to fix lp:1043706

113. By Marek Bardoński

Added the cheatsheet revert option

114. By Marek Bardoński

Merged from lp:~bdfhjk/clicompanion/fix-627622

115. By Marek Bardoński

Merged from lp:~bdfhjk/clicompanion/fix-951637

116. By Marek Bardoński

Merged from lp:~bdfhjk/clicompanion/fix-1644957

Unmerged revisions

116. By Marek Bardoński

Merged from lp:~bdfhjk/clicompanion/fix-1644957

115. By Marek Bardoński

Merged from lp:~bdfhjk/clicompanion/fix-951637

114. By Marek Bardoński

Merged from lp:~bdfhjk/clicompanion/fix-627622

113. By Marek Bardoński

Added the cheatsheet revert option

112. By stlsaint

Merge to fix lp:1043706

111. By Duane Hinnen

Merged from lp:~pablorubianes-uy/clicompanion/dev fixes mispelling in LocalCommandList.py

110. By Duane Hinnen

Merged from lp:~dcaro/clicompanion/fix-1035073.2 fixes lp:1035073

109. By Marek Bardoński

Merged with lp:~benny/clicompanion/fix-for-946817

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

Merged from lp:~dcaro/clicompanion/fix-923535

107. By Duane Hinnen

merge from lp:~dcaro/clicompanion/fix-623475 fixes lp:623475

Preview Diff

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

Subscribers

People subscribed via source and target branches