1
by alon
initialize |
1 |
#!/usr/bin/env python
|
2 |
#------------------------------------------------------------------------------------
|
|
3 |
#
|
|
22
by alon
Migration from 0.1's obsolte data, and start of nice standaloning |
4 |
# TopShelf - A 'Currently Important Files' Applet
|
5 |
#
|
|
1
by alon
initialize |
6 |
# Copyright (C) 2007 Alon Zakai ('Kripken')
|
7 |
#
|
|
15
by alon
pre-0.1 cleaning |
8 |
# This program is free software; you can redistribute it and/or
|
1
by alon
initialize |
9 |
# modify it under the terms of the GNU General Public License
|
15
by alon
pre-0.1 cleaning |
10 |
# as published by the Free Software Foundation; either version 2
|
1
by alon
initialize |
11 |
# of the License, or (at your option) any later version.
|
12 |
#
|
|
13 |
# This program is distributed in the hope that it will be useful,
|
|
15
by alon
pre-0.1 cleaning |
14 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
1
by alon
initialize |
15 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16 |
# GNU General Public License for more details.
|
|
17 |
#
|
|
18 |
# You should have received a copy of the GNU General Public License
|
|
15
by alon
pre-0.1 cleaning |
19 |
# along with this program; if not, write to the Free Software
|
1
by alon
initialize |
20 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
15
by alon
pre-0.1 cleaning |
21 |
#
|
1
by alon
initialize |
22 |
#-------------------------------------------------------------------------------------
|
23 |
||
24 |
import sys |
|
2
by alon
about dialog |
25 |
import pickle |
26 |
import os.path |
|
7
by alon
mimetypes |
27 |
import subprocess |
28 |
import mimetypes |
|
9
by alon
Drag&Drop |
29 |
import urllib |
2
by alon
about dialog |
30 |
|
7
by alon
mimetypes |
31 |
import gobject |
1
by alon
initialize |
32 |
import gtk, gtk.gdk |
33 |
import pygtk |
|
34 |
pygtk.require('2.0') |
|
35 |
||
36 |
import gnomeapplet |
|
37 |
||
28
by alon
Started work on help |
38 |
## Global constants
|
39 |
||
40 |
OPEN_APP = "gnome-open" |
|
41 |
HELP_APP = "yelp" |
|
42 |
||
43 |
HELP_FILE = "/usr/share/doc/topshelf/topshelf.docbook" |
|
1
by alon
initialize |
44 |
|
7
by alon
mimetypes |
45 |
## Global utilities
|
46 |
||
19
by alon
New transparency-free icons, and filename color depends on whether it exists. Also an error if the file could not be opened. |
47 |
def popup_warning(window, message): |
48 |
dlg = gtk.MessageDialog(parent = window, |
|
49 |
flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, |
|
50 |
buttons = gtk.BUTTONS_OK, |
|
51 |
message_format = message, |
|
52 |
type = gtk.MESSAGE_WARNING) |
|
53 |
||
31
by alon
Key actions finished. Window now is not popup, and works it seems |
54 |
window.present() # Ensure we are on top, for our losing keep_above to not hide us |
19
by alon
New transparency-free icons, and filename color depends on whether it exists. Also an error if the file could not be opened. |
55 |
window.set_keep_above(False) |
56 |
result = dlg.run() |
|
57 |
dlg.destroy() |
|
58 |
window.set_keep_above(True) |
|
59 |
||
11
by alon
are you sure for removal. Minor gui improvements |
60 |
def popup_question(window, message): |
19
by alon
New transparency-free icons, and filename color depends on whether it exists. Also an error if the file could not be opened. |
61 |
dlg = gtk.MessageDialog(parent = window, |
62 |
flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, |
|
63 |
buttons = gtk.BUTTONS_YES_NO, |
|
64 |
message_format = message, |
|
65 |
type = gtk.MESSAGE_QUESTION) |
|
11
by alon
are you sure for removal. Minor gui improvements |
66 |
|
31
by alon
Key actions finished. Window now is not popup, and works it seems |
67 |
window.present() # Ensure we are on top, for our losing keep_above to not hide us |
11
by alon
are you sure for removal. Minor gui improvements |
68 |
window.set_keep_above(False) |
19
by alon
New transparency-free icons, and filename color depends on whether it exists. Also an error if the file could not be opened. |
69 |
result = dlg.run() |
70 |
dlg.destroy() |
|
11
by alon
are you sure for removal. Minor gui improvements |
71 |
window.set_keep_above(True) |
72 |
||
73 |
if result == gtk.RESPONSE_YES: |
|
74 |
return True |
|
75 |
elif result == gtk.RESPONSE_NO: |
|
76 |
return False |
|
77 |
elif result == gtk.RESPONSE_DELETE_EVENT: |
|
78 |
return False |
|
79 |
else: |
|
80 |
return False |
|
81 |
||
21
by alon
Show nice icon for directories and code cleanup for mime-lookup |
82 |
def try_load_icon(icon_theme, icon_name, icon_size): |
83 |
try: |
|
84 |
return icon_theme.load_icon(icon_name, icon_size, 0) |
|
85 |
except gobject.GError: |
|
86 |
return None |
|
87 |
||
7
by alon
mimetypes |
88 |
def guess_mime_icon(filename): |
89 |
ICON_SIZE = 24 |
|
90 |
||
91 |
icon_theme = gtk.icon_theme_get_default() |
|
21
by alon
Show nice icon for directories and code cleanup for mime-lookup |
92 |
|
93 |
if os.path.isdir(to_filename(filename)): |
|
94 |
ret = try_load_icon(icon_theme, "gnome-fs-directory", ICON_SIZE) |
|
95 |
if ret is None: |
|
96 |
ret = try_load_icon(icon_theme, "gtk-directory", ICON_SIZE) |
|
97 |
return ret |
|
98 |
||
7
by alon
mimetypes |
99 |
filename = mimetypes.guess_type(os.path.basename(filename))[0] |
21
by alon
Show nice icon for directories and code cleanup for mime-lookup |
100 |
|
7
by alon
mimetypes |
101 |
if filename is None: |
21
by alon
Show nice icon for directories and code cleanup for mime-lookup |
102 |
return try_load_icon(icon_theme, "unknown", ICON_SIZE) |
103 |
||
104 |
ret = try_load_icon(icon_theme, filename, ICON_SIZE) |
|
7
by alon
mimetypes |
105 |
|
106 |
filename = filename.replace("/", "-") |
|
107 |
||
21
by alon
Show nice icon for directories and code cleanup for mime-lookup |
108 |
if ret is None: |
109 |
ret = try_load_icon(icon_theme, filename, ICON_SIZE) |
|
110 |
||
111 |
if ret is None: |
|
112 |
ret = try_load_icon(icon_theme, "gnome-mime-" + filename, ICON_SIZE) |
|
113 |
||
114 |
if ret is None: |
|
115 |
ret = try_load_icon(icon_theme, "gnome-mime-" + filename[0:filename.index("-")], ICON_SIZE) |
|
116 |
||
117 |
if ret is None: |
|
118 |
ret = try_load_icon(icon_theme, "unknown", ICON_SIZE) |
|
119 |
||
120 |
return ret |
|
121 |
||
122 |
def to_filename(uri_or_something): |
|
123 |
return uri_or_something.replace("file://", "") |
|
1
by alon
initialize |
124 |
|
125 |
||
2
by alon
about dialog |
126 |
### Persistent data
|
127 |
||
128 |
PERSISTENT_FILE = os.path.join(os.path.expanduser("~"), ".topshelf.conf") |
|
129 |
||
22
by alon
Migration from 0.1's obsolte data, and start of nice standaloning |
130 |
# Tests whether persistent data is 0.1, and hence needs migration
|
131 |
def test_point1(data): |
|
132 |
try: |
|
133 |
temp = data.data |
|
134 |
return False |
|
135 |
except AttributeError: |
|
136 |
print "Migration needed to 0.2" |
|
137 |
return True |
|
138 |
||
139 |
# Migrates persistent data from 0.1 to 0.2
|
|
140 |
def migrate_point1_to_point2(old): |
|
23
by alon
standalone now finished |
141 |
print "Migrating to 0.2;", len(old.current_files), "files transferred" |
22
by alon
Migration from 0.1's obsolte data, and start of nice standaloning |
142 |
new = ModernPersistentData() |
143 |
new.set(current_files, old.current_files) |
|
144 |
new.set('w', old.w) |
|
145 |
new.set('h', old.h) |
|
146 |
return new |
|
147 |
||
148 |
class PersistentData: ## Legacy! |
|
149 |
def __init__(self): |
|
150 |
self.current_files = [] |
|
151 |
self.w = -1 |
|
152 |
self.h = -1 |
|
153 |
||
23
by alon
standalone now finished |
154 |
current_files = "current_files" # A constant, really... |
155 |
||
156 |
DEFAULT_VALUES = { current_files: [], |
|
157 |
'x': 400, # For standalone app |
|
158 |
'y': 300, # For standalone app |
|
159 |
'w': 600, |
|
160 |
'h': 350 } |
|
161 |
||
22
by alon
Migration from 0.1's obsolte data, and start of nice standaloning |
162 |
class ModernPersistentData: |
163 |
def __init__(self): |
|
164 |
self.data = { } |
|
165 |
||
166 |
def get(self, key): |
|
167 |
try: |
|
168 |
return self.data[key] |
|
169 |
except KeyError: |
|
170 |
try: |
|
171 |
self.data[key] = DEFAULT_VALUES[key] |
|
172 |
return self.data[key] |
|
173 |
except KeyError: |
|
174 |
print "Fatal error: Key with no default value in ModernPersistentData" |
|
175 |
||
176 |
def set(self, key, value): |
|
177 |
self.data[key] = value |
|
178 |
||
179 |
||
180 |
### Data for a single file
|
|
181 |
||
19
by alon
New transparency-free icons, and filename color depends on whether it exists. Also an error if the file could not be opened. |
182 |
NORMAL_BG_COLOR = "black" |
183 |
DISABLE_BG_COLOR = "#FF0000" |
|
184 |
||
5
by alon
progress on syncing |
185 |
class FileData: |
19
by alon
New transparency-free icons, and filename color depends on whether it exists. Also an error if the file could not be opened. |
186 |
def __init__(self, filename, position, short_filename=None, mime_icon=None, color=None): |
7
by alon
mimetypes |
187 |
self.filename = filename |
188 |
self.position = position |
|
5
by alon
progress on syncing |
189 |
|
190 |
def listify(self): |
|
19
by alon
New transparency-free icons, and filename color depends on whether it exists. Also an error if the file could not be opened. |
191 |
return (self.filename, self.position, os.path.basename(self.filename), guess_mime_icon(self.filename), NORMAL_BG_COLOR) |
5
by alon
progress on syncing |
192 |
|
193 |
||
1
by alon
initialize |
194 |
### Main class
|
195 |
||
19
by alon
New transparency-free icons, and filename color depends on whether it exists. Also an error if the file could not be opened. |
196 |
# full-path, position, short-filename, mime-type-icon, color
|
197 |
FILE_STORE_FULLPATH = 0 |
|
198 |
FILE_STORE_POSITION = 1 |
|
199 |
FILE_STORE_SHORTNAME = 2 |
|
200 |
FILE_STORE_MIME_ICON = 3 |
|
201 |
FILE_STORE_COLOR = 4 |
|
202 |
||
1
by alon
initialize |
203 |
class TopShelf: |
23
by alon
standalone now finished |
204 |
def __init__(self, applet, standalone_window=None): |
3
by alon
popup window style |
205 |
self.applet = applet |
22
by alon
Migration from 0.1's obsolte data, and start of nice standaloning |
206 |
self.applet.ts = self # Needed for standalone |
207 |
||
23
by alon
standalone now finished |
208 |
self.standalone_window = standalone_window |
209 |
||
1
by alon
initialize |
210 |
self.window = None |
18
by alon
0.1 release |
211 |
|
212 |
self.pressed = False |
|
213 |
self.resizing = False |
|
214 |
||
215 |
self.panel_pb = gtk.gdk.pixbuf_new_from_file("/usr/share/pixmaps/topshelf-24.png") |
|
216 |
self.panel_off_pb = gtk.gdk.pixbuf_new_from_file("/usr/share/pixmaps/topshelf-24-off.png") |
|
217 |
||
218 |
self.panel_image = gtk.Image() |
|
219 |
self.panel_image.set_from_pixbuf(self.panel_pb) |
|
26
by alon
tooltips and a title label for the window |
220 |
self.panel_image.set_tooltip_text("TopShelf") |
18
by alon
0.1 release |
221 |
self.eventbox = gtk.EventBox() |
30
by alon
Moving to a normal - nonpopup - window. FOr key events to work... |
222 |
self.eventbox.connect("button-press-event", self.do_button_press) |
18
by alon
0.1 release |
223 |
self.eventbox.drag_dest_set(gtk.DEST_DEFAULT_ALL, [('text/uri-list', 0, 81)], gtk.gdk.ACTION_LINK) |
30
by alon
Moving to a normal - nonpopup - window. FOr key events to work... |
224 |
self.eventbox.connect("drag-data-received", self.do_drag_data_received) |
225 |
self.eventbox.connect("key-press-event", self.do_key_press) |
|
18
by alon
0.1 release |
226 |
|
227 |
self.eventbox.add(self.panel_image) |
|
228 |
self.applet.add(self.eventbox) |
|
229 |
self.applet.show_all() |
|
16
by alon
preparations for 0.1 |
230 |
|
19
by alon
New transparency-free icons, and filename color depends on whether it exists. Also an error if the file could not be opened. |
231 |
# full-path, position, short-filename, mime-type-icon, color
|
232 |
self.files_store = gtk.ListStore(str, int, str, gtk.gdk.Pixbuf, str) |
|
4
by alon
starting to persistent and add files |
233 |
|
2
by alon
about dialog |
234 |
try: |
235 |
self.unpickle() |
|
236 |
except IOError: |
|
22
by alon
Migration from 0.1's obsolte data, and start of nice standaloning |
237 |
self.persistent_data = ModernPersistentData() |
2
by alon
about dialog |
238 |
|
23
by alon
standalone now finished |
239 |
if self.standalone_window is not None: |
240 |
self.standalone_window.move(self.persistent_data.get('x'), self.persistent_data.get('y')) |
|
241 |
self.standalone_window.connect("configure-event", self.standalone_window_configure_event) |
|
25
by alon
Keep standalone always on top |
242 |
self.standalone_window.set_keep_above(True) |
23
by alon
standalone now finished |
243 |
|
1
by alon
initialize |
244 |
# Events
|
245 |
||
18
by alon
0.1 release |
246 |
def do_button_toggle(self, param1=None): |
247 |
if not self.pressed: |
|
1
by alon
initialize |
248 |
self.show_window() |
249 |
else: |
|
250 |
self.hide_window() |
|
251 |
||
252 |
def do_button_press(self, btn, event): |
|
18
by alon
0.1 release |
253 |
if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1: |
254 |
if not self.resizing: |
|
255 |
self.do_button_toggle() |
|
256 |
else: |
|
257 |
self.do_stop_resizing() |
|
258 |
elif event.type == gtk.gdk.BUTTON_PRESS and event.button == 3: |
|
1
by alon
initialize |
259 |
btn.emit_stop_by_name("button_press_event") |
260 |
propxml=""" |
|
261 |
<popup name="button3">
|
|
28
by alon
Started work on help |
262 |
<menuitem name="Item 3" verb="Help" label="_Help" pixtype="stock" pixname="gtk-help"/>
|
263 |
<menuitem name="Item 4" verb="About" label="_About" pixtype="stock" pixname="gtk-about"/>
|
|
1
by alon
initialize |
264 |
</popup>"""
|
28
by alon
Started work on help |
265 |
verbs = [("About", self.do_about), |
266 |
("Help", self.do_help) ] |
|
1
by alon
initialize |
267 |
self.applet.setup_menu(propxml, verbs, None) |
268 |
||
23
by alon
standalone now finished |
269 |
def standalone_window_configure_event(self, param1=None, param2=None): |
270 |
x, y = self.standalone_window.get_position() |
|
31
by alon
Key actions finished. Window now is not popup, and works it seems |
271 |
if x != self.persistent_data.get('x') or y != self.persistent_data.get('y'): |
272 |
self.persistent_data.set('x', x) |
|
273 |
self.persistent_data.set('y', y) |
|
274 |
self.pickle() # This might thrash, if we get many many events at once. TODO. |
|
23
by alon
standalone now finished |
275 |
|
276 |
||
2
by alon
about dialog |
277 |
# Actions
|
278 |
||
279 |
def do_about(self, applet, menu_action): |
|
18
by alon
0.1 release |
280 |
self.hide_window() # So we are not concealed! |
281 |
||
10
by alon
icons |
282 |
logo_pb = gtk.gdk.pixbuf_new_from_file("/usr/share/pixmaps/topshelf-64.png") |
283 |
||
2
by alon
about dialog |
284 |
dlg = gtk.AboutDialog() |
17
by alon
minor bugs & issues |
285 |
dlg.set_icon(logo_pb) |
2
by alon
about dialog |
286 |
|
287 |
dlg.set_name("TopShelf") |
|
288 |
dlg.set_program_name("TopShelf") |
|
20
by alon
Brand for 0.2-development |
289 |
dlg.set_version("0.2 - Development") |
26
by alon
tooltips and a title label for the window |
290 |
dlg.set_comments("Currently Important Files Applet") |
2
by alon
about dialog |
291 |
dlg.set_authors(["Alon Zakai (Kripken)"]) |
292 |
dlg.set_license("TopShelf is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version\n2 of the License, or (at your option) any later version.") |
|
293 |
dlg.set_copyright("(c) 2007 Alon Zakai ('Kripken')") |
|
294 |
dlg.set_website("http://launchpad.net/topshelf") |
|
10
by alon
icons |
295 |
dlg.set_logo(logo_pb) |
296 |
||
2
by alon
about dialog |
297 |
dlg.run() |
298 |
dlg.destroy() |
|
299 |
||
5
by alon
progress on syncing |
300 |
return False |
301 |
||
302 |
def do_open(self, param1=None, param2=None, param3=None): |
|
303 |
model, iter = self.get_selected() |
|
304 |
if iter is not None: |
|
19
by alon
New transparency-free icons, and filename color depends on whether it exists. Also an error if the file could not be opened. |
305 |
filename = model.get_value(iter, 0) |
28
by alon
Started work on help |
306 |
ret = subprocess.call(OPEN_APP + " '" + filename + "'", shell=True) |
8
by alon
cleanup and bugfixes |
307 |
self.hide_window() # After opening, we hide... for convenience |
19
by alon
New transparency-free icons, and filename color depends on whether it exists. Also an error if the file could not be opened. |
308 |
if ret != 0: |
309 |
popup_warning(self.window, "The file '" + filename + "' could not be opened due to an error.") |
|
5
by alon
progress on syncing |
310 |
|
311 |
return False |
|
312 |
||
24
by alon
Open containing folder |
313 |
def do_open_folder(self, param1=None, param2=None, param3=None): |
314 |
model, iter = self.get_selected() |
|
315 |
if iter is not None: |
|
316 |
dirname = os.path.dirname(model.get_value(iter, 0)) |
|
28
by alon
Started work on help |
317 |
ret = subprocess.call(OPEN_APP + " '" + dirname + "'", shell=True) |
24
by alon
Open containing folder |
318 |
self.hide_window() # After opening, we hide... for convenience |
319 |
if ret != 0: |
|
320 |
popup_warning(self.window, "The directory '" + dirname + "' could not be opened due to an error.") |
|
321 |
||
322 |
return False |
|
323 |
||
5
by alon
progress on syncing |
324 |
def do_move_up(self, param1=None): |
325 |
model, iter = self.get_selected() |
|
326 |
if iter is not None: |
|
327 |
old_val = model.get(iter, 1)[0] |
|
328 |
new_val = old_val - 1 |
|
6
by alon
basic add/remove and persistence |
329 |
self.swap_file_position(model, iter, old_val, new_val) |
5
by alon
progress on syncing |
330 |
|
331 |
return False |
|
332 |
||
333 |
def do_move_down(self, param1=None): |
|
334 |
model, iter = self.get_selected() |
|
335 |
if iter is not None: |
|
336 |
old_val = model.get(iter, 1)[0] |
|
337 |
new_val = old_val + 1 |
|
6
by alon
basic add/remove and persistence |
338 |
self.swap_file_position(model, iter, old_val, new_val) |
5
by alon
progress on syncing |
339 |
|
340 |
return False |
|
341 |
||
4
by alon
starting to persistent and add files |
342 |
def do_add(self, wgt): |
18
by alon
0.1 release |
343 |
self.hide_window() # So we are not concealed |
344 |
chooser = gtk.FileChooserDialog("Select a file to add to TopShelf:", |
|
4
by alon
starting to persistent and add files |
345 |
self.window, |
346 |
gtk.FILE_CHOOSER_ACTION_OPEN, |
|
347 |
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) |
|
348 |
||
15
by alon
pre-0.1 cleaning |
349 |
# chooser.set_current_folder(...)
|
6
by alon
basic add/remove and persistence |
350 |
# chooser.set_select_multiple(True)
|
4
by alon
starting to persistent and add files |
351 |
|
352 |
response = chooser.run() |
|
353 |
filename = chooser.get_filenames() if response == gtk.RESPONSE_OK else None |
|
354 |
chooser.destroy() |
|
355 |
||
18
by alon
0.1 release |
356 |
self.show_window() |
357 |
||
4
by alon
starting to persistent and add files |
358 |
if filename is not None: |
359 |
self.add_file(filename[0]) |
|
360 |
||
5
by alon
progress on syncing |
361 |
return False |
362 |
||
4
by alon
starting to persistent and add files |
363 |
def do_remove(self, wgt): |
5
by alon
progress on syncing |
364 |
model, iter = self.get_selected() |
365 |
if iter is not None: |
|
30
by alon
Moving to a normal - nonpopup - window. FOr key events to work... |
366 |
if popup_question(self.window, |
367 |
"Are you sure you want to remove the file '" + model.get(iter, 2)[0] + |
|
368 |
"' from the list? (The actual file will not be touched)"): |
|
11
by alon
are you sure for removal. Minor gui improvements |
369 |
old_position = model.get(iter, 1)[0] |
370 |
self.files_store.remove(iter) |
|
371 |
self.remove_file_position(model, old_position) |
|
6
by alon
basic add/remove and persistence |
372 |
|
11
by alon
are you sure for removal. Minor gui improvements |
373 |
self.pickle() |
5
by alon
progress on syncing |
374 |
|
375 |
return False |
|
4
by alon
starting to persistent and add files |
376 |
|
9
by alon
Drag&Drop |
377 |
def do_drag_data_received(self, widget, drag_context, x, y, selection_data, info, timestamp): |
378 |
uris = selection_data.data.strip().split() |
|
379 |
for uri in uris: |
|
380 |
filename = urllib.url2pathname(uri) |
|
381 |
self.add_file(filename) |
|
382 |
||
11
by alon
are you sure for removal. Minor gui improvements |
383 |
def do_package(self, param1=None): |
13
by alon
packaging |
384 |
if self.get_n_files() == 0: |
385 |
return
|
|
386 |
||
387 |
chooser = gtk.FileChooserDialog("Select a filename to save the zipped files as:", |
|
388 |
self.window, |
|
389 |
gtk.FILE_CHOOSER_ACTION_SAVE, |
|
390 |
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK)) |
|
391 |
||
392 |
response = chooser.run() |
|
393 |
package_name = chooser.get_filenames()[0] if response == gtk.RESPONSE_OK else None |
|
394 |
chooser.destroy() |
|
395 |
||
396 |
if package_name is None: |
|
397 |
return
|
|
398 |
||
399 |
file_names = "" |
|
400 |
iter = self.files_store.get_iter_first() |
|
401 |
while iter is not None: |
|
402 |
file_names = file_names + " '" + urllib.url2pathname(self.files_store.get(iter, 0)[0]) + "' " |
|
403 |
iter = self.files_store.iter_next(iter) |
|
404 |
||
21
by alon
Show nice icon for directories and code cleanup for mime-lookup |
405 |
file_names = to_filename(file_names) |
13
by alon
packaging |
406 |
|
407 |
command = "zip '" + package_name + "' " + file_names |
|
408 |
subprocess.call(command, shell=True) |
|
409 |
||
410 |
return False |
|
11
by alon
are you sure for removal. Minor gui improvements |
411 |
|
28
by alon
Started work on help |
412 |
def do_help(self, param1=None, param2=None, param3=None): |
29
by alon
docbook help |
413 |
self.hide_window() |
28
by alon
Started work on help |
414 |
ret = subprocess.call(HELP_APP + " '" + HELP_FILE + "'", shell=True) |
415 |
if ret != 0: |
|
416 |
popup_warning(self.window, "The help file could not be opened due to an error.") |
|
417 |
||
418 |
return False |
|
419 |
||
30
by alon
Moving to a normal - nonpopup - window. FOr key events to work... |
420 |
def do_key_press(self, widget, event): |
421 |
if event.keyval == 65307: |
|
422 |
self.hide_window() |
|
423 |
elif event.keyval == 65535: |
|
424 |
self.do_remove(None) |
|
425 |
||
426 |
||
2
by alon
about dialog |
427 |
# Persistency
|
428 |
||
429 |
def unpickle(self): |
|
430 |
pickle_file = open(PERSISTENT_FILE, 'rb') |
|
12
by alon
fancy resizing, and persistency in size |
431 |
self.persistent_data = pickle.load(pickle_file) |
2
by alon
about dialog |
432 |
pickle_file.close() |
433 |
||
22
by alon
Migration from 0.1's obsolte data, and start of nice standaloning |
434 |
# Migrate, if needed
|
435 |
if test_point1(self.persistent_data): |
|
436 |
self.persistent_data = migrate_point1_to_point2(self.persistent_data) |
|
437 |
||
438 |
for file_data in self.persistent_data.get(current_files): |
|
6
by alon
basic add/remove and persistence |
439 |
self.files_store.insert(0, file_data.listify()) |
4
by alon
starting to persistent and add files |
440 |
|
2
by alon
about dialog |
441 |
def pickle(self): |
22
by alon
Migration from 0.1's obsolte data, and start of nice standaloning |
442 |
self.persistent_data.set(current_files, []) |
6
by alon
basic add/remove and persistence |
443 |
|
444 |
iter = self.files_store.get_iter_first() |
|
445 |
while iter is not None: |
|
22
by alon
Migration from 0.1's obsolte data, and start of nice standaloning |
446 |
self.persistent_data.get(current_files).append(FileData(*self.files_store.get(iter, *range(self.files_store.get_n_columns())))) |
6
by alon
basic add/remove and persistence |
447 |
iter = self.files_store.iter_next(iter) |
448 |
||
2
by alon
about dialog |
449 |
pickle_file = open(PERSISTENT_FILE, 'wb') |
12
by alon
fancy resizing, and persistency in size |
450 |
pickle.dump(self.persistent_data, pickle_file) |
2
by alon
about dialog |
451 |
pickle_file.close() |
452 |
||
4
by alon
starting to persistent and add files |
453 |
# File management
|
454 |
||
455 |
def add_file(self, filename): |
|
7
by alon
mimetypes |
456 |
self.files_store.insert(0, FileData(filename, self.get_n_files()).listify()) |
6
by alon
basic add/remove and persistence |
457 |
|
4
by alon
starting to persistent and add files |
458 |
self.pickle() |
459 |
||
5
by alon
progress on syncing |
460 |
def get_file_iter_by_filename(self, filename): |
461 |
class Ret: |
|
462 |
def __init__(self): |
|
463 |
self.val = None |
|
464 |
||
465 |
def search(self, m, p, i, filename): |
|
466 |
if m.get(i, 0)[0] == filename: |
|
467 |
self.val = i |
|
468 |
||
469 |
ret = Ret() |
|
470 |
self.files_store.foreach(ret.search, (filename)) |
|
471 |
return ret.val |
|
472 |
||
473 |
# Window functions
|
|
1
by alon
initialize |
474 |
|
475 |
def show_window(self): |
|
476 |
if self.window is None: |
|
3
by alon
popup window style |
477 |
self.create_window() |
1
by alon
initialize |
478 |
|
18
by alon
0.1 release |
479 |
if not self.resizing: |
480 |
self.panel_image.set_from_pixbuf(self.panel_off_pb) |
|
481 |
self.pressed = True |
|
1
by alon
initialize |
482 |
|
31
by alon
Key actions finished. Window now is not popup, and works it seems |
483 |
# These four commands - present, keep-above, present, position - are very odd. But they work, in THIS order.
|
19
by alon
New transparency-free icons, and filename color depends on whether it exists. Also an error if the file could not be opened. |
484 |
self.window.present() |
30
by alon
Moving to a normal - nonpopup - window. FOr key events to work... |
485 |
self.window.set_keep_above(True) # Force it! |
31
by alon
Key actions finished. Window now is not popup, and works it seems |
486 |
self.window.present() |
487 |
self.position_window() # Needed here because of odd behavior otherwise (after adding, hide, show, it moved) |
|
19
by alon
New transparency-free icons, and filename color depends on whether it exists. Also an error if the file could not be opened. |
488 |
|
489 |
self.check_files_thread() # One manual operation, then later as a background thread |
|
490 |
# gobject.timeout_add(2000, self.check_files_thread) No thread for now, since it wastes CPU. Later, add an option to do this.
|
|
491 |
||
5
by alon
progress on syncing |
492 |
def hide_window(self, param1=False, param2=False): |
18
by alon
0.1 release |
493 |
if self.window is None: |
494 |
return True |
|
495 |
||
4
by alon
starting to persistent and add files |
496 |
self.window.hide() |
18
by alon
0.1 release |
497 |
|
498 |
self.panel_image.set_from_pixbuf(self.panel_pb) |
|
499 |
self.pressed = False |
|
4
by alon
starting to persistent and add files |
500 |
|
5
by alon
progress on syncing |
501 |
return True # Stop delete events |
502 |
||
30
by alon
Moving to a normal - nonpopup - window. FOr key events to work... |
503 |
def create_window(self): |
504 |
self.window = gtk.Window() |
|
18
by alon
0.1 release |
505 |
|
30
by alon
Moving to a normal - nonpopup - window. FOr key events to work... |
506 |
self.window.set_title("TopShelf") |
11
by alon
are you sure for removal. Minor gui improvements |
507 |
self.window.connect("delete-event", self.hide_window) |
508 |
self.window.connect("window-state-event", self.window_state_event) |
|
30
by alon
Moving to a normal - nonpopup - window. FOr key events to work... |
509 |
self.window.connect("configure-event", self.configure_event) |
510 |
||
511 |
self.window.connect("key-press-event", self.do_key_press) |
|
512 |
||
9
by alon
Drag&Drop |
513 |
self.window.set_keep_above(True) |
22
by alon
Migration from 0.1's obsolte data, and start of nice standaloning |
514 |
self.window.resize(self.persistent_data.get('w'), self.persistent_data.get('h')) |
9
by alon
Drag&Drop |
515 |
|
516 |
self.window.drag_dest_set(gtk.DEST_DEFAULT_ALL, [('text/uri-list', 0, 81)], gtk.gdk.ACTION_LINK) |
|
30
by alon
Moving to a normal - nonpopup - window. FOr key events to work... |
517 |
self.window.connect("drag-data-received", self.do_drag_data_received) |
4
by alon
starting to persistent and add files |
518 |
|
519 |
# Main view
|
|
520 |
||
5
by alon
progress on syncing |
521 |
self.treeview = gtk.TreeView() |
522 |
self.treeview.set_model(self.files_store) |
|
7
by alon
mimetypes |
523 |
col = self.append_column(self.treeview, gtk.TreeViewColumn("#", gtk.CellRendererText(), text =1), 1) |
19
by alon
New transparency-free icons, and filename color depends on whether it exists. Also an error if the file could not be opened. |
524 |
tmp = self.append_column(self.treeview, gtk.TreeViewColumn("", gtk.CellRendererPixbuf(), pixbuf=3)) |
525 |
tmp = self.append_column(self.treeview, gtk.TreeViewColumn("File", gtk.CellRendererText(), text =2), 2, |
|
526 |
foreground_id=FILE_STORE_COLOR) |
|
7
by alon
mimetypes |
527 |
|
528 |
col.clicked() # Set default sort |
|
5
by alon
progress on syncing |
529 |
self.treeview.connect("row-activated", self.do_open) |
4
by alon
starting to persistent and add files |
530 |
|
9
by alon
Drag&Drop |
531 |
scrolled_window = gtk.ScrolledWindow() |
532 |
scrolled_window.add(self.treeview) |
|
533 |
scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) |
|
534 |
||
4
by alon
starting to persistent and add files |
535 |
# Toolbar & buttons on bottom
|
536 |
||
537 |
toolbar = gtk.Toolbar() |
|
538 |
||
26
by alon
tooltips and a title label for the window |
539 |
self.add_stock_button (toolbar, gtk.STOCK_OPEN, self.do_open , "Open file") |
540 |
self.add_altstock_button (toolbar, gtk.STOCK_DIRECTORY, "Open Folder", self.do_open_folder, "Open folder containing file") |
|
541 |
self.add_stock_button (toolbar, gtk.STOCK_GO_UP, self.do_move_up , "Move file up in order") |
|
542 |
self.add_stock_button (toolbar, gtk.STOCK_GO_DOWN, self.do_move_down , "Move file down in order") |
|
543 |
self.add_stock_button (toolbar, gtk.STOCK_ADD, self.do_add , "Add a file to the list") |
|
544 |
self.add_stock_button (toolbar, gtk.STOCK_REMOVE, self.do_remove , "Remove file from the list") |
|
14
by alon
pre-0.1 |
545 |
# self.add_pixbuf_button (toolbar, guess_mime_icon("a.zip"), "Package Files", self.do_package)
|
28
by alon
Started work on help |
546 |
# self.add_stock_button (toolbar, gtk.STOCK_HELP, self.do_help , "Help")
|
4
by alon
starting to persistent and add files |
547 |
|
548 |
# Main vbox
|
|
549 |
||
550 |
vbox = gtk.VBox() |
|
30
by alon
Moving to a normal - nonpopup - window. FOr key events to work... |
551 |
|
552 |
# label = gtk.Label("<b>TopShelf: Currently Important Files</b>")
|
|
553 |
# label.set_use_markup(True)
|
|
554 |
# vbox.add(label)
|
|
555 |
# vbox.child_set_property(label, "expand", False)
|
|
26
by alon
tooltips and a title label for the window |
556 |
# vbox.child_set_property(label, "border", 2)
|
30
by alon
Moving to a normal - nonpopup - window. FOr key events to work... |
557 |
|
9
by alon
Drag&Drop |
558 |
vbox.add(scrolled_window) |
4
by alon
starting to persistent and add files |
559 |
vbox.add(toolbar) |
560 |
vbox.child_set_property(toolbar, "expand", False) |
|
561 |
||
562 |
# Frames
|
|
3
by alon
popup window style |
563 |
|
564 |
inner_frame = gtk.Frame() |
|
565 |
inner_frame.set_shadow_type(gtk.SHADOW_IN) |
|
18
by alon
0.1 release |
566 |
inner_frame.set_border_width(self.panel_image.get_allocation().height/8) |
3
by alon
popup window style |
567 |
|
4
by alon
starting to persistent and add files |
568 |
outer_frame = gtk.Frame() |
569 |
outer_frame.set_shadow_type(gtk.SHADOW_OUT) |
|
570 |
||
571 |
inner_frame.add(vbox) |
|
3
by alon
popup window style |
572 |
outer_frame.add(inner_frame) |
4
by alon
starting to persistent and add files |
573 |
|
574 |
# Finalize main window
|
|
575 |
||
3
by alon
popup window style |
576 |
self.window.add(outer_frame) |
577 |
self.position_window() |
|
578 |
self.window.show_all() |
|
579 |
||
11
by alon
are you sure for removal. Minor gui improvements |
580 |
def window_state_event(self, widget, event): |
581 |
if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED: |
|
582 |
self.hide_window() |
|
583 |
self.window.deiconify() |
|
584 |
||
30
by alon
Moving to a normal - nonpopup - window. FOr key events to work... |
585 |
def configure_event(self, widget, event): |
586 |
w, h = self.window.get_size() |
|
587 |
||
588 |
if w != self.persistent_data.get('w') or h != self.persistent_data.get('h'): |
|
589 |
self.persistent_data.set('w', w) |
|
590 |
self.persistent_data.set('h', h) |
|
591 |
||
592 |
self.pickle() |
|
593 |
||
26
by alon
tooltips and a title label for the window |
594 |
def add_button(self, toolbar, image, text, callback, tooltip): |
11
by alon
are you sure for removal. Minor gui improvements |
595 |
btn = gtk.ToolButton() |
596 |
btn.set_icon_widget(image) |
|
597 |
btn.set_label(text) |
|
598 |
btn.connect("clicked", callback) |
|
26
by alon
tooltips and a title label for the window |
599 |
btn.set_tooltip_text(tooltip) |
11
by alon
are you sure for removal. Minor gui improvements |
600 |
toolbar.insert(btn, -1) |
601 |
||
26
by alon
tooltips and a title label for the window |
602 |
def add_altstock_button(self, toolbar, icon_id, text, callback, tooltip): |
11
by alon
are you sure for removal. Minor gui improvements |
603 |
icon = gtk.Image() |
604 |
icon.set_from_stock(icon_id, gtk.ICON_SIZE_LARGE_TOOLBAR) |
|
26
by alon
tooltips and a title label for the window |
605 |
self.add_button(toolbar, icon, text, callback, tooltip) |
606 |
||
607 |
# def add_pixbuf_button(self, toolbar, pixbuf, text, callback, ):
|
|
608 |
# icon = gtk.Image()
|
|
609 |
# icon.set_from_pixbuf(pixbuf)
|
|
610 |
# self.add_button(toolbar, icon, text, callback)
|
|
611 |
||
612 |
def add_stock_button(self, toolbar, stock_id, callback, tooltip): |
|
5
by alon
progress on syncing |
613 |
btn = gtk.ToolButton(stock_id) |
614 |
btn.connect("clicked", callback) |
|
26
by alon
tooltips and a title label for the window |
615 |
btn.set_tooltip_text(tooltip) |
5
by alon
progress on syncing |
616 |
toolbar.insert(btn, -1) |
617 |
||
19
by alon
New transparency-free icons, and filename color depends on whether it exists. Also an error if the file could not be opened. |
618 |
def append_column(self, treeview, column, sort_id=None, foreground_id=None): |
619 |
renderer = column.get_cell_renderers()[0] |
|
7
by alon
mimetypes |
620 |
if sort_id is not None: |
621 |
column.set_sort_column_id(sort_id) |
|
19
by alon
New transparency-free icons, and filename color depends on whether it exists. Also an error if the file could not be opened. |
622 |
if foreground_id is not None: |
623 |
column.add_attribute(renderer, "foreground", foreground_id) |
|
5
by alon
progress on syncing |
624 |
treeview.append_column(column) |
7
by alon
mimetypes |
625 |
return column |
5
by alon
progress on syncing |
626 |
|
1
by alon
initialize |
627 |
def position_window(self): |
628 |
||
629 |
# This function was converted from a C function in clock.c in the GNOME
|
|
630 |
# panel applet 'clock', which is GPL 2+ (like this code). Thanks to the
|
|
631 |
# GNOME authors,
|
|
632 |
# Miguel de Icaza
|
|
633 |
# Frederico Mena
|
|
634 |
# Stuart Parmenter
|
|
635 |
# Alexander Larsson
|
|
636 |
# George Lebl
|
|
637 |
# Gediminas Paulauskas
|
|
638 |
# Mark McLoughlin
|
|
639 |
||
18
by alon
0.1 release |
640 |
x,y = self.panel_image.window.get_origin() |
1
by alon
initialize |
641 |
|
642 |
w,h = self.window.get_size() |
|
643 |
||
18
by alon
0.1 release |
644 |
button_w = self.panel_image.get_allocation().width |
645 |
button_h = self.panel_image.get_allocation().height |
|
1
by alon
initialize |
646 |
|
647 |
screen = self.window.get_screen() |
|
648 |
||
649 |
n = screen.get_n_monitors() |
|
650 |
found_monitor = False |
|
651 |
monitor = None |
|
652 |
for i in range(n): |
|
653 |
monitor = screen.get_monitor_geometry(i) |
|
654 |
if (x >= monitor.x and x <= monitor.x + monitor.width and |
|
655 |
y >= monitor.y and y <= monitor.y + monitor.height): |
|
656 |
found_monitor = True |
|
657 |
break
|
|
658 |
||
659 |
if not found_monitor: |
|
660 |
monitor = gtk.gdk.Rectangle() |
|
661 |
monitor.x = 0 |
|
662 |
monitor.y = 0 |
|
663 |
monitor.width = screen.get_width() |
|
664 |
monitor.height = screen.get_height() |
|
665 |
||
9
by alon
Drag&Drop |
666 |
if x > monitor.width/2: |
667 |
x -= w |
|
1
by alon
initialize |
668 |
x += button_w |
669 |
||
670 |
if y < monitor.height/2: |
|
671 |
y += button_h |
|
672 |
else: |
|
673 |
y -= h |
|
674 |
||
675 |
self.window.move(x, y) |
|
9
by alon
Drag&Drop |
676 |
|
1
by alon
initialize |
677 |
|
19
by alon
New transparency-free icons, and filename color depends on whether it exists. Also an error if the file could not be opened. |
678 |
# Threads
|
679 |
||
680 |
def check_files_thread(self): |
|
681 |
if self.pressed: |
|
682 |
iter = self.files_store.get_iter_first() |
|
683 |
while iter is not None: |
|
21
by alon
Show nice icon for directories and code cleanup for mime-lookup |
684 |
filename = to_filename(self.files_store.get(iter, 0)[0]) |
19
by alon
New transparency-free icons, and filename color depends on whether it exists. Also an error if the file could not be opened. |
685 |
if os.path.exists(filename): |
686 |
self.files_store.set(iter, FILE_STORE_COLOR, NORMAL_BG_COLOR) |
|
687 |
else: |
|
688 |
self.files_store.set(iter, FILE_STORE_COLOR, DISABLE_BG_COLOR) |
|
689 |
iter = self.files_store.iter_next(iter) |
|
690 |
return True |
|
691 |
else: |
|
692 |
return False |
|
693 |
||
5
by alon
progress on syncing |
694 |
# Utilities
|
695 |
||
696 |
def get_selected(self): |
|
697 |
return self.treeview.get_selection().get_selected() |
|
698 |
||
6
by alon
basic add/remove and persistence |
699 |
def get_n_files(self): |
5
by alon
progress on syncing |
700 |
ret = self.files_store.iter_n_children(None) |
701 |
return ret |
|
1
by alon
initialize |
702 |
|
6
by alon
basic add/remove and persistence |
703 |
def swap_file_position(self, model, iter, old_val, new_val): |
704 |
if old_val >= 0 and new_val >= 0 and old_val < self.get_n_files() and new_val < self.get_n_files(): |
|
705 |
model.foreach(lambda m, p, i, vs: None if m.get(i, 1)[0] != vs[1] else m.set(i, 1, vs[0]), |
|
706 |
(old_val, new_val)) |
|
707 |
model.set(iter, 1, new_val) |
|
708 |
||
8
by alon
cleanup and bugfixes |
709 |
self.pickle() |
710 |
||
6
by alon
basic add/remove and persistence |
711 |
def remove_file_position(self, model, old_position): |
712 |
model.foreach(lambda m, p, i, op: m.set(i, 1, m.get(i, 1)[0]-1) if m.get(i, 1)[0] > op else None, |
|
713 |
old_position) |
|
714 |
||
7
by alon
mimetypes |
715 |
|
1
by alon
initialize |
716 |
### Factory
|
717 |
||
23
by alon
standalone now finished |
718 |
def factory(applet, iid, standalone_window=None): |
719 |
ts = TopShelf(applet, standalone_window) |
|
1
by alon
initialize |
720 |
|
721 |
return True |
|
722 |
||
723 |
||
724 |
### Main
|
|
725 |
||
23
by alon
standalone now finished |
726 |
if len(sys.argv) == 1: |
22
by alon
Migration from 0.1's obsolte data, and start of nice standaloning |
727 |
# Run standalone
|
728 |
main_window = gtk.Window(gtk.WINDOW_TOPLEVEL) |
|
729 |
main_window.set_title("TopShelf") |
|
730 |
main_window.connect("destroy", gtk.main_quit) |
|
731 |
||
732 |
applet = gnomeapplet.Applet() |
|
23
by alon
standalone now finished |
733 |
factory(applet, None, standalone_window=main_window) |
22
by alon
Migration from 0.1's obsolte data, and start of nice standaloning |
734 |
applet.reparent(main_window) |
735 |
main_window.show_all() |
|
736 |
gtk.main() |
|
737 |
||
738 |
sys.exit() |
|
1
by alon
initialize |
739 |
|
740 |
if __name__ == '__main__': |
|
3
by alon
popup window style |
741 |
gnomeapplet.bonobo_factory("OAFIID:Gnome_Panel_TopShelf_Factory", gnomeapplet.Applet.__gtype__, "A current files manager", "1.0", factory) |
1
by alon
initialize |
742 |