erikg / Revelation
Revelation is a password manager for the GNOME desktop, released under the GNU GPL license. It stores all your accounts and passwords in a single, secure place, and gives you access to it through a user-friendly graphical interface.
| commit 40: | 3794d9b38446 |
| parent 39: | 9b3c9903350d |
| branch: | default |
Changed (Δ5.5 KB):
ChangeLog (3 lines added, 0 lines removed)
TODO (0 lines added, 1 lines removed)
src/lib/data.py (0 lines added, 2 lines removed)
src/lib/dialog.py (64 lines added, 9 lines removed)
src/lib/entry.py (27 lines added, 0 lines removed)
src/lib/misc.py (6 lines added, 0 lines removed)
src/lib/stock.py (1 lines added, 0 lines removed)
src/lib/ui.py (27 lines added, 8 lines removed)
src/lib/widget.py (350 lines added, 154 lines removed)
src/revelation (23 lines added, 1 lines removed)
| … | … | @@ -6,6 +6,9 @@ 2004-07-27 Erik Grinaker <erikg@codepoe |
6 |
6 |
|
7 |
7 |
* rewrote the app configuration handling |
8 |
8 |
|
9 |
* cleaned up the widget code, and added docstrings to all |
|
10 |
classes, methods and functions |
|
11 |
||
9 |
12 |
2004-07-15 Erik Grinaker <erikg@codepoet.no> |
10 |
13 |
|
11 |
14 |
* when adding an entry the default type is Generic (not Folder) |
| … | … | @@ -7,7 +7,6 @@ 0.3.x: |
7 |
7 |
- string cleanups |
8 |
8 |
- program-launchers for misc account types (announce on rvl-list |
9 |
9 |
when done in svn) |
10 |
- add docstrings to all objects, methods and functions |
|
11 |
10 |
- export to XHTML/CSS files |
12 |
11 |
|
13 |
12 |
0.4.x: |
Up to file-list src/lib/data.py:
| … | … | @@ -117,8 +117,6 @@ class Config(gobject.GObject): |
117 |
117 |
def __cb_notify(self, client, id, entry, data): |
118 |
118 |
"Callback for handling notifications" |
119 |
119 |
|
120 |
print "Config callbacks: ", len(self.callbacks), id |
|
121 |
||
122 |
120 |
# get the value contents |
123 |
121 |
value = entry.get_value() |
124 |
122 |
Up to file-list src/lib/dialog.py:
| … | … | @@ -31,6 +31,7 @@ RESPONSE_PREVIOUS = 11 |
31 |
31 |
|
32 |
32 |
# first we define a few base classes |
33 |
33 |
class Dialog(gtk.Dialog): |
34 |
"Base class for dialogs" |
|
34 |
35 |
|
35 |
36 |
def __init__(self, parent, title, buttons, default = None): |
36 |
37 |
gtk.Dialog.__init__(self, title, parent, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL | gtk.DIALOG_NO_SEPARATOR) |
| … | … | @@ -50,23 +51,34 @@ class Dialog(gtk.Dialog): |
50 |
51 |
|
51 |
52 |
|
52 |
53 |
def __cb_keypress(self, widget, data): |
54 |
"Callback for handling keypresses" |
|
55 |
||
56 |
# close the dialog on Escape |
|
53 |
57 |
if data.keyval == 65307: |
54 |
58 |
self.response(gtk.RESPONSE_CLOSE) |
55 |
59 |
|
56 |
60 |
|
57 |
61 |
def get_button(self, index): |
62 |
"Get one of the dialogs buttons" |
|
63 |
||
58 |
64 |
buttons = self.action_area.get_children() |
59 |
return index < len(buttons) and buttons[index] or None |
|
65 |
||
66 |
if index < len(buttons): |
|
67 |
return buttons[index] |
|
68 |
||
69 |
else: |
|
70 |
return None |
|
60 |
71 |
|
61 |
72 |
|
62 |
73 |
|
63 |
74 |
class Hig(Dialog): |
75 |
"A HIG-ified message dialog" |
|
64 |
76 |
|
65 |
77 |
def __init__(self, parent, pritext, sectext, stockimage, buttons, default = None): |
66 |
78 |
Dialog.__init__(self, parent, "", buttons, default) |
67 |
79 |
|
68 |
80 |
# hbox separating dialog image and contents |
69 |
hbox = |
|
81 |
hbox = revelation.widget.HBox() |
|
70 |
82 |
hbox.set_spacing(12) |
71 |
83 |
hbox.set_border_width(6) |
72 |
84 |
self.vbox.pack_start(hbox) |
| … | … | @@ -78,7 +90,7 @@ class Hig(Dialog): |
78 |
90 |
hbox.pack_start(image, gtk.FALSE, gtk.FALSE) |
79 |
91 |
|
80 |
92 |
# set up main content area |
81 |
self.contents = |
|
93 |
self.contents = revelation.widget.VBox() |
|
82 |
94 |
self.contents.set_spacing(10) |
83 |
95 |
hbox.pack_start(self.contents) |
84 |
96 |
|
| … | … | @@ -88,6 +100,8 @@ class Hig(Dialog): |
88 |
100 |
|
89 |
101 |
|
90 |
102 |
def run(self): |
103 |
"Display the dialog" |
|
104 |
||
91 |
105 |
self.show_all() |
92 |
106 |
response = gtk.Dialog.run(self) |
93 |
107 |
self.destroy() |
| … | … | @@ -97,6 +111,7 @@ class Hig(Dialog): |
97 |
111 |
|
98 |
112 |
|
99 |
113 |
class Property(Dialog): |
114 |
"A property dialog" |
|
100 |
115 |
|
101 |
116 |
def __init__(self, parent, title, buttons, default = None): |
102 |
117 |
Dialog.__init__(self, parent, title, buttons, default) |
| … | … | @@ -109,6 +124,8 @@ class Property(Dialog): |
109 |
124 |
|
110 |
125 |
|
111 |
126 |
def add_section(self, title, description = None): |
127 |
"Adds an input section to the dialog" |
|
128 |
||
112 |
129 |
section = revelation.widget.InputSection(title, self.sizegroup, description) |
113 |
130 |
self.vbox.pack_start(section) |
114 |
131 |
return section |
| … | … | @@ -118,6 +135,7 @@ class Property(Dialog): |
118 |
135 |
|
119 |
136 |
# simple message dialogs |
120 |
137 |
class Error(Hig): |
138 |
"Displays an error message" |
|
121 |
139 |
|
122 |
140 |
def __init__(self, parent, pritext, sectext): |
123 |
141 |
Hig.__init__( |
| … | … | @@ -152,6 +170,7 @@ class FileExportInsecure(Hig): |
152 |
170 |
|
153 |
171 |
|
154 |
172 |
class FileOverwrite(Hig): |
173 |
"Asks for file overwrite confirmation" |
|
155 |
174 |
|
156 |
175 |
def __init__(self, parent, file): |
157 |
176 |
Hig.__init__( |
| … | … | @@ -163,6 +182,8 @@ class FileOverwrite(Hig): |
163 |
182 |
|
164 |
183 |
|
165 |
184 |
def run(self): |
185 |
"Displays the dialog" |
|
186 |
||
166 |
187 |
response = Hig.run(self) |
167 |
188 |
|
168 |
189 |
if response == gtk.RESPONSE_OK: |
| … | … | @@ -174,6 +195,7 @@ class FileOverwrite(Hig): |
174 |
195 |
|
175 |
196 |
|
176 |
197 |
class RemoveEntry(Hig): |
198 |
"Asks for confirmation when removing entries" |
|
177 |
199 |
|
178 |
200 |
def __init__(self, parent, pritext, sectext): |
179 |
201 |
Hig.__init__( |
| … | … | @@ -184,11 +206,14 @@ class RemoveEntry(Hig): |
184 |
206 |
|
185 |
207 |
|
186 |
208 |
def run(self): |
209 |
"Displays the dialog" |
|
210 |
||
187 |
211 |
return Hig.run(self) == gtk.RESPONSE_OK |
188 |
212 |
|
189 |
213 |
|
190 |
214 |
|
191 |
215 |
class SaveChanges(Hig): |
216 |
"Asks the user if she wants to save her changes" |
|
192 |
217 |
|
193 |
218 |
def __init__(self, parent, pritext, sectext): |
194 |
219 |
Hig.__init__( |
| … | … | @@ -196,7 +221,10 @@ class SaveChanges(Hig): |
196 |
221 |
[ [ revelation.stock.STOCK_DISCARD, gtk.RESPONSE_CLOSE ], [ gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL ], [ gtk.STOCK_SAVE, gtk.RESPONSE_OK ] ] |
197 |
222 |
) |
198 |
223 |
|
224 |
||
199 |
225 |
def run(self): |
226 |
"Displays the dialog" |
|
227 |
||
200 |
228 |
response = Hig.run(self) |
201 |
229 |
|
202 |
230 |
if response == gtk.RESPONSE_CANCEL: |
| … | … | @@ -220,8 +248,7 @@ class FileSelector(gtk.FileSelection): |
220 |
248 |
def add_widget(self, title, widget): |
221 |
249 |
"Adds a widget to the file selector" |
222 |
250 |
|
223 |
hbox = gtk.HBox() |
|
224 |
hbox.set_spacing(5) |
|
251 |
hbox = revelation.widget.HBox() |
|
225 |
252 |
self.main_vbox.pack_start(hbox) |
226 |
253 |
|
227 |
254 |
if title is not None: |
| … | … | @@ -320,6 +347,7 @@ class ImportFileSelector(FileSelector): |
320 |
347 |
|
321 |
348 |
# more complex dialogs |
322 |
349 |
class About(gnome.ui.About): |
350 |
"An about dialog" |
|
323 |
351 |
|
324 |
352 |
def __init__(self, parent): |
325 |
353 |
gnome.ui.About.__init__( |
| … | … | @@ -334,11 +362,14 @@ class About(gnome.ui.About): |
334 |
362 |
|
335 |
363 |
|
336 |
364 |
def run(self): |
365 |
"Displays the dialog" |
|
366 |
||
337 |
367 |
self.show_all() |
338 |
368 |
|
339 |
369 |
|
340 |
370 |
|
341 |
371 |
class EditEntry(Property): |
372 |
"A dialog for editing entries" |
|
342 |
373 |
|
343 |
374 |
def __init__(self, parent, title, entry = None): |
344 |
375 |
Property.__init__( |
| … | … | @@ -375,15 +406,26 @@ class EditEntry(Property): |
375 |
406 |
|
376 |
407 |
|
377 |
408 |
def __cb_entry_description_changed(self, widget, data = None): |
409 |
"Updates the description data" |
|
410 |
||
378 |
411 |
self.entry.description = widget.get_text() |
379 |
412 |
|
413 |
||
380 |
414 |
def __cb_entry_name_changed(self, widget, data = None): |
415 |
"Updates the name data" |
|
416 |
||
381 |
417 |
self.entry.name = widget.get_text() |
382 |
418 |
|
419 |
||
383 |
420 |
def __cb_entry_field_changed(self, widget, id): |
421 |
"Updates field data" |
|
422 |
||
384 |
423 |
self.entry.set_field(id, widget.get_text()) |
385 |
424 |
|
425 |
||
386 |
426 |
def __cb_dropdown_changed(self, object): |
427 |
"Updates the entry type data" |
|
428 |
||
387 |
429 |
type = self.dropdown.get_active_item().type |
388 |
430 |
|
389 |
431 |
if type != self.entry.type: |
| … | … | @@ -392,6 +434,8 @@ class EditEntry(Property): |
392 |
434 |
|
393 |
435 |
|
394 |
436 |
def run(self): |
437 |
"Displays the dialog" |
|
438 |
||
395 |
439 |
if Property.run(self) == gtk.RESPONSE_OK: |
396 |
440 |
|
397 |
441 |
if self.entry.name == "": |
| … | … | @@ -409,10 +453,14 @@ class EditEntry(Property): |
409 |
453 |
|
410 |
454 |
|
411 |
455 |
def set_typechange_allowed(self, allow): |
456 |
"Sets whether to allow type changes" |
|
457 |
||
412 |
458 |
self.dropdown.set_sensitive(allow) |
413 |
459 |
|
414 |
460 |
|
415 |
461 |
def update(self, type = None): |
462 |
"Updates the dialog to a given type" |
|
463 |
||
416 |
464 |
if len(self.vbox.get_children()) > 2: |
417 |
465 |
self.vbox.get_children().pop(1).destroy() |
418 |
466 |
|
| … | … | @@ -434,14 +482,12 @@ class EditEntry(Property): |
434 |
482 |
self.tooltips.set_tip(entry, field.description) |
435 |
483 |
|
436 |
484 |
if field.id == revelation.entry.FIELD_GENERIC_PASSWORD: |
437 |
hbox = gtk.HBox() |
|
438 |
hbox.set_spacing(6) |
|
485 |
hbox = revelation.widget.HBox() |
|
439 |
486 |
section.add_inputrow(field.name, hbox) |
440 |
487 |
|
441 |
488 |
hbox.pack_start(entry) |
442 |
489 |
|
443 |
button = gtk.Button("Generate") |
|
444 |
button.connect("clicked", lambda w: entry.set_text(revelation.misc.generate_password())) |
|
490 |
button = revelation.widget.Button("Generate", lambda w: entry.set_text(revelation.misc.generate_password())) |
|
445 |
491 |
hbox.pack_start(button, gtk.FALSE, gtk.FALSE) |
446 |
492 |
|
447 |
493 |
|
| … | … | @@ -501,18 +547,23 @@ class Find(Property): |
501 |
547 |
|
502 |
548 |
|
503 |
549 |
def __cb_entry_changed(self, widget, data = None): |
550 |
"Sets the Find button sensitivity based on entry contents" |
|
551 |
||
504 |
552 |
active = len(self.entry_phrase.get_text()) > 0 |
505 |
553 |
self.get_button(0).set_sensitive(active) |
506 |
554 |
self.get_button(1).set_sensitive(active) |
507 |
555 |
|
508 |
556 |
|
509 |
557 |
def run(self): |
558 |
"Displays the dialog" |
|
559 |
||
510 |
560 |
self.show_all() |
511 |
561 |
return Property.run(self) |
512 |
562 |
|
513 |
563 |
|
514 |
564 |
|
515 |
565 |
class Password(Hig): |
566 |
"A dialog which asks for passwords" |
|
516 |
567 |
|
517 |
568 |
def __init__(self, parent, title, text, current = gtk.TRUE, new = gtk.FALSE): |
518 |
569 |
Hig.__init__( |
| … | … | @@ -548,6 +599,8 @@ class Password(Hig): |
548 |
599 |
|
549 |
600 |
|
550 |
601 |
def __cb_entry_changed(self, widget, data = None): |
602 |
"Sets the OK button sensitivity based on the entries" |
|
603 |
||
551 |
604 |
if ( |
552 |
605 |
(self.entry_password is None or self.entry_password.get_text() != "") |
553 |
606 |
and (self.entry_new is None or self.entry_new.get_text() != "") |
| … | … | @@ -559,6 +612,8 @@ class Password(Hig): |
559 |
612 |
|
560 |
613 |
|
561 |
614 |
def run(self): |
615 |
"Displays the dialog" |
|
616 |
||
562 |
617 |
while 1: |
563 |
618 |
self.show_all() |
564 |
619 |
Up to file-list src/lib/entry.py:
| … | … | @@ -259,6 +259,7 @@ class EntryTypeError(EntryError): |
259 |
259 |
|
260 |
260 |
|
261 |
261 |
class Entry(gobject.GObject): |
262 |
"An entry object" |
|
262 |
263 |
|
263 |
264 |
def __init__(self, type = ENTRY_FOLDER): |
264 |
265 |
gobject.GObject.__init__(self) |
| … | … | @@ -276,10 +277,14 @@ class Entry(gobject.GObject): |
276 |
277 |
|
277 |
278 |
|
278 |
279 |
def copy(self): |
280 |
"Create a copy of the entry" |
|
281 |
||
279 |
282 |
return copy.deepcopy(self) |
280 |
283 |
|
281 |
284 |
|
282 |
285 |
def get_field(self, id): |
286 |
"Get one of the entrys fields" |
|
287 |
||
283 |
288 |
try: |
284 |
289 |
return self.fields[id] |
285 |
290 |
|
| … | … | @@ -288,6 +293,8 @@ class Entry(gobject.GObject): |
288 |
293 |
|
289 |
294 |
|
290 |
295 |
def get_fields(self): |
296 |
"Get all the entrys fields" |
|
297 |
||
291 |
298 |
fields = [] |
292 |
299 |
for id in ENTRYDATA[self.type]["fields"]: |
293 |
300 |
fields.append(self.get_field(id)) |
| … | … | @@ -296,18 +303,26 @@ class Entry(gobject.GObject): |
296 |
303 |
|
297 |
304 |
|
298 |
305 |
def get_updated_age(self): |
306 |
"Get the age of an entry" |
|
307 |
||
299 |
308 |
return revelation.misc.timediff_simple(self.updated) |
300 |
309 |
|
301 |
310 |
|
302 |
311 |
def get_updated_iso(self): |
312 |
"Get the update time in ISO format" |
|
313 |
||
303 |
314 |
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.updated)) |
304 |
315 |
|
305 |
316 |
|
306 |
317 |
def has_field(self, id): |
318 |
"Checks if the entry has a particular field" |
|
319 |
||
307 |
320 |
return self.fields.has_key(id) |
308 |
321 |
|
309 |
322 |
|
310 |
323 |
def set_field(self, id, value): |
324 |
"Sets one of the entries fields to a value" |
|
325 |
||
311 |
326 |
if not self.fields.has_key(id): |
312 |
327 |
raise EntryFieldError |
313 |
328 |
|
| … | … | @@ -315,6 +330,7 @@ class Entry(gobject.GObject): |
315 |
330 |
|
316 |
331 |
|
317 |
332 |
def set_type(self, type): |
333 |
"Sets the type of entry" |
|
318 |
334 |
|
319 |
335 |
# backwards-compatability |
320 |
336 |
if type == "usenet": |
| … | … | @@ -343,6 +359,7 @@ class Entry(gobject.GObject): |
343 |
359 |
|
344 |
360 |
|
345 |
361 |
class Field(gobject.GObject): |
362 |
"An entry field object" |
|
346 |
363 |
|
347 |
364 |
def __init__(self, id = None, value = ""): |
348 |
365 |
gobject.GObject.__init__(self) |
| … | … | @@ -353,3 +370,13 @@ class Field(gobject.GObject): |
353 |
370 |
self.name = FIELDDATA[id]["name"] |
354 |
371 |
self.description = FIELDDATA[id]["description"] |
355 |
372 |
|
373 |
||
374 |
||
375 |
def get_entry_list(): |
|
376 |
"Returns a sorted list of all available entry types" |
|
377 |
||
378 |
typelist = ENTRYDATA.keys() |
|
379 |
typelist.sort() |
|
380 |
||
381 |
return typelist |
|
382 |
Up to file-list src/lib/misc.py:
| … | … | @@ -27,14 +27,18 @@ import time, random, gconf, gtk |
27 |
27 |
|
28 |
28 |
|
29 |
29 |
def escape_markup(string): |
30 |
"Escapes a string so it can be placed in a markup string" |
|
31 |
||
30 |
32 |
string = string.replace("&", "&") |
31 |
33 |
string = string.replace("<", "<") |
32 |
34 |
string = string.replace(">", ">") |
35 |
||
33 |
36 |
return string |
34 |
37 |
|
35 |
38 |
|
36 |
39 |
|
37 |
40 |
def generate_password(): |
41 |
"Generates a random password" |
|
38 |
42 |
|
39 |
43 |
def get_random_item(list): |
40 |
44 |
return list[int(random.random() * len(list))] |
| … | … | @@ -100,6 +104,8 @@ def generate_password(): |
100 |
104 |
|
101 |
105 |
|
102 |
106 |
def timediff_simple(start, end = None): |
107 |
"Returns an approximate time difference in human-readable format" |
|
108 |
||
103 |
109 |
if end is None: |
104 |
110 |
end = time.time() |
105 |
111 |
Up to file-list src/lib/stock.py:
| … | … | @@ -80,6 +80,7 @@ gtk.stock_add(( |
80 |
80 |
|
81 |
81 |
|
82 |
82 |
class IconFactory(gtk.IconFactory): |
83 |
"A stock icon factory" |
|
83 |
84 |
|
84 |
85 |
def __init__(self, widget): |
85 |
86 |
gtk.IconFactory.__init__(self) |
Up to file-list src/lib/ui.py:
26 |
26 |
import gobject, gtk, gtk.gdk, gnome.ui, revelation, time, os, gconf |
27 |
27 |
|
28 |
28 |
|
29 |
class DataView( |
|
29 |
class DataView(revelation.widget.VBox): |
|
30 |
"An UI component for displaying an entry" |
|
30 |
31 |
|
31 |
32 |
def __init__(self): |
32 |
|
|
33 |
revelation.widget.VBox.__init__(self) |
|
33 |
34 |
self.set_spacing(15) |
34 |
35 |
self.set_border_width(10) |
35 |
36 |
|
| … | … | @@ -40,13 +41,20 @@ class DataView(gtk.VBox): |
40 |
41 |
|
41 |
42 |
|
42 |
43 |
def clear(self, force = gtk.FALSE): |
44 |
"Clears the data view" |
|
45 |
||
46 |
# only clear if containing an entry, or if forced |
|
43 |
47 |
if force == gtk.TRUE or self.entry is not None: |
48 |
||
44 |
49 |
self.entry = None |
50 |
||
45 |
51 |
for child in self.get_children(): |
46 |
52 |
child.destroy() |
47 |
53 |
|
48 |
54 |
|
49 |
55 |
def display_entry(self, entry): |
56 |
"Displays an entry" |
|
57 |
||
50 |
58 |
if entry is None: |
51 |
59 |
self.clear() |
52 |
60 |
return |
| … | … | @@ -55,8 +63,7 @@ class DataView(gtk.VBox): |
55 |
63 |
self.entry = entry |
56 |
64 |
|
57 |
65 |
# set up metadata display |
58 |
metabox = gtk.VBox() |
|
59 |
metabox.set_spacing(4) |
|
66 |
metabox = revelation.widget.VBox() |
|
60 |
67 |
self.pack_start(metabox) |
61 |
68 |
|
62 |
69 |
metabox.pack_start(revelation.widget.ImageLabel( |
| … | … | @@ -73,8 +80,7 @@ class DataView(gtk.VBox): |
73 |
80 |
if field.value == "": |
74 |
81 |
continue |
75 |
82 |
|
76 |
row = gtk.HBox() |
|
77 |
row.set_spacing(5) |
|
83 |
row = revelation.widget.HBox() |
|
78 |
84 |
rows.append(row) |
79 |
85 |
|
80 |
86 |
label = revelation.widget.Label("<span weight=\"bold\">" + revelation.misc.escape_markup(field.name) + ":</span>", gtk.JUSTIFY_RIGHT) |
| … | … | @@ -100,7 +106,7 @@ class DataView(gtk.VBox): |
100 |
106 |
|
101 |
107 |
|
102 |
108 |
if len(rows) > 0: |
103 |
fieldlist = |
|
109 |
fieldlist = revelation.widget.VBox() |
|
104 |
110 |
fieldlist.set_spacing(2) |
105 |
111 |
self.pack_start(fieldlist) |
106 |
112 |
|
| … | … | @@ -115,6 +121,8 @@ class DataView(gtk.VBox): |
115 |
121 |
|
116 |
122 |
|
117 |
123 |
def display_info(self): |
124 |
"Displays info about the application" |
|
125 |
||
118 |
126 |
self.clear(gtk.TRUE) |
119 |
127 |
|
120 |
128 |
self.pack_start(revelation.widget.ImageLabel( |
| … | … | @@ -133,13 +141,16 @@ class DataView(gtk.VBox): |
133 |
141 |
|
134 |
142 |
|
135 |
143 |
def pack_start(self, widget): |
144 |
"Adds a widget to the data view" |
|
145 |
||
136 |
146 |
alignment = gtk.Alignment(0.5, 0.5, 0, 0) |
137 |
147 |
alignment.add(widget) |
138 |
|
|
148 |
revelation.widget.VBox.pack_start(self, alignment) |
|
139 |
149 |
|
140 |
150 |
|
141 |
151 |
|
142 |
152 |
class Tree(revelation.widget.TreeView): |
153 |
"The entry tree" |
|
143 |
154 |
|
144 |
155 |
def __init__(self, datastore = None): |
145 |
156 |
revelation.widget.TreeView.__init__(self, datastore) |
| … | … | @@ -161,12 +172,18 @@ class Tree(revelation.widget.TreeView): |
161 |
172 |
|
162 |
173 |
|
163 |
174 |
def __cb_row_collapsed(self, object, iter, extra): |
175 |
"Updates folder icons when collapsed" |
|
176 |
||
164 |
177 |
self.model.set_folder_state(iter, gtk.FALSE) |
165 |
178 |
|
166 |
179 |
|
167 |
180 |
def __cb_row_expanded(self, object, iter, extra): |
181 |
"Updates folder icons when expanded" |
|
182 |
||
183 |
# make sure all children are collapsed (some may have lingering expand icons) |
|
168 |
184 |
for i in range(self.model.iter_n_children(iter)): |
169 |
185 |
child = self.model.iter_nth_child(iter, i) |
186 |
||
170 |
187 |
if self.row_expanded(self.model.get_path(child)) == gtk.FALSE: |
171 |
188 |
self.model.set_folder_state(child, gtk.FALSE) |
172 |
189 |
|
| … | … | @@ -174,6 +191,8 @@ class Tree(revelation.widget.TreeView): |
174 |
191 |
|
175 |
192 |
|
176 |
193 |
def set_model(self, model): |
194 |
"Sets the model displayed by the tree view" |
|
195 |
||
177 |
196 |
revelation.widget.TreeView.set_model(self, model) |
178 |
197 |
|
179 |
198 |
if model is not None: |
Up to file-list src/lib/widget.py:
26 |
26 |
import gobject, gtk, gtk.gdk, gnome.ui, revelation, os.path, gconf |
27 |
27 |
|
28 |
28 |
|
29 |
# first, some simple subclasses - replacements for gtk widgets |
|
30 |
class App(gnome.ui.App): |
|
29 |
# simple subclasses for gtk widgets |
|
31 |
30 |
|
32 |
def __init__(self, appname): |
|
33 |
gnome.ui.App.__init__(self, appname, appname) |
|
34 |
self.appname = appname |
|
31 |
class Button(gtk.Button): |
|
32 |
"A normal button" |
|
35 |
33 |
|
36 |
self.toolbar = Toolbar() |
|
37 |
self.toolbar.connect("hide", self.__cb_toolbar_hide) |
|
38 |
self.toolbar.connect("show", self.__cb_toolbar_show) |
|
39 |
self.set_toolbar(self.toolbar) |
|
34 |
def __init__(self, label, callback = None): |
|
35 |
gtk.Button.__init__(self, label) |
|
40 |
36 |
|
41 |
self.statusbar = gnome.ui.AppBar(gtk.FALSE, gtk.TRUE, gnome.ui.PREFERENCES_USER) |
|
42 |
self.set_statusbar(self.statusbar) |
|
43 |
||
44 |
self.accelgroup = gtk.AccelGroup() |
|
45 |
self.add_accel_group(self.accelgroup) |
|
46 |
||
47 |
||
48 |
def __cb_toolbar_hide(self, object, data = None): |
|
49 |
self.get_dock_item_by_name("Toolbar").hide() |
|
50 |
||
51 |
||
52 |
def __cb_toolbar_show(self, object, data = None): |
|
53 |
self.get_dock_item_by_name("Toolbar").show() |
|
54 |
||
55 |
||
56 |
def __cb_menudesc(self, object, item, show): |
|
57 |
if show: |
|
58 |
self.statusbar.set_status(item.get_data("description")) |
|
59 |
else: |
|
60 |
self.statusbar.set_status("") |
|
61 |
||
62 |
||
63 |
def __create_itemfactory(self, widget, accelgroup, items): |
|
64 |
itemfactory = MenuFactory(widget, accelgroup) |
|
65 |
itemfactory.create_items(items) |
|
66 |
itemfactory.connect("item-selected", self.__cb_menudesc, gtk.TRUE) |
|
67 |
itemfactory.connect("item-deselected", self.__cb_menudesc, gtk.FALSE) |
|
68 |
return itemfactory |
|
69 |
||
70 |
||
71 |
def create_menu(self, menuitems): |
|
72 |
self.if_menu = self.__create_itemfactory(gtk.MenuBar, self.accelgroup, menuitems) |
|
73 |
self.set_menus(self.if_menu.get_widget("<main>")) |
|
74 |
||
75 |
||
76 |
def popup(self, menuitems, x, y, button, time): |
|
77 |
itemfactory = self.__create_itemfactory(gtk.Menu, self.accelgroup, menuitems) |
|
78 |
itemfactory.popup(x, y, button, time) |
|
79 |
||
80 |
||
81 |
def run(self): |
|
82 |
self.show_all() |
|
83 |
gtk.main() |
|
84 |
||
85 |
||
86 |
def set_title(self, title): |
|
87 |
|
|
37 |
if callback is not None: |
|
38 |
self.connect("clicked", callback) |
|
88 |
39 |
|
89 |
40 |
|
90 |
41 |
|
91 |
42 |
class CheckButton(gtk.CheckButton): |
43 |
"A checkbutton" |
|
92 |
44 |
|
93 |
45 |
def __init__(self, label = None): |
94 |
46 |
gtk.CheckButton.__init__(self, label) |
| … | … | @@ -96,14 +48,18 @@ class CheckButton(gtk.CheckButton): |
96 |
48 |
|
97 |
49 |
|
98 |
50 |
class Entry(gtk.Entry): |
51 |
"An input entry" |
|
99 |
52 |
|
100 |
53 |
def __init__(self, text = None): |
101 |
54 |
gtk.Entry.__init__(self) |
55 |
||
102 |
56 |
self.set_activates_default(gtk.TRUE) |
103 |
57 |
self.set_text(text) |
104 |
58 |
|
105 |
59 |
|
106 |
60 |
def set_text(self, text): |
61 |
"Sets the entry contents" |
|
62 |
||
107 |
63 |
if text == None: |
108 |
64 |
text = "" |
109 |
65 |
|
| … | … | @@ -111,49 +67,8 @@ class Entry(gtk.Entry): |
111 |
67 |
|
112 |
68 |
|
113 |
69 |
|
114 |
class FileEntry(gtk.HBox): |
|
115 |
||
116 |
def __init__(self, title, filename = None): |
|
117 |
gtk.HBox.__init__(self) |
|
118 |
self.set_spacing(5) |
|
119 |
self.title = title |
|
120 |
||
121 |
self.entry = Entry() |
|
122 |
self.pack_start(self.entry) |
|
123 |
||
124 |
self.button = gtk.Button("Browse...") |
|
125 |
self.button.connect("clicked", self.__cb_filesel) |
|
126 |
self.pack_start(self.button, gtk.FALSE, gtk.FALSE) |
|
127 |
||
128 |
if filename is not None: |
|
129 |
self.set_filename(filename) |
|
130 |
||
131 |
||
132 |
def __cb_filesel(self, object, data = None): |
|
133 |
fsel = gtk.FileSelection(self.title) |
|
134 |
fsel.set_modal(gtk.TRUE) |
|
135 |
fsel.set_filename(self.get_filename()) |
|
136 |
||
137 |
fsel.show_all() |
|
138 |
response = fsel.run() |
|
139 |
||
140 |
if response == gtk.RESPONSE_OK: |
|
141 |
self.set_filename(fsel.get_filename()) |
|
142 |
||
143 |
fsel.destroy() |
|
144 |
||
145 |
||
146 |
def get_filename(self): |
|
147 |
return self.entry.get_text() |
|
148 |
||
149 |
||
150 |
def set_filename(self, filename): |
|
151 |
self.entry.set_text(os.path.normpath(filename)) |
|
152 |
self.entry.set_position(-1) |
|
153 |
||
154 |
||
155 |
||
156 |
70 |
class HPaned(gtk.HPaned): |
71 |
"Horizontal pane" |
|
157 |
72 |
|
158 |
73 |
def __init__(self, content_left = None, content_right = None, position = None): |
159 |
74 |
gtk.HPaned.__init__(self) |
| … | … | @@ -170,7 +85,21 @@ class HPaned(gtk.HPaned): |
170 |
85 |
|
171 |
86 |
|
172 |
87 |
|
88 |
class HBox(gtk.HBox): |
|
89 |
"A horizontal container" |
|
90 |
||
91 |
def __init__(self, *args): |
|
92 |
gtk.HBox.__init__(self) |
|
93 |
self.set_spacing(6) |
|
94 |
self.set_border_width(0) |
|
95 |
||
96 |
for widget in args: |
|
97 |
self.pack_start(widget) |
|
98 |
||
99 |
||
100 |
||
173 |
101 |
class HRef(gnome.ui.HRef): |
102 |
"A button containing a link" |
|
174 |
103 |
|
175 |
104 |
def __init__(self, url, text): |
176 |
105 |
gnome.ui.HRef.__init__(self, url, text) |
| … | … | @@ -179,6 +108,7 @@ class HRef(gnome.ui.HRef): |
179 |
108 |
|
180 |
109 |
|
181 |
110 |
class Image(gtk.Image): |
111 |
"A widget for displaying an image" |
|
182 |
112 |
|
183 |
113 |
def __init__(self, stock = None, size = None): |
184 |
114 |
gtk.Image.__init__(self) |
| … | … | @@ -189,6 +119,7 @@ class Image(gtk.Image): |
189 |
119 |
|
190 |
120 |
|
191 |
121 |
class ImageMenuItem(gtk.ImageMenuItem): |
122 |
"A menuitem with a stock icon" |
|
192 |
123 |
|
193 |
124 |
def __init__(self, stock, text = None): |
194 |
125 |
gtk.ImageMenuItem.__init__(self, stock) |
| … | … | @@ -199,24 +130,30 @@ class ImageMenuItem(gtk.ImageMenuItem): |
199 |
130 |
if text is not None: |
200 |
131 |
self.set_text(text) |
201 |
132 |
|
133 |
||
202 |
134 |
def set_stock(self, stock): |
135 |
"Set the stock item to use as icon" |
|
136 |
||
203 |
137 |
self.image.set_from_stock(stock, gtk.ICON_SIZE_MENU) |
204 |
138 |
|
139 |
||
205 |
140 |
def set_text(self, text): |
141 |
"Set the item text" |
|
142 |
||
206 |
143 |
self.label.set_text(text) |
207 |
144 |
|
208 |
145 |
|
209 |
146 |
|
210 |
147 |
class Label(gtk.Label): |
148 |
"A text label" |
|
211 |
149 |
|
212 |
150 |
def __init__(self, text = None, justify = gtk.JUSTIFY_LEFT): |
213 |
151 |
gtk.Label.__init__(self) |
214 |
152 |
|
153 |
self.set_text(text) |
|
154 |
self.set_justify(justify) |
|
215 |
155 |
self.set_use_markup(gtk.TRUE) |
216 |
156 |
self.set_line_wrap(gtk.TRUE) |
217 |
self.set_text(text) |
|
218 |
||
219 |
self.set_justify(justify) |
|
220 |
157 |
|
221 |
158 |
if justify == gtk.JUSTIFY_LEFT: |
222 |
159 |
self.set_alignment(0, 0.5) |
| … | … | @@ -227,48 +164,17 @@ class Label(gtk.Label): |
227 |
164 |
elif justify == gtk.JUSTIFY_RIGHT: |
228 |
165 |
self.set_alignment(1, 0.5) |
229 |
166 |
|
167 |
||
230 |
168 |
def set_text(self, text): |
169 |
"Sets the text of the label" |
|
170 |
||
231 |
171 |
if text is not None: |
232 |
172 |
gtk.Label.set_markup(self, text) |
233 |
173 |
|
234 |
174 |
|
235 |
175 |
|
236 |
class MenuFactory(gtk.ItemFactory): |
|
237 |
||
238 |
def __init__(self, widget, accelgroup): |
|
239 |
gtk.ItemFactory.__init__(self, widget, "<main>", accelgroup) |
|
240 |
||
241 |
def __cb_select(self, object): |
|
242 |
self.emit("item-selected", object) |
|
243 |
||
244 |
def __cb_deselect(self, object): |
|
245 |
self.emit("item-deselected", object) |
|
246 |
||
247 |
||
248 |
def create_items(self, items): |
|
249 |
||
250 |
# strip description from items, and create the items |
|
251 |
ifitems = [] |
|
252 |
for item in items: |
|
253 |
ifitems.append(item[0:2] + item[3:]) |
|
254 |
||
255 |
gtk.ItemFactory.create_items(self, ifitems) |
|
256 |
||
257 |
# set up description for items |
|
258 |
for item in items: |
|
259 |
if item[5] in ["<Item>", "<StockItem>", "<CheckItem>"]: |
|
260 |
widget = self.get_widget("<main>" + item[0].replace("_", "")) |
|
261 |
widget.set_data("description", item[2]) |
|
262 |
widget.connect("select", self.__cb_select) |
|
263 |
widget.connect("deselect", self.__cb_deselect) |
|
264 |
||
265 |
||
266 |
gobject.signal_new("item-selected", MenuFactory, gobject.SIGNAL_ACTION, gobject.TYPE_BOOLEAN, (gtk.MenuItem,)) |
|
267 |
gobject.signal_new("item-deselected", MenuFactory, gobject.SIGNAL_ACTION, gobject.TYPE_BOOLEAN, (gtk.MenuItem,)) |
|
268 |
||
269 |
||
270 |
||
271 |
176 |
class OptionMenu(gtk.OptionMenu): |
177 |
"An option menu (dropdown)" |
|
272 |
178 |
|
273 |
179 |
def __init__(self, menu = None): |
274 |
180 |
gtk.OptionMenu.__init__(self) |
| … | … | @@ -278,35 +184,59 @@ class OptionMenu(gtk.OptionMenu): |
278 |
184 |
|
279 |
185 |
self.set_menu(menu) |
280 |
186 |
|
187 |
||
281 |
188 |
def append_item(self, item): |
189 |
"Appends an item to the dropdown menu" |
|
190 |
||
282 |
191 |
self.menu.append(item) |
192 |
||
283 |
193 |
if len(self.menu.get_children()) == 1: |
284 |
194 |
self.set_history(0) |
285 |
195 |
|
196 |
||
286 |
197 |
def get_active_item(self): |
198 |
"Returns the currently active menu item" |
|
199 |
||
287 |
200 |
return self.menu.get_children()[self.get_history()] |
288 |
201 |
|
202 |
||
289 |
203 |
def get_item(self, index): |
204 |
"Get an item from the menu" |
|
205 |
||
290 |
206 |
items = self.menu.get_children() |
291 |
return index < len(items) and items[index] or None |
|
207 |
||
208 |
if index > len(items): |
|
209 |
return items[index] |
|
210 |
||
211 |
else: |
|
212 |
return None |
|
213 |
||
292 |
214 |
|
293 |
215 |
def set_active_item(self, activeitem): |
216 |
"Set a menu item as the currently active item" |
|
217 |
||
294 |
218 |
items = self.get_menu().get_children() |
295 |
219 |
|
296 |
220 |
for i, item in zip(range(len(items)), items): |
297 |
221 |
if activeitem == item: |
298 |
222 |
self.set_history(i) |
299 |
223 |
|
224 |
||
300 |
225 |
def set_menu(self, menu): |
226 |
"Set the menu of the dropdown" |
|
227 |
||
301 |
228 |
self.menu = menu |
302 |
229 |
gtk.OptionMenu.set_menu(self, menu) |
303 |
230 |
|
304 |
231 |
|
305 |
232 |
|
233 |
||
306 |
234 |
class ScrolledWindow(gtk.ScrolledWindow): |
235 |
"A scrolled window which partially displays a different widget" |
|
307 |
236 |
|
308 |
237 |
def __init__(self, contents = None): |
309 |
238 |
gtk.ScrolledWindow.__init__(self) |
239 |
||
310 |
240 |
self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) |
311 |
241 |
|
312 |
242 |
if contents is not None: |
| … | … | @@ -315,14 +245,39 @@ class ScrolledWindow(gtk.ScrolledWindow) |
315 |
245 |
|
316 |
246 |
|
317 |
247 |
class SpinButton(gtk.SpinButton): |
248 |
"An entry for numbers" |
|
318 |
249 |
|
319 |
250 |
def __init__(self, adjustment = None, climb_rate = 0.0, digits = 0): |
320 |
251 |
gtk.SpinButton.__init__(self, adjustment, climb_rate, digits) |
252 |
||
321 |
253 |
self.set_increments(1, 1) |
322 |
254 |
self.set_numeric(gtk.TRUE) |
323 |
255 |
|
324 |
256 |
|
325 |
257 |
|
258 |
class Statusbar(gtk.Statusbar): |
|
259 |
"A window statusbar" |
|
260 |
||
261 |
def __init__(self): |
|
262 |
gtk.Statusbar.__init__(self) |
|
263 |
||
264 |
self.contextid = self.get_context_id("statusbar") |
|
265 |
||
266 |
||
267 |
def clear(self): |
|
268 |
"Clears the statusbar" |
|
269 |
||
270 |
self.pop(self.contextid) |
|
271 |
||
272 |
||
273 |
def set_status(self, text): |
|
274 |
"Displays a text in the statusbar" |
|
275 |
||
276 |
self.clear() |
|
277 |
self.push(self.contextid, text) |
|
278 |
||
279 |
||
280 |
||
326 |
281 |
class TreeStore(gtk.TreeStore): |
327 |
282 |
"An enhanced gtk.TreeStore" |
328 |
283 |
|
| … | … | @@ -428,6 +383,7 @@ class TreeStore(gtk.TreeStore): |
428 |
383 |
|
429 |
384 |
|
430 |
385 |
class TreeView(gtk.TreeView): |
386 |
"A widget for displaying a tree" |
|
431 |
387 |
|
432 |
388 |
def __init__(self, model = None): |
433 |
389 |
gtk.TreeView.__init__(self, model) |
| … | … | @@ -442,6 +398,9 @@ class TreeView(gtk.TreeView): |
442 |
398 |
|
443 |
399 |
|
444 |
400 |
def __cb_buttonpress(self, object, data): |
401 |
"Callback for handling mouse clicks" |
|
402 |
||
403 |
# handle doubleclick |
|
445 |
404 |
if data.button == 1 and data.type == gtk.gdk._2BUTTON_PRESS: |
446 |
405 |
path = self.get_path_at_pos(int(data.x), int(data.y)) |
447 |
406 |
|
| … | … | @@ -452,70 +411,98 @@ class TreeView(gtk.TreeView): |
452 |
411 |
|
453 |
412 |
|
454 |
413 |
def __cb_keypress(self, object, data): |
414 |
"Callback for handling key presses" |
|
415 |
||
416 |
# expand/collapse an item when space is pressed |
|
455 |
417 |
if data.keyval == 32: |
456 |
418 |
self.toggle_expanded(self.get_active()) |
457 |
419 |
|
458 |
420 |
|
459 |
421 |
def collapse_row(self, iter): |
422 |
"Collapse a tree row" |
|
423 |
||
460 |
424 |
gtk.TreeView.collapse_row(self, self.model.get_path(iter)) |
461 |
425 |
|
462 |
426 |
|
463 |
427 |
def expand_row(self, iter): |
428 |
"Expand a tree row" |
|
429 |
||
464 |
430 |
if iter is not None and self.model.iter_n_children(iter) > 0: |
465 |
431 |
gtk.TreeView.expand_row(self, self.model.get_path(iter), gtk.FALSE) |
466 |
432 |
|
467 |
433 |
|
468 |
434 |
def expand_to_iter(self, iter): |
435 |
"Expand all items up to and including a given iter" |
|
436 |
||
469 |
437 |
path = self.model.get_path(iter) |
438 |
||
470 |
439 |
for i in range(len(path)): |
471 |
440 |
iter = self.model.get_iter(path[0:i]) |
472 |
441 |
self.expand_row(iter) |
473 |
442 |
|
474 |
443 |
|
475 |
444 |
def get_active(self): |
445 |
"Get the currently active row" |
|
446 |
||
476 |
447 |
iter = self.model.get_iter(self.get_cursor()[0]) |
477 |
448 |
|
478 |
if iter |
|
449 |
if iter is None or self.selection.iter_is_selected(iter) == gtk.FALSE: |
|
479 |
450 |
return None |
480 |
451 |
|
481 |
452 |
return iter |
482 |
453 |
|
483 |
454 |
|
484 |
455 |
def get_selected(self): |
456 |
"Get a list of currently selected rows" |
|
457 |
||
485 |
458 |
list = [] |
486 |
459 |
self.selection.selected_foreach(lambda model, path, iter: list.append(iter)) |
460 |
||
487 |
461 |
return list |
488 |
462 |
|
489 |
463 |
|
490 |
464 |
def select(self, iter): |
465 |
"Select a particular row" |
|
466 |
||
491 |
467 |
if iter == None: |
492 |
468 |
self.unselect_all() |
469 |
||
493 |
470 |
else: |
494 |
471 |
self.expand_to_iter(iter) |
495 |
472 |
self.set_cursor(self.model.get_path(iter)) |
496 |
473 |
|
497 |
474 |
|
498 |
475 |
def select_all(self): |
476 |
"Select all rows in the tree" |
|
477 |
||
499 |
478 |
self.selection.select_all() |
500 |
479 |
self.selection.emit("changed") |
501 |
480 |
self.emit("cursor_changed") |
502 |
481 |
|
503 |
482 |
|
504 |
483 |
def set_model(self, model): |
484 |
"Change the tree model which is being displayed" |
|
485 |
||
505 |
486 |
gtk.TreeView.set_model(self, model) |
506 |
487 |
self.model = model |
507 |
488 |
|
508 |
489 |
|
509 |
490 |
def toggle_expanded(self, iter): |
510 |
|
|
491 |
"Toggle the expanded state of a row" |
|
492 |
||
493 |
if iter is None: |
|
511 |
494 |
return |
495 |
||
512 |
496 |
elif self.row_expanded(self.model.get_path(iter)): |
513 |
497 |
self.collapse_row(iter) |
498 |
||
514 |
499 |
else: |
515 |
500 |
self.expand_row(iter) |
516 |
501 |
|
517 |
502 |
|
518 |
503 |
def unselect_all(self): |
504 |
"Unselect all rows in the tree" |
|
505 |
||
519 |
506 |
self.selection.unselect_all() |
520 |
507 |
self.selection.emit("changed") |
521 |
508 |
self.emit("cursor_changed") |
| … | … | @@ -538,14 +525,108 @@ class Toolbar(gtk.Toolbar): |
538 |
525 |
return self.insert_stock(stock, tooltip, None, callback, "", -1) |
539 |
526 |
|
540 |
527 |
|
528 |
||
529 |
class VBox(gtk.VBox): |
|
530 |
"A vertical container" |
|
531 |
||
532 |
def __init__(self, *args): |
|
533 |
gtk.VBox.__init__(self) |
|
534 |
self.set_spacing(6) |
|
535 |
self.set_border_width(0) |
|
536 |
||
537 |
for widget in args: |
|
538 |
self.pack_start(widget) |
|
539 |
||
540 |
||
541 |
||
541 |
542 |
# more extensive custom widgets |
543 |
||
544 |
class App(gnome.ui.App): |
|
545 |
"An application window" |
|
546 |
||
547 |
def __init__(self, appname): |
|
548 |
gnome.ui.App.__init__(self, appname, appname) |
|
549 |
self.appname = appname |
|
550 |
||
551 |
self.toolbar = Toolbar() |
|
552 |
self.set_toolbar(self.toolbar) |
|
553 |
self.toolbar.connect("hide", self.__cb_toolbar_hide) |
|
554 |
self.toolbar.connect("show", self.__cb_toolbar_show) |
|
555 |
||
556 |
self.statusbar = Statusbar() |
|
557 |
self.set_statusbar(self.statusbar) |
|
558 |
||
559 |
self.accelgroup = gtk.AccelGroup() |
|
560 |
self.add_accel_group(self.accelgroup) |
|
561 |
||
562 |
||
563 |
def __cb_toolbar_hide(self, object, data = None): |
|
564 |
"Hides the toolbar dock when the toolbar is hidden" |
|
565 |
||
566 |
self.get_dock_item_by_name("Toolbar").hide() |
|
567 |
||
568 |
||
569 |
def __cb_toolbar_show(self, object, data = None): |
|
570 |
"Shows the toolbar dock when the toolbar is hidden" |
|
571 |
||
572 |
self.get_dock_item_by_name("Toolbar").show() |
|
573 |
||
574 |
||
575 |
def __cb_menudesc(self, object, item, show): |
|
576 |
"Displays menu descriptions in the statusbar" |
|
577 |
||
578 |
if show: |
|
579 |
self.statusbar.set_status(item.get_data("description")) |
|
580 |
else: |
|
581 |
self.statusbar.set_status("") |
|
582 |
||
583 |
||
584 |
def __create_itemfactory(self, widget, accelgroup, items): |
|
585 |
"Creates an item factory" |
|
586 |
||
587 |
itemfactory = MenuFactory(widget, accelgroup) |
|
588 |
itemfactory.create_items(items) |
|
589 |
itemfactory.connect("item-selected", self.__cb_menudesc, gtk.TRUE) |
|
590 |
itemfactory.connect("item-deselected", self.__cb_menudesc, gtk.FALSE) |
|
591 |
||
592 |
return itemfactory |
|
593 |
||
594 |
||
595 |
def create_menu(self, menuitems): |
|
596 |
"Creates an application menu" |
|
597 |
||
598 |
self.if_menu = self.__create_itemfactory(gtk.MenuBar, self.accelgroup, menuitems) |
|
599 |
self.set_menus(self.if_menu.get_widget("<main>")) |
|
600 |
||
601 |
||
602 |
def popup(self, menuitems, x, y, button, time): |
|
603 |
"Displays a popup menu" |
|
604 |
||
605 |
itemfactory = self.__create_itemfactory(gtk.Menu, self.accelgroup, menuitems) |
|
606 |
itemfactory.popup(x, y, button, time) |
|
607 |
||
608 |
||
609 |
def run(self): |
|
610 |
"Runs the application" |
|
611 |
||
612 |
self.show_all() |
|
613 |
gtk.main() |
|
614 |
||
615 |
||
616 |
def set_title(self, title): |
|
617 |
"Sets the window title" |
|
618 |
||
619 |
gnome.ui.App.set_title(self, title + " - " + self.appname) |
|
620 |
||
621 |
||
622 |
||
542 |
623 |
class EntryDropdown(OptionMenu): |
624 |
"A dropdown menu with all available entry types" |
|
543 |
625 |
|
544 |
626 |
def __init__(self): |
545 |
627 |
revelation.widget.OptionMenu.__init__(self) |
546 |
628 |
|
547 |
typelist = revelation.entry.ENTRYDATA.keys() |
|
548 |
typelist.sort() |
|
629 |
typelist = revelation.entry.get_entry_list() |
|
549 |
630 |
typelist.remove(revelation.entry.ENTRY_FOLDER) |
550 |
631 |
typelist.insert(0, revelation.entry.ENTRY_FOLDER) |
551 |
632 |
|
| … | … | @@ -559,24 +640,83 @@ class EntryDropdown(OptionMenu): |
559 |
640 |
|
560 |
641 |
|
561 |
642 |
def get_type(self): |
562 |
item = self.get_active_item() |
|
563 |
return hasattr(item, "type") and item.type or None |
|
643 |
"Get the currently active type" |
|
644 |
||
645 |
try: |
|
646 |
return self.get_active_item().type |
|
647 |
||
648 |
except AttributeError: |
|
649 |
return None |
|
564 |
650 |
|
565 |
651 |
|
566 |
652 |
def set_type(self, type): |
653 |
"Set the active type" |
|
654 |
||
567 |
655 |
for item in self.get_menu().get_children(): |
568 |
if hasattr(item, "type") and item.type == type: |
|
569 |
self.set_active_item(item) |
|
656 |
||
657 |
try: |
|
658 |
if item.type == type: |
|
659 |
self.set_active_item(item) |
|
660 |
||
661 |
except AttributeError: |
|
662 |
pass |
|
663 |
||
664 |
||
665 |
||
666 |
class FileEntry(HBox): |
|
667 |
"An entry for file names with a Browse button" |
|
668 |
||
669 |
def __init__(self, title, filename = None): |
|
670 |
HBox.__init__(self) |
|
671 |
self.title = title |
|
672 |
||
673 |
self.entry = Entry() |
|
674 |
self.pack_start(self.entry) |
|
675 |
||
676 |
self.button = gtk.Button("Browse...", self.__cb_filesel) |
|
677 |
self.pack_start(self.button, gtk.FALSE, gtk.FALSE) |
|
678 |
||
679 |
if filename is not None: |
|
680 |
self.set_filename(filename) |
|
681 |
||
682 |
||
683 |
def __cb_filesel(self, object, data = None): |
|
684 |
"Displays a file selector when Browse is pressed" |
|
685 |
||
686 |
fsel = gtk.FileSelection(self.title) |
|
687 |
fsel.set_modal(gtk.TRUE) |
|
688 |
fsel.set_filename(self.get_filename()) |
|
689 |
||
690 |
fsel.show_all() |
|
691 |
response = fsel.run() |
|
692 |
||
693 |
if response == gtk.RESPONSE_OK: |
|
694 |
self.set_filename(fsel.get_filename()) |
|
695 |
||
696 |
fsel.destroy() |
|
697 |
||
698 |
||
699 |
def get_filename(self): |
|
700 |
"Gets the current filename" |
|
701 |
||
702 |
return self.entry.get_text() |
|
703 |
||
704 |
||
705 |
def set_filename(self, filename): |
|
706 |
"Sets the filename of the entry" |
|
707 |
||
708 |
self.entry.set_text(os.path.normpath(filename)) |
|
709 |
self.entry.set_position(-1) |
|
570 |
710 |
|
571 |
711 |
|
572 |
712 |
|
573 |
713 |
class ImageLabel(gtk.Alignment): |
714 |
"A label with an image" |
|
574 |
715 |
|
575 |
716 |
def __init__(self, stock, size, text): |
576 |
717 |
gtk.Alignment.__init__(self, 0.5, 0.5, 0, 0) |
577 |
718 |
|
578 |
self.hbox = gtk.HBox() |
|
579 |
self.hbox.set_spacing(5) |
|
719 |
self.hbox = HBox() |
|
580 |
720 |
self.add(self.hbox) |
581 |
721 |
|
582 |
722 |
self.image = Image() |
| … | … | @@ -586,24 +726,34 @@ class ImageLabel(gtk.Alignment): |
586 |
726 |
self.label = Label(text, gtk.JUSTIFY_CENTER) |
587 |
727 |
self.hbox.pack_start(self.label) |
588 |
728 |
|
729 |
||
589 |
730 |
def set_stock(self, stock, size): |
731 |
"Sets the label icon" |
|
732 |
||
590 |
733 |
self.image.set_from_stock(stock, size) |
591 |
734 |
|
735 |
||
592 |
736 |
def set_text(self, text): |
737 |
"Sets the label text" |
|
738 |
||
593 |
739 |
self.label.set_text(text) |
594 |
740 |
|
595 |
741 |
|
596 |
742 |
|
597 |
class InputSection( |
|
743 |
class InputSection(VBox): |
|
744 |
"A section of input fields" |
|
598 |
745 |
|
599 |
746 |
def __init__(self, title = None, sizegroup = None, description = None): |
600 |
gtk.VBox.__init__(self) |
|
601 |
self.set_border_width(0) |
|
602 |
|
|
747 |
VBox.__init__(self) |
|
603 |
748 |
|
604 |
749 |
self.title = None |
605 |
750 |
self.description = None |
606 |
self.sizegroup = sizegroup is not None and sizegroup or gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) |
|
751 |
||
752 |
if sizegroup is None: |
|
753 |
self.sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) |
|
754 |
||
755 |
else: |
|
756 |
self.sizegroup = sizegroup |
|
607 |
757 |
|
608 |
758 |
if title is not None: |
609 |
759 |
self.title = Label("<span weight=\"bold\">" + revelation.misc.escape_markup(title) + "</span>") |
| … | … | @@ -615,8 +765,9 @@ class InputSection(gtk.VBox): |
615 |
765 |
|
616 |
766 |
|
617 |
767 |
def add_inputrow(self, title, widget): |
618 |
row = gtk.HBox() |
|
619 |
row.set_spacing(6) |
|
768 |
"Adds an input row to the section" |
|
769 |
||
770 |
row = HBox() |
|
620 |
771 |
self.pack_start(row) |
621 |
772 |
|
622 |
773 |
if self.title is not None: |
| … | … | @@ -633,12 +784,57 @@ class InputSection(gtk.VBox): |
633 |
784 |
|
634 |
785 |
|
635 |
786 |
def clear(self): |
787 |
"Clears the input section" |
|
788 |
||
636 |
789 |
for child in self.get_children(): |
637 |
790 |
if child not in [ self.label, self.description ]: |
638 |
791 |
child.destroy() |
639 |
792 |
|
640 |
793 |
|
641 |
794 |
|
795 |
class MenuFactory(gtk.ItemFactory): |
|
796 |
"A factory for menus" |
|
797 |
||
798 |
def __init__(self, widget, accelgroup): |
|
799 |
gtk.ItemFactory.__init__(self, widget, "<main>", accelgroup) |
|
800 |
||
801 |
||
802 |
def __cb_select(self, object): |
|
803 |
"Emits the item-selected signal when a menu item is selected" |
|
804 |
||
805 |
self.emit("item-selected", object) |
|
806 |
||
807 |
||
808 |
def __cb_deselect(self, object): |
|
809 |
"Emits the item-deselected signal when a menu item is deselected" |
|
810 |
||
811 |
self.emit("item-deselected", object) |
|
812 |
||
813 |
||
814 |
def create_items(self, items): |
|
815 |
"Create a menu from a data structure" |
|
816 |
||
817 |
# strip description from items, and create the items |
|
818 |
ifitems = [] |
|
819 |
for item in items: |
|
820 |
ifitems.append(item[0:2] + item[3:]) |
|
821 |
||
822 |
gtk.ItemFactory.create_items(self, ifitems) |
|
823 |
||
824 |
# set up description for items |
|
825 |
for item in items: |
|
826 |
if item[5] in ["<Item>", "<StockItem>", "<CheckItem>"]: |
|
827 |
widget = self.get_widget("<main>" + item[0].replace("_", "")) |
|
828 |
widget.set_data("description", item[2]) |
|
829 |
widget.connect("select", self.__cb_select) |
|
830 |
widget.connect("deselect", self.__cb_deselect) |
|
831 |
||
832 |
||
833 |
gobject.signal_new("item-selected", MenuFactory, gobject.SIGNAL_ACTION, gobject.TYPE_BOOLEAN, (gtk.MenuItem,)) |
|
834 |
gobject.signal_new("item-deselected", MenuFactory, gobject.SIGNAL_ACTION, gobject.TYPE_BOOLEAN, (gtk.MenuItem,)) |
|
835 |
||
836 |
||
837 |
||
642 |
838 |
class PasswordEntry(Entry): |
643 |
839 |
"An entry which edits a password (follows the 'show passwords' preference" |
644 |
840 |
Up to file-list src/revelation:
| … | … | @@ -383,7 +383,7 @@ class Revelation(revelation.widget.App): |
383 |
383 |
if self.file is None or self.password is None: |
384 |
384 |
return |
385 |
385 |
|
386 |
if not self.c |
|
386 |
if not self.config.get("file/autosave"): |
|
387 |
387 |
return |
388 |
388 |
|
389 |
389 |
self.file_save(self.file, self.password) |
| … | … | @@ -517,6 +517,8 @@ class Revelation(revelation.widget.App): |
517 |
517 |
|
518 |
518 |
# public methods |
519 |
519 |
def change_password(self): |
520 |
"Changes the password of the current data file" |
|
521 |
||
520 |
522 |
try: |
521 |
523 |
dialog = revelation.dialog.Password( |
522 |
524 |
self, "Enter new password", |
| … | … | @@ -544,11 +546,15 @@ class Revelation(revelation.widget.App): |
544 |
546 |
|
545 |
547 |
|
546 |
548 |
def clip_copy(self): |
549 |
"Copies selected entries to the clipboard" |
|
550 |
||
547 |
551 |
iters = self.data.filter_parents(self.tree.get_selected()) |
548 |
552 |
self.clipboard.copy(self.data, iters) |
549 |
553 |
|
550 |
554 |
|
551 |
555 |
def clip_cut(self): |
556 |
"Cuts selected entries to the clipboard" |
|
557 |
||
552 |
558 |
iters = self.data.filter_parents(self.tree.get_selected()) |
553 |
559 |
self.undoqueue.add_action(revelation.data.UNDO_ACTION_CUT, iters) |
554 |
560 |
self.clipboard.cut(self.data, iters) |
| … | … | @@ -556,6 +562,8 @@ class Revelation(revelation.widget.App): |
556 |
562 |
|
557 |
563 |
|
558 |
564 |
def clip_paste(self): |
565 |
"Pastes entries from the clipboard" |
|
566 |
||
559 |
567 |
if not self.clipboard.has_contents(): |
560 |
568 |
return |
561 |
569 |
|
| … | … | @@ -565,6 +573,8 @@ class Revelation(revelation.widget.App): |
565 |
573 |
|
566 |
574 |
|
567 |
575 |
def entry_add(self): |
576 |
"Adds an entry" |
|
577 |
||
568 |
578 |
try: |
569 |
579 |
entry = revelation.dialog.EditEntry(self, "Add entry").run() |
570 |
580 |
iter = self.data.add_entry(self.tree.get_active(), entry) |
| … | … | @@ -579,6 +589,8 @@ class Revelation(revelation.widget.App): |
579 |
589 |
|
580 |
590 |
|
581 |
591 |
def entry_edit(self): |
592 |
"Edits an entry" |
|
593 |
||
582 |
594 |
iter = self.tree.get_active() |
583 |
595 |
|
584 |
596 |
if iter is None: |
| … | … | @@ -603,6 +615,8 @@ class Revelation(revelation.widget.App): |
603 |
615 |
|
604 |
616 |
|
605 |
617 |
def entry_find(self): |
618 |
"Searches for an entry" |
|
619 |
||
606 |
620 |
dialog = revelation.dialog.Find(self, self.config) |
607 |
621 |
dialog.entry_phrase.set_text(self.finder.string) |
608 |
622 |
dialog.dropdown.set_type(self.finder.type) |
| … | … | @@ -624,6 +638,8 @@ class Revelation(revelation.widget.App): |
624 |
638 |
|
625 |
639 |
|
626 |
640 |
def entry_remove(self): |
641 |
"Removes one or more entries" |
|
642 |
||
627 |
643 |
iters = self.tree.get_selected() |
628 |
644 |
|
629 |
645 |
if len(iters) == 0: |
| … | … | @@ -809,6 +825,8 @@ class Revelation(revelation.widget.App): |
809 |
825 |
|
810 |
826 |
|
811 |
827 |
def save_changes(self, pritext, sectext): |
828 |
"Asks the user if she wants to save her changes" |
|
829 |
||
812 |
830 |
if self.data.changed == gtk.FALSE: |
813 |
831 |
return gtk.TRUE |
814 |
832 |
|
| … | … | @@ -823,6 +841,8 @@ class Revelation(revelation.widget.App): |
823 |
841 |
|
824 |
842 |
|
825 |
843 |
def quit(self): |
844 |
"Quits the application" |
|
845 |
||
826 |
846 |
if not self.save_changes("Save changes before quitting?", "You have made changes which have not been saved. If you quit without saving, then these changes will be discarded."): |
827 |
847 |
self.statusbar.set_status("Quit cancelled") |
828 |
848 |
return gtk.FALSE |
| … | … | @@ -854,6 +874,8 @@ class Revelation(revelation.widget.App): |
854 |
874 |
|
855 |
875 |
|
856 |
876 |
def run(self, file = None): |
877 |
"Run the application" |
|
878 |
||
857 |
879 |
if file is not None: |
858 |
880 |
self.file_open(file) |
859 |
881 |
elif self.config.get("file/autoload"): |
