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.

Clone this repository (size: 1.8 MB): HTTPS / SSH
$ hg clone http://oss.codepoet.no/revelation
commit 40: 3794d9b38446
parent 39: 9b3c9903350d
branch: default
cleaned up widget code, and added docstrings
Erik Grinaker / erikg
6 years ago

Changed (Δ5.5 KB):

raw changeset »

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)

Up to file-list ChangeLog:

@@ -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)

Up to file-list TODO:

@@ -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 = gtk.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 = gtk.VBox()
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(gtk.VBox):
29
class DataView(revelation.widget.VBox):
30
	"An UI component for displaying an entry"
30
31
31
32
	def __init__(self):
32
		gtk.VBox.__init__(self)
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 = gtk.VBox()
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
		gtk.VBox.pack_start(self, alignment)
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
		gnome.ui.App.set_title(self, title + " - " + self.appname)
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 == None or self.selection.iter_is_selected(iter) == gtk.FALSE:
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
		if iter == None:
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(gtk.VBox):
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
		self.set_spacing(6)
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.client.get("file/autosave"):
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"):