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.in

commit
ba434a17e651
parent
e6bf198935fe
branch
default
tags
revelation-0.4.6

set release dates

1
a207757f8451
#!/usr/bin/env python
2
a207757f8451
3
a207757f8451
#
4
8bcba268295a
# Revelation 0.4.6 - a password manager for GNOME 2
5
a207757f8451
# http://oss.codepoet.no/revelation/
6
a207757f8451
# $Id$
7
a207757f8451
#
8
d230b54e5239
# Copyright (c) 2003-2006 Erik Grinaker
9
a207757f8451
#
10
a207757f8451
# This program is free software; you can redistribute it and/or
11
a207757f8451
# modify it under the terms of the GNU General Public License
12
a207757f8451
# as published by the Free Software Foundation; either version 2
13
a207757f8451
# of the License, or (at your option) any later version.
14
a207757f8451
#
15
a207757f8451
# This program is distributed in the hope that it will be useful,
16
a207757f8451
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17
a207757f8451
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
a207757f8451
# GNU General Public License for more details.
19
a207757f8451
#
20
a207757f8451
# You should have received a copy of the GNU General Public License
21
a207757f8451
# along with this program; if not, write to the Free Software
22
a207757f8451
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23
a207757f8451
#
24
a207757f8451
25
bffe619c6cec
import gnome, gobject, gtk, gtk.gdk, os, pwd, sys
26
a207757f8451
27
9ed2b9f71fd9
if "@pyexecdir@" not in sys.path:
28
9ed2b9f71fd9
	sys.path.insert(0, "@pyexecdir@")
29
a207757f8451
30
0f8c7965fdf3
from revelation import gnomemisc, config, data, datahandler, dialog, entry, io, ui, util
31
a207757f8451
32
a207757f8451
33
c9d89f3ff91d
class Revelation(ui.App):
34
c9d89f3ff91d
	"The Revelation application"
35
a207757f8451
36
a207757f8451
	def __init__(self):
37
a207757f8451
		sys.excepthook = self.__cb_exception
38
086abbb6fc3c
		os.umask(0077)
39
a207757f8451
40
59be49691b29
		self.program = gnome.init(
41
59be49691b29
			config.APPNAME, config.APPNAME,
42
59be49691b29
			gnome.libgnome_module_info_get(), sys.argv, []
43
59be49691b29
		)
44
59be49691b29
45
0f8c7965fdf3
		gnomemisc.gnome_authentication_manager_init()
46
c9d89f3ff91d
		ui.App.__init__(self, config.APPNAME)
47
a207757f8451
48
693fe3e0223f
		self.connect("delete-event", self.__cb_quit)
49
a207757f8451
50
c9d89f3ff91d
		try:
51
c9d89f3ff91d
			self.__init_facilities()
52
c9d89f3ff91d
			self.__init_ui()
53
c9d89f3ff91d
			self.__init_states()
54
a207757f8451
55
c9d89f3ff91d
		except IOError:
56
c9d89f3ff91d
			dialog.Error(self, "Missing data files", "Some of Revelations system files could not be found, please reinstall Revelation.").run()
57
c9d89f3ff91d
			sys.exit(1)
58
c9d89f3ff91d
59
c9d89f3ff91d
		except config.ConfigError:
60
c9d89f3ff91d
			dialog.Error(self, "Missing configuration data", "Revelation could not find its configuration data, please reinstall Revelation.").run()
61
c9d89f3ff91d
			sys.exit(1)
62
c9d89f3ff91d
63
c9d89f3ff91d
		except ui.DataError:
64
c9d89f3ff91d
			dialog.Error(self, "Invalid data files", "Some of Revelations system files contain invalid data, please reinstall Revelation.").run()
65
c9d89f3ff91d
			sys.exit(1)
66
c9d89f3ff91d
67
c9d89f3ff91d
68
c9d89f3ff91d
	def __init_facilities(self):
69
c9d89f3ff91d
		"Sets up various facilities"
70
c9d89f3ff91d
71
c9d89f3ff91d
		self.clipboard		= data.Clipboard()
72
c9d89f3ff91d
		self.config		= config.Config()
73
c9d89f3ff91d
		self.datafile		= io.DataFile(datahandler.Revelation)
74
c9d89f3ff91d
		self.entryclipboard	= data.EntryClipboard()
75
c9d89f3ff91d
		self.entrystore		= data.EntryStore()
76
c9d89f3ff91d
		self.entrysearch	= data.EntrySearch(self.entrystore)
77
c9d89f3ff91d
		self.items		= ui.ItemFactory(self)
78
89c3dd775abc
		self.locktimer		= data.Timer()
79
59be49691b29
		self.sessionclient	= gnome.ui.master_client()
80
c9d89f3ff91d
		self.undoqueue		= data.UndoQueue()
81
c9d89f3ff91d
82
c9d89f3ff91d
		self.datafile.connect("changed", lambda w,f: self.__state_file(f))
83
bffe619c6cec
		self.datafile.connect("content-changed", self.__cb_file_content_changed)
84
c9d89f3ff91d
		self.entryclipboard.connect("content-toggled", lambda w,d: self.__state_clipboard(d))
85
bffe619c6cec
		self.locktimer.connect("ring", self.__cb_file_autolock)
86
c9d89f3ff91d
		self.undoqueue.connect("changed", lambda w: self.__state_undo(self.undoqueue.get_undo_action(), self.undoqueue.get_redo_action()))
87
c9d89f3ff91d
88
bffe619c6cec
		# workaround for gnome-python 2.9.x api bug
89
bffe619c6cec
		gobject.GObject.connect(self.sessionclient, "die", lambda w: self.quit())
90
bffe619c6cec
		gobject.GObject.connect(self.sessionclient, "save-yourself", self.__cb_session_save)
91
bffe619c6cec
92
e5681e842641
		# check if configuration is updated, install schema if not
93
e5681e842641
		if self.__check_config() == False:
94
e5681e842641
95
e5681e842641
			if config.install_schema("%s/revelation.schemas" % config.DIR_GCONFSCHEMAS) == False:
96
e5681e842641
				raise config.ConfigError
97
e5681e842641
98
92c45e701471
			self.config.client.clear_cache()
99
92c45e701471
100
e5681e842641
			if self.__check_config() == False:
101
e5681e842641
				raise config.ConfigError
102
e5681e842641
103
bffe619c6cec
		self.config.monitor("file/autolock_timeout",	lambda k,v,d: self.locktimer.start(v * 60))
104
bffe619c6cec
105
bffe619c6cec
		dialog.EVENT_FILTER = self.__cb_event_filter
106
c9d89f3ff91d
107
c9d89f3ff91d
108
c9d89f3ff91d
	def __init_states(self):
109
c9d89f3ff91d
		"Sets the initial application state"
110
c9d89f3ff91d
111
c9d89f3ff91d
		# set window states
112
c9d89f3ff91d
		self.set_default_size(
113
c9d89f3ff91d
			self.config.get("view/window-width"),
114
c9d89f3ff91d
			self.config.get("view/window-height")
115
c9d89f3ff91d
		)
116
c9d89f3ff91d
117
c9d89f3ff91d
		self.move(
118
c9d89f3ff91d
			self.config.get("view/window-position-x"),
119
c9d89f3ff91d
			self.config.get("view/window-position-y")
120
c9d89f3ff91d
		)
121
c9d89f3ff91d
122
c9d89f3ff91d
		self.hpaned.set_position(
123
c9d89f3ff91d
			self.config.get("view/pane-position")
124
c9d89f3ff91d
		)
125
c9d89f3ff91d
126
c9d89f3ff91d
		# bind ui widgets to config keys
127
c9d89f3ff91d
		bind = {
128
c9d89f3ff91d
			"view/passwords"	: "/menubar/menu-view/view-passwords",
129
c9d89f3ff91d
			"view/searchbar"	: "/menubar/menu-view/view-searchbar",
130
c9d89f3ff91d
			"view/statusbar"	: "/menubar/menu-view/view-statusbar",
131
c9d89f3ff91d
			"view/toolbar"		: "/menubar/menu-view/view-toolbar"
132
c9d89f3ff91d
		}
133
c9d89f3ff91d
134
c9d89f3ff91d
		for key, path in bind.items():
135
c9d89f3ff91d
			ui.config_bind(self.config, key, self.uimanager.get_widget(path))
136
c9d89f3ff91d
137
c9d89f3ff91d
138
c9d89f3ff91d
		self.entryview.display_info()
139
c9d89f3ff91d
		self.show_all()
140
c9d89f3ff91d
141
bffe619c6cec
		self.window.add_filter(self.__cb_event_filter)
142
bffe619c6cec
143
bffe619c6cec
144
c9d89f3ff91d
		# set some variables
145
c9d89f3ff91d
		self.entrysearch.string	= ""
146
c9d89f3ff91d
		self.entrysearch.type	= None
147
c9d89f3ff91d
148
c9d89f3ff91d
		# set ui widget states
149
c9d89f3ff91d
		self.__state_clipboard(self.entryclipboard.has_contents())
150
c9d89f3ff91d
		self.__state_entry([])
151
c9d89f3ff91d
		self.__state_file(None)
152
f80290f7e569
		self.__state_find(self.searchbar.entry.get_text())
153
c9d89f3ff91d
		self.__state_undo(None, None)
154
c9d89f3ff91d
155
c9d89f3ff91d
		# set states from config
156
c9d89f3ff91d
		self.config.monitor("view/searchbar", self.__cb_config_toolbar, self.searchbar)
157
c9d89f3ff91d
		self.config.monitor("view/statusbar", self.__cb_config_toolbar, self.statusbar)
158
c9d89f3ff91d
		self.config.monitor("view/toolbar", self.__cb_config_toolbar, self.toolbar)
159
3fdc3c8552be
		self.config.monitor("view/toolbar_style", self.__cb_config_toolbar_style)
160
c9d89f3ff91d
161
167384ef2e8d
		# give focus to searchbar entry if shown
162
167384ef2e8d
		if self.searchbar.get_property("visible") == True:
163
167384ef2e8d
			self.searchbar.entry.grab_focus()
164
167384ef2e8d
165
c9d89f3ff91d
166
086abbb6fc3c
	def __init_ui(self):
167
c9d89f3ff91d
		"Sets up the UI"
168
a207757f8451
169
c9d89f3ff91d
		# set window icons
170
c70268e6a3c3
		pixbufs = [ self.items.load_icon("revelation", size) for size in ( 48, 32, 24, 16) ]
171
c70268e6a3c3
		pixbufs = [ pixbuf for pixbuf in pixbufs if pixbuf != None ]
172
c70268e6a3c3
173
c70268e6a3c3
		if len(pixbufs) > 0:
174
c70268e6a3c3
			gtk.window_set_default_icon_list(*pixbufs)
175
3dcf95408d8d
176
c9d89f3ff91d
		# load UI definitions
177
c9d89f3ff91d
		self.uimanager.add_actions_from_file(config.DIR_UI + "/actions.xml")
178
c9d89f3ff91d
		self.uimanager.add_ui_from_file(config.DIR_UI + "/menubar.xml")
179
c9d89f3ff91d
		self.uimanager.add_ui_from_file(config.DIR_UI + "/popup-tree.xml")
180
c9d89f3ff91d
		self.uimanager.add_ui_from_file(config.DIR_UI + "/toolbar.xml")
181
a207757f8451
182
c9d89f3ff91d
		# set up callbacks for actions
183
c9d89f3ff91d
		callbacks = {
184
4674492151c3
			"clip-chain"		: lambda w: self.clip_chain(self.entrystore.get_entry(self.tree.get_active())),
185
5a85d838a653
			"clip-copy"		: self.__cb_clip_copy,
186
5a85d838a653
			"clip-cut"		: self.__cb_clip_cut,
187
5a85d838a653
			"clip-paste"		: self.__cb_clip_paste,
188
4674492151c3
			"entry-add"		: lambda w: self.entry_add(None, self.tree.get_active()),
189
4674492151c3
			"entry-edit"		: lambda w: self.entry_edit(self.tree.get_active()),
190
a1eabf1a2e68
			"entry-folder"		: lambda w: self.entry_folder(None, self.tree.get_active()),
191
095f4008dc7f
			"entry-goto"		: lambda w: self.entry_goto(self.tree.get_selected()),
192
4674492151c3
			"entry-remove"		: lambda w: self.entry_remove(self.tree.get_selected()),
193
c9d89f3ff91d
			"file-change-password"	: lambda w: self.file_change_password(),
194
693fe3e0223f
			"file-close"		: self.__cb_quit,
195
c9d89f3ff91d
			"file-export"		: lambda w: self.file_export(),
196
c9d89f3ff91d
			"file-import"		: lambda w: self.file_import(),
197
c9d89f3ff91d
			"file-lock"		: lambda w: self.file_lock(),
198
c9d89f3ff91d
			"file-new"		: lambda w: self.file_new(),
199
c9d89f3ff91d
			"file-open"		: lambda w: self.file_open(),
200
c9d89f3ff91d
			"file-save"		: lambda w: self.file_save(self.datafile.get_file(), self.datafile.get_password()),
201
c9d89f3ff91d
			"file-save-as"		: lambda w: self.file_save(None, None),
202
c9d89f3ff91d
			"find"			: lambda w: self.entry_find(),
203
f80290f7e569
			"find-next"		: lambda w: self.__entry_find(self, self.searchbar.entry.get_text(), self.searchbar.dropdown.get_active_type(), data.SEARCH_NEXT),
204
f80290f7e569
			"find-previous"		: lambda w: self.__entry_find(self, self.searchbar.entry.get_text(), self.searchbar.dropdown.get_active_type(), data.SEARCH_PREVIOUS),
205
e5681e842641
			"help-about"		: lambda w: self.about(),
206
c9d89f3ff91d
			"help-homepage"		: lambda w: gnome.url_show(config.URL),
207
e5681e842641
			"prefs"			: lambda w: self.prefs(),
208
e5681e842641
			"pwchecker"		: lambda w: self.pwcheck(),
209
e5681e842641
			"pwgenerator"		: lambda w: self.pwgen(),
210
693fe3e0223f
			"quit"			: self.__cb_quit,
211
c9d89f3ff91d
			"redo"			: lambda w: self.redo(),
212
c9d89f3ff91d
			"select-all"		: lambda w: self.tree.select_all(),
213
c9d89f3ff91d
			"select-none"		: lambda w: self.tree.unselect_all(),
214
c9d89f3ff91d
			"undo"			: lambda w: self.undo()
215
c9d89f3ff91d
		}
216
a207757f8451
217
c9d89f3ff91d
		for id, callback in callbacks.items():
218
c9d89f3ff91d
			action = self.uimanager.get_action(id)
219
c9d89f3ff91d
			action.connect("activate", callback)
220
a207757f8451
221
c9d89f3ff91d
		# set up toolbar and menus
222
c9d89f3ff91d
		self.set_menus(self.uimanager.get_widget("/menubar"))
223
a207757f8451
224
c9d89f3ff91d
		self.toolbar = self.uimanager.get_widget("/toolbar")
225
3fdc3c8552be
		self.toolbar.connect("popup-context-menu", lambda w,x,y,b: True)
226
c9d89f3ff91d
		self.set_toolbar(self.toolbar)
227
a207757f8451
228
47caac1545f2
		try:
229
47caac1545f2
			detachable = self.config.get("/desktop/gnome/interface/toolbar_detachable")
230
47caac1545f2
231
47caac1545f2
		except config.ConfigError:
232
47caac1545f2
			detachable = False
233
47caac1545f2
234
c9d89f3ff91d
		self.searchbar = ui.Searchbar()
235
47caac1545f2
		self.add_toolbar(self.searchbar, "searchbar", 2, detachable)
236
a207757f8451
237
c9d89f3ff91d
		# set up main application widgets
238
c9d89f3ff91d
		self.tree = ui.EntryTree(self.entrystore)
239
c9d89f3ff91d
		self.scrolledwindow = ui.ScrolledWindow(self.tree)
240
a207757f8451
241
8812d70e39d9
		self.entryview = ui.EntryView(self.config, self.clipboard)
242
c9d89f3ff91d
		alignment = gtk.Alignment(0.5, 0.4, 0, 0)
243
c9d89f3ff91d
		alignment.add(self.entryview)
244
a7c01d3b4b22
245
c9d89f3ff91d
		self.hpaned = ui.HPaned(self.scrolledwindow, alignment)
246
c9d89f3ff91d
		self.set_contents(self.hpaned)
247
c9d89f3ff91d
248
8077271f872d
		# set up drag-and-drop
249
f5f9aadf2e19
		self.drag_dest_set(gtk.DEST_DEFAULT_ALL, ( ( "text/uri-list", 0, 0 ), ), gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE | gtk.gdk.ACTION_LINK )
250
f5f9aadf2e19
		self.connect("drag_data_received", self.__cb_drag_dest)
251
f5f9aadf2e19
252
8077271f872d
		self.tree.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, ( ( "revelation/treerow", gtk.TARGET_SAME_APP | gtk.TARGET_SAME_WIDGET, 0), ), gtk.gdk.ACTION_MOVE)
253
8077271f872d
		self.tree.enable_model_drag_dest(( ( "revelation/treerow", gtk.TARGET_SAME_APP | gtk.TARGET_SAME_WIDGET, 0), ), gtk.gdk.ACTION_MOVE)
254
8077271f872d
		self.tree.connect("drag_data_received", self.__cb_tree_drag_received)
255
8077271f872d
256
c9d89f3ff91d
		# set up callbacks
257
f80290f7e569
		self.searchbar.connect("key-press-event", self.__cb_searchbar_key_press)
258
f80290f7e569
		self.searchbar.button_next.connect("clicked", self.__cb_searchbar_button_clicked, data.SEARCH_NEXT)
259
f80290f7e569
		self.searchbar.button_prev.connect("clicked", self.__cb_searchbar_button_clicked, data.SEARCH_PREVIOUS)
260
f80290f7e569
		self.searchbar.entry.connect("changed", lambda w: self.__state_find(self.searchbar.entry.get_text()))
261
c9d89f3ff91d
262
c9d89f3ff91d
		self.tree.connect("popup", lambda w,d: self.popup(self.uimanager.get_widget("/popup-tree"), d.button, d.time))
263
6cf6849eed72
		self.tree.connect("doubleclick", self.__cb_tree_doubleclick)
264
c9d89f3ff91d
		self.tree.connect("key-press-event", self.__cb_tree_keypress)
265
c9d89f3ff91d
		self.tree.selection.connect("changed", lambda w: self.entryview.display_entry(self.entrystore.get_entry(self.tree.get_active())))
266
c9d89f3ff91d
		self.tree.selection.connect("changed", lambda w: self.__state_entry(self.tree.get_selected()))
267
c9d89f3ff91d
268
c9d89f3ff91d
269
c9d89f3ff91d
270
c9d89f3ff91d
	##### STATE HANDLERS #####
271
c9d89f3ff91d
272
59be49691b29
	def __save_state(self):
273
59be49691b29
		"Saves the current application state"
274
59be49691b29
275
59be49691b29
		width, height = self.get_size()
276
59be49691b29
		self.config.set("view/window-width", width)
277
59be49691b29
		self.config.set("view/window-height", height)
278
59be49691b29
279
59be49691b29
		x, y = self.get_position()
280
59be49691b29
		self.config.set("view/window-position-x", x)
281
59be49691b29
		self.config.set("view/window-position-y", y)
282
59be49691b29
283
59be49691b29
		self.config.set("view/pane-position", self.hpaned.get_position())
284
59be49691b29
285
59be49691b29
286
c9d89f3ff91d
	def __state_clipboard(self, has_contents):
287
c9d89f3ff91d
		"Sets states based on the clipboard contents"
288
c9d89f3ff91d
289
c9d89f3ff91d
		self.uimanager.get_action("clip-paste").set_property("sensitive", has_contents)
290
c9d89f3ff91d
291
c9d89f3ff91d
292
c9d89f3ff91d
	def __state_entry(self, iters):
293
c9d89f3ff91d
		"Sets states for entry-dependant ui items"
294
c9d89f3ff91d
295
c9d89f3ff91d
		# widget sensitivity based on number of entries
296
c9d89f3ff91d
		self.uimanager.get_action_group("entry-multiple").set_sensitive(len(iters) > 0)
297
c9d89f3ff91d
		self.uimanager.get_action_group("entry-single").set_sensitive(len(iters) == 1)
298
c9d89f3ff91d
		self.uimanager.get_action_group("entry-optional").set_sensitive(len(iters) < 2)
299
c9d89f3ff91d
300
260e1dfe3e88
301
260e1dfe3e88
		# copy password sensitivity
302
260e1dfe3e88
		s = False
303
260e1dfe3e88
304
260e1dfe3e88
		for iter in iters:
305
260e1dfe3e88
			e = self.entrystore.get_entry(iter)
306
260e1dfe3e88
307
260e1dfe3e88
			for f in e.fields:
308
260e1dfe3e88
				if f.datatype == entry.DATATYPE_PASSWORD and f.value != "":
309
260e1dfe3e88
					s = True
310
260e1dfe3e88
311
deb6e8bed5dc
		self.uimanager.get_action("clip-chain").set_property("sensitive", s)
312
260e1dfe3e88
313
260e1dfe3e88
314
095f4008dc7f
		# goto sensitivity
315
c9d89f3ff91d
		try:
316
c9d89f3ff91d
			for iter in iters:
317
c9d89f3ff91d
				e = self.entrystore.get_entry(iter)
318
c9d89f3ff91d
319
c9d89f3ff91d
				if self.config.get("launcher/%s" % e.id) not in ( "", None ):
320
c9d89f3ff91d
					s = True
321
c9d89f3ff91d
					break
322
c9d89f3ff91d
323
c9d89f3ff91d
			else:
324
c9d89f3ff91d
				s = False
325
c9d89f3ff91d
326
c9d89f3ff91d
		except config.ConfigError:
327
c9d89f3ff91d
			s = False
328
c9d89f3ff91d
329
095f4008dc7f
		self.uimanager.get_action_group("entry-goto").set_sensitive(s)
330
dad9b7b301d1
331
a207757f8451
332
c9d89f3ff91d
	def __state_file(self, file):
333
c9d89f3ff91d
		"Sets states based on file"
334
a207757f8451
335
c9d89f3ff91d
		self.uimanager.get_action_group("file-exists").set_sensitive(file is not None)
336
a207757f8451
337
c9d89f3ff91d
		if file is not None:
338
c9d89f3ff91d
			self.set_title(os.path.basename(file))
339
a207757f8451
340
c9d89f3ff91d
			if io.file_is_local(file):
341
c9d89f3ff91d
				os.chdir(os.path.dirname(file))
342
a7c01d3b4b22
343
c9d89f3ff91d
		else:
344
c9d89f3ff91d
			self.set_title("[New file]")
345
a207757f8451
346
086abbb6fc3c
347
c9d89f3ff91d
	def __state_find(self, string):
348
c9d89f3ff91d
		"Sets states based on the current search string"
349
086abbb6fc3c
350
c9d89f3ff91d
		self.uimanager.get_action_group("find").set_sensitive(string != "")
351
086abbb6fc3c
352
086abbb6fc3c
353
c9d89f3ff91d
	def __state_undo(self, undoaction, redoaction):
354
c9d89f3ff91d
		"Sets states based on undoqueue actions"
355
086abbb6fc3c
356
c9d89f3ff91d
		if undoaction is None:
357
c9d89f3ff91d
			s, l = False, "_Undo"
358
086abbb6fc3c
359
c9d89f3ff91d
		else:
360
c9d89f3ff91d
			s, l = True, "_Undo %s" % undoaction[1].lower()
361
086abbb6fc3c
362
c9d89f3ff91d
		action = self.uimanager.get_action("undo")
363
c9d89f3ff91d
		action.set_property("sensitive", s)
364
c9d89f3ff91d
		action.set_property("label", l)
365
a207757f8451
366
a207757f8451
367
c9d89f3ff91d
		if redoaction is None:
368
c9d89f3ff91d
			s, l = False, "_Redo"
369
a207757f8451
370
c9d89f3ff91d
		else:
371
c9d89f3ff91d
			s, l = True, "_Redo %s" % redoaction[1].lower()
372
a207757f8451
373
c9d89f3ff91d
		action = self.uimanager.get_action("redo")
374
c9d89f3ff91d
		action.set_property("sensitive", s)
375
c9d89f3ff91d
		action.set_property("label", l)
376
a207757f8451
377
a207757f8451
378
a207757f8451
379
a207757f8451
380
c9d89f3ff91d
	##### MISC CALLBACKS #####
381
086abbb6fc3c
382
5a85d838a653
	def __cb_clip_copy(self, widget, data = None):
383
5a85d838a653
		"Handles copying to the clipboard"
384
5a85d838a653
385
5a85d838a653
		focuswidget = self.get_focus()
386
5a85d838a653
387
5a85d838a653
		if focuswidget is self.tree:
388
5a85d838a653
			self.clip_copy(self.tree.get_selected())
389
5a85d838a653
390
5a85d838a653
		elif isinstance(focuswidget, gtk.Label) or isinstance(focuswidget, gtk.Entry):
391
5a85d838a653
			focuswidget.emit("copy-clipboard")
392
5a85d838a653
393
5a85d838a653
394
5a85d838a653
	def __cb_clip_cut(self, widget, data = None):
395
5a85d838a653
		"Handles cutting to clipboard"
396
5a85d838a653
397
5a85d838a653
		focuswidget = self.get_focus()
398
5a85d838a653
399
5a85d838a653
		if focuswidget is self.tree:
400
5a85d838a653
			self.clip_cut(self.tree.get_selected())
401
5a85d838a653
402
5a85d838a653
		elif isinstance(focuswidget, gtk.Entry):
403
5a85d838a653
			focuswidget.emit("cut-clipboard")
404
5a85d838a653
405
5a85d838a653
406
5a85d838a653
	def __cb_clip_paste(self, widget, data = None):
407
5a85d838a653
		"Handles pasting from clipboard"
408
5a85d838a653
409
5a85d838a653
		focuswidget = self.get_focus()
410
5a85d838a653
411
5a85d838a653
		if focuswidget is self.tree:
412
5a85d838a653
			self.clip_paste(self.entryclipboard.get(), self.tree.get_active())
413
5a85d838a653
414
5a85d838a653
		elif isinstance(focuswidget, gtk.Entry):
415
5a85d838a653
			focuswidget.emit("paste-clipboard")
416
5a85d838a653
417
5a85d838a653
418
f5f9aadf2e19
	def __cb_drag_dest(self, widget, context, x, y, seldata, info, time, userdata = None):
419
f5f9aadf2e19
		"Handles file drops"
420
f5f9aadf2e19
421
f5f9aadf2e19
		if seldata.data is None:
422
f5f9aadf2e19
			return
423
f5f9aadf2e19
424
f5f9aadf2e19
		files = [ file.strip() for file in seldata.data.split("\n") if file.strip() != "" ]
425
f5f9aadf2e19
426
b57efdd083f5
		if len(files) > 0:
427
b57efdd083f5
			self.file_open(files[0])
428
f5f9aadf2e19
429
f5f9aadf2e19
430
bffe619c6cec
	def __cb_event_filter(self, event):
431
bffe619c6cec
		"Event filter for gdk window"
432
bffe619c6cec
433
bffe619c6cec
		self.locktimer.reset()
434
bffe619c6cec
		return gtk.gdk.FILTER_CONTINUE
435
bffe619c6cec
436
bffe619c6cec
437
a207757f8451
	def __cb_exception(self, type, value, trace):
438
a207757f8451
		"Callback for unhandled exceptions"
439
a207757f8451
440
9e161d94bffc
		if type == KeyboardInterrupt:
441
9e161d94bffc
			sys.exit(1)
442
9e161d94bffc
443
c9d89f3ff91d
		traceback = util.trace_exception(type, value, trace)
444
a207757f8451
		sys.stderr.write(traceback)
445
4db37324fd05
446
c9d89f3ff91d
		if dialog.Exception(self, traceback).run() == True:
447
c9d89f3ff91d
			gtk.main()
448
4db37324fd05
449
4db37324fd05
		else:
450
4db37324fd05
			sys.exit(1)
451
a207757f8451
452
a207757f8451
453
bffe619c6cec
	def __cb_file_content_changed(self, widget, file):
454
bffe619c6cec
		"Callback for changed file"
455
bffe619c6cec
456
bffe619c6cec
		try:
457
bffe619c6cec
			if dialog.FileChanged(self, file).run() == True:
458
bffe619c6cec
				self.file_open(self.datafile.get_file(), self.datafile.get_password())
459
bffe619c6cec
460
bffe619c6cec
		except dialog.CancelError:
461
bffe619c6cec
			self.statusbar.set_status("Open cancelled")
462
bffe619c6cec
463
bffe619c6cec
464
bffe619c6cec
	def __cb_file_autolock(self, widget, data = None):
465
bffe619c6cec
		"Callback for locking the file"
466
bffe619c6cec
467
bffe619c6cec
		if self.config.get("file/autolock") == True:
468
bffe619c6cec
			self.file_lock()
469
bffe619c6cec
470
bffe619c6cec
471
693fe3e0223f
	def __cb_quit(self, widget, data = None):
472
693fe3e0223f
		"Callback for quit"
473
693fe3e0223f
474
693fe3e0223f
		if self.quit() == False:
475
693fe3e0223f
			return True
476
693fe3e0223f
477
693fe3e0223f
		else:
478
693fe3e0223f
			return False
479
693fe3e0223f
480
693fe3e0223f
481
f80290f7e569
	def __cb_searchbar_button_clicked(self, widget, direction = data.SEARCH_NEXT):
482
bd0aa2a05f0f
		"Callback for searchbar button clicks"
483
bd0aa2a05f0f
484
f80290f7e569
		self.__entry_find(self, self.searchbar.entry.get_text(), self.searchbar.dropdown.get_active_type(), direction)
485
bd0aa2a05f0f
		self.searchbar.entry.select_region(0, -1)
486
bd0aa2a05f0f
487
bd0aa2a05f0f
488
f80290f7e569
	def __cb_searchbar_key_press(self, widget, data):
489
f80290f7e569
		"Callback for searchbar key presses"
490
f80290f7e569
491
f80290f7e569
		# escape
492
f80290f7e569
		if data.keyval == 65307:
493
f80290f7e569
			self.config.set("view/searchbar", False)
494
f80290f7e569
495
f80290f7e569
496
59be49691b29
	def __cb_session_save(self, widget, phase, what, end, interaction, fast, data = None):
497
59be49691b29
		"Handles session saves"
498
59be49691b29
499
59be49691b29
		self.sessionclient.set_current_directory(os.getcwd())
500
59be49691b29
		self.sessionclient.set_clone_command(sys.argv[0])
501
59be49691b29
502
59be49691b29
		if self.datafile.get_file() == None:
503
59be49691b29
			self.sessionclient.set_restart_command(1, [ sys.argv[0] ])
504
59be49691b29
505
59be49691b29
		else:
506
59be49691b29
			self.sessionclient.set_restart_command(2, [ sys.argv[0], self.datafile.get_file() ] )
507
59be49691b29
508
59be49691b29
		self.__save_state()
509
59be49691b29
510
59be49691b29
		return True
511
59be49691b29
512
59be49691b29
513
6cf6849eed72
	def __cb_tree_doubleclick(self, widget, data = None):
514
6cf6849eed72
		"Handles doubleclicks on the tree"
515
6cf6849eed72
516
7f9cbee4434c
		if self.config.get("behavior/doubleclick") == "edit":
517
7f9cbee4434c
			self.entry_edit(self.tree.get_active())
518
7f9cbee4434c
519
bb1810f15e75
		elif self.config.get("behavior/doubleclick") == "copy":
520
bb1810f15e75
			self.clip_chain(self.entrystore.get_entry(self.tree.get_active()))
521
bb1810f15e75
522
7f9cbee4434c
		else:
523
6ad3b900b15b
			self.entry_goto((self.tree.get_active(),))
524
6cf6849eed72
525
6cf6849eed72
526
8077271f872d
	def __cb_tree_drag_received(self, tree, context, x, y, seldata, info, time):
527
8077271f872d
		"Callback for drag drops on the treeview"
528
8077271f872d
529
8077271f872d
		# get source and destination data
530
8077271f872d
		sourceiters = self.entrystore.filter_parents(self.tree.get_selected())
531
8077271f872d
		destrow = self.tree.get_dest_row_at_pos(x, y)
532
8077271f872d
533
8077271f872d
		if destrow is None:
534
8077271f872d
			destpath = ( self.entrystore.iter_n_children(None) - 1, )
535
8077271f872d
			pos = gtk.TREE_VIEW_DROP_AFTER
536
8077271f872d
537
8077271f872d
		else:
538
8077271f872d
			destpath, pos = destrow
539
8077271f872d
540
8077271f872d
		destiter = self.entrystore.get_iter(destpath)
541
9e1db933e6ed
		destpath = self.entrystore.get_path(destiter)
542
8077271f872d
543
9e1db933e6ed
		# avoid drops to current iter or descentants
544
fe71b6d3fc2a
		# (the OverflowError exceptions are basically workarounds
545
7a5c80e512e5
		# for a suspected pygtk bug - see bug 300012)
546
8077271f872d
		for sourceiter in sourceiters:
547
9e1db933e6ed
			sourcepath = self.entrystore.get_path(sourceiter)
548
9e1db933e6ed
549
9e1db933e6ed
			if self.entrystore.is_ancestor(sourceiter, destiter) == True or sourcepath == destpath:
550
fe71b6d3fc2a
				try:
551
fe71b6d3fc2a
					context.finish(False, False, long(time))
552
fe71b6d3fc2a
553
fe71b6d3fc2a
				except OverflowError:
554
fe71b6d3fc2a
					context.finish(False, False, 0L)
555
fe71b6d3fc2a
556
8077271f872d
				return
557
8077271f872d
558
8650886bcc92
			elif pos == gtk.TREE_VIEW_DROP_BEFORE and sourcepath[:-1] == destpath[:-1] and sourcepath[-1] == destpath[-1] - 1:
559
fe71b6d3fc2a
				try:
560
fe71b6d3fc2a
					context.finish(False, False, long(time))
561
fe71b6d3fc2a
562
fe71b6d3fc2a
				except OverflowError:
563
fe71b6d3fc2a
					context.finish(False, False, 0L)
564
fe71b6d3fc2a
565
9e1db933e6ed
				return
566
9e1db933e6ed
567
8650886bcc92
			elif pos == gtk.TREE_VIEW_DROP_AFTER and sourcepath[:-1] == destpath[:-1] and sourcepath[-1] == destpath[-1] + 1:
568
fe71b6d3fc2a
				try:
569
fe71b6d3fc2a
					context.finish(False, False, long(time))
570
fe71b6d3fc2a
571
fe71b6d3fc2a
				except OverflowError:
572
fe71b6d3fc2a
					context.finish(False, False, 0L)
573
fe71b6d3fc2a
574
9e1db933e6ed
				return
575
9e1db933e6ed
576
9e1db933e6ed
577
8077271f872d
		# move the entries
578
8077271f872d
		if pos in ( gtk.TREE_VIEW_DROP_INTO_OR_BEFORE, gtk.TREE_VIEW_DROP_INTO_OR_AFTER):
579
8077271f872d
			parent = destiter
580
8077271f872d
			sibling = None
581
8077271f872d
582
8077271f872d
		elif pos == gtk.TREE_VIEW_DROP_BEFORE:
583
8077271f872d
			parent = self.entrystore.iter_parent(destiter)
584
8077271f872d
			sibling = destiter
585
8077271f872d
586
8077271f872d
		elif pos == gtk.TREE_VIEW_DROP_AFTER:
587
8077271f872d
			parent = self.entrystore.iter_parent(destiter)
588
8077271f872d
589
8077271f872d
			sibpath = list(destpath)
590
8077271f872d
			sibpath[-1] += 1
591
8077271f872d
			sibling = self.entrystore.get_iter(sibpath)
592
8077271f872d
593
8077271f872d
		self.entry_move(sourceiters, parent, sibling)
594
fe71b6d3fc2a
595
fe71b6d3fc2a
		try:
596
fe71b6d3fc2a
			context.finish(False, False, long(time))
597
fe71b6d3fc2a
598
fe71b6d3fc2a
		except OverflowError:
599
fe71b6d3fc2a
			context.finish(False, False, 0L)
600
8077271f872d
601
8077271f872d
602
c9d89f3ff91d
	def __cb_tree_keypress(self, widget, data = None):
603
a207757f8451
		"Handles key presses for the tree"
604
a207757f8451
605
a207757f8451
		# return
606
a207757f8451
		if data.keyval == 65293:
607
5f46bd4ac49c
			self.entry_edit(self.tree.get_active())
608
a207757f8451
609
a207757f8451
		# insert
610
a207757f8451
		elif data.keyval == 65379:
611
5f46bd4ac49c
			self.entry_add(None, self.tree.get_active())
612
a207757f8451
613
a207757f8451
		# delete
614
a207757f8451
		elif data.keyval == 65535:
615
5f46bd4ac49c
			self.entry_remove(self.tree.get_selected())
616
a207757f8451
617
a207757f8451
618
a207757f8451
619
c9d89f3ff91d
	##### CONFIG CALLBACKS #####
620
c9d89f3ff91d
621
c9d89f3ff91d
	def __cb_config_toolbar(self, config, value, toolbar):
622
c9d89f3ff91d
		"Config callback for showing toolbars"
623
c9d89f3ff91d
624
c9d89f3ff91d
		if value == True:
625
c9d89f3ff91d
			toolbar.show()
626
c9d89f3ff91d
627
c9d89f3ff91d
		else:
628
c9d89f3ff91d
			toolbar.hide()
629
c9d89f3ff91d
630
c9d89f3ff91d
631
3fdc3c8552be
	def __cb_config_toolbar_style(self, config, value, data = None):
632
3fdc3c8552be
		"Config callback for setting toolbar style"
633
3fdc3c8552be
634
3fdc3c8552be
		if value == "both":
635
3fdc3c8552be
			self.toolbar.set_style(gtk.TOOLBAR_BOTH)
636
3fdc3c8552be
637
3fdc3c8552be
		elif value == "both-horiz":
638
3fdc3c8552be
			self.toolbar.set_style(gtk.TOOLBAR_BOTH_HORIZ)
639
3fdc3c8552be
640
3fdc3c8552be
		elif value == "icons":
641
3fdc3c8552be
			self.toolbar.set_style(gtk.TOOLBAR_ICONS)
642
3fdc3c8552be
643
3fdc3c8552be
		elif value == "text":
644
3fdc3c8552be
			self.toolbar.set_style(gtk.TOOLBAR_TEXT)
645
3fdc3c8552be
646
3fdc3c8552be
		else:
647
3fdc3c8552be
			self.toolbar.unset_style()
648
3fdc3c8552be
649
c9d89f3ff91d
650
c9d89f3ff91d
	#### UNDO / REDO CALLBACKS #####
651
c9d89f3ff91d
652
c9d89f3ff91d
	def __cb_redo_add(self, name, actiondata):
653
c9d89f3ff91d
		"Redoes an add action"
654
c9d89f3ff91d
655
c9d89f3ff91d
		path, e = actiondata
656
c9d89f3ff91d
		parent = self.entrystore.get_iter(path[:-1])
657
c9d89f3ff91d
		sibling = self.entrystore.get_iter(path)
658
c9d89f3ff91d
659
c9d89f3ff91d
		iter = self.entrystore.add_entry(e, parent, sibling)
660
c9d89f3ff91d
		self.tree.select(iter)
661
c9d89f3ff91d
662
c9d89f3ff91d
663
c9d89f3ff91d
	def __cb_redo_edit(self, name, actiondata):
664
c9d89f3ff91d
		"Redoes an edit action"
665
c9d89f3ff91d
666
c9d89f3ff91d
		path, preentry, postentry = actiondata
667
c9d89f3ff91d
		iter = self.entrystore.get_iter(path)
668
c9d89f3ff91d
669
bffe619c6cec
		self.entrystore.update_entry(iter, postentry)
670
c9d89f3ff91d
		self.tree.select(iter)
671
c9d89f3ff91d
672
c9d89f3ff91d
673
c9d89f3ff91d
	def __cb_redo_import(self, name, actiondata):
674
c9d89f3ff91d
		"Redoes an import action"
675
c9d89f3ff91d
676
c9d89f3ff91d
		paths, entrystore = actiondata
677
c9d89f3ff91d
		self.entrystore.import_entry(entrystore, None)
678
c9d89f3ff91d
679
c9d89f3ff91d
680
8077271f872d
	def __cb_redo_move(self, name, actiondata):
681
8077271f872d
		"Redoes a move action"
682
8077271f872d
683
8077271f872d
		newiters = []
684
8077271f872d
685
8077271f872d
		for prepath, postpath in actiondata:
686
8077271f872d
			prepath, postpath = list(prepath), list(postpath)
687
8077271f872d
688
8077271f872d
			# adjust path if necessary
689
de53973fd104
			if len(prepath) <= len(postpath):
690
de53973fd104
				if prepath[:-1] == postpath[:len(prepath) - 1]:
691
de53973fd104
					if prepath[-1] <= postpath[len(prepath) - 1]:
692
de53973fd104
						postpath[len(prepath) - 1] += 1
693
8077271f872d
694
8077271f872d
			newiter = self.entrystore.move_entry(
695
8077271f872d
				self.entrystore.get_iter(prepath),
696
8077271f872d
				self.entrystore.get_iter(postpath[:-1]),
697
8077271f872d
				self.entrystore.get_iter(postpath)
698
8077271f872d
			)
699
8077271f872d
700
8077271f872d
			newiters.append(newiter)
701
8077271f872d
702
8077271f872d
		if len(newiters) > 0:
703
8077271f872d
			self.tree.select(newiters[0])
704
8077271f872d
705
8077271f872d
706
c9d89f3ff91d
	def __cb_redo_paste(self, name, actiondata):
707
c9d89f3ff91d
		"Redoes a paste action"
708
c9d89f3ff91d
709
c9d89f3ff91d
		entrystore, parentpath, paths = actiondata
710
c9d89f3ff91d
		iters = self.entrystore.import_entry(entrystore, None, self.entrystore.get_iter(parentpath))
711
c9d89f3ff91d
712
c9d89f3ff91d
		if len(iters) > 0:
713
c9d89f3ff91d
			self.tree.select(iters[0])
714
c9d89f3ff91d
715
c9d89f3ff91d
716
c9d89f3ff91d
	def __cb_redo_remove(self, name, actiondata):
717
c9d89f3ff91d
		"Redoes a remove action"
718
c9d89f3ff91d
719
c9d89f3ff91d
		iters = []
720
c9d89f3ff91d
		for path, entrystore in actiondata:
721
c9d89f3ff91d
			iters.append(self.entrystore.get_iter(path))
722
c9d89f3ff91d
723
c9d89f3ff91d
		for iter in iters:
724
c9d89f3ff91d
			self.entrystore.remove_entry(iter)
725
c9d89f3ff91d
726
c9d89f3ff91d
		self.tree.unselect_all()
727
c9d89f3ff91d
728
c9d89f3ff91d
729
c9d89f3ff91d
	def __cb_undo_add(self, name, actiondata):
730
c9d89f3ff91d
		"Undoes an add action"
731
c9d89f3ff91d
732
c9d89f3ff91d
		path, e = actiondata
733
c9d89f3ff91d
734
c9d89f3ff91d
		self.entrystore.remove_entry(self.entrystore.get_iter(path))
735
c9d89f3ff91d
		self.tree.unselect_all()
736
c9d89f3ff91d
737
c9d89f3ff91d
738
c9d89f3ff91d
	def __cb_undo_edit(self, name, actiondata):
739
c9d89f3ff91d
		"Undoes an edit action"
740
c9d89f3ff91d
741
c9d89f3ff91d
		path, preentry, postentry = actiondata
742
c9d89f3ff91d
		iter = self.entrystore.get_iter(path)
743
c9d89f3ff91d
744
bffe619c6cec
		self.entrystore.update_entry(iter, preentry)
745
c9d89f3ff91d
		self.tree.select(iter)
746
c9d89f3ff91d
747
c9d89f3ff91d
748
c9d89f3ff91d
	def __cb_undo_import(self, name, actiondata):
749
c9d89f3ff91d
		"Undoes an import action"
750
c9d89f3ff91d
751
c9d89f3ff91d
		paths, entrystore = actiondata
752
c9d89f3ff91d
		iters = [ self.entrystore.get_iter(path) for path in paths ]
753
c9d89f3ff91d
754
c9d89f3ff91d
		for iter in iters:
755
c9d89f3ff91d
			self.entrystore.remove_entry(iter)
756
c9d89f3ff91d
757
c9d89f3ff91d
		self.tree.unselect_all()
758
c9d89f3ff91d
759
c9d89f3ff91d
760
8077271f872d
	def __cb_undo_move(self, name, actiondata):
761
8077271f872d
		"Undoes a move action"
762
8077271f872d
763
6bb7821fddaf
		actiondata = actiondata[:]
764
6bb7821fddaf
		actiondata.reverse()
765
6bb7821fddaf
766
8077271f872d
		newiters = []
767
8077271f872d
768
8077271f872d
		for prepath, postpath in actiondata:
769
8077271f872d
			prepath, postpath = list(prepath), list(postpath)
770
8077271f872d
771
8077271f872d
			# adjust path if necessary
772
de53973fd104
			if len(postpath) <= len(prepath):
773
de53973fd104
				if postpath[:-1] == prepath[:len(postpath) - 1]:
774
de53973fd104
					if postpath[-1] <= prepath[len(postpath) - 1]:
775
de53973fd104
						prepath[len(postpath) - 1] += 1
776
8077271f872d
777
8077271f872d
			newiter = self.entrystore.move_entry(
778
8077271f872d
				self.entrystore.get_iter(postpath),
779
8077271f872d
				self.entrystore.get_iter(prepath[:-1]),
780
8077271f872d
				self.entrystore.get_iter(prepath)
781
8077271f872d
			)
782
8077271f872d
783
8077271f872d
			newiters.append(newiter)
784
8077271f872d
785
8077271f872d
		if len(newiters) > 0:
786
6bb7821fddaf
			self.tree.select(newiters[-1])
787
8077271f872d
788
8077271f872d
789
c9d89f3ff91d
	def __cb_undo_paste(self, name, actiondata):
790
c9d89f3ff91d
		"Undoes a paste action"
791
c9d89f3ff91d
792
c9d89f3ff91d
		entrystore, parentpath, paths = actiondata
793
c9d89f3ff91d
		iters = [ self.entrystore.get_iter(path) for path in paths ]
794
c9d89f3ff91d
795
c9d89f3ff91d
		for iter in iters:
796
c9d89f3ff91d
			self.entrystore.remove_entry(iter)
797
c9d89f3ff91d
798
c9d89f3ff91d
		self.tree.unselect_all()
799
c9d89f3ff91d
800
c9d89f3ff91d
801
c9d89f3ff91d
	def __cb_undo_remove(self, name, actiondata):
802
c9d89f3ff91d
		"Undoes a remove action"
803
c9d89f3ff91d
804
c9d89f3ff91d
		iters = []
805
c9d89f3ff91d
		for path, entrystore in actiondata:
806
c9d89f3ff91d
			parent = self.entrystore.get_iter(path[:-1])
807
c9d89f3ff91d
			sibling = self.entrystore.get_iter(path)
808
c9d89f3ff91d
809
c9d89f3ff91d
			iter = self.entrystore.import_entry(entrystore, entrystore.iter_nth_child(None, 0), parent, sibling)
810
c9d89f3ff91d
			iters.append(iter)
811
c9d89f3ff91d
812
c9d89f3ff91d
		self.tree.select(iters[0])
813
c9d89f3ff91d
814
c9d89f3ff91d
815
c9d89f3ff91d
816
c9d89f3ff91d
	##### PRIVATE METHODS #####
817
c9d89f3ff91d
818
e5681e842641
	def __check_config(self):
819
e5681e842641
		"Checks if the configuration is correct"
820
e5681e842641
821
e5681e842641
		try:
822
e5681e842641
			self.config.get("launcher/website")
823
e5681e842641
			self.config.get("view/searchbar")
824
e5681e842641
			self.config.get("clipboard/chain_username")
825
e5681e842641
			self.config.get("behavior/doubleclick")
826
3fdc3c8552be
			self.config.get("view/toolbar_style")
827
e5681e842641
828
e5681e842641
			return True
829
e5681e842641
830
e5681e842641
		except config.ConfigError:
831
e5681e842641
			return False
832
e5681e842641
833
e5681e842641
834
c9d89f3ff91d
	def __entry_find(self, parent, string, entrytype, direction = data.SEARCH_NEXT):
835
a207757f8451
		"Searches for an entry"
836
a207757f8451
837
c9d89f3ff91d
		match = self.entrysearch.find(string, entrytype, self.tree.get_active(), direction)
838
a207757f8451
839
f80290f7e569
		if match != None:
840
c9d89f3ff91d
			self.tree.select(match)
841
f80290f7e569
			self.statusbar.set_status("Match found for '%s'" % string)
842
a207757f8451
843
a207757f8451
		else:
844
f80290f7e569
			self.statusbar.set_status("No match found for '%s'" % string)
845
f80290f7e569
			dialog.Error(parent, "No match found", "The string '%s' does not match any entries. Try searching for a different phrase." % string).run()
846
a207757f8451
847
a207757f8451
848
a207757f8451
	def __file_autosave(self):
849
c9d89f3ff91d
		"Autosaves the current file if needed"
850
a207757f8451
851
c9d89f3ff91d
		if self.datafile.get_file() is None or self.datafile.get_password() is None:
852
a207757f8451
			return
853
a207757f8451
854
c9d89f3ff91d
		if self.config.get("file/autosave") == False:
855
a207757f8451
			return
856
a207757f8451
857
c9d89f3ff91d
		self.__file_save(self.datafile.get_file(), self.datafile.get_password())
858
c9d89f3ff91d
		self.entrystore.changed = False
859
a207757f8451
860
a207757f8451
861
9e161d94bffc
	def __file_load(self, file, password, datafile = None):
862
c9d89f3ff91d
		"Loads data from a data file into an entrystore"
863
9e161d94bffc
864
a207757f8451
		try:
865
c9d89f3ff91d
			if datafile is None:
866
c9d89f3ff91d
				datafile = self.datafile
867
c9d89f3ff91d
868
9e161d94bffc
			while 1:
869
9e161d94bffc
				try:
870
c9d89f3ff91d
					return datafile.load(file, password, lambda: dialog.PasswordOpen(self, os.path.basename(file)).run())
871
a207757f8451
872
c9d89f3ff91d
				except datahandler.PasswordError:
873
c9d89f3ff91d
					dialog.Error(self, "Incorrect password", "The password you entered for the file'%s' was not correct." % file).run()
874
a207757f8451
875
c9d89f3ff91d
		except datahandler.FormatError:
876
c9d89f3ff91d
			self.statusbar.set_status("Open failed")
877
c9d89f3ff91d
			dialog.Error(self, "Invalid file format", "The file '%s' contains invalid data." % file).run()
878
a207757f8451
879
f75bf9f5bc0d
		except ( datahandler.DataError, entry.EntryTypeError, entry.EntryFieldError ):
880
c9d89f3ff91d
			self.statusbar.set_status("Open failed")
881
c9d89f3ff91d
			dialog.Error(self, "Unknown data", "The file '%s' contains unknown data. It may have been created by a more recent version of Revelation.." % file).run()
882
a207757f8451
883
c9d89f3ff91d
		except datahandler.VersionError:
884
c9d89f3ff91d
			self.statusbar.set_status("Open failed")
885
f5f9aadf2e19
			dialog.Error(self, "Unknown data version", "The file '%s' has a future version number, please upgrade Revelation to open it." % file).run()
886
a207757f8451
887
c9d89f3ff91d
		except datahandler.DetectError:
888
a207757f8451
			self.statusbar.set_status("Open failed")
889
f5f9aadf2e19
			dialog.Error(self, "Unable to detect filetype", "The file type of the file '%s' could not be automatically detected. Try specifying the file type manually." % file).run()
890
a207757f8451
891
a207757f8451
		except IOError:
892
a207757f8451
			self.statusbar.set_status("Open failed")
893
f5f9aadf2e19
			dialog.Error(self, "Unable to open file", "The file '%s' could not be opened. Make sure that the file exists, and that you have permissions to open it." % file).run()
894
a207757f8451
895
a207757f8451
896
9e161d94bffc
	def __file_save(self, file, password, datafile = None):
897
a207757f8451
		"Saves data to a file"
898
a207757f8451
899
c9d89f3ff91d
		try:
900
c9d89f3ff91d
			if datafile is None:
901
c9d89f3ff91d
				datafile = self.datafile
902
9e161d94bffc
903
c9d89f3ff91d
			if io.file_normpath(str(datafile)) != io.file_normpath(file) and io.file_exists(file):
904
c9d89f3ff91d
				dialog.FileOverwrite(self, file).run()
905
a207757f8451
906
c9d89f3ff91d
			if datafile.get_handler().encryption == True:
907
9e161d94bffc
				if password is None:
908
c9d89f3ff91d
					password = dialog.PasswordSave(self, file).run()
909
9e161d94bffc
910
9e161d94bffc
			else:
911
c9d89f3ff91d
				dialog.FileSaveInsecure(self).run()
912
a207757f8451
913
c9d89f3ff91d
			datafile.save(self.entrystore, file, password)
914
a207757f8451
915
a207757f8451
		except IOError:
916
dae43bdfcbd0
			dialog.Error(self, "Unable to write to file", "The file '%s' could not be opened for writing. Make sure that you have the proper permissions to write to it." % file).run()
917
a207757f8451
			self.statusbar.set_status("Save failed")
918
a207757f8451
919
a207757f8451
920
c9d89f3ff91d
	def __get_common_usernames(self, e = None):
921
c9d89f3ff91d
		"Returns a list of possibly relevant usernames"
922
a207757f8451
923
c9d89f3ff91d
		list = []
924
c9d89f3ff91d
925
c9d89f3ff91d
		if e is not None and e.has_field(entry.UsernameField):
926
c9d89f3ff91d
			list.append(e[entry.UsernameField])
927
c9d89f3ff91d
928
c9d89f3ff91d
		list.append(pwd.getpwuid(os.getuid())[0])
929
c9d89f3ff91d
		list.extend(self.entrystore.get_popular_values(entry.UsernameField, 3))
930
c9d89f3ff91d
931
c9d89f3ff91d
		list = {}.fromkeys(list).keys()
932
c9d89f3ff91d
		list.sort()
933
c9d89f3ff91d
934
c9d89f3ff91d
		return list
935
c9d89f3ff91d
936
c9d89f3ff91d
937
c9d89f3ff91d
	def __save_changes(self, d):
938
a207757f8451
		"Asks the user if she wants to save her changes"
939
a207757f8451
940
c9d89f3ff91d
		if self.entrystore.changed == True and d(self).run() == True:
941
c9d89f3ff91d
			if self.file_save(self.datafile.get_file(), self.datafile.get_password()) == False:
942
c9d89f3ff91d
				raise dialog.CancelError
943
a207757f8451
944
a207757f8451
945
a207757f8451
946
c9d89f3ff91d
	##### PUBLIC METHODS #####
947
a207757f8451
948
e5681e842641
	def about(self):
949
e5681e842641
		"Displays the about dialog"
950
e5681e842641
951
dce7200431f0
		dialog.run_unique(dialog.About, self)
952
e5681e842641
953
e5681e842641
954
4674492151c3
	def clip_chain(self, e):
955
260e1dfe3e88
		"Copies all passwords from an entry as a chain"
956
b57efdd083f5
957
4674492151c3
		if e == None:
958
b57efdd083f5
			return
959
b57efdd083f5
960
b57efdd083f5
		secrets = [ field.value for field in e.fields if field.datatype == entry.DATATYPE_PASSWORD and field.value != "" ]
961
b57efdd083f5
962
260e1dfe3e88
		if self.config.get("clipboard/chain_username") == True and len(secrets) > 0 and e.has_field(entry.UsernameField) and e[entry.UsernameField] != "":
963
b57efdd083f5
			secrets.insert(0, e[entry.UsernameField])
964
b57efdd083f5
965
bb1810f15e75
		if len(secrets) == 0:
966
bb1810f15e75
			self.statusbar.set_status("Entry has no password to copy")
967
bb1810f15e75
968
bb1810f15e75
		else:
969
8812d70e39d9
			self.clipboard.set(secrets, True)
970
bb1810f15e75
			self.statusbar.set_status("Password copied to clipboard")
971
b57efdd083f5
972
b57efdd083f5
973
4674492151c3
	def clip_copy(self, iters):
974
c9d89f3ff91d
		"Copies entries to the clipboard"
975
a207757f8451
976
4674492151c3
		self.entryclipboard.set(self.entrystore, iters)
977
c9d89f3ff91d
		self.statusbar.set_status("Entries copied")
978
a207757f8451
979
a207757f8451
980
4674492151c3
	def clip_cut(self, iters):
981
c9d89f3ff91d
		"Cuts entries to the clipboard"
982
a207757f8451
983
c9d89f3ff91d
		iters = self.entrystore.filter_parents(iters)
984
c9d89f3ff91d
		self.entryclipboard.set(self.entrystore, iters)
985
c9d89f3ff91d
986
c9d89f3ff91d
		# store undo data (need paths)
987
c9d89f3ff91d
		undoactions = []
988
c9d89f3ff91d
		for iter in iters:
989
c9d89f3ff91d
			undostore = data.EntryStore()
990
c9d89f3ff91d
			undostore.import_entry(self.entrystore, iter)
991
c9d89f3ff91d
			path = self.entrystore.get_path(iter)
992
c9d89f3ff91d
			undoactions.append( ( path, undostore ) )
993
c9d89f3ff91d
994
c9d89f3ff91d
		# remove data
995
c9d89f3ff91d
		for iter in iters:
996
c9d89f3ff91d
			self.entrystore.remove_entry(iter)
997
c9d89f3ff91d
998
c9d89f3ff91d
		self.undoqueue.add_action(
999
c9d89f3ff91d
			"Cut entries", self.__cb_undo_remove, self.__cb_redo_remove,
1000
c9d89f3ff91d
			undoactions
1001
c9d89f3ff91d
		)
1002
c9d89f3ff91d
1003
a207757f8451
		self.__file_autosave()
1004
c9d89f3ff91d
1005
a207757f8451
		self.tree.unselect_all()
1006
c9d89f3ff91d
		self.statusbar.set_status("Entries cut")
1007
a207757f8451
1008
a207757f8451
1009
4674492151c3
	def clip_paste(self, entrystore, parent):
1010
a207757f8451
		"Pastes entries from the clipboard"
1011
a207757f8451
1012
4674492151c3
		if entrystore == None:
1013
a207757f8451
			return
1014
a207757f8451
1015
c9d89f3ff91d
		parent = self.tree.get_active()
1016
c9d89f3ff91d
		iters = self.entrystore.import_entry(entrystore, None, parent)
1017
c9d89f3ff91d
1018
c9d89f3ff91d
		paths = [ self.entrystore.get_path(iter) for iter in iters ]
1019
c9d89f3ff91d
1020
c9d89f3ff91d
		self.undoqueue.add_action(
1021
c9d89f3ff91d
			"Paste entries", self.__cb_undo_paste, self.__cb_redo_paste,
1022
c9d89f3ff91d
			( entrystore, self.entrystore.get_path(parent), paths )
1023
c9d89f3ff91d
		)
1024
c9d89f3ff91d
1025
c9d89f3ff91d
		if len(iters) > 0:
1026
c9d89f3ff91d
			self.tree.select(iters[0])
1027
c9d89f3ff91d
1028
c9d89f3ff91d
		self.statusbar.set_status("Entries pasted")
1029
a207757f8451
1030
a207757f8451
1031
4674492151c3
	def entry_add(self, e = None, parent = None, sibling = None):
1032
a207757f8451
		"Adds an entry"
1033
a207757f8451
1034
a207757f8451
		try:
1035
4674492151c3
			if e == None:
1036
8812d70e39d9
				d = dialog.EntryEdit(self, "Add Entry", None, self.config, self.clipboard)
1037
4674492151c3
				d.set_fieldwidget_data(entry.UsernameField, self.__get_common_usernames())
1038
4674492151c3
				e = d.run()
1039
a207757f8451
1040
4674492151c3
			iter = self.entrystore.add_entry(e, parent, sibling)
1041
c9d89f3ff91d
1042
c9d89f3ff91d
			self.undoqueue.add_action(
1043
c9d89f3ff91d
				"Add entry", self.__cb_undo_add, self.__cb_redo_add,
1044
c9d89f3ff91d
				( self.entrystore.get_path(iter), e.copy() )
1045
c9d89f3ff91d
			)
1046
c9d89f3ff91d
1047
a207757f8451
			self.__file_autosave()
1048
4674492151c3
			self.tree.select(iter)
1049
c9d89f3ff91d
			self.statusbar.set_status("Entry added")
1050
a207757f8451
1051
c9d89f3ff91d
		except dialog.CancelError:
1052
a207757f8451
			self.statusbar.set_status("Add entry cancelled")
1053
a207757f8451
1054
a207757f8451
1055
4674492151c3
	def entry_edit(self, iter):
1056
a207757f8451
		"Edits an entry"
1057
a207757f8451
1058
c9d89f3ff91d
		try:
1059
4674492151c3
			if iter == None:
1060
4674492151c3
				return
1061
4674492151c3
1062
c9d89f3ff91d
			e = self.entrystore.get_entry(iter)
1063
a207757f8451
1064
a1eabf1a2e68
			if type(e) == entry.FolderEntry:
1065
4b8d8b54da7b
				d = dialog.FolderEdit(self, "Edit Folder", e)
1066
a1eabf1a2e68
1067
a1eabf1a2e68
			else:
1068
8812d70e39d9
				d = dialog.EntryEdit(self, "Edit Entry", e, self.config, self.clipboard)
1069
a1eabf1a2e68
				d.set_fieldwidget_data(entry.UsernameField, self.__get_common_usernames(e))
1070
a207757f8451
1071
e5681e842641
1072
c9d89f3ff91d
			n = d.run()
1073
c9d89f3ff91d
			self.entrystore.update_entry(iter, n)
1074
c9d89f3ff91d
			self.tree.select(iter)
1075
a207757f8451
1076
c9d89f3ff91d
			self.undoqueue.add_action(
1077
c9d89f3ff91d
				"Update entry", self.__cb_undo_edit, self.__cb_redo_edit,
1078
c9d89f3ff91d
				( self.entrystore.get_path(iter), e.copy(), n.copy() )
1079
c9d89f3ff91d
			)
1080
a207757f8451
1081
a207757f8451
			self.__file_autosave()
1082
c9d89f3ff91d
			self.statusbar.set_status("Entry updated")
1083
c9d89f3ff91d
1084
c9d89f3ff91d
		except dialog.CancelError:
1085
c9d89f3ff91d
			self.statusbar.set_status("Edit entry cancelled")
1086
a207757f8451
1087
a207757f8451
1088
a207757f8451
	def entry_find(self):
1089
a207757f8451
		"Searches for an entry"
1090
a207757f8451
1091
f80290f7e569
		self.config.set("view/searchbar", True)
1092
f80290f7e569
		self.searchbar.entry.select_region(0, -1)
1093
f80290f7e569
		self.searchbar.entry.grab_focus()
1094
a207757f8451
1095
a207757f8451
1096
a1eabf1a2e68
	def entry_folder(self, e = None, parent = None, sibling = None):
1097
a1eabf1a2e68
		"Adds a folder"
1098
a1eabf1a2e68
1099
a1eabf1a2e68
		try:
1100
a1eabf1a2e68
			if e == None:
1101
a1eabf1a2e68
				e = dialog.FolderEdit(self, "Add Folder").run()
1102
a1eabf1a2e68
1103
a1eabf1a2e68
			iter = self.entrystore.add_entry(e, parent, sibling)
1104
a1eabf1a2e68
1105
a1eabf1a2e68
			self.undoqueue.add_action(
1106
a1eabf1a2e68
				"Add folder", self.__cb_undo_add, self.__cb_redo_add,
1107
a1eabf1a2e68
				( self.entrystore.get_path(iter), e.copy() )
1108
a1eabf1a2e68
			)
1109
a1eabf1a2e68
1110
a1eabf1a2e68
			self.__file_autosave()
1111
a1eabf1a2e68
			self.tree.select(iter)
1112
a1eabf1a2e68
			self.statusbar.set_status("Folder added")
1113
a1eabf1a2e68
1114
a1eabf1a2e68
		except dialog.CancelError:
1115
a1eabf1a2e68
			self.statusbar.set_status("Add folder cancelled")
1116
a1eabf1a2e68
1117
a1eabf1a2e68
1118
095f4008dc7f
	def entry_goto(self, iters):
1119
095f4008dc7f
		"Goes to an entry"
1120
a207757f8451
1121
a207757f8451
		for iter in iters:
1122
a207757f8451
			try:
1123
e5330daca1cd
1124
095f4008dc7f
				# get goto data for entry
1125
c9d89f3ff91d
				e = self.entrystore.get_entry(iter)
1126
6cf6849eed72
				command = self.config.get("launcher/%s" % e.id)
1127
a207757f8451
1128
c9d89f3ff91d
				if command in ( "", None ):
1129
095f4008dc7f
					self.statusbar.set_status("No goto command found for " + e.typename + " entries")
1130
c9d89f3ff91d
					return
1131
c371b70d69d8
1132
c9d89f3ff91d
				subst = {}
1133
c9d89f3ff91d
				for field in e.fields:
1134
c9d89f3ff91d
					subst[field.symbol] = field.value
1135
c371b70d69d8
1136
e5681e842641
				# copy passwords to clipboard
1137
095f4008dc7f
				chain = []
1138
e5330daca1cd
1139
095f4008dc7f
				for field in e.fields:
1140
095f4008dc7f
					if field.datatype == entry.DATATYPE_PASSWORD and field.value != "":
1141
095f4008dc7f
						chain.append(field.value)
1142
095f4008dc7f
1143
260e1dfe3e88
				if self.config.get("clipboard/chain_username") == True and len(chain) > 0 and e.has_field(entry.UsernameField) == True and e[entry.UsernameField] != "" and "%" + entry.UsernameField.symbol not in command:
1144
260e1dfe3e88
					chain.insert(0, e[entry.UsernameField])
1145
260e1dfe3e88
1146
8812d70e39d9
				self.clipboard.set(chain, True)
1147
095f4008dc7f
1148
095f4008dc7f
				# generate and run goto command
1149
c9d89f3ff91d
				command = util.parse_subst(command, subst)
1150
c9d89f3ff91d
				util.execute_child(command)
1151
a207757f8451
1152
095f4008dc7f
				self.statusbar.set_status("Entry opened")
1153
e5330daca1cd
1154
c9d89f3ff91d
			except ( util.SubstFormatError, config.ConfigError ):
1155
095f4008dc7f
				dialog.Error(self, "Invalid goto command format", "The goto command for '" + e.typename + "' entries is invalid, please correct this in the preferences.").run()
1156
a207757f8451
1157
c9d89f3ff91d
			except util.SubstValueError:
1158
095f4008dc7f
				dialog.Error(self, "Missing entry data", "The entry '" + e.name + "' does not have all the data required to go to it.").run()
1159
8077271f872d
1160
8077271f872d
1161
8077271f872d
	def entry_move(self, sourceiters, parent = None, sibling = None):
1162
8077271f872d
		"Moves a set of entries"
1163
8077271f872d
1164
8077271f872d
		if type(sourceiters) != list:
1165
8077271f872d
			sourceiters = [ sourceiters ]
1166
8077271f872d
1167
8077271f872d
		newiters = []
1168
8077271f872d
		undoactions = []
1169
8077271f872d
1170
8077271f872d
		for sourceiter in sourceiters:
1171
8077271f872d
			sourcepath = self.entrystore.get_path(sourceiter)
1172
8077271f872d
			newiter = self.entrystore.move_entry(sourceiter, parent, sibling)
1173
8077271f872d
			newpath = self.entrystore.get_path(newiter)
1174
8077271f872d
1175
8077271f872d
			undoactions.append( ( sourcepath, newpath ) )
1176
8077271f872d
			newiters.append(newiter)
1177
8077271f872d
1178
8077271f872d
		self.undoqueue.add_action(
1179
8077271f872d
			"Move entry", self.__cb_undo_move, self.__cb_redo_move,
1180
8077271f872d
			undoactions
1181
8077271f872d
		)
1182
8077271f872d
1183
8077271f872d
		if len(newiters) > 0:
1184
8077271f872d
			self.tree.select(newiters[0])
1185
8077271f872d
1186
249de64f5d68
		self.__file_autosave()
1187
8077271f872d
		self.statusbar.set_status("Entries moved")
1188
a207757f8451
1189
a207757f8451
1190
4674492151c3
	def entry_remove(self, iters):
1191
c9d89f3ff91d
		"Removes the selected entries"
1192
a207757f8451
1193
a207757f8451
		try:
1194
c9d89f3ff91d
			if len(iters) == 0:
1195
c9d89f3ff91d
				return
1196
a207757f8451
1197
c9d89f3ff91d
			entries = [ self.entrystore.get_entry(iter) for iter in iters ]
1198
c9d89f3ff91d
			dialog.EntryRemove(self, entries).run()
1199
c9d89f3ff91d
			iters = self.entrystore.filter_parents(iters)
1200
a207757f8451
1201
c9d89f3ff91d
			# store undo data (need paths)
1202
c9d89f3ff91d
			undoactions = []
1203
c9d89f3ff91d
			for iter in iters:
1204
c9d89f3ff91d
				undostore = data.EntryStore()
1205
c9d89f3ff91d
				undostore.import_entry(self.entrystore, iter)
1206
c9d89f3ff91d
				path = self.entrystore.get_path(iter)
1207
c9d89f3ff91d
				undoactions.append( ( path, undostore ) )
1208
a207757f8451
1209
c9d89f3ff91d
			# remove data
1210
a207757f8451
			for iter in iters:
1211
c9d89f3ff91d
				self.entrystore.remove_entry(iter)
1212
c9d89f3ff91d
1213
c9d89f3ff91d
			self.undoqueue.add_action(
1214
c9d89f3ff91d
				"Remove entry", self.__cb_undo_remove, self.__cb_redo_remove,
1215
c9d89f3ff91d
				undoactions
1216
c9d89f3ff91d
			)
1217
a207757f8451
1218
4674492151c3
			self.tree.unselect_all()
1219
a207757f8451
			self.__file_autosave()
1220
c9d89f3ff91d
			self.statusbar.set_status("Entries removed")
1221
a207757f8451
1222
c9d89f3ff91d
		except dialog.CancelError:
1223
c9d89f3ff91d
			self.statusbar.set_status("Entry removal cancelled")
1224
c9d89f3ff91d
1225
c9d89f3ff91d
1226
4674492151c3
	def file_change_password(self, password = None):
1227
c9d89f3ff91d
		"Changes the password of the current data file"
1228
c9d89f3ff91d
1229
c9d89f3ff91d
		try:
1230
4674492151c3
			if password == None:
1231
4674492151c3
				password = dialog.PasswordChange(self, self.datafile.get_password()).run()
1232
4674492151c3
1233
c9d89f3ff91d
			self.datafile.set_password(password)
1234
c9d89f3ff91d
			self.entrystore.changed = True
1235
c9d89f3ff91d
1236
c9d89f3ff91d
			self.__file_autosave()
1237
c9d89f3ff91d
			self.statusbar.set_status("Password changed")
1238
c9d89f3ff91d
1239
c9d89f3ff91d
		except dialog.CancelError:
1240
c9d89f3ff91d
			self.statusbar.set_status("Password change cancelled")
1241
a207757f8451
1242
a207757f8451
1243
a207757f8451
	def file_export(self):
1244
a207757f8451
		"Exports data to a foreign file format"
1245
a207757f8451
1246
a207757f8451
		try:
1247
c9d89f3ff91d
			file, handler = dialog.ExportFileSelector(self).run()
1248
c9d89f3ff91d
			datafile = io.DataFile(handler)
1249
9e161d94bffc
			self.__file_save(file, None, datafile)
1250
a207757f8451
1251
c9d89f3ff91d
			self.statusbar.set_status("Data exported to %s" % datafile.get_file())
1252
c9d89f3ff91d
1253
c9d89f3ff91d
		except dialog.CancelError:
1254
a207757f8451
			self.statusbar.set_status("Export cancelled")
1255
a207757f8451
1256
a207757f8451
1257
a207757f8451
	def file_import(self):
1258
a207757f8451
		"Imports data from a foreign file"
1259
a207757f8451
1260
a207757f8451
		try:
1261
c9d89f3ff91d
			file, handler = dialog.ImportFileSelector(self).run()
1262
c9d89f3ff91d
			datafile = io.DataFile(handler)
1263
9e161d94bffc
			entrystore = self.__file_load(file, None, datafile)
1264
a207757f8451
1265
c9d89f3ff91d
			if entrystore is not None:
1266
c9d89f3ff91d
				newiters = self.entrystore.import_entry(entrystore, None)
1267
c9d89f3ff91d
				paths = [ self.entrystore.get_path(iter) for iter in newiters ]
1268
a207757f8451
1269
c9d89f3ff91d
				self.undoqueue.add_action(
1270
c9d89f3ff91d
					"Import data", self.__cb_undo_import, self.__cb_redo_import,
1271
c9d89f3ff91d
					( paths, entrystore )
1272
c9d89f3ff91d
				)
1273
c9d89f3ff91d
1274
c9d89f3ff91d
				self.statusbar.set_status("Data imported from %s" % datafile.get_file())
1275
c9d89f3ff91d
1276
a207757f8451
			self.__file_autosave()
1277
a207757f8451
1278
c9d89f3ff91d
		except dialog.CancelError:
1279
a207757f8451
			self.statusbar.set_status("Import cancelled")
1280
a207757f8451
1281
a207757f8451
1282
a207757f8451
	def file_lock(self):
1283
c9d89f3ff91d
		"Locks the current file"
1284
a207757f8451
1285
c9d89f3ff91d
		password = self.datafile.get_password()
1286
c9d89f3ff91d
1287
c9d89f3ff91d
		if password is None:
1288
a207757f8451
			return
1289
a207757f8451
1290
72bff8816fca
		self.locktimer.stop()
1291
72bff8816fca
1292
c8f7da645a99
		# TODO can this be done more elegantly?
1293
c8f7da645a99
		transients = [ window for window in gtk.window_list_toplevels() if window.get_transient_for() == self ]
1294
c8f7da645a99
1295
c9d89f3ff91d
		# store current state
1296
c9d89f3ff91d
		activeiter = self.tree.get_active()
1297
c9d89f3ff91d
		oldtitle = self.get_title()
1298
c9d89f3ff91d
1299
c8f7da645a99
		# clear application contents
1300
a207757f8451
		self.tree.set_model(None)
1301
c9d89f3ff91d
		self.entryview.clear()
1302
c9d89f3ff91d
		self.set_title("[Locked]")
1303
a207757f8451
		self.statusbar.set_status("File locked")
1304
a207757f8451
1305
c8f7da645a99
		# hide any dialogs
1306
c8f7da645a99
		for window in transients:
1307
c8f7da645a99
			window.hide()
1308
c8f7da645a99
1309
c8f7da645a99
		# lock file
1310
5411d685ecc6
		try:
1311
5411d685ecc6
			d = dialog.PasswordLock(self, password)
1312
5411d685ecc6
1313
5411d685ecc6
			if self.entrystore.changed == True:
1314
5411d685ecc6
				d.get_button(1).set_sensitive(False)
1315
5411d685ecc6
1316
5411d685ecc6
			d.run()
1317
5411d685ecc6
1318
5411d685ecc6
		except dialog.CancelError:
1319
5411d685ecc6
			self.quit()
1320
a207757f8451
1321
c9d89f3ff91d
		# unlock the file and restore state
1322
c9d89f3ff91d
		self.tree.set_model(self.entrystore)
1323
c9d89f3ff91d
		self.tree.select(activeiter)
1324
c9d89f3ff91d
		self.set_title(oldtitle)
1325
a207757f8451
		self.statusbar.set_status("File unlocked")
1326
a207757f8451
1327
c8f7da645a99
		for window in transients:
1328
c8f7da645a99
			window.show()
1329
c8f7da645a99
1330
72bff8816fca
		self.locktimer.start(self.config.get("file/autolock_timeout") * 60)
1331
72bff8816fca
1332
a207757f8451
1333
a207757f8451
	def file_new(self):
1334
a207757f8451
		"Opens a new file"
1335
a207757f8451
1336
c9d89f3ff91d
		try:
1337
bffe619c6cec
			self.__save_changes(dialog.FileChangesNew)
1338
c9d89f3ff91d
1339
c9d89f3ff91d
			self.entrystore.clear()
1340
c9d89f3ff91d
			self.datafile.close()
1341
8eba4e20d67b
			self.undoqueue.clear()
1342
c9d89f3ff91d
			self.statusbar.set_status("New file created")
1343
c9d89f3ff91d
1344
c9d89f3ff91d
		except dialog.CancelError:
1345
a207757f8451
			self.statusbar.set_status("New file cancelled")
1346
a207757f8451
1347
a207757f8451
1348
a207757f8451
	def file_open(self, file = None, password = None):
1349
a207757f8451
		"Opens a data file"
1350
a207757f8451
1351
a207757f8451
		try:
1352
bffe619c6cec
			self.__save_changes(dialog.FileChangesOpen)
1353
a207757f8451
1354
a207757f8451
			if file is None:
1355
c9d89f3ff91d
				file = dialog.OpenFileSelector(self).run()
1356
a207757f8451
1357
9e161d94bffc
			entrystore = self.__file_load(file, password)
1358
a207757f8451
1359
a207757f8451
			if entrystore is None:
1360
a207757f8451
				return
1361
a207757f8451
1362
c9d89f3ff91d
			self.entrystore.clear()
1363
c9d89f3ff91d
			self.entrystore.import_entry(entrystore, None)
1364
c9d89f3ff91d
			self.entrystore.changed = False
1365
8eba4e20d67b
			self.undoqueue.clear()
1366
a207757f8451
1367
f5f9aadf2e19
			self.statusbar.set_status("Opened file %s" % self.datafile.get_file())
1368
a207757f8451
1369
c9d89f3ff91d
		except dialog.CancelError:
1370
a207757f8451
			self.statusbar.set_status("Open cancelled")
1371
a207757f8451
1372
a207757f8451
1373
a207757f8451
	def file_save(self, file = None, password = None):
1374
c9d89f3ff91d
		"Saves data to a file"
1375
a207757f8451
1376
a207757f8451
		try:
1377
a207757f8451
			if file is None:
1378
c9d89f3ff91d
				file = dialog.SaveFileSelector(self).run()
1379
a207757f8451
1380
c9d89f3ff91d
			self.__file_save(file, password)
1381
c9d89f3ff91d
			self.entrystore.changed = False
1382
c9d89f3ff91d
			self.statusbar.set_status("Data saved to file %s" % file)
1383
a207757f8451
1384
c9d89f3ff91d
			return True
1385
c9d89f3ff91d
1386
c9d89f3ff91d
		except dialog.CancelError:
1387
a207757f8451
			self.statusbar.set_status("Save cancelled")
1388
9e161d94bffc
			return False
1389
a207757f8451
1390
a207757f8451
1391
e5681e842641
	def prefs(self):
1392
e5681e842641
		"Displays the application preferences"
1393
e5681e842641
1394
dce7200431f0
		dialog.run_unique(dialog.Preferences, self, self.config)
1395
e5681e842641
1396
e5681e842641
1397
e5681e842641
	def pwcheck(self):
1398
e5681e842641
		"Displays the password checking dialog"
1399
e5681e842641
1400
dce7200431f0
		dialog.run_unique(dialog.PasswordChecker, self)
1401
e5681e842641
1402
e5681e842641
1403
e5681e842641
	def pwgen(self):
1404
e5681e842641
		"Displays the password generator dialog"
1405
e5681e842641
1406
dce7200431f0
		dialog.run_unique(dialog.PasswordGenerator, self, self.config)
1407
e5681e842641
1408
e5681e842641
1409
a207757f8451
	def quit(self):
1410
a207757f8451
		"Quits the application"
1411
a207757f8451
1412
c9d89f3ff91d
		try:
1413
bffe619c6cec
			self.__save_changes(dialog.FileChangesQuit)
1414
c9d89f3ff91d
1415
c9d89f3ff91d
			self.clipboard.clear()
1416
c9d89f3ff91d
			self.entryclipboard.clear()
1417
c9d89f3ff91d
1418
59be49691b29
			self.__save_state()
1419
c9d89f3ff91d
1420
c9d89f3ff91d
			gtk.main_quit()
1421
c9d89f3ff91d
			sys.exit(0)
1422
693fe3e0223f
			return True
1423
c9d89f3ff91d
1424
c9d89f3ff91d
		except dialog.CancelError:
1425
a207757f8451
			self.statusbar.set_status("Quit cancelled")
1426
c9d89f3ff91d
			return False
1427
a207757f8451
1428
a207757f8451
1429
a207757f8451
	def redo(self):
1430
c9d89f3ff91d
		"Redoes the previous action"
1431
a207757f8451
1432
c9d89f3ff91d
		action = self.undoqueue.get_redo_action()
1433
c9d89f3ff91d
1434
c9d89f3ff91d
		if action is None:
1435
a207757f8451
			return
1436
a207757f8451
1437
c9d89f3ff91d
		self.undoqueue.redo()
1438
c9d89f3ff91d
		self.statusbar.set_status("%s redone" % action[1])
1439
c9d89f3ff91d
		self.__file_autosave()
1440
a207757f8451
1441
c9d89f3ff91d
1442
c9d89f3ff91d
	def run(self):
1443
c9d89f3ff91d
		"Runs the application"
1444
c9d89f3ff91d
1445
59be49691b29
		args, argdict = self.program.get_popt_args()
1446
59be49691b29
1447
59be49691b29
		if len(args) > 0:
1448
59be49691b29
			file = args[0]
1449
c9d89f3ff91d
1450
c9d89f3ff91d
		elif self.config.get("file/autoload") == True:
1451
c9d89f3ff91d
			file = self.config.get("file/autoload_file")
1452
a207757f8451
1453
a207757f8451
		else:
1454
c9d89f3ff91d
			file = ""
1455
a207757f8451
1456
a207757f8451
1457
c9d89f3ff91d
		if file != "":
1458
c9d89f3ff91d
			self.file_open(io.file_normpath(file))
1459
a207757f8451
1460
a207757f8451
		gtk.main()
1461
a207757f8451
1462
a207757f8451
1463
a207757f8451
	def undo(self):
1464
c9d89f3ff91d
		"Undoes the previous action"
1465
a207757f8451
1466
c9d89f3ff91d
		action = self.undoqueue.get_undo_action()
1467
c9d89f3ff91d
1468
c9d89f3ff91d
		if action is None:
1469
a207757f8451
			return
1470
a207757f8451
1471
c9d89f3ff91d
		self.undoqueue.undo()
1472
c9d89f3ff91d
		self.statusbar.set_status("%s undone" % action[1])
1473
a207757f8451
		self.__file_autosave()
1474
a207757f8451
1475
a207757f8451
1476
a207757f8451
1477
a207757f8451
if __name__ == "__main__":
1478
c9d89f3ff91d
	app = Revelation()
1479
c9d89f3ff91d
	app.run()
1480
a207757f8451