Erik Grinaker is sharing code with you

Bitbucket is a code hosting site. Unlimited public and private repositories. Free for small teams.

Don't show this again

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: 2.1 MB): HTTPS / SSH
hg clone https://bitbucket.org/erikg/revelation
hg clone ssh://hg@bitbucket.org/erikg/revelation

Revelation / src / revelation

commit
7d9375f894bb
parent
f4e8220f0a3a
branch
default
tags
revelation-0.3.1

set release date

1
cea4127a11c0
#!/usr/bin/env python
2
cea4127a11c0
3
cea4127a11c0
#
4
f5321aba18db
# Revelation 0.3.1 - a password manager for GNOME 2
5
b181693cfdc7
# http://oss.codepoet.no/revelation/
6
dc69203e823e
# $Id$
7
cea4127a11c0
#
8
cea4127a11c0
# Copyright (c) 2003-2004 Erik Grinaker
9
cea4127a11c0
#
10
cea4127a11c0
# This program is free software; you can redistribute it and/or
11
cea4127a11c0
# modify it under the terms of the GNU General Public License
12
cea4127a11c0
# as published by the Free Software Foundation; either version 2
13
cea4127a11c0
# of the License, or (at your option) any later version.
14
cea4127a11c0
#
15
cea4127a11c0
# This program is distributed in the hope that it will be useful,
16
cea4127a11c0
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17
cea4127a11c0
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
cea4127a11c0
# GNU General Public License for more details.
19
cea4127a11c0
#
20
cea4127a11c0
# You should have received a copy of the GNU General Public License
21
cea4127a11c0
# along with this program; if not, write to the Free Software
22
cea4127a11c0
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23
cea4127a11c0
#
24
cea4127a11c0
25
cea4127a11c0
import pygtk
26
cea4127a11c0
pygtk.require("2.0")
27
9b3c9903350d
28
9b3c9903350d
import gtk, gnome, revelation, os, os.path, sys, gobject
29
cea4127a11c0
30
2fd8a0ec71c3
31
0f15e637f284
class Revelation(revelation.widget.App):
32
0f15e637f284
	"Main application class"
33
cea4127a11c0
34
cea4127a11c0
	def __init__(self):
35
0f15e637f284
		gnome.init(revelation.APPNAME, revelation.APPNAME)
36
a877d43f0987
		revelation.widget.App.__init__(self, revelation.APPNAME)
37
cea4127a11c0
38
a877d43f0987
		os.umask(0077)
39
cea4127a11c0
40
0f15e637f284
		gtk.window_set_default_icon_list(
41
0f15e637f284
			gtk.gdk.pixbuf_new_from_file(revelation.DATADIR + "/pixmaps/revelation.png"),
42
0f15e637f284
			gtk.gdk.pixbuf_new_from_file(revelation.DATADIR + "/pixmaps/revelation-16x16.png")
43
0f15e637f284
		)
44
cea4127a11c0
45
0f15e637f284
		self.__init_facilities()
46
0f15e637f284
		self.__init_toolbar()
47
0f15e637f284
		self.__init_menu()
48
0f15e637f284
		self.__init_mainarea()
49
0f15e637f284
		self.__init_states()
50
0f15e637f284
51
0f15e637f284
52
b181693cfdc7
	# init methods
53
0f15e637f284
	def __init_facilities(self):
54
0f15e637f284
		"Sets up various application facilities"
55
0f15e637f284
56
5c5c063d3896
		try:
57
f54fce826711
			self.config = revelation.data.Config()
58
5c5c063d3896
59
5c5c063d3896
		except revelation.data.ConfigError:
60
5c5c063d3896
			revelation.dialog.Error(
61
5c5c063d3896
				None, "Configuration error",
62
5c5c063d3896
				"The Revelation configuration data could not be found in gconf. This indicates a fault in your installation, please re-install Revelation."
63
5c5c063d3896
			).run()
64
5c5c063d3896
65
5c5c063d3896
			sys.exit(1)
66
5c5c063d3896
67
5c5c063d3896
68
0f15e637f284
		self.icons	= revelation.stock.IconFactory(self)
69
0f15e637f284
		self.data	= revelation.data.EntryStore()
70
0f15e637f284
		self.clipboard	= revelation.data.EntryClipboard()
71
0f15e637f284
		self.undoqueue	= revelation.data.UndoQueue(self.data)
72
0f15e637f284
		self.finder	= revelation.data.EntrySearch(self.data)
73
0f15e637f284
74
a877d43f0987
		self.data.connect("file-changed", self.__cb_state_file)
75
a877d43f0987
76
0f15e637f284
		self.clipboard.connect("copy", self.__cb_state_clipboard)
77
0f15e637f284
		self.clipboard.connect("cut", self.__cb_state_clipboard)
78
0f15e637f284
79
0f15e637f284
		self.undoqueue.connect("changed", self.__cb_state_undo)
80
0f15e637f284
81
0f15e637f284
		self.finder.connect("changed", self.__cb_state_find)
82
0f15e637f284
83
0f15e637f284
84
0f15e637f284
	def __init_mainarea(self):
85
0f15e637f284
		"Sets up the main application area"
86
0f15e637f284
87
fd5f1af41bae
		self.tree = revelation.widget.Tree(self.data)
88
b181693cfdc7
		self.tree.connect("popup", self.__cb_popup_tree)
89
a877d43f0987
		self.tree.connect("doubleclick", lambda w,d: self.entry_edit())
90
a877d43f0987
		self.tree.selection.connect("changed", lambda w: self.dataview.display_entry(self.data.get_entry(self.tree.get_active())))
91
0f15e637f284
		self.tree.selection.connect("changed", self.__cb_state_entry)
92
0f15e637f284
		scrolledwindow = revelation.widget.ScrolledWindow(self.tree)
93
cea4127a11c0
94
fd5f1af41bae
		self.dataview = revelation.widget.DataView()
95
0f15e637f284
		alignment = gtk.Alignment(0.5, 0.4, 0, 0)
96
0f15e637f284
		alignment.add(self.dataview)
97
cea4127a11c0
98
9b3c9903350d
		self.hpaned = revelation.widget.HPaned(scrolledwindow, alignment, self.config.get("view/pane-position"))
99
0f15e637f284
		self.set_contents(self.hpaned)
100
0f15e637f284
101
0f15e637f284
102
0f15e637f284
	def __init_menu(self):
103
0f15e637f284
		"Sets up the application menu"
104
0f15e637f284
105
a877d43f0987
		self.create_menu((
106
0f15e637f284
			("/_File",		None,			None,					None,							0,	"<Branch>"),
107
0f15e637f284
			("/File/_New",		"<Control>N",		"Create a new file",			lambda w,d: self.file_new(),				0,	"<StockItem>",	gtk.STOCK_NEW),
108
0f15e637f284
			("/File/_Open...",	"<Control>O",		"Open a file",				lambda w,d: self.file_open(),				0,	"<StockItem>",	gtk.STOCK_OPEN),
109
0f15e637f284
			("/File/sep1",		None,			None,					None,							0,	"<Separator>"),
110
a877d43f0987
			("/File/_Save",		"<Control>S",		"Save data to file",			lambda w,d: self.file_save(self.data.file, self.data.password),	0,	"<StockItem>",	gtk.STOCK_SAVE),
111
0f15e637f284
			("/File/Save _As...",	"<Shift><Control>S",	"Save data to different file",		lambda w,d: self.file_save(),				0,	"<StockItem>",	gtk.STOCK_SAVE_AS),
112
0f15e637f284
			("/File/_Revert",	None,			"Revert to the saved copy of the file",	lambda w,d: self.file_revert(),				0,	"<StockItem>",	gtk.STOCK_REVERT_TO_SAVED),
113
0f15e637f284
			("/File/sep2",		None,			None,					None,							0,	"<Separator>"),
114
0f15e637f284
			("/File/Change _Password...",	None,		"Change password of current file",	lambda w,d: self.change_password(),			0,	"<StockItem>",	revelation.stock.STOCK_PASSWORD),
115
0f15e637f284
			("/File/_Lock...",	"<Control>L",		"Lock the current data file",		lambda w,d: self.file_lock(),				0,	"<StockItem>",	revelation.stock.STOCK_LOCK),
116
0f15e637f284
			("/File/sep3",		None,			None,					None,							0,	"<Separator>"),
117
0f15e637f284
			("/File/_Import...",	None,			"Import data from a foreign file",	lambda w,d: self.file_import(),				0,	"<StockItem>",	revelation.stock.STOCK_IMPORT),
118
0f15e637f284
			("/File/_Export...",	None,			"Export data to a different format",	lambda w,d: self.file_export(),				0,	"<StockItem>",	revelation.stock.STOCK_EXPORT),
119
0f15e637f284
			("/File/sep4",		None,			None,					None,							0,	"<Separator>"),
120
a877d43f0987
			("/File/_Close",	"<Control>W",		"Close the application",		lambda w,d: self.quit(),				0, 	"<StockItem>",	gtk.STOCK_CLOSE),
121
a877d43f0987
			("/File/_Quit",		"<Control>Q",		"Quit the application",			lambda w,d: self.quit(),				0, 	"<StockItem>",	gtk.STOCK_QUIT),
122
0f15e637f284
123
0f15e637f284
			("/_Edit",		None,			None,					None,							0,	"<Branch>"),
124
0f15e637f284
			("/Edit/_Add Entry...",	"Insert",		"Create a new entry",			lambda w,d: self.entry_add(),				0,	"<StockItem>",	revelation.stock.STOCK_ADD),
125
0f15e637f284
			("/Edit/_Edit",		"Return",		"Edit the selected entry",		lambda w,d: self.entry_edit(),				0,	"<StockItem>",	revelation.stock.STOCK_EDIT),
126
0f15e637f284
			("/Edit/Re_move",	"Delete",		"Remove the selected entry",		lambda w,d: self.entry_remove(),			0,	"<StockItem>",	revelation.stock.STOCK_REMOVE),
127
0f15e637f284
			("/Edit/sep1",		None,			None,					None,							0,	"<Separator>"),
128
0f15e637f284
			("/Edit/_Undo",		"<Control>Z",		"Undo the last action",			lambda w,d: self.undo(),				0,	"<StockItem>",	gtk.STOCK_UNDO),
129
0f15e637f284
			("/Edit/_Redo",		"<Shift><Control>Z",	"Redo the previously undone action",	lambda w,d: self.redo(),				0,	"<StockItem>",	gtk.STOCK_REDO),
130
0f15e637f284
			("/Edit/sep2",		None,			None,					None,							0,	"<Separator>"),
131
0f15e637f284
			("/Edit/Cu_t",		"<Control>X",		"Cut the entry to the clipboard",	lambda w,d: self.clip_cut(),				0,	"<StockItem>",	gtk.STOCK_CUT),
132
0f15e637f284
			("/Edit/_Copy",		"<Control>C",		"Copy the entry to the clipboard",	lambda w,d: self.clip_copy(),				0,	"<StockItem>",	gtk.STOCK_COPY),
133
0f15e637f284
			("/Edit/_Paste",	"<Control>V",		"Paste entry from clipboard",		lambda w,d: self.clip_paste(),				0,	"<StockItem>",	gtk.STOCK_PASTE),
134
0f15e637f284
			("/Edit/sep3",		None,			None,					None,							0,	"<Separator>"),
135
0f15e637f284
			("/Edit/_Find...",	"<Control>F",		"Search for an entry",			lambda w,d: self.entry_find(),				0,	"<StockItem>",	gtk.STOCK_FIND),
136
0f15e637f284
			("/Edit/Find Ne_xt",	"<Control>G",		"Find the next search match",		lambda w,d: self.__entry_find(self, revelation.data.SEARCH_NEXT),	0,	"<Item>"),
137
784a991e653d
			("/Edit/Find Pre_vious",	"<Shift><Control>G",		"Find the previous search match",	lambda w,d: self.__entry_find(self, revelation.data.SEARCH_PREV),	0,	"<Item>"),
138
0f15e637f284
			("/Edit/sep4",		None,			None,					None,							0,	"<Separator>"),
139
0f15e637f284
			("/Edit/_Select All",	"<Control>A",		"Select all entries",			lambda w,d: self.tree.select_all(),			0,	"<Item>"),
140
0f15e637f284
			("/Edit/_Deselect All",	"<Shift><Control>A",	"Deselect all entries",			lambda w,d: self.tree.unselect_all(),			0,	"<Item>"),
141
0f15e637f284
			("/Edit/sep5",		None,			None,					None,							0,	"<Separator>"),
142
9b3c9903350d
			("/Edit/Prefere_nces",	None,			"Edit preferences",			lambda w,d: revelation.dialog.Preferences(self, self.config).run(),	0,	"<StockItem>",	gtk.STOCK_PREFERENCES),
143
0f15e637f284
144
0f15e637f284
			("/_View",		None,			None,					None,							0,	"<Branch>"),
145
9b3c9903350d
			("/View/_Toolbar",	None,			"Toggle display of the toolbar",	None,							0,	"<CheckItem>"),
146
9b3c9903350d
			("/View/_Statusbar",	None,			"Toggle display of the statusbar",	None,							0,	"<CheckItem>"),
147
0f15e637f284
			("/View/sep1",		None,			None,					None,							0,	"<Separator>"),
148
9b3c9903350d
			("/View/Show _Passwords",	"<Control>P",	"Show passwords",			None,							0,	"<CheckItem>"),
149
0f15e637f284
150
0f15e637f284
			("/_Help",		None,			None,					None,							0,	"<Branch>"),
151
0f15e637f284
			("/Help/_Homepage",	None,			"Visit the Revelation homepage",	lambda w,d: gnome.url_show(revelation.URL),		0,	"<StockItem>", gtk.STOCK_HOME),
152
0f15e637f284
			("/Help/_About",	None,			"Show info about this application",	lambda w,d: revelation.dialog.About(self).run(),	0,	"<StockItem>", "gnome-stock-about")
153
a877d43f0987
		))
154
0f15e637f284
155
0f15e637f284
156
0f15e637f284
	def __init_states(self):
157
0f15e637f284
		"Sets the initial application state"
158
0f15e637f284
159
9b3c9903350d
		self.set_default_size(self.config.get("view/window-width"), self.config.get("view/window-height"))
160
f4e8220f0a3a
		self.move(self.config.get("view/window-position-x"), self.config.get("view/window-position-y"))
161
b181693cfdc7
		self.connect("delete_event", lambda w,d: gtk.TRUE ^ self.quit())
162
b181693cfdc7
163
0f15e637f284
		self.tree.select(None)
164
0f15e637f284
		self.dataview.display_info()
165
5e73d779ae0d
		self.data.set_file(None)
166
0f15e637f284
167
0f15e637f284
		self.if_menu.get_widget("<main>/Edit/Find Next").set_sensitive(gtk.FALSE)
168
0f15e637f284
		self.if_menu.get_widget("<main>/Edit/Find Previous").set_sensitive(gtk.FALSE)
169
0f15e637f284
		self.if_menu.get_widget("<main>/Edit/Undo").set_sensitive(gtk.FALSE)
170
0f15e637f284
		self.if_menu.get_widget("<main>/Edit/Redo").set_sensitive(gtk.FALSE)
171
0f15e637f284
172
0f15e637f284
		self.show_all()
173
0f15e637f284
174
9b3c9903350d
		self.config.bind_widget("view/passwords", self.if_menu.get_widget("<main>/View/Show Passwords"))
175
9b3c9903350d
		self.config.bind_widget("view/statusbar", self.if_menu.get_widget("<main>/View/Statusbar"))
176
9b3c9903350d
		self.config.bind_widget("view/toolbar", self.if_menu.get_widget("<main>/View/Toolbar"))
177
0f15e637f284
178
9b3c9903350d
		self.config.notify_add("view/statusbar", self.__cb_config_statusbar)
179
9b3c9903350d
		self.config.notify_add("view/toolbar", self.__cb_config_toolbar)
180
0f15e637f284
181
0f15e637f284
182
0f15e637f284
	def __init_toolbar(self):
183
0f15e637f284
		"Sets up the application toolbar"
184
0f15e637f284
185
db7970bbbb93
		self.toolbar.button_new		= self.toolbar.append_stock(gtk.STOCK_NEW, "New file", lambda w,d: self.file_new())
186
db7970bbbb93
		self.toolbar.button_open	= self.toolbar.append_stock(gtk.STOCK_OPEN, "Open file", lambda w,d: self.file_open())
187
a877d43f0987
		self.toolbar.button_save	= self.toolbar.append_stock(gtk.STOCK_SAVE, "Save file", lambda w,d: self.file_save(self.data.file, self.data.password))
188
0f15e637f284
189
0f15e637f284
		self.toolbar.append_space()
190
0f15e637f284
191
db7970bbbb93
		self.toolbar.button_entry_add	= self.toolbar.append_stock(revelation.stock.STOCK_ADD, "Add a new entry", lambda w,d: self.entry_add())
192
db7970bbbb93
		self.toolbar.button_entry_edit	= self.toolbar.append_stock(revelation.stock.STOCK_EDIT, "Edit the selected entry", lambda w,d: self.entry_edit())
193
db7970bbbb93
		self.toolbar.button_entry_remove = self.toolbar.append_stock(revelation.stock.STOCK_REMOVE, "Remove the selected entry", lambda w,d: self.entry_remove())
194
0f15e637f284
195
0f15e637f284
196
0f15e637f284
197
9b3c9903350d
	# config callbacks
198
9b3c9903350d
	def __cb_config_statusbar(self, config, value, data):
199
9b3c9903350d
		"Config callback for statusbar changes"
200
0f15e637f284
201
9b3c9903350d
		if value == gtk.TRUE:
202
0f15e637f284
			self.statusbar.show()
203
0f15e637f284
204
0f15e637f284
		else:
205
0f15e637f284
			self.statusbar.hide()
206
0f15e637f284
207
0f15e637f284
208
9b3c9903350d
	def __cb_config_toolbar(self, config, value, data):
209
9b3c9903350d
		"Config callback for toolbar changes"
210
0f15e637f284
211
9b3c9903350d
		if value == gtk.TRUE:
212
0f15e637f284
			self.toolbar.show()
213
0f15e637f284
214
0f15e637f284
		else:
215
0f15e637f284
			self.toolbar.hide()
216
0f15e637f284
217
0f15e637f284
218
0f15e637f284
219
0f15e637f284
	# callbacks for handling ui states
220
0f15e637f284
	def __cb_state_clipboard(self, widget, data = None):
221
0f15e637f284
		"Sets clipboard item sensitivity as appropriate"
222
0f15e637f284
223
0f15e637f284
		self.if_menu.get_widget("<main>/Edit/Paste").set_sensitive(self.clipboard.has_contents())
224
0f15e637f284
225
0f15e637f284
226
0f15e637f284
	def __cb_state_entry(self, widget, data = None):
227
0f15e637f284
		"Sets state for entry-dependent ui items"
228
0f15e637f284
229
0f15e637f284
		selcount = len(self.tree.get_selected())
230
0f15e637f284
231
0f15e637f284
		self.toolbar.button_entry_add.set_sensitive(selcount < 2)
232
0f15e637f284
		self.if_menu.get_widget("<main>/Edit/Add Entry...").set_sensitive(selcount < 2)
233
0f15e637f284
		self.if_menu.get_widget("<main>/Edit/Paste").set_sensitive(selcount < 2 and self.clipboard.has_contents())
234
0f15e637f284
235
0f15e637f284
		self.toolbar.button_entry_edit.set_sensitive(selcount == 1)
236
0f15e637f284
		self.if_menu.get_widget("<main>/Edit/Edit").set_sensitive(selcount == 1)
237
0f15e637f284
238
0f15e637f284
		self.toolbar.button_entry_remove.set_sensitive(selcount > 0)
239
0f15e637f284
		self.if_menu.get_widget("<main>/Edit/Remove").set_sensitive(selcount > 0)
240
0f15e637f284
		self.if_menu.get_widget("<main>/Edit/Cut").set_sensitive(selcount > 0)
241
0f15e637f284
		self.if_menu.get_widget("<main>/Edit/Copy").set_sensitive(selcount > 0)
242
0f15e637f284
243
0f15e637f284
244
a877d43f0987
	def __cb_state_file(self, widget, file = None):
245
0f15e637f284
		"Sets various states based on current file"
246
0f15e637f284
247
a877d43f0987
		self.if_menu.get_widget("<main>/File/Revert").set_sensitive(file is not None)
248
a877d43f0987
		self.if_menu.get_widget("<main>/File/Lock...").set_sensitive(file is not None)
249
0f15e637f284
250
a877d43f0987
		if file is None:
251
0f15e637f284
			self.set_title("[New file]")
252
0f15e637f284
253
0f15e637f284
		else:
254
a877d43f0987
			self.set_title(os.path.basename(file))
255
a877d43f0987
			os.chdir(os.path.dirname(file))
256
0f15e637f284
257
0f15e637f284
258
0f15e637f284
	def __cb_state_find(self, widget, data = None):
259
0f15e637f284
		"Sets ui item states for find-related items"
260
0f15e637f284
261
0f15e637f284
		self.if_menu.get_widget("<main>/Edit/Find Next").set_sensitive(self.finder.string != "")
262
0f15e637f284
		self.if_menu.get_widget("<main>/Edit/Find Previous").set_sensitive(self.finder.string != "")
263
0f15e637f284
264
0f15e637f284
265
0f15e637f284
	def __cb_state_undo(self, widget, data = None):
266
0f15e637f284
		"Sets states for undo-related ui items"
267
0f15e637f284
268
0f15e637f284
		# update undo widgets
269
0f15e637f284
		widget = self.if_menu.get_widget("<main>/Edit/Undo")
270
0f15e637f284
		action = "_Undo"
271
0f15e637f284
272
0f15e637f284
		if self.undoqueue.can_undo():
273
0f15e637f284
			widget.get_children()[0].set_label(action + " " + self.undoqueue.get_action(revelation.data.UNDO).name)
274
0f15e637f284
			widget.set_sensitive(gtk.TRUE)
275
0f15e637f284
276
0f15e637f284
		else:
277
0f15e637f284
			widget.get_children()[0].set_label(action)
278
0f15e637f284
			widget.set_sensitive(gtk.FALSE)
279
0f15e637f284
280
0f15e637f284
		# update redo widgets
281
0f15e637f284
		widget = self.if_menu.get_widget("<main>/Edit/Redo")
282
0f15e637f284
		action = "_Redo"
283
0f15e637f284
284
0f15e637f284
		if self.undoqueue.can_redo():
285
0f15e637f284
			widget.get_children()[0].set_label(action + " " + self.undoqueue.get_action(revelation.data.REDO).name)
286
0f15e637f284
			widget.set_sensitive(gtk.TRUE)
287
0f15e637f284
288
0f15e637f284
		else:
289
0f15e637f284
			widget.get_children()[0].set_label(action)
290
0f15e637f284
			widget.set_sensitive(gtk.FALSE)
291
0f15e637f284
292
0f15e637f284
293
0f15e637f284
294
0f15e637f284
	# other, normal callbacks
295
b181693cfdc7
	def __cb_popup_tree(self, widget, menuitems):
296
b181693cfdc7
		"Create a popup-menu for the treeview"
297
0f15e637f284
298
0f15e637f284
		# create the popup menu
299
0f15e637f284
		iters = self.tree.get_selected()
300
b181693cfdc7
301
b181693cfdc7
		if len(iters) == 0 or (len(iters) == 1 and self.data.get_entry(iters[0]).type == revelation.entry.ENTRY_FOLDER):
302
b181693cfdc7
			menuitems.append(("/_Add Entry...", None, "Create a new entry", lambda w,d: self.entry_add(), 0, "<StockItem>", revelation.stock.STOCK_ADD))
303
cea4127a11c0
304
cea4127a11c0
		if len(iters) == 1:
305
b181693cfdc7
			menuitems.append(("/_Edit", None, "Edit the selected entry", lambda w,d: self.entry_edit(), 0, "<StockItem>", revelation.stock.STOCK_EDIT))
306
cea4127a11c0
307
cea4127a11c0
		if len(iters) > 0:
308
b181693cfdc7
			menuitems.append(("/Re_move", None, "Remove the selected entry", lambda w,d: self.entry_remove(), 0, "<StockItem>", revelation.stock.STOCK_REMOVE))
309
cea4127a11c0
310
0f15e637f284
311
cea4127a11c0
		clipboardmenu = []
312
cea4127a11c0
313
cea4127a11c0
		if len(iters) > 0:
314
b181693cfdc7
			clipboardmenu.append(("/Cu_t", "", "Cut the selected entry to the clipboard", lambda w,d: self.clip_cut(), 0, "<StockItem>", gtk.STOCK_CUT))
315
b181693cfdc7
			clipboardmenu.append(("/_Copy", "", "Copy the selected entry to the keyboard", lambda w,d: self.clip_copy(), 0, "<StockItem>", gtk.STOCK_COPY))
316
cea4127a11c0
317
cea4127a11c0
		if len(iters) < 2 and self.clipboard.has_contents():
318
b181693cfdc7
			clipboardmenu.append(("/_Paste", "", "Paste entry from clipboard", lambda w,d: self.clip_paste(), 0, "<StockItem>", gtk.STOCK_PASTE))
319
cea4127a11c0
320
0f15e637f284
321
cea4127a11c0
		if len(clipboardmenu) > 0:
322
cea4127a11c0
			menuitems.append(("/sep1", None, None, None, 0, "<Separator>"))
323
cea4127a11c0
			menuitems.extend(clipboardmenu)
324
cea4127a11c0
325
0f15e637f284
326
0f15e637f284
327
0f15e637f284
	# various private methods
328
cea4127a11c0
	def __entry_find(self, parent, direction = revelation.data.SEARCH_NEXT):
329
a877d43f0987
		"Searches for an entry"
330
a877d43f0987
331
9b3c9903350d
		self.finder.folders = self.config.get("search/folders")
332
9b3c9903350d
		self.finder.casesens = self.config.get("search/casesens")
333
9b3c9903350d
		self.finder.namedesc = self.config.get("search/namedesc")
334
cea4127a11c0
335
cea4127a11c0
		match = self.finder.find(self.tree.get_active(), direction)
336
cea4127a11c0
337
0f15e637f284
		if match is None:
338
cea4127a11c0
			revelation.dialog.Error(parent, "No match found", "The string you searched for did not match any entries. Try using a different search-phrase.").run()
339
a877d43f0987
340
cea4127a11c0
		else:
341
cea4127a11c0
			self.tree.select(match)
342
cea4127a11c0
343
cea4127a11c0
344
71831418a015
	def __file_autosave(self):
345
71831418a015
		"Autosaves the current file, due to data modification"
346
71831418a015
347
a877d43f0987
		if self.data.file is None or self.data.password is None:
348
71831418a015
			return
349
71831418a015
350
3794d9b38446
		if not self.config.get("file/autosave"):
351
71831418a015
			return
352
71831418a015
353
a877d43f0987
		self.file_save(self.data.file, self.data.password)
354
71831418a015
355
71831418a015
356
4ccca6b59faa
	def __file_load(self, datafile):
357
4ccca6b59faa
		"Loads data from a file into an entrystore"
358
4ccca6b59faa
359
4ccca6b59faa
		try:
360
4ccca6b59faa
			if datafile.handler is None:
361
4ccca6b59faa
				datafile.detect_type()
362
4ccca6b59faa
363
4ccca6b59faa
			datafile.check_file()
364
4ccca6b59faa
365
a877d43f0987
			dialog = revelation.dialog.PasswordLoad(self, datafile.file)
366
4ccca6b59faa
			entrystore = None
367
4ccca6b59faa
368
4ccca6b59faa
			while 1:
369
4ccca6b59faa
370
4ccca6b59faa
				# load datafile, ask for password if needed
371
4ccca6b59faa
				try:
372
4ccca6b59faa
					if datafile.needs_password() and datafile.password is None:
373
fe0f16515591
						datafile.password = dialog.run()
374
4ccca6b59faa
375
4ccca6b59faa
					entrystore = datafile.load()
376
4ccca6b59faa
377
4ccca6b59faa
				except revelation.datahandler.PasswordError:
378
4ccca6b59faa
					datafile.password = None
379
4ccca6b59faa
					revelation.dialog.Error(
380
4ccca6b59faa
						self, "Incorrect password",
381
4ccca6b59faa
						"The password you entered for the file'" + datafile.file + "' was not correct."
382
4ccca6b59faa
					).run()
383
4ccca6b59faa
384
4ccca6b59faa
				except:
385
4ccca6b59faa
					dialog.destroy()
386
4ccca6b59faa
					raise
387
4ccca6b59faa
388
4ccca6b59faa
				else:
389
4ccca6b59faa
					dialog.destroy()
390
4ccca6b59faa
					break
391
4ccca6b59faa
392
a78b3bc44c12
			return entrystore
393
a78b3bc44c12
394
4ccca6b59faa
		except revelation.datahandler.FormatError:
395
4ccca6b59faa
			self.statusbar.set_status("Open failed")
396
4ccca6b59faa
			revelation.dialog.Error(
397
4ccca6b59faa
				self, "Invalid file format",
398
784a991e653d
				"The file '" + datafile.file + "' contains invalid data."
399
4ccca6b59faa
			).run()
400
4ccca6b59faa
401
4ccca6b59faa
		except revelation.entry.EntryError:
402
4ccca6b59faa
			self.statusbar.set_status("Open failed")
403
4ccca6b59faa
			revelation.dialog.Error(
404
4ccca6b59faa
				self, "Unknown data",
405
784a991e653d
				"The file '" + datafile.file + "' contains unknown data. It may have been created by a future version of Revelation, try upgrading to a newer version."
406
4ccca6b59faa
			).run()
407
4ccca6b59faa
408
4ccca6b59faa
		except revelation.datahandler.VersionError:
409
4ccca6b59faa
			self.statusbar.set_status("Open failed")
410
4ccca6b59faa
			revelation.dialog.Error(
411
4ccca6b59faa
				self, "Unknown data version",
412
4ccca6b59faa
				"The file '" + datafile.file + "' has a future version number - upgrade Revelation to a more recent version to open it."
413
4ccca6b59faa
			).run()
414
4ccca6b59faa
415
4ccca6b59faa
		except revelation.io.DetectError:
416
4ccca6b59faa
			self.statusbar.set_status("Open failed")
417
4ccca6b59faa
			revelation.dialog.Error(
418
4ccca6b59faa
				self, "Filetype autodetection failed",
419
4ccca6b59faa
				"The format of the file '" + datafile.file + "' could not be detected automatically. It may still be possible to open the file, try specifying the file type manually."
420
4ccca6b59faa
			).run()
421
4ccca6b59faa
422
4ccca6b59faa
		except IOError:
423
4ccca6b59faa
			self.statusbar.set_status("Open failed")
424
4ccca6b59faa
			revelation.dialog.Error(
425
4ccca6b59faa
				self, "Unable to open file",
426
4ccca6b59faa
				"The file '" + datafile.file + "' could not be opened. Make sure that the file exists, and that you have the proper permissions to open it."
427
4ccca6b59faa
			).run()
428
4ccca6b59faa
429
4ccca6b59faa
		except revelation.CancelError:
430
4ccca6b59faa
			dialog.destroy()
431
b181693cfdc7
			raise
432
4ccca6b59faa
433
4ccca6b59faa
434
4ccca6b59faa
	def __file_save(self, datafile):
435
4ccca6b59faa
		"Saves data to a file"
436
4ccca6b59faa
437
4ccca6b59faa
		try:
438
a877d43f0987
			if datafile.file != self.data.file and revelation.io.file_exists(datafile.file):
439
4ccca6b59faa
				revelation.dialog.FileOverwrite(self, datafile.file).run()
440
4ccca6b59faa
441
4ccca6b59faa
			if not datafile.needs_password():
442
4ccca6b59faa
				revelation.dialog.FileExportInsecure(self).run()
443
4ccca6b59faa
444
4ccca6b59faa
			elif datafile.password is None:
445
4ccca6b59faa
				try:
446
fe0f16515591
					dialog = revelation.dialog.PasswordSave(self, datafile.file)
447
fe0f16515591
					datafile.password = dialog.run()
448
4ccca6b59faa
					dialog.destroy()
449
4ccca6b59faa
450
4ccca6b59faa
				except revelation.CancelError:
451
4ccca6b59faa
					dialog.destroy()
452
4ccca6b59faa
					raise
453
4ccca6b59faa
454
4ccca6b59faa
455
4ccca6b59faa
			datafile.save(self.data)
456
4ccca6b59faa
457
a78b3bc44c12
			return gtk.TRUE
458
4ccca6b59faa
459
4ccca6b59faa
		except IOError:
460
4ccca6b59faa
			revelation.dialog.Error(self, "Unable to write to file", "The file '" + datafile.file + "' could not be opened for writing. Make sure that you have the proper permissions to write to it.").run()
461
4ccca6b59faa
			self.statusbar.set_status("Save failed")
462
a78b3bc44c12
463
4ccca6b59faa
			return gtk.FALSE
464
4ccca6b59faa
465
4ccca6b59faa
466
a877d43f0987
	def __save_changes(self, dialog):
467
a877d43f0987
		"Asks the user if she wants to save her changes"
468
a877d43f0987
469
a877d43f0987
		try:
470
a877d43f0987
			if not self.data.changed:
471
a877d43f0987
				return gtk.TRUE
472
a877d43f0987
473
a877d43f0987
			if dialog(self).run() == gtk.TRUE:
474
a877d43f0987
				return self.file_save(self.data.file, self.data.password)
475
a877d43f0987
476
a78b3bc44c12
			return gtk.TRUE
477
a78b3bc44c12
478
a877d43f0987
		except revelation.CancelError:
479
a877d43f0987
			return gtk.FALSE
480
a877d43f0987
481
a877d43f0987
482
4ccca6b59faa
483
0f15e637f284
	# public methods
484
cea4127a11c0
	def change_password(self):
485
3794d9b38446
		"Changes the password of the current data file"
486
3794d9b38446
487
cea4127a11c0
		try:
488
b181693cfdc7
			dialog = revelation.dialog.PasswordChange(self, self.data.password)
489
b181693cfdc7
			self.data.password = dialog.run()
490
cea4127a11c0
491
fe0f16515591
			self.__file_autosave()
492
fe0f16515591
			self.statusbar.set_status("Password changed")
493
cea4127a11c0
494
cea4127a11c0
		except revelation.CancelError:
495
cea4127a11c0
			self.statusbar.set_status("Password change cancelled")
496
cea4127a11c0
497
cea4127a11c0
		dialog.destroy()
498
cea4127a11c0
499
cea4127a11c0
500
cea4127a11c0
	def clip_copy(self):
501
3794d9b38446
		"Copies selected entries to the clipboard"
502
3794d9b38446
503
cea4127a11c0
		iters = self.data.filter_parents(self.tree.get_selected())
504
cea4127a11c0
		self.clipboard.copy(self.data, iters)
505
cea4127a11c0
506
cea4127a11c0
507
cea4127a11c0
	def clip_cut(self):
508
3794d9b38446
		"Cuts selected entries to the clipboard"
509
3794d9b38446
510
cea4127a11c0
		iters = self.data.filter_parents(self.tree.get_selected())
511
268984dc47cf
		self.undoqueue.add_action(revelation.data.UNDO_ACTION_CUT, iters)
512
cea4127a11c0
		self.clipboard.cut(self.data, iters)
513
b181693cfdc7
		self.__file_autosave()
514
cea4127a11c0
		self.tree.unselect_all()
515
cea4127a11c0
516
cea4127a11c0
517
cea4127a11c0
	def clip_paste(self):
518
3794d9b38446
		"Pastes entries from the clipboard"
519
3794d9b38446
520
4787d15f40a4
		if not self.clipboard.has_contents():
521
cea4127a11c0
			return
522
cea4127a11c0
523
4787d15f40a4
		iters = self.clipboard.paste(self.data, self.tree.get_active())
524
268984dc47cf
		self.undoqueue.add_action(revelation.data.UNDO_ACTION_PASTE, iters)
525
b181693cfdc7
		self.__file_autosave()
526
cea4127a11c0
		self.tree.select(iters[0])
527
cea4127a11c0
528
cea4127a11c0
529
cea4127a11c0
	def entry_add(self):
530
3794d9b38446
		"Adds an entry"
531
3794d9b38446
532
cea4127a11c0
		try:
533
f54fce826711
			entry = revelation.dialog.EntryEdit(self, "Add entry").run()
534
dc936312d815
			iter = self.data.add_entry(self.tree.get_active(), entry)
535
71831418a015
536
268984dc47cf
			self.undoqueue.add_action(revelation.data.UNDO_ACTION_ADD, iter)
537
71831418a015
			self.__file_autosave()
538
b181693cfdc7
539
cea4127a11c0
			self.tree.select(iter)
540
dc936312d815
			self.statusbar.set_status("Added entry '" + entry.name + "'")
541
cea4127a11c0
542
cea4127a11c0
		except revelation.CancelError:
543
cea4127a11c0
			self.statusbar.set_status("Add entry cancelled")
544
cea4127a11c0
545
cea4127a11c0
546
cea4127a11c0
	def entry_edit(self):
547
3794d9b38446
		"Edits an entry"
548
3794d9b38446
549
cea4127a11c0
		iter = self.tree.get_active()
550
cea4127a11c0
551
0f15e637f284
		if iter is None:
552
cea4127a11c0
			return
553
cea4127a11c0
554
cea4127a11c0
		try:
555
dc936312d815
			entry = self.data.get_entry(iter)
556
f54fce826711
			dialog = revelation.dialog.EntryEdit(self, "Edit entry", entry)
557
cea4127a11c0
558
dc936312d815
			if entry.type == revelation.entry.ENTRY_FOLDER and self.data.iter_n_children(iter) > 0:
559
cea4127a11c0
				dialog.set_typechange_allowed(gtk.FALSE)
560
cea4127a11c0
561
dc936312d815
			newentry = dialog.run()
562
dc936312d815
			self.data.update_entry(iter, newentry)
563
268984dc47cf
			self.undoqueue.add_action(revelation.data.UNDO_ACTION_EDIT, iter, entry)
564
b181693cfdc7
565
71831418a015
			self.__file_autosave()
566
cea4127a11c0
			self.tree.select(iter)
567
dc936312d815
			self.statusbar.set_status("Updated entry '" + newentry.name + "'")
568
cea4127a11c0
569
cea4127a11c0
		except revelation.CancelError:
570
cea4127a11c0
			self.statusbar.set_status("Update entry cancelled")
571
cea4127a11c0
572
cea4127a11c0
573
cea4127a11c0
	def entry_find(self):
574
3794d9b38446
		"Searches for an entry"
575
3794d9b38446
576
9b3c9903350d
		dialog = revelation.dialog.Find(self, self.config)
577
cea4127a11c0
		dialog.entry_phrase.set_text(self.finder.string)
578
cea4127a11c0
		dialog.dropdown.set_type(self.finder.type)
579
cea4127a11c0
580
cea4127a11c0
		while 1:
581
cea4127a11c0
			response = dialog.run()
582
cea4127a11c0
			self.finder.string = dialog.entry_phrase.get_text()
583
cea4127a11c0
			self.finder.type = dialog.dropdown.get_type()
584
cea4127a11c0
585
cea4127a11c0
			if response == revelation.dialog.RESPONSE_NEXT:
586
cea4127a11c0
				self.__entry_find(dialog, revelation.data.SEARCH_NEXT)
587
cea4127a11c0
588
cea4127a11c0
			elif response == revelation.dialog.RESPONSE_PREVIOUS:
589
cea4127a11c0
				self.__entry_find(dialog, revelation.data.SEARCH_PREV)
590
cea4127a11c0
591
cea4127a11c0
			else:
592
cea4127a11c0
				dialog.destroy()
593
cea4127a11c0
				break
594
cea4127a11c0
595
cea4127a11c0
596
cea4127a11c0
	def entry_remove(self):
597
3794d9b38446
		"Removes one or more entries"
598
3794d9b38446
599
cea4127a11c0
		iters = self.tree.get_selected()
600
cea4127a11c0
601
cea4127a11c0
		if len(iters) == 0:
602
cea4127a11c0
			return
603
cea4127a11c0
604
a877d43f0987
		entries = self.data.get_entries(iters)
605
cea4127a11c0
606
f54fce826711
607
a877d43f0987
		try:
608
a877d43f0987
			if revelation.dialog.EntryRemove(self, entries).run() != gtk.TRUE:
609
a877d43f0987
				raise revelation.CancelError
610
f54fce826711
611
f54fce826711
			if len(iters) == 1:
612
f54fce826711
				statustext = "Removed entry '" + entries[0].name + "'"
613
cea4127a11c0
614
cea4127a11c0
			else:
615
f54fce826711
				statustext = "Removed " + str(len(entries)) + " entries"
616
cea4127a11c0
617
cea4127a11c0
			iters = self.data.filter_parents(iters)
618
268984dc47cf
			self.undoqueue.add_action(revelation.data.UNDO_ACTION_REMOVE, iters)
619
cea4127a11c0
620
cea4127a11c0
			for iter in iters:
621
cea4127a11c0
				self.data.remove_entry(iter)
622
cea4127a11c0
623
71831418a015
			self.__file_autosave()
624
cea4127a11c0
			self.tree.unselect_all()
625
cea4127a11c0
			self.statusbar.set_status(statustext)
626
cea4127a11c0
627
a877d43f0987
		except revelation.CancelError:
628
cea4127a11c0
			self.statusbar.set_status("Remove entry cancelled")
629
cea4127a11c0
630
cea4127a11c0
631
f54fce826711
632
cea4127a11c0
	def file_export(self):
633
4ccca6b59faa
		"Exports data to a foreign file format"
634
4ccca6b59faa
635
cea4127a11c0
		try:
636
4ccca6b59faa
			file, handler = revelation.dialog.ExportFileSelector(self).run()
637
4ccca6b59faa
			datafile = revelation.io.DataFile(file, handler)
638
4ccca6b59faa
			self.__file_save(datafile)
639
a78b3bc44c12
			self.statusbar.set_status("Data exported to " + datafile.file)
640
cea4127a11c0
641
cea4127a11c0
		except revelation.CancelError:
642
cea4127a11c0
			self.statusbar.set_status("Export cancelled")
643
cea4127a11c0
644
cea4127a11c0
645
4ccca6b59faa
	def file_import(self):
646
4ccca6b59faa
		"Imports data from a foreign file"
647
cea4127a11c0
648
cea4127a11c0
		try:
649
4ccca6b59faa
			file, handler = revelation.dialog.ImportFileSelector(self).run()
650
4ccca6b59faa
			datafile = revelation.io.DataFile(file, handler)
651
4ccca6b59faa
			entrystore = self.__file_load(datafile)
652
cea4127a11c0
653
4ccca6b59faa
			if entrystore is None:
654
4ccca6b59faa
				return
655
cea4127a11c0
656
cea4127a11c0
			iters = self.data.import_entrystore(entrystore)
657
268984dc47cf
			self.undoqueue.add_action(revelation.data.UNDO_ACTION_IMPORT, iters)
658
71831418a015
			self.__file_autosave()
659
cea4127a11c0
			self.statusbar.set_status("Data imported from " + datafile.file)
660
cea4127a11c0
661
a78b3bc44c12
		except revelation.CancelError:
662
a78b3bc44c12
			self.statusbar.set_status("Import cancelled")
663
a78b3bc44c12
664
cea4127a11c0
665
cea4127a11c0
	def file_lock(self):
666
4ccca6b59faa
		"Locks the current data file"
667
4ccca6b59faa
668
b181693cfdc7
		if self.data.password is None:
669
cea4127a11c0
			return
670
cea4127a11c0
671
cea4127a11c0
		iter = self.tree.get_active()
672
cea4127a11c0
		self.tree.set_model(None)
673
cea4127a11c0
		self.dataview.clear()
674
cea4127a11c0
		self.statusbar.set_status("File locked")
675
cea4127a11c0
676
fe0f16515591
		dialog = revelation.dialog.PasswordLock(self)
677
cea4127a11c0
678
cea4127a11c0
		while 1:
679
b181693cfdc7
			if dialog.run() == self.data.password:
680
fe0f16515591
				break
681
cea4127a11c0
682
fe0f16515591
			else:
683
fe0f16515591
				revelation.dialog.Error(
684
fe0f16515591
					dialog, "Incorrect password",
685
fe0f16515591
					"The password you entered was not correct, please try again."
686
fe0f16515591
				).run()
687
4ccca6b59faa
688
cea4127a11c0
689
cea4127a11c0
		dialog.destroy()
690
cea4127a11c0
691
cea4127a11c0
		self.tree.set_model(self.data)
692
cea4127a11c0
		self.tree.select(iter)
693
4ccca6b59faa
		self.statusbar.set_status("File unlocked")
694
cea4127a11c0
695
cea4127a11c0
696
cea4127a11c0
	def file_new(self):
697
4ccca6b59faa
		"Opens a new file"
698
4ccca6b59faa
699
a877d43f0987
		if not self.__save_changes(revelation.dialog.FileChangedNew):
700
cea4127a11c0
			self.statusbar.set_status("New file cancelled")
701
4ccca6b59faa
			return
702
cea4127a11c0
703
cea4127a11c0
		self.data.clear()
704
cea4127a11c0
		self.statusbar.set_status("New file created")
705
cea4127a11c0
706
cea4127a11c0
707
4ccca6b59faa
	def file_open(self, file = None, password = None):
708
4ccca6b59faa
		"Opens a data file"
709
cea4127a11c0
710
cea4127a11c0
		try:
711
a877d43f0987
			if not self.__save_changes(revelation.dialog.FileChangedOpen):
712
a877d43f0987
				raise revelation.CancelError
713
cea4127a11c0
714
4ccca6b59faa
			if file is None:
715
b33428fed2ea
				file = revelation.dialog.FileSelector(self, "Select file to open").run()
716
cea4127a11c0
717
4ccca6b59faa
			datafile = revelation.io.DataFile(file, revelation.datahandler.Revelation, password)
718
4ccca6b59faa
			entrystore = self.__file_load(datafile)
719
cea4127a11c0
720
4ccca6b59faa
			if entrystore is None:
721
4ccca6b59faa
				return
722
cea4127a11c0
723
a78b3bc44c12
			self.data.replace_entrystore(entrystore)
724
a78b3bc44c12
			self.statusbar.set_status("Opened file " + datafile.file)
725
a78b3bc44c12
726
a78b3bc44c12
727
cea4127a11c0
		except revelation.CancelError:
728
cea4127a11c0
			self.statusbar.set_status("Open cancelled")
729
cea4127a11c0
730
cea4127a11c0
731
cea4127a11c0
	def file_revert(self):
732
4ccca6b59faa
		"Reverts to the saved version of the file"
733
4ccca6b59faa
734
a877d43f0987
		if not self.__save_changes(revelation.dialog.FileChangedRevert):
735
f54fce826711
			self.statusbar.set_status("Revert cancelled")
736
cea4127a11c0
			return gtk.FALSE
737
cea4127a11c0
738
f54fce826711
		self.data.changed = gtk.FALSE
739
a877d43f0987
		self.file_open(self.data.file, self.data.filepassword)
740
cea4127a11c0
741
cea4127a11c0
742
cea4127a11c0
	def file_save(self, file = None, password = None):
743
4ccca6b59faa
		"Saves a data file"
744
4ccca6b59faa
745
cea4127a11c0
		try:
746
4ccca6b59faa
			if file is None:
747
b33428fed2ea
				file = revelation.dialog.FileSelector(self, "Select file to save data to").run()
748
cea4127a11c0
749
4ccca6b59faa
			datafile = revelation.io.DataFile(file, revelation.datahandler.Revelation, password)
750
cea4127a11c0
751
4ccca6b59faa
			if self.__file_save(datafile) == gtk.TRUE:
752
b181693cfdc7
				self.data.set_file(file, password)
753
4ccca6b59faa
				self.statusbar.set_status("Data saved to file " + file)
754
a877d43f0987
755
4ccca6b59faa
				return gtk.TRUE
756
cea4127a11c0
757
cea4127a11c0
		except revelation.CancelError:
758
cea4127a11c0
			self.statusbar.set_status("Save cancelled")
759
cea4127a11c0
			return gtk.FALSE
760
cea4127a11c0
761
cea4127a11c0
762
cea4127a11c0
	def quit(self):
763
3794d9b38446
		"Quits the application"
764
3794d9b38446
765
a877d43f0987
		if not self.__save_changes(revelation.dialog.FileChangedQuit):
766
cea4127a11c0
			self.statusbar.set_status("Quit cancelled")
767
cea4127a11c0
			return gtk.FALSE
768
cea4127a11c0
769
1aa324cfd765
		width, height = self.get_size()
770
9b3c9903350d
		self.config.set("view/window-width", width)
771
9b3c9903350d
		self.config.set("view/window-height", height)
772
f4e8220f0a3a
773
f4e8220f0a3a
		x, y = self.get_position()
774
f4e8220f0a3a
		self.config.set("view/window-position-x", x)
775
f4e8220f0a3a
		self.config.set("view/window-position-y", y)
776
f4e8220f0a3a
777
9b3c9903350d
		self.config.set("view/pane-position", self.hpaned.get_position())
778
1aa324cfd765
779
cea4127a11c0
		gtk.mainquit()
780
cea4127a11c0
		return gtk.TRUE
781
cea4127a11c0
782
cea4127a11c0
783
268984dc47cf
	def redo(self):
784
268984dc47cf
		"Redo the previously undone operation"
785
268984dc47cf
786
268984dc47cf
		if not self.undoqueue.can_redo():
787
268984dc47cf
			return
788
268984dc47cf
789
268984dc47cf
		action = self.undoqueue.get_action(revelation.data.REDO)
790
268984dc47cf
		iters = self.undoqueue.redo()
791
268984dc47cf
792
268984dc47cf
		if len(iters) > 0:
793
268984dc47cf
			self.tree.select(iters[0])
794
b181693cfdc7
795
268984dc47cf
		else:
796
268984dc47cf
			self.tree.unselect_all()
797
268984dc47cf
798
b181693cfdc7
		self.__file_autosave()
799
268984dc47cf
		self.statusbar.set_status(action.name.capitalize() + " redone")
800
268984dc47cf
801
268984dc47cf
802
b181693cfdc7
	def run(self):
803
3794d9b38446
		"Run the application"
804
3794d9b38446
805
b181693cfdc7
		if len(sys.argv) > 1:
806
b181693cfdc7
			self.file_open(os.path.abspath(sys.argv[1]))
807
a877d43f0987
808
a877d43f0987
		elif self.config.get("file/autoload") and self.config.get("file/autoload_file") != "":
809
9b3c9903350d
			self.file_open(self.config.get("file/autoload_file"))
810
cea4127a11c0
811
cea4127a11c0
		gtk.main()
812
cea4127a11c0
813
cea4127a11c0
814
268984dc47cf
	def undo(self):
815
268984dc47cf
		"Attempts to undo the previous operation"
816
cea4127a11c0
817
268984dc47cf
		if not self.undoqueue.can_undo():
818
268984dc47cf
			return
819
cea4127a11c0
820
268984dc47cf
		action = self.undoqueue.get_action()
821
268984dc47cf
		iters = self.undoqueue.undo()
822
cea4127a11c0
823
cea4127a11c0
		if len(iters) > 0:
824
cea4127a11c0
			self.tree.select(iters[0])
825
b181693cfdc7
826
cea4127a11c0
		else:
827
cea4127a11c0
			self.tree.unselect_all()
828
cea4127a11c0
829
b181693cfdc7
		self.__file_autosave()
830
268984dc47cf
		self.statusbar.set_status(action.name.capitalize() + " undone")
831
cea4127a11c0
832
cea4127a11c0
833
cea4127a11c0
834
cea4127a11c0
if __name__ == "__main__":
835
b181693cfdc7
	Revelation().run()
836
cea4127a11c0