~kripkenstein/topshelf/main

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