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 | ||||||||||||||||||||||||||||
Related bugs: |
|
||||||||||||||||||||||||||||
Related blueprints: |
CommandlineFu view
(Low)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
David Caro (community) | Needs Information | ||
Duane Hinnen | Needs Resubmitting | ||
Review via email: mp+80925@code.launchpad.net |
Commit message
Description of the change
Update from trunk revision 83
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
- 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/clicompani
on.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
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).
- 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
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' |
3521 | Binary 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' |
3523 | Binary 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' |
3848 | Binary 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' |
3850 | Binary 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' |
3856 | Binary 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' |
4020 | Binary 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' |
4024 | Binary 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' |
4028 | Binary 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' |
4032 | Binary 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' |
4036 | Binary 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' |
4040 | Binary 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' |
4044 | Binary 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' |
4048 | Binary 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' |
4052 | Binary 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' |
4056 | Binary 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: |
clicompanion 1.0 branch not ready for merge. Still has Debian branch, etc