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

Revelation / src / revelation.in

commit
8d3661dc2974
parent
117efb089a91
branch
default
tags
revelation-0.4.0-pre1

don't use stock module

1
a207757f8451
#!/usr/bin/env python
2
a207757f8451
3
a207757f8451
#
4
b6047522c64d
# Revelation 0.4.0 - a password manager for GNOME 2
5
a207757f8451
# http://oss.codepoet.no/revelation/
6
a207757f8451
# $Id$
7
a207757f8451
#
8
07c1fb2b0c27
# Copyright (c) 2003-2005 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
f5f9aadf2e19
import gnome, gtk, gtk.gdk, os, pwd, sys
26
a207757f8451
27
a207757f8451
if "@pythondir@" not in sys.path:
28
e63d5ad72ae4
	sys.path.insert(0, "@pythondir@")
29
a207757f8451
30
5d99fc2aa75a
from revelation import authmanager, config, data, datahandler, dialog, entry, io, ui, util
31
a207757f8451
32
a207757f8451
33
c9d89f3ff91d
34
c9d89f3ff91d
class Revelation(ui.App):
35
c9d89f3ff91d
	"The Revelation application"
36
a207757f8451
37
a207757f8451
	def __init__(self):
38
a207757f8451
		sys.excepthook = self.__cb_exception
39
086abbb6fc3c
		os.umask(0077)
40
a207757f8451
41
59be49691b29
		self.program = gnome.init(
42
59be49691b29
			config.APPNAME, config.APPNAME,
43
59be49691b29
			gnome.libgnome_module_info_get(), sys.argv, []
44
59be49691b29
		)
45
59be49691b29
46
5d99fc2aa75a
		authmanager.gnome_authentication_manager_init()
47
c9d89f3ff91d
		ui.App.__init__(self, config.APPNAME)
48
a207757f8451
49
c9d89f3ff91d
		self.connect("delete-event", lambda w,d: True ^ self.quit())
50
a207757f8451
51
c9d89f3ff91d
		try:
52
c9d89f3ff91d
			self.__init_facilities()
53
c9d89f3ff91d
			self.__init_ui()
54
c9d89f3ff91d
			self.__init_states()
55
a207757f8451
56
c9d89f3ff91d
		except IOError:
57
c9d89f3ff91d
			dialog.Error(self, "Missing data files", "Some of Revelations system files could not be found, please reinstall Revelation.").run()
58
c9d89f3ff91d
			sys.exit(1)
59
c9d89f3ff91d
60
c9d89f3ff91d
		except config.ConfigError:
61
c9d89f3ff91d
			dialog.Error(self, "Missing configuration data", "Revelation could not find its configuration data, please reinstall Revelation.").run()
62
c9d89f3ff91d
			sys.exit(1)
63
c9d89f3ff91d
64
c9d89f3ff91d
		except ui.DataError:
65
c9d89f3ff91d
			dialog.Error(self, "Invalid data files", "Some of Revelations system files contain invalid data, please reinstall Revelation.").run()
66
c9d89f3ff91d
			sys.exit(1)
67
c9d89f3ff91d
68
c9d89f3ff91d
69
c9d89f3ff91d
	def __init_facilities(self):
70
c9d89f3ff91d
		"Sets up various facilities"
71
c9d89f3ff91d
72
c9d89f3ff91d
		self.clipboard		= data.Clipboard()
73
c9d89f3ff91d
		self.config		= config.Config()
74
c9d89f3ff91d
		self.datafile		= io.DataFile(datahandler.Revelation)
75
c9d89f3ff91d
		self.entryclipboard	= data.EntryClipboard()
76
c9d89f3ff91d
		self.entrystore		= data.EntryStore()
77
c9d89f3ff91d
		self.entrysearch	= data.EntrySearch(self.entrystore)
78
c9d89f3ff91d
		self.items		= ui.ItemFactory(self)
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
c9d89f3ff91d
		self.entryclipboard.connect("content-toggled", lambda w,d: self.__state_clipboard(d))
84
59be49691b29
		self.sessionclient.connect("die", lambda w: self.quit())
85
59be49691b29
		self.sessionclient.connect("save-yourself", self.__cb_session_save)
86
c9d89f3ff91d
		self.undoqueue.connect("changed", lambda w: self.__state_undo(self.undoqueue.get_undo_action(), self.undoqueue.get_redo_action()))
87
c9d89f3ff91d
88
c9d89f3ff91d
		self.config.monitor("search/folders",	lambda k,v,d: setattr(self.entrysearch, "folders", v))
89
c9d89f3ff91d
		self.config.monitor("search/namedesc",	lambda k,v,d: setattr(self.entrysearch, "namedesconly", v))
90
c9d89f3ff91d
		self.config.monitor("search/casesens",	lambda k,v,d: setattr(self.entrysearch, "casesensitive", v))
91
c9d89f3ff91d
92
c9d89f3ff91d
93
c9d89f3ff91d
	def __init_states(self):
94
c9d89f3ff91d
		"Sets the initial application state"
95
c9d89f3ff91d
96
c9d89f3ff91d
		# set window states
97
c9d89f3ff91d
		self.set_default_size(
98
c9d89f3ff91d
			self.config.get("view/window-width"),
99
c9d89f3ff91d
			self.config.get("view/window-height")
100
c9d89f3ff91d
		)
101
c9d89f3ff91d
102
c9d89f3ff91d
		self.move(
103
c9d89f3ff91d
			self.config.get("view/window-position-x"),
104
c9d89f3ff91d
			self.config.get("view/window-position-y")
105
c9d89f3ff91d
		)
106
c9d89f3ff91d
107
c9d89f3ff91d
		self.hpaned.set_position(
108
c9d89f3ff91d
			self.config.get("view/pane-position")
109
c9d89f3ff91d
		)
110
c9d89f3ff91d
111
c9d89f3ff91d
		# bind ui widgets to config keys
112
c9d89f3ff91d
		bind = {
113
c9d89f3ff91d
			"view/passwords"	: "/menubar/menu-view/view-passwords",
114
c9d89f3ff91d
			"view/searchbar"	: "/menubar/menu-view/view-searchbar",
115
c9d89f3ff91d
			"view/statusbar"	: "/menubar/menu-view/view-statusbar",
116
c9d89f3ff91d
			"view/toolbar"		: "/menubar/menu-view/view-toolbar"
117
c9d89f3ff91d
		}
118
c9d89f3ff91d
119
c9d89f3ff91d
		for key, path in bind.items():
120
c9d89f3ff91d
			ui.config_bind(self.config, key, self.uimanager.get_widget(path))
121
c9d89f3ff91d
122
c9d89f3ff91d
123
c9d89f3ff91d
		self.entryview.display_info()
124
c9d89f3ff91d
		self.show_all()
125
c9d89f3ff91d
126
c9d89f3ff91d
		# set some variables
127
c9d89f3ff91d
		self.entrysearch.string	= ""
128
c9d89f3ff91d
		self.entrysearch.type	= None
129
c9d89f3ff91d
130
c9d89f3ff91d
		# set ui widget states
131
c9d89f3ff91d
		self.__state_clipboard(self.entryclipboard.has_contents())
132
c9d89f3ff91d
		self.__state_entry([])
133
c9d89f3ff91d
		self.__state_file(None)
134
c9d89f3ff91d
		self.__state_find(self.entrysearch.string)
135
c9d89f3ff91d
		self.__state_undo(None, None)
136
c9d89f3ff91d
137
c9d89f3ff91d
		# set states from config
138
c9d89f3ff91d
		self.config.monitor("view/searchbar", self.__cb_config_toolbar, self.searchbar)
139
c9d89f3ff91d
		self.config.monitor("view/statusbar", self.__cb_config_toolbar, self.statusbar)
140
c9d89f3ff91d
		self.config.monitor("view/toolbar", self.__cb_config_toolbar, self.toolbar)
141
c9d89f3ff91d
142
c9d89f3ff91d
143
086abbb6fc3c
	def __init_ui(self):
144
c9d89f3ff91d
		"Sets up the UI"
145
a207757f8451
146
c9d89f3ff91d
		# set window icons
147
3dcf95408d8d
		gtk.window_set_default_icon_list(
148
3dcf95408d8d
			self.items.load_icon("revelation", 48),
149
620759d3032d
			self.items.load_icon("revelation", 32),
150
620759d3032d
			self.items.load_icon("revelation", 24),
151
3dcf95408d8d
			self.items.load_icon("revelation", 16)
152
3dcf95408d8d
		)
153
3dcf95408d8d
154
c9d89f3ff91d
		# load UI definitions
155
c9d89f3ff91d
		self.uimanager.add_actions_from_file(config.DIR_UI + "/actions.xml")
156
c9d89f3ff91d
		self.uimanager.add_ui_from_file(config.DIR_UI + "/menubar.xml")
157
c9d89f3ff91d
		self.uimanager.add_ui_from_file(config.DIR_UI + "/popup-tree.xml")
158
c9d89f3ff91d
		self.uimanager.add_ui_from_file(config.DIR_UI + "/toolbar.xml")
159
a207757f8451
160
c9d89f3ff91d
		# set up callbacks for actions
161
c9d89f3ff91d
		callbacks = {
162
4674492151c3
			"clip-chain"		: lambda w: self.clip_chain(self.entrystore.get_entry(self.tree.get_active())),
163
4674492151c3
			"clip-copy"		: lambda w: self.clip_copy(self.tree.get_selected()),
164
4674492151c3
			"clip-cut"		: lambda w: self.clip_cut(self.tree.get_selected()),
165
4674492151c3
			"clip-paste"		: lambda w: self.clip_paste(self.entryclipboard.get(), self.tree.get_active()),
166
4674492151c3
			"entry-add"		: lambda w: self.entry_add(None, self.tree.get_active()),
167
4674492151c3
			"entry-edit"		: lambda w: self.entry_edit(self.tree.get_active()),
168
095f4008dc7f
			"entry-goto"		: lambda w: self.entry_goto(self.tree.get_selected()),
169
4674492151c3
			"entry-remove"		: lambda w: self.entry_remove(self.tree.get_selected()),
170
c9d89f3ff91d
			"file-change-password"	: lambda w: self.file_change_password(),
171
c9d89f3ff91d
			"file-close"		: lambda w: self.quit(),
172
c9d89f3ff91d
			"file-export"		: lambda w: self.file_export(),
173
c9d89f3ff91d
			"file-import"		: lambda w: self.file_import(),
174
c9d89f3ff91d
			"file-lock"		: lambda w: self.file_lock(),
175
c9d89f3ff91d
			"file-new"		: lambda w: self.file_new(),
176
c9d89f3ff91d
			"file-open"		: lambda w: self.file_open(),
177
c9d89f3ff91d
			"file-save"		: lambda w: self.file_save(self.datafile.get_file(), self.datafile.get_password()),
178
c9d89f3ff91d
			"file-save-as"		: lambda w: self.file_save(None, None),
179
c9d89f3ff91d
			"find"			: lambda w: self.entry_find(),
180
c9d89f3ff91d
			"find-next"		: lambda w: self.__entry_find(self, self.entrysearch.string, self.entrysearch.type, data.SEARCH_NEXT),
181
c9d89f3ff91d
			"find-previous"		: lambda w: self.__entry_find(self, self.entrysearch.string, self.entrysearch.type, data.SEARCH_PREVIOUS),
182
c9d89f3ff91d
			"help-about"		: lambda w: dialog.About(self).run(),
183
c9d89f3ff91d
			"help-homepage"		: lambda w: gnome.url_show(config.URL),
184
c9d89f3ff91d
			"prefs"			: lambda w: dialog.Preferences(self, self.config).run(),
185
c9d89f3ff91d
			"pwgenerator"		: lambda w: dialog.PasswordGenerator(self, self.config).run(),
186
c9d89f3ff91d
			"quit"			: lambda w: self.quit(),
187
c9d89f3ff91d
			"redo"			: lambda w: self.redo(),
188
c9d89f3ff91d
			"select-all"		: lambda w: self.tree.select_all(),
189
c9d89f3ff91d
			"select-none"		: lambda w: self.tree.unselect_all(),
190
c9d89f3ff91d
			"undo"			: lambda w: self.undo()
191
c9d89f3ff91d
		}
192
a207757f8451
193
c9d89f3ff91d
		for id, callback in callbacks.items():
194
c9d89f3ff91d
			action = self.uimanager.get_action(id)
195
c9d89f3ff91d
			action.connect("activate", callback)
196
a207757f8451
197
c9d89f3ff91d
		# set up toolbar and menus
198
c9d89f3ff91d
		self.set_menus(self.uimanager.get_widget("/menubar"))
199
a207757f8451
200
c9d89f3ff91d
		self.toolbar = self.uimanager.get_widget("/toolbar")
201
c9d89f3ff91d
		self.set_toolbar(self.toolbar)
202
a207757f8451
203
47caac1545f2
		try:
204
47caac1545f2
			detachable = self.config.get("/desktop/gnome/interface/toolbar_detachable")
205
47caac1545f2
206
47caac1545f2
		except config.ConfigError:
207
47caac1545f2
			detachable = False
208
47caac1545f2
209
c9d89f3ff91d
		self.searchbar = ui.Searchbar()
210
47caac1545f2
		self.add_toolbar(self.searchbar, "searchbar", 2, detachable)
211
a207757f8451
212
c9d89f3ff91d
		# set up main application widgets
213
c9d89f3ff91d
		self.tree = ui.EntryTree(self.entrystore)
214
c9d89f3ff91d
		self.scrolledwindow = ui.ScrolledWindow(self.tree)
215
a207757f8451
216
c9d89f3ff91d
		self.entryview = ui.EntryView(self.config)
217
c9d89f3ff91d
		alignment = gtk.Alignment(0.5, 0.4, 0, 0)
218
c9d89f3ff91d
		alignment.add(self.entryview)
219
a7c01d3b4b22
220
c9d89f3ff91d
		self.hpaned = ui.HPaned(self.scrolledwindow, alignment)
221
c9d89f3ff91d
		self.set_contents(self.hpaned)
222
c9d89f3ff91d
223
8077271f872d
		# set up drag-and-drop
224
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 )
225
f5f9aadf2e19
		self.connect("drag_data_received", self.__cb_drag_dest)
226
f5f9aadf2e19
227
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)
228
8077271f872d
		self.tree.enable_model_drag_dest(( ( "revelation/treerow", gtk.TARGET_SAME_APP | gtk.TARGET_SAME_WIDGET, 0), ), gtk.gdk.ACTION_MOVE)
229
8077271f872d
		self.tree.connect("drag_data_received", self.__cb_tree_drag_received)
230
8077271f872d
231
c9d89f3ff91d
		# set up callbacks
232
c9d89f3ff91d
		self.searchbar.button.connect("clicked", lambda w: self.__entry_find(self, self.searchbar.entry.get_text(), None))
233
c9d89f3ff91d
234
c9d89f3ff91d
		self.tree.connect("popup", lambda w,d: self.popup(self.uimanager.get_widget("/popup-tree"), d.button, d.time))
235
6cf6849eed72
		self.tree.connect("doubleclick", self.__cb_tree_doubleclick)
236
c9d89f3ff91d
		self.tree.connect("key-press-event", self.__cb_tree_keypress)
237
c9d89f3ff91d
		self.tree.selection.connect("changed", lambda w: self.entryview.display_entry(self.entrystore.get_entry(self.tree.get_active())))
238
c9d89f3ff91d
		self.tree.selection.connect("changed", lambda w: self.__state_entry(self.tree.get_selected()))
239
c9d89f3ff91d
240
c9d89f3ff91d
241
c9d89f3ff91d
242
c9d89f3ff91d
	##### STATE HANDLERS #####
243
c9d89f3ff91d
244
59be49691b29
	def __save_state(self):
245
59be49691b29
		"Saves the current application state"
246
59be49691b29
247
59be49691b29
		width, height = self.get_size()
248
59be49691b29
		self.config.set("view/window-width", width)
249
59be49691b29
		self.config.set("view/window-height", height)
250
59be49691b29
251
59be49691b29
		x, y = self.get_position()
252
59be49691b29
		self.config.set("view/window-position-x", x)
253
59be49691b29
		self.config.set("view/window-position-y", y)
254
59be49691b29
255
59be49691b29
		self.config.set("view/pane-position", self.hpaned.get_position())
256
59be49691b29
257
59be49691b29
258
c9d89f3ff91d
	def __state_clipboard(self, has_contents):
259
c9d89f3ff91d
		"Sets states based on the clipboard contents"
260
c9d89f3ff91d
261
c9d89f3ff91d
		self.uimanager.get_action("clip-paste").set_property("sensitive", has_contents)
262
c9d89f3ff91d
263
c9d89f3ff91d
264
c9d89f3ff91d
	def __state_entry(self, iters):
265
c9d89f3ff91d
		"Sets states for entry-dependant ui items"
266
c9d89f3ff91d
267
c9d89f3ff91d
		# widget sensitivity based on number of entries
268
c9d89f3ff91d
		self.uimanager.get_action_group("entry-multiple").set_sensitive(len(iters) > 0)
269
c9d89f3ff91d
		self.uimanager.get_action_group("entry-single").set_sensitive(len(iters) == 1)
270
c9d89f3ff91d
		self.uimanager.get_action_group("entry-optional").set_sensitive(len(iters) < 2)
271
c9d89f3ff91d
272
095f4008dc7f
		# goto sensitivity
273
c9d89f3ff91d
		try:
274
c9d89f3ff91d
			for iter in iters:
275
c9d89f3ff91d
				e = self.entrystore.get_entry(iter)
276
c9d89f3ff91d
277
c9d89f3ff91d
				if self.config.get("launcher/%s" % e.id) not in ( "", None ):
278
c9d89f3ff91d
					s = True
279
c9d89f3ff91d
					break
280
c9d89f3ff91d
281
c9d89f3ff91d
			else:
282
c9d89f3ff91d
				s = False
283
c9d89f3ff91d
284
c9d89f3ff91d
		except config.ConfigError:
285
c9d89f3ff91d
			s = False
286
c9d89f3ff91d
287
095f4008dc7f
		self.uimanager.get_action_group("entry-goto").set_sensitive(s)
288
dad9b7b301d1
289
dad9b7b301d1
		# dynamic menu items for field copying
290
dad9b7b301d1
		for menupath in ( "/popup-tree", "/menubar/menu-edit"):
291
dad9b7b301d1
			menu = self.uimanager.get_widget(menupath)
292
dad9b7b301d1
293
dad9b7b301d1
			if menupath == "/menubar/menu-edit":
294
dad9b7b301d1
				menu = menu.get_submenu()
295
dad9b7b301d1
296
dad9b7b301d1
			# scan for copy action, and remove dynamic items
297
dad9b7b301d1
			children = menu.get_children()
298
b57efdd083f5
			index_chain = children.index(self.uimanager.get_widget("%s/clip-chain" % menupath))
299
dad9b7b301d1
			index_paste = children.index(self.uimanager.get_widget("%s/clip-paste" % menupath))
300
dad9b7b301d1
301
b57efdd083f5
			for i in range(index_chain + 1, index_paste):
302
dad9b7b301d1
				children[i].destroy()
303
dad9b7b301d1
304
b57efdd083f5
			# fetch entry data
305
dad9b7b301d1
			if len(iters) != 1:
306
dad9b7b301d1
				continue
307
dad9b7b301d1
308
dad9b7b301d1
			e = self.entrystore.get_entry(iters[0])
309
b57efdd083f5
			secretfields = [ field for field in e.fields if (type(field) == entry.UsernameField or field.datatype == entry.DATATYPE_PASSWORD) and field.value != "" ]
310
dad9b7b301d1
311
b57efdd083f5
			# create menuitems
312
b57efdd083f5
			i = index_chain + 1
313
b57efdd083f5
			for field in secretfields:
314
dad9b7b301d1
				menuitem = ui.ImageMenuItem(gtk.STOCK_COPY, "Copy %s" % field.name)
315
dad9b7b301d1
				menuitem.tooltip = "Copy the %s to the clipboard" % field.name.lower()
316
dad9b7b301d1
				menuitem.connect("activate", self.__cb_clip_set, field.value)
317
dad9b7b301d1
				menuitem.connect("select", self.cb_menudesc, True)
318
dad9b7b301d1
				menuitem.connect("deselect", self.cb_menudesc, False)
319
dad9b7b301d1
				menu.insert(menuitem, i)
320
dad9b7b301d1
321
dad9b7b301d1
				i += 1
322
dad9b7b301d1
323
dad9b7b301d1
			menu.show_all()
324
dad9b7b301d1
325
a207757f8451
326
c9d89f3ff91d
	def __state_file(self, file):
327
c9d89f3ff91d
		"Sets states based on file"
328
a207757f8451
329
c9d89f3ff91d
		self.uimanager.get_action_group("file-exists").set_sensitive(file is not None)
330
a207757f8451
331
c9d89f3ff91d
		if file is not None:
332
c9d89f3ff91d
			self.set_title(os.path.basename(file))
333
a207757f8451
334
c9d89f3ff91d
			if io.file_is_local(file):
335
c9d89f3ff91d
				os.chdir(os.path.dirname(file))
336
a7c01d3b4b22
337
c9d89f3ff91d
		else:
338
c9d89f3ff91d
			self.set_title("[New file]")
339
a207757f8451
340
086abbb6fc3c
341
c9d89f3ff91d
	def __state_find(self, string):
342
c9d89f3ff91d
		"Sets states based on the current search string"
343
086abbb6fc3c
344
c9d89f3ff91d
		self.uimanager.get_action_group("find").set_sensitive(string != "")
345
086abbb6fc3c
346
086abbb6fc3c
347
c9d89f3ff91d
	def __state_undo(self, undoaction, redoaction):
348
c9d89f3ff91d
		"Sets states based on undoqueue actions"
349
086abbb6fc3c
350
c9d89f3ff91d
		if undoaction is None:
351
c9d89f3ff91d
			s, l = False, "_Undo"
352
086abbb6fc3c
353
c9d89f3ff91d
		else:
354
c9d89f3ff91d
			s, l = True, "_Undo %s" % undoaction[1].lower()
355
086abbb6fc3c
356
c9d89f3ff91d
		action = self.uimanager.get_action("undo")
357
c9d89f3ff91d
		action.set_property("sensitive", s)
358
c9d89f3ff91d
		action.set_property("label", l)
359
a207757f8451
360
a207757f8451
361
c9d89f3ff91d
		if redoaction is None:
362
c9d89f3ff91d
			s, l = False, "_Redo"
363
a207757f8451
364
c9d89f3ff91d
		else:
365
c9d89f3ff91d
			s, l = True, "_Redo %s" % redoaction[1].lower()
366
a207757f8451
367
c9d89f3ff91d
		action = self.uimanager.get_action("redo")
368
c9d89f3ff91d
		action.set_property("sensitive", s)
369
c9d89f3ff91d
		action.set_property("label", l)
370
a207757f8451
371
a207757f8451
372
a207757f8451
373
a207757f8451
374
c9d89f3ff91d
	##### MISC CALLBACKS #####
375
086abbb6fc3c
376
dad9b7b301d1
	def __cb_clip_set(self, widget, data):
377
dad9b7b301d1
		"Sets data on the clipboars"
378
dad9b7b301d1
379
dad9b7b301d1
		self.clipboard.set(data)
380
dad9b7b301d1
381
dad9b7b301d1
382
f5f9aadf2e19
	def __cb_drag_dest(self, widget, context, x, y, seldata, info, time, userdata = None):
383
f5f9aadf2e19
		"Handles file drops"
384
f5f9aadf2e19
385
f5f9aadf2e19
		if seldata.data is None:
386
f5f9aadf2e19
			return
387
f5f9aadf2e19
388
f5f9aadf2e19
		files = [ file.strip() for file in seldata.data.split("\n") if file.strip() != "" ]
389
f5f9aadf2e19
390
b57efdd083f5
		if len(files) > 0:
391
b57efdd083f5
			self.file_open(files[0])
392
f5f9aadf2e19
393
f5f9aadf2e19
394
a207757f8451
	def __cb_exception(self, type, value, trace):
395
a207757f8451
		"Callback for unhandled exceptions"
396
a207757f8451
397
9e161d94bffc
		if type == KeyboardInterrupt:
398
9e161d94bffc
			sys.exit(1)
399
9e161d94bffc
400
c9d89f3ff91d
		traceback = util.trace_exception(type, value, trace)
401
a207757f8451
		sys.stderr.write(traceback)
402
4db37324fd05
403
c9d89f3ff91d
		if dialog.Exception(self, traceback).run() == True:
404
c9d89f3ff91d
			gtk.main()
405
4db37324fd05
406
4db37324fd05
		else:
407
4db37324fd05
			sys.exit(1)
408
a207757f8451
409
a207757f8451
410
59be49691b29
	def __cb_session_save(self, widget, phase, what, end, interaction, fast, data = None):
411
59be49691b29
		"Handles session saves"
412
59be49691b29
413
59be49691b29
		self.sessionclient.set_current_directory(os.getcwd())
414
59be49691b29
		self.sessionclient.set_clone_command(sys.argv[0])
415
59be49691b29
416
59be49691b29
		if self.datafile.get_file() == None:
417
59be49691b29
			self.sessionclient.set_restart_command(1, [ sys.argv[0] ])
418
59be49691b29
419
59be49691b29
		else:
420
59be49691b29
			self.sessionclient.set_restart_command(2, [ sys.argv[0], self.datafile.get_file() ] )
421
59be49691b29
422
59be49691b29
		self.__save_state()
423
59be49691b29
424
59be49691b29
		return True
425
59be49691b29
426
59be49691b29
427
6cf6849eed72
	def __cb_tree_doubleclick(self, widget, data = None):
428
6cf6849eed72
		"Handles doubleclicks on the tree"
429
6cf6849eed72
430
095f4008dc7f
		self.entry_goto(self.tree.get_selected())
431
6cf6849eed72
432
6cf6849eed72
433
8077271f872d
	def __cb_tree_drag_received(self, tree, context, x, y, seldata, info, time):
434
8077271f872d
		"Callback for drag drops on the treeview"
435
8077271f872d
436
8077271f872d
		# get source and destination data
437
8077271f872d
		sourceiters = self.entrystore.filter_parents(self.tree.get_selected())
438
8077271f872d
		destrow = self.tree.get_dest_row_at_pos(x, y)
439
8077271f872d
440
8077271f872d
		if destrow is None:
441
8077271f872d
			destpath = ( self.entrystore.iter_n_children(None) - 1, )
442
8077271f872d
			pos = gtk.TREE_VIEW_DROP_AFTER
443
8077271f872d
444
8077271f872d
		else:
445
8077271f872d
			destpath, pos = destrow
446
8077271f872d
447
8077271f872d
		destiter = self.entrystore.get_iter(destpath)
448
9e1db933e6ed
		destpath = self.entrystore.get_path(destiter)
449
8077271f872d
450
9e1db933e6ed
		# avoid drops to current iter or descentants
451
8077271f872d
		for sourceiter in sourceiters:
452
9e1db933e6ed
			sourcepath = self.entrystore.get_path(sourceiter)
453
9e1db933e6ed
454
9e1db933e6ed
			if self.entrystore.is_ancestor(sourceiter, destiter) == True or sourcepath == destpath:
455
8077271f872d
				context.finish(False, False, time)
456
8077271f872d
				return
457
8077271f872d
458
9e1db933e6ed
			elif pos in ( gtk.TREE_VIEW_DROP_INTO_OR_BEFORE, gtk.TREE_VIEW_DROP_BEFORE ) and sourcepath[:-1] == destpath[:-1] and sourcepath[-1] == destpath[-1] - 1:
459
9e1db933e6ed
				context.finish(False, False, time)
460
9e1db933e6ed
				return
461
9e1db933e6ed
462
9e1db933e6ed
			elif pos in ( gtk.TREE_VIEW_DROP_INTO_OR_AFTER, gtk.TREE_VIEW_DROP_AFTER ) and sourcepath[:-1] == destpath[:-1] and sourcepath[-1] == destpath[-1] + 1:
463
9e1db933e6ed
				context.finish(False, False, time)
464
9e1db933e6ed
				return
465
9e1db933e6ed
466
9e1db933e6ed
467
8077271f872d
		# move the entries
468
8077271f872d
		if pos in ( gtk.TREE_VIEW_DROP_INTO_OR_BEFORE, gtk.TREE_VIEW_DROP_INTO_OR_AFTER):
469
8077271f872d
			parent = destiter
470
8077271f872d
			sibling = None
471
8077271f872d
472
8077271f872d
		elif pos == gtk.TREE_VIEW_DROP_BEFORE:
473
8077271f872d
			parent = self.entrystore.iter_parent(destiter)
474
8077271f872d
			sibling = destiter
475
8077271f872d
476
8077271f872d
		elif pos == gtk.TREE_VIEW_DROP_AFTER:
477
8077271f872d
			parent = self.entrystore.iter_parent(destiter)
478
8077271f872d
479
8077271f872d
			sibpath = list(destpath)
480
8077271f872d
			sibpath[-1] += 1
481
8077271f872d
			sibling = self.entrystore.get_iter(sibpath)
482
8077271f872d
483
8077271f872d
		self.entry_move(sourceiters, parent, sibling)
484
8077271f872d
		context.finish(True, True, time)
485
8077271f872d
486
8077271f872d
487
c9d89f3ff91d
	def __cb_tree_keypress(self, widget, data = None):
488
a207757f8451
		"Handles key presses for the tree"
489
a207757f8451
490
a207757f8451
		# return
491
a207757f8451
		if data.keyval == 65293:
492
a207757f8451
			self.entry_edit()
493
a207757f8451
494
a207757f8451
		# insert
495
a207757f8451
		elif data.keyval == 65379:
496
a207757f8451
			self.entry_add()
497
a207757f8451
498
a207757f8451
		# delete
499
a207757f8451
		elif data.keyval == 65535:
500
a207757f8451
			self.entry_remove()
501
a207757f8451
502
a207757f8451
503
a207757f8451
504
c9d89f3ff91d
	##### CONFIG CALLBACKS #####
505
c9d89f3ff91d
506
c9d89f3ff91d
	def __cb_config_toolbar(self, config, value, toolbar):
507
c9d89f3ff91d
		"Config callback for showing toolbars"
508
c9d89f3ff91d
509
c9d89f3ff91d
		if value == True:
510
c9d89f3ff91d
			toolbar.show()
511
c9d89f3ff91d
512
c9d89f3ff91d
		else:
513
c9d89f3ff91d
			toolbar.hide()
514
c9d89f3ff91d
515
c9d89f3ff91d
516
c9d89f3ff91d
517
c9d89f3ff91d
	#### UNDO / REDO CALLBACKS #####
518
c9d89f3ff91d
519
c9d89f3ff91d
	def __cb_redo_add(self, name, actiondata):
520
c9d89f3ff91d
		"Redoes an add action"
521
c9d89f3ff91d
522
c9d89f3ff91d
		path, e = actiondata
523
c9d89f3ff91d
		parent = self.entrystore.get_iter(path[:-1])
524
c9d89f3ff91d
		sibling = self.entrystore.get_iter(path)
525
c9d89f3ff91d
526
c9d89f3ff91d
		iter = self.entrystore.add_entry(e, parent, sibling)
527
c9d89f3ff91d
		self.tree.select(iter)
528
c9d89f3ff91d
529
c9d89f3ff91d
530
c9d89f3ff91d
	def __cb_redo_edit(self, name, actiondata):
531
c9d89f3ff91d
		"Redoes an edit action"
532
c9d89f3ff91d
533
c9d89f3ff91d
		path, preentry, postentry = actiondata
534
c9d89f3ff91d
		iter = self.entrystore.get_iter(path)
535
c9d89f3ff91d
536
c9d89f3ff91d
		self.entrystore.update_entry(iter, postdata)
537
c9d89f3ff91d
		self.tree.select(iter)
538
c9d89f3ff91d
539
c9d89f3ff91d
540
c9d89f3ff91d
	def __cb_redo_import(self, name, actiondata):
541
c9d89f3ff91d
		"Redoes an import action"
542
c9d89f3ff91d
543
c9d89f3ff91d
		paths, entrystore = actiondata
544
c9d89f3ff91d
		self.entrystore.import_entry(entrystore, None)
545
c9d89f3ff91d
546
c9d89f3ff91d
547
8077271f872d
	def __cb_redo_move(self, name, actiondata):
548
8077271f872d
		"Redoes a move action"
549
8077271f872d
550
8077271f872d
		newiters = []
551
8077271f872d
552
8077271f872d
		for prepath, postpath in actiondata:
553
8077271f872d
			prepath, postpath = list(prepath), list(postpath)
554
8077271f872d
555
8077271f872d
			# adjust path if necessary
556
8077271f872d
			if postpath[:len(prepath) - 1] == prepath[:-1]:
557
8077271f872d
				if prepath[-1] <= postpath[len(prepath) - 1]:
558
8077271f872d
					postpath[len(prepath) - 1] += 1
559
8077271f872d
560
8077271f872d
			newiter = self.entrystore.move_entry(
561
8077271f872d
				self.entrystore.get_iter(prepath),
562
8077271f872d
				self.entrystore.get_iter(postpath[:-1]),
563
8077271f872d
				self.entrystore.get_iter(postpath)
564
8077271f872d
			)
565
8077271f872d
566
8077271f872d
			newiters.append(newiter)
567
8077271f872d
568
8077271f872d
		if len(newiters) > 0:
569
8077271f872d
			self.tree.select(newiters[0])
570
8077271f872d
571
8077271f872d
572
c9d89f3ff91d
	def __cb_redo_paste(self, name, actiondata):
573
c9d89f3ff91d
		"Redoes a paste action"
574
c9d89f3ff91d
575
c9d89f3ff91d
		entrystore, parentpath, paths = actiondata
576
c9d89f3ff91d
		iters = self.entrystore.import_entry(entrystore, None, self.entrystore.get_iter(parentpath))
577
c9d89f3ff91d
578
c9d89f3ff91d
		if len(iters) > 0:
579
c9d89f3ff91d
			self.tree.select(iters[0])
580
c9d89f3ff91d
581
c9d89f3ff91d
582
c9d89f3ff91d
	def __cb_redo_remove(self, name, actiondata):
583
c9d89f3ff91d
		"Redoes a remove action"
584
c9d89f3ff91d
585
c9d89f3ff91d
		iters = []
586
c9d89f3ff91d
		for path, entrystore in actiondata:
587
c9d89f3ff91d
			iters.append(self.entrystore.get_iter(path))
588
c9d89f3ff91d
589
c9d89f3ff91d
		for iter in iters:
590
c9d89f3ff91d
			self.entrystore.remove_entry(iter)
591
c9d89f3ff91d
592
c9d89f3ff91d
		self.tree.unselect_all()
593
c9d89f3ff91d
594
c9d89f3ff91d
595
c9d89f3ff91d
	def __cb_undo_add(self, name, actiondata):
596
c9d89f3ff91d
		"Undoes an add action"
597
c9d89f3ff91d
598
c9d89f3ff91d
		path, e = actiondata
599
c9d89f3ff91d
600
c9d89f3ff91d
		self.entrystore.remove_entry(self.entrystore.get_iter(path))
601
c9d89f3ff91d
		self.tree.unselect_all()
602
c9d89f3ff91d
603
c9d89f3ff91d
604
c9d89f3ff91d
	def __cb_undo_edit(self, name, actiondata):
605
c9d89f3ff91d
		"Undoes an edit action"
606
c9d89f3ff91d
607
c9d89f3ff91d
		path, preentry, postentry = actiondata
608
c9d89f3ff91d
		iter = self.entrystore.get_iter(path)
609
c9d89f3ff91d
610
c9d89f3ff91d
		self.entrystore.update_entry(iter, postentry)
611
c9d89f3ff91d
		self.tree.select(iter)
612
c9d89f3ff91d
613
c9d89f3ff91d
614
c9d89f3ff91d
	def __cb_undo_import(self, name, actiondata):
615
c9d89f3ff91d
		"Undoes an import action"
616
c9d89f3ff91d
617
c9d89f3ff91d
		paths, entrystore = actiondata
618
c9d89f3ff91d
		iters = [ self.entrystore.get_iter(path) for path in paths ]
619
c9d89f3ff91d
620
c9d89f3ff91d
		for iter in iters:
621
c9d89f3ff91d
			self.entrystore.remove_entry(iter)
622
c9d89f3ff91d
623
c9d89f3ff91d
		self.tree.unselect_all()
624
c9d89f3ff91d
625
c9d89f3ff91d
626
8077271f872d
	def __cb_undo_move(self, name, actiondata):
627
8077271f872d
		"Undoes a move action"
628
8077271f872d
629
6bb7821fddaf
		actiondata = actiondata[:]
630
6bb7821fddaf
		actiondata.reverse()
631
6bb7821fddaf
632
8077271f872d
		newiters = []
633
8077271f872d
634
8077271f872d
		for prepath, postpath in actiondata:
635
8077271f872d
			prepath, postpath = list(prepath), list(postpath)
636
8077271f872d
637
8077271f872d
			# adjust path if necessary
638
8077271f872d
			if postpath[:-1] == prepath[:-1]:
639
8077271f872d
				if prepath[-1] > postpath[len(prepath) - 1]:
640
8077271f872d
					prepath[-1] += 1
641
8077271f872d
642
8077271f872d
			newiter = self.entrystore.move_entry(
643
8077271f872d
				self.entrystore.get_iter(postpath),
644
8077271f872d
				self.entrystore.get_iter(prepath[:-1]),
645
8077271f872d
				self.entrystore.get_iter(prepath)
646
8077271f872d
			)
647
8077271f872d
648
8077271f872d
			newiters.append(newiter)
649
8077271f872d
650
8077271f872d
		if len(newiters) > 0:
651
6bb7821fddaf
			self.tree.select(newiters[-1])
652
8077271f872d
653
8077271f872d
654
c9d89f3ff91d
	def __cb_undo_paste(self, name, actiondata):
655
c9d89f3ff91d
		"Undoes a paste action"
656
c9d89f3ff91d
657
c9d89f3ff91d
		entrystore, parentpath, paths = actiondata
658
c9d89f3ff91d
		iters = [ self.entrystore.get_iter(path) for path in paths ]
659
c9d89f3ff91d
660
c9d89f3ff91d
		for iter in iters:
661
c9d89f3ff91d
			self.entrystore.remove_entry(iter)
662
c9d89f3ff91d
663
c9d89f3ff91d
		self.tree.unselect_all()
664
c9d89f3ff91d
665
c9d89f3ff91d
666
c9d89f3ff91d
	def __cb_undo_remove(self, name, actiondata):
667
c9d89f3ff91d
		"Undoes a remove action"
668
c9d89f3ff91d
669
c9d89f3ff91d
		iters = []
670
c9d89f3ff91d
		for path, entrystore in actiondata:
671
c9d89f3ff91d
			parent = self.entrystore.get_iter(path[:-1])
672
c9d89f3ff91d
			sibling = self.entrystore.get_iter(path)
673
c9d89f3ff91d
674
c9d89f3ff91d
			iter = self.entrystore.import_entry(entrystore, entrystore.iter_nth_child(None, 0), parent, sibling)
675
c9d89f3ff91d
			iters.append(iter)
676
c9d89f3ff91d
677
c9d89f3ff91d
		self.tree.select(iters[0])
678
c9d89f3ff91d
679
c9d89f3ff91d
680
c9d89f3ff91d
681
c9d89f3ff91d
	##### PRIVATE METHODS #####
682
c9d89f3ff91d
683
c9d89f3ff91d
	def __entry_find(self, parent, string, entrytype, direction = data.SEARCH_NEXT):
684
a207757f8451
		"Searches for an entry"
685
a207757f8451
686
c9d89f3ff91d
		match = self.entrysearch.find(string, entrytype, self.tree.get_active(), direction)
687
a207757f8451
688
c9d89f3ff91d
		self.entrysearch.string	= string
689
c9d89f3ff91d
		self.entrysearch.type	= entrytype
690
5ff135ae2b70
691
c9d89f3ff91d
		self.__state_find(string)
692
a207757f8451
693
c9d89f3ff91d
		if match is not None:
694
c9d89f3ff91d
			self.tree.select(match)
695
a207757f8451
696
a207757f8451
		else:
697
c9d89f3ff91d
			dialog.Error(parent, "No match found", "The string you searched for did not match any entries. Try searching for a different phrase.").run()
698
a207757f8451
699
a207757f8451
700
a207757f8451
	def __file_autosave(self):
701
c9d89f3ff91d
		"Autosaves the current file if needed"
702
a207757f8451
703
c9d89f3ff91d
		if self.datafile.get_file() is None or self.datafile.get_password() is None:
704
a207757f8451
			return
705
a207757f8451
706
c9d89f3ff91d
		if self.config.get("file/autosave") == False:
707
a207757f8451
			return
708
a207757f8451
709
c9d89f3ff91d
		self.__file_save(self.datafile.get_file(), self.datafile.get_password())
710
c9d89f3ff91d
		self.entrystore.changed = False
711
a207757f8451
712
a207757f8451
713
9e161d94bffc
	def __file_load(self, file, password, datafile = None):
714
c9d89f3ff91d
		"Loads data from a data file into an entrystore"
715
9e161d94bffc
716
a207757f8451
		try:
717
c9d89f3ff91d
			if datafile is None:
718
c9d89f3ff91d
				datafile = self.datafile
719
c9d89f3ff91d
720
9e161d94bffc
			while 1:
721
9e161d94bffc
				try:
722
c9d89f3ff91d
					return datafile.load(file, password, lambda: dialog.PasswordOpen(self, os.path.basename(file)).run())
723
a207757f8451
724
c9d89f3ff91d
				except datahandler.PasswordError:
725
c9d89f3ff91d
					dialog.Error(self, "Incorrect password", "The password you entered for the file'%s' was not correct." % file).run()
726
a207757f8451
727
c9d89f3ff91d
		except datahandler.FormatError:
728
c9d89f3ff91d
			self.statusbar.set_status("Open failed")
729
c9d89f3ff91d
			dialog.Error(self, "Invalid file format", "The file '%s' contains invalid data." % file).run()
730
a207757f8451
731
c9d89f3ff91d
		except ( entry.EntryTypeError, entry.EntryFieldError ):
732
c9d89f3ff91d
			self.statusbar.set_status("Open failed")
733
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()
734
a207757f8451
735
c9d89f3ff91d
		except datahandler.VersionError:
736
c9d89f3ff91d
			self.statusbar.set_status("Open failed")
737
f5f9aadf2e19
			dialog.Error(self, "Unknown data version", "The file '%s' has a future version number, please upgrade Revelation to open it." % file).run()
738
a207757f8451
739
c9d89f3ff91d
		except datahandler.DetectError:
740
a207757f8451
			self.statusbar.set_status("Open failed")
741
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()
742
a207757f8451
743
a207757f8451
		except IOError:
744
a207757f8451
			self.statusbar.set_status("Open failed")
745
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()
746
a207757f8451
747
a207757f8451
748
9e161d94bffc
	def __file_save(self, file, password, datafile = None):
749
a207757f8451
		"Saves data to a file"
750
a207757f8451
751
c9d89f3ff91d
		try:
752
c9d89f3ff91d
			if datafile is None:
753
c9d89f3ff91d
				datafile = self.datafile
754
9e161d94bffc
755
c9d89f3ff91d
			if io.file_normpath(str(datafile)) != io.file_normpath(file) and io.file_exists(file):
756
c9d89f3ff91d
				dialog.FileOverwrite(self, file).run()
757
a207757f8451
758
c9d89f3ff91d
			if datafile.get_handler().encryption == True:
759
9e161d94bffc
				if password is None:
760
c9d89f3ff91d
					password = dialog.PasswordSave(self, file).run()
761
9e161d94bffc
762
9e161d94bffc
			else:
763
c9d89f3ff91d
				dialog.FileSaveInsecure(self).run()
764
a207757f8451
765
c9d89f3ff91d
			datafile.save(self.entrystore, file, password)
766
a207757f8451
767
a207757f8451
		except IOError:
768
c9d89f3ff91d
			revelation.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()
769
a207757f8451
			self.statusbar.set_status("Save failed")
770
a207757f8451
771
a207757f8451
772
c9d89f3ff91d
	def __get_common_usernames(self, e = None):
773
c9d89f3ff91d
		"Returns a list of possibly relevant usernames"
774
a207757f8451
775
c9d89f3ff91d
		list = []
776
c9d89f3ff91d
777
c9d89f3ff91d
		if e is not None and e.has_field(entry.UsernameField):
778
c9d89f3ff91d
			list.append(e[entry.UsernameField])
779
c9d89f3ff91d
780
c9d89f3ff91d
		list.append(pwd.getpwuid(os.getuid())[0])
781
c9d89f3ff91d
		list.extend(self.entrystore.get_popular_values(entry.UsernameField, 3))
782
c9d89f3ff91d
783
c9d89f3ff91d
		list = {}.fromkeys(list).keys()
784
c9d89f3ff91d
		list.sort()
785
c9d89f3ff91d
786
c9d89f3ff91d
		return list
787
c9d89f3ff91d
788
c9d89f3ff91d
789
c9d89f3ff91d
	def __save_changes(self, d):
790
a207757f8451
		"Asks the user if she wants to save her changes"
791
a207757f8451
792
c9d89f3ff91d
		if self.entrystore.changed == True and d(self).run() == True:
793
c9d89f3ff91d
			if self.file_save(self.datafile.get_file(), self.datafile.get_password()) == False:
794
c9d89f3ff91d
				raise dialog.CancelError
795
a207757f8451
796
a207757f8451
797
a207757f8451
798
c9d89f3ff91d
	##### PUBLIC METHODS #####
799
a207757f8451
800
4674492151c3
	def clip_chain(self, e):
801
b57efdd083f5
		"Copies any usernames and passwords from an entry as a chain"
802
b57efdd083f5
803
4674492151c3
		if e == None:
804
b57efdd083f5
			return
805
b57efdd083f5
806
b57efdd083f5
		secrets = [ field.value for field in e.fields if field.datatype == entry.DATATYPE_PASSWORD and field.value != "" ]
807
b57efdd083f5
808
b57efdd083f5
		if e.has_field(entry.UsernameField) == True and e[entry.UsernameField] != "":
809
b57efdd083f5
			secrets.insert(0, e[entry.UsernameField])
810
b57efdd083f5
811
b57efdd083f5
		self.clipboard.set(secrets)
812
4674492151c3
		self.statusbar.set_status("Password chain copied to clipboard")
813
b57efdd083f5
814
b57efdd083f5
815
4674492151c3
	def clip_copy(self, iters):
816
c9d89f3ff91d
		"Copies entries to the clipboard"
817
a207757f8451
818
4674492151c3
		self.entryclipboard.set(self.entrystore, iters)
819
c9d89f3ff91d
		self.statusbar.set_status("Entries copied")
820
a207757f8451
821
a207757f8451
822
4674492151c3
	def clip_cut(self, iters):
823
c9d89f3ff91d
		"Cuts entries to the clipboard"
824
a207757f8451
825
c9d89f3ff91d
		iters = self.entrystore.filter_parents(iters)
826
c9d89f3ff91d
		self.entryclipboard.set(self.entrystore, iters)
827
c9d89f3ff91d
828
c9d89f3ff91d
		# store undo data (need paths)
829
c9d89f3ff91d
		undoactions = []
830
c9d89f3ff91d
		for iter in iters:
831
c9d89f3ff91d
			undostore = data.EntryStore()
832
c9d89f3ff91d
			undostore.import_entry(self.entrystore, iter)
833
c9d89f3ff91d
			path = self.entrystore.get_path(iter)
834
c9d89f3ff91d
			undoactions.append( ( path, undostore ) )
835
c9d89f3ff91d
836
c9d89f3ff91d
		# remove data
837
c9d89f3ff91d
		for iter in iters:
838
c9d89f3ff91d
			self.entrystore.remove_entry(iter)
839
c9d89f3ff91d
840
c9d89f3ff91d
		self.undoqueue.add_action(
841
c9d89f3ff91d
			"Cut entries", self.__cb_undo_remove, self.__cb_redo_remove,
842
c9d89f3ff91d
			undoactions
843
c9d89f3ff91d
		)
844
c9d89f3ff91d
845
a207757f8451
		self.__file_autosave()
846
c9d89f3ff91d
847
a207757f8451
		self.tree.unselect_all()
848
c9d89f3ff91d
		self.statusbar.set_status("Entries cut")
849
a207757f8451
850
a207757f8451
851
4674492151c3
	def clip_paste(self, entrystore, parent):
852
a207757f8451
		"Pastes entries from the clipboard"
853
a207757f8451
854
4674492151c3
		if entrystore == None:
855
a207757f8451
			return
856
a207757f8451
857
c9d89f3ff91d
		parent = self.tree.get_active()
858
c9d89f3ff91d
		iters = self.entrystore.import_entry(entrystore, None, parent)
859
c9d89f3ff91d
860
c9d89f3ff91d
		paths = [ self.entrystore.get_path(iter) for iter in iters ]
861
c9d89f3ff91d
862
c9d89f3ff91d
		self.undoqueue.add_action(
863
c9d89f3ff91d
			"Paste entries", self.__cb_undo_paste, self.__cb_redo_paste,
864
c9d89f3ff91d
			( entrystore, self.entrystore.get_path(parent), paths )
865
c9d89f3ff91d
		)
866
c9d89f3ff91d
867
c9d89f3ff91d
		if len(iters) > 0:
868
c9d89f3ff91d
			self.tree.select(iters[0])
869
c9d89f3ff91d
870
c9d89f3ff91d
		self.statusbar.set_status("Entries pasted")
871
a207757f8451
872
a207757f8451
873
4674492151c3
	def entry_add(self, e = None, parent = None, sibling = None):
874
a207757f8451
		"Adds an entry"
875
a207757f8451
876
a207757f8451
		try:
877
4674492151c3
			if e == None:
878
4674492151c3
				d = dialog.EntryEdit(self, self.config, "Add entry")
879
4674492151c3
				d.set_fieldwidget_data(entry.UsernameField, self.__get_common_usernames())
880
4674492151c3
				e = d.run()
881
a207757f8451
882
4674492151c3
			iter = self.entrystore.add_entry(e, parent, sibling)
883
c9d89f3ff91d
884
c9d89f3ff91d
			self.undoqueue.add_action(
885
c9d89f3ff91d
				"Add entry", self.__cb_undo_add, self.__cb_redo_add,
886
c9d89f3ff91d
				( self.entrystore.get_path(iter), e.copy() )
887
c9d89f3ff91d
			)
888
c9d89f3ff91d
889
a207757f8451
			self.__file_autosave()
890
4674492151c3
			self.tree.select(iter)
891
c9d89f3ff91d
			self.statusbar.set_status("Entry added")
892
a207757f8451
893
c9d89f3ff91d
		except dialog.CancelError:
894
a207757f8451
			self.statusbar.set_status("Add entry cancelled")
895
a207757f8451
896
a207757f8451
897
4674492151c3
	def entry_edit(self, iter):
898
a207757f8451
		"Edits an entry"
899
a207757f8451
900
c9d89f3ff91d
		try:
901
4674492151c3
			if iter == None:
902
4674492151c3
				return
903
4674492151c3
904
c9d89f3ff91d
			e = self.entrystore.get_entry(iter)
905
c9d89f3ff91d
			d = dialog.EntryEdit(self, self.config, "Edit entry", e)
906
c9d89f3ff91d
			d.set_fieldwidget_data(entry.UsernameField, self.__get_common_usernames(e))
907
a207757f8451
908
c9d89f3ff91d
			if type(e) == entry.FolderEntry and self.entrystore.iter_n_children(iter) > 0:
909
c9d89f3ff91d
				d.allow_typechange(False)
910
a207757f8451
911
c9d89f3ff91d
			n = d.run()
912
c9d89f3ff91d
			self.entrystore.update_entry(iter, n)
913
c9d89f3ff91d
			self.tree.select(iter)
914
a207757f8451
915
c9d89f3ff91d
			self.undoqueue.add_action(
916
c9d89f3ff91d
				"Update entry", self.__cb_undo_edit, self.__cb_redo_edit,
917
c9d89f3ff91d
				( self.entrystore.get_path(iter), e.copy(), n.copy() )
918
c9d89f3ff91d
			)
919
a207757f8451
920
a207757f8451
			self.__file_autosave()
921
c9d89f3ff91d
			self.statusbar.set_status("Entry updated")
922
c9d89f3ff91d
923
c9d89f3ff91d
		except dialog.CancelError:
924
c9d89f3ff91d
			self.statusbar.set_status("Edit entry cancelled")
925
a207757f8451
926
a207757f8451
927
a207757f8451
	def entry_find(self):
928
a207757f8451
		"Searches for an entry"
929
a207757f8451
930
c9d89f3ff91d
		d = dialog.Find(self, self.config)
931
c9d89f3ff91d
		d.entry_string.set_text(self.entrysearch.string)
932
c9d89f3ff91d
		d.dropdown.set_active_type(self.entrysearch.type)
933
a207757f8451
934
a207757f8451
		while 1:
935
c9d89f3ff91d
			response	= d.run()
936
c9d89f3ff91d
			string		= d.entry_string.get_text()
937
c9d89f3ff91d
			entrytype	= d.dropdown.get_active_type()
938
a207757f8451
939
c9d89f3ff91d
			if response == dialog.RESPONSE_NEXT:
940
c9d89f3ff91d
				self.__entry_find(d, string, entrytype, data.SEARCH_NEXT)
941
a207757f8451
942
c9d89f3ff91d
			elif response == dialog.RESPONSE_PREVIOUS:
943
c9d89f3ff91d
				self.__entry_find(d, string, entrytype, data.SEARCH_PREVIOUS)
944
a207757f8451
945
a207757f8451
			else:
946
c9d89f3ff91d
				d.destroy()
947
a207757f8451
				break
948
a207757f8451
949
a207757f8451
950
095f4008dc7f
	def entry_goto(self, iters):
951
095f4008dc7f
		"Goes to an entry"
952
a207757f8451
953
a207757f8451
		for iter in iters:
954
a207757f8451
			try:
955
e5330daca1cd
956
095f4008dc7f
				# get goto data for entry
957
c9d89f3ff91d
				e = self.entrystore.get_entry(iter)
958
6cf6849eed72
				command = self.config.get("launcher/%s" % e.id)
959
a207757f8451
960
c9d89f3ff91d
				if command in ( "", None ):
961
095f4008dc7f
					self.statusbar.set_status("No goto command found for " + e.typename + " entries")
962
c9d89f3ff91d
					return
963
c371b70d69d8
964
c9d89f3ff91d
				subst = {}
965
c9d89f3ff91d
				for field in e.fields:
966
c9d89f3ff91d
					subst[field.symbol] = field.value
967
c371b70d69d8
968
095f4008dc7f
				# copy passwords to clipboar
969
095f4008dc7f
				chain = []
970
e5330daca1cd
971
095f4008dc7f
				if e.has_field(entry.UsernameField) == True and e[entry.UsernameField] != "" and "%" + entry.UsernameField.symbol not in command:
972
095f4008dc7f
					chain.append(e[entry.UsernameField])
973
095f4008dc7f
974
095f4008dc7f
				for field in e.fields:
975
095f4008dc7f
					if field.datatype == entry.DATATYPE_PASSWORD and field.value != "":
976
095f4008dc7f
						chain.append(field.value)
977
095f4008dc7f
978
095f4008dc7f
				self.clipboard.set(chain)
979
095f4008dc7f
980
095f4008dc7f
				# generate and run goto command
981
c9d89f3ff91d
				command = util.parse_subst(command, subst)
982
c9d89f3ff91d
				util.execute_child(command)
983
a207757f8451
984
095f4008dc7f
				self.statusbar.set_status("Entry opened")
985
e5330daca1cd
986
c9d89f3ff91d
			except ( util.SubstFormatError, config.ConfigError ):
987
095f4008dc7f
				dialog.Error(self, "Invalid goto command format", "The goto command for '" + e.typename + "' entries is invalid, please correct this in the preferences.").run()
988
a207757f8451
989
c9d89f3ff91d
			except util.SubstValueError:
990
095f4008dc7f
				dialog.Error(self, "Missing entry data", "The entry '" + e.name + "' does not have all the data required to go to it.").run()
991
8077271f872d
992
8077271f872d
993
8077271f872d
	def entry_move(self, sourceiters, parent = None, sibling = None):
994
8077271f872d
		"Moves a set of entries"
995
8077271f872d
996
8077271f872d
		if type(sourceiters) != list:
997
8077271f872d
			sourceiters = [ sourceiters ]
998
8077271f872d
999
8077271f872d
		newiters = []
1000
8077271f872d
		undoactions = []
1001
8077271f872d
1002
8077271f872d
		for sourceiter in sourceiters:
1003
8077271f872d
			sourcepath = self.entrystore.get_path(sourceiter)
1004
8077271f872d
			newiter = self.entrystore.move_entry(sourceiter, parent, sibling)
1005
8077271f872d
			newpath = self.entrystore.get_path(newiter)
1006
8077271f872d
1007
8077271f872d
			undoactions.append( ( sourcepath, newpath ) )
1008
8077271f872d
			newiters.append(newiter)
1009
8077271f872d
1010
8077271f872d
		self.undoqueue.add_action(
1011
8077271f872d
			"Move entry", self.__cb_undo_move, self.__cb_redo_move,
1012
8077271f872d
			undoactions
1013
8077271f872d
		)
1014
8077271f872d
1015
8077271f872d
		if len(newiters) > 0:
1016
8077271f872d
			self.tree.select(newiters[0])
1017
8077271f872d
1018
8077271f872d
		self.statusbar.set_status("Entries moved")
1019
a207757f8451
1020
a207757f8451
1021
4674492151c3
	def entry_remove(self, iters):
1022
c9d89f3ff91d
		"Removes the selected entries"
1023
a207757f8451
1024
a207757f8451
		try:
1025
c9d89f3ff91d
			if len(iters) == 0:
1026
c9d89f3ff91d
				return
1027
a207757f8451
1028
c9d89f3ff91d
			entries = [ self.entrystore.get_entry(iter) for iter in iters ]
1029
c9d89f3ff91d
			dialog.EntryRemove(self, entries).run()
1030
c9d89f3ff91d
			iters = self.entrystore.filter_parents(iters)
1031
a207757f8451
1032
c9d89f3ff91d
			# store undo data (need paths)
1033
c9d89f3ff91d
			undoactions = []
1034
c9d89f3ff91d
			for iter in iters:
1035
c9d89f3ff91d
				undostore = data.EntryStore()
1036
c9d89f3ff91d
				undostore.import_entry(self.entrystore, iter)
1037
c9d89f3ff91d
				path = self.entrystore.get_path(iter)
1038
c9d89f3ff91d
				undoactions.append( ( path, undostore ) )
1039
a207757f8451
1040
c9d89f3ff91d
			# remove data
1041
a207757f8451
			for iter in iters:
1042
c9d89f3ff91d
				self.entrystore.remove_entry(iter)
1043
c9d89f3ff91d
1044
c9d89f3ff91d
			self.undoqueue.add_action(
1045
c9d89f3ff91d
				"Remove entry", self.__cb_undo_remove, self.__cb_redo_remove,
1046
c9d89f3ff91d
				undoactions
1047
c9d89f3ff91d
			)
1048
a207757f8451
1049
4674492151c3
			self.tree.unselect_all()
1050
a207757f8451
			self.__file_autosave()
1051
c9d89f3ff91d
			self.statusbar.set_status("Entries removed")
1052
a207757f8451
1053
c9d89f3ff91d
		except dialog.CancelError:
1054
c9d89f3ff91d
			self.statusbar.set_status("Entry removal cancelled")
1055
c9d89f3ff91d
1056
c9d89f3ff91d
1057
4674492151c3
	def file_change_password(self, password = None):
1058
c9d89f3ff91d
		"Changes the password of the current data file"
1059
c9d89f3ff91d
1060
c9d89f3ff91d
		try:
1061
4674492151c3
			if password == None:
1062
4674492151c3
				password = dialog.PasswordChange(self, self.datafile.get_password()).run()
1063
4674492151c3
1064
c9d89f3ff91d
			self.datafile.set_password(password)
1065
c9d89f3ff91d
			self.entrystore.changed = True
1066
c9d89f3ff91d
1067
c9d89f3ff91d
			self.__file_autosave()
1068
c9d89f3ff91d
			self.statusbar.set_status("Password changed")
1069
c9d89f3ff91d
1070
c9d89f3ff91d
		except dialog.CancelError:
1071
c9d89f3ff91d
			self.statusbar.set_status("Password change cancelled")
1072
a207757f8451
1073
a207757f8451
1074
a207757f8451
	def file_export(self):
1075
a207757f8451
		"Exports data to a foreign file format"
1076
a207757f8451
1077
a207757f8451
		try:
1078
c9d89f3ff91d
			file, handler = dialog.ExportFileSelector(self).run()
1079
c9d89f3ff91d
			datafile = io.DataFile(handler)
1080
9e161d94bffc
			self.__file_save(file, None, datafile)
1081
a207757f8451
1082
c9d89f3ff91d
			self.statusbar.set_status("Data exported to %s" % datafile.get_file())
1083
c9d89f3ff91d
1084
c9d89f3ff91d
		except dialog.CancelError:
1085
a207757f8451
			self.statusbar.set_status("Export cancelled")
1086
a207757f8451
1087
a207757f8451
1088
a207757f8451
	def file_import(self):
1089
a207757f8451
		"Imports data from a foreign file"
1090
a207757f8451
1091
a207757f8451
		try:
1092
c9d89f3ff91d
			file, handler = dialog.ImportFileSelector(self).run()
1093
c9d89f3ff91d
			datafile = io.DataFile(handler)
1094
9e161d94bffc
			entrystore = self.__file_load(file, None, datafile)
1095
a207757f8451
1096
c9d89f3ff91d
			if entrystore is not None:
1097
c9d89f3ff91d
				newiters = self.entrystore.import_entry(entrystore, None)
1098
c9d89f3ff91d
				paths = [ self.entrystore.get_path(iter) for iter in newiters ]
1099
a207757f8451
1100
c9d89f3ff91d
				self.undoqueue.add_action(
1101
c9d89f3ff91d
					"Import data", self.__cb_undo_import, self.__cb_redo_import,
1102
c9d89f3ff91d
					( paths, entrystore )
1103
c9d89f3ff91d
				)
1104
c9d89f3ff91d
1105
c9d89f3ff91d
				self.statusbar.set_status("Data imported from %s" % datafile.get_file())
1106
c9d89f3ff91d
1107
a207757f8451
			self.__file_autosave()
1108
a207757f8451
1109
c9d89f3ff91d
		except dialog.CancelError:
1110
a207757f8451
			self.statusbar.set_status("Import cancelled")
1111
a207757f8451
1112
a207757f8451
1113
a207757f8451
	def file_lock(self):
1114
c9d89f3ff91d
		"Locks the current file"
1115
a207757f8451
1116
c9d89f3ff91d
		password = self.datafile.get_password()
1117
c9d89f3ff91d
1118
c9d89f3ff91d
		if password is None:
1119
a207757f8451
			return
1120
a207757f8451
1121
c9d89f3ff91d
		# store current state
1122
c9d89f3ff91d
		activeiter = self.tree.get_active()
1123
c9d89f3ff91d
		oldtitle = self.get_title()
1124
c9d89f3ff91d
1125
c9d89f3ff91d
		# lock the file
1126
a207757f8451
		self.tree.set_model(None)
1127
c9d89f3ff91d
		self.entryview.clear()
1128
c9d89f3ff91d
		self.set_title("[Locked]")
1129
a207757f8451
		self.statusbar.set_status("File locked")
1130
a207757f8451
1131
c9d89f3ff91d
		dialog.PasswordLock(self, password).run()
1132
a207757f8451
1133
c9d89f3ff91d
		# unlock the file and restore state
1134
c9d89f3ff91d
		self.tree.set_model(self.entrystore)
1135
c9d89f3ff91d
		self.tree.select(activeiter)
1136
c9d89f3ff91d
		self.set_title(oldtitle)
1137
a207757f8451
		self.statusbar.set_status("File unlocked")
1138
a207757f8451
1139
a207757f8451
1140
a207757f8451
	def file_new(self):
1141
a207757f8451
		"Opens a new file"
1142
a207757f8451
1143
c9d89f3ff91d
		try:
1144
c9d89f3ff91d
			self.__save_changes(dialog.FileChangedNew)
1145
c9d89f3ff91d
1146
c9d89f3ff91d
			self.entrystore.clear()
1147
c9d89f3ff91d
			self.datafile.close()
1148
8eba4e20d67b
			self.undoqueue.clear()
1149
c9d89f3ff91d
			self.statusbar.set_status("New file created")
1150
c9d89f3ff91d
1151
c9d89f3ff91d
		except dialog.CancelError:
1152
a207757f8451
			self.statusbar.set_status("New file cancelled")
1153
a207757f8451
1154
a207757f8451
1155
a207757f8451
	def file_open(self, file = None, password = None):
1156
a207757f8451
		"Opens a data file"
1157
a207757f8451
1158
a207757f8451
		try:
1159
c9d89f3ff91d
			self.__save_changes(dialog.FileChangedOpen)
1160
a207757f8451
1161
a207757f8451
			if file is None:
1162
c9d89f3ff91d
				file = dialog.OpenFileSelector(self).run()
1163
a207757f8451
1164
9e161d94bffc
			entrystore = self.__file_load(file, password)
1165
a207757f8451
1166
a207757f8451
			if entrystore is None:
1167
a207757f8451
				return
1168
a207757f8451
1169
c9d89f3ff91d
			self.entrystore.clear()
1170
c9d89f3ff91d
			self.entrystore.import_entry(entrystore, None)
1171
c9d89f3ff91d
			self.entrystore.changed = False
1172
8eba4e20d67b
			self.undoqueue.clear()
1173
a207757f8451
1174
f5f9aadf2e19
			self.statusbar.set_status("Opened file %s" % self.datafile.get_file())
1175
a207757f8451
1176
c9d89f3ff91d
		except dialog.CancelError:
1177
a207757f8451
			self.statusbar.set_status("Open cancelled")
1178
a207757f8451
1179
a207757f8451
1180
a207757f8451
	def file_save(self, file = None, password = None):
1181
c9d89f3ff91d
		"Saves data to a file"
1182
a207757f8451
1183
a207757f8451
		try:
1184
a207757f8451
			if file is None:
1185
c9d89f3ff91d
				file = dialog.SaveFileSelector(self).run()
1186
a207757f8451
1187
c9d89f3ff91d
			self.__file_save(file, password)
1188
c9d89f3ff91d
			self.entrystore.changed = False
1189
c9d89f3ff91d
			self.statusbar.set_status("Data saved to file %s" % file)
1190
a207757f8451
1191
c9d89f3ff91d
			return True
1192
c9d89f3ff91d
1193
c9d89f3ff91d
		except dialog.CancelError:
1194
a207757f8451
			self.statusbar.set_status("Save cancelled")
1195
9e161d94bffc
			return False
1196
a207757f8451
1197
a207757f8451
1198
a207757f8451
	def quit(self):
1199
a207757f8451
		"Quits the application"
1200
a207757f8451
1201
c9d89f3ff91d
		try:
1202
c9d89f3ff91d
			self.__save_changes(dialog.FileChangedQuit)
1203
c9d89f3ff91d
1204
c9d89f3ff91d
			self.clipboard.clear()
1205
c9d89f3ff91d
			self.entryclipboard.clear()
1206
c9d89f3ff91d
1207
59be49691b29
			self.__save_state()
1208
c9d89f3ff91d
1209
c9d89f3ff91d
			gtk.main_quit()
1210
c9d89f3ff91d
			sys.exit(0)
1211
c9d89f3ff91d
1212
c9d89f3ff91d
		except dialog.CancelError:
1213
a207757f8451
			self.statusbar.set_status("Quit cancelled")
1214
c9d89f3ff91d
			return False
1215
a207757f8451
1216
a207757f8451
1217
a207757f8451
	def redo(self):
1218
c9d89f3ff91d
		"Redoes the previous action"
1219
a207757f8451
1220
c9d89f3ff91d
		action = self.undoqueue.get_redo_action()
1221
c9d89f3ff91d
1222
c9d89f3ff91d
		if action is None:
1223
a207757f8451
			return
1224
a207757f8451
1225
c9d89f3ff91d
		self.undoqueue.redo()
1226
c9d89f3ff91d
		self.statusbar.set_status("%s redone" % action[1])
1227
c9d89f3ff91d
		self.__file_autosave()
1228
a207757f8451
1229
c9d89f3ff91d
1230
c9d89f3ff91d
	def run(self):
1231
c9d89f3ff91d
		"Runs the application"
1232
c9d89f3ff91d
1233
59be49691b29
		args, argdict = self.program.get_popt_args()
1234
59be49691b29
1235
59be49691b29
		if len(args) > 0:
1236
59be49691b29
			file = args[0]
1237
c9d89f3ff91d
1238
c9d89f3ff91d
		elif self.config.get("file/autoload") == True:
1239
c9d89f3ff91d
			file = self.config.get("file/autoload_file")
1240
a207757f8451
1241
a207757f8451
		else:
1242
c9d89f3ff91d
			file = ""
1243
a207757f8451
1244
a207757f8451
1245
c9d89f3ff91d
		if file != "":
1246
c9d89f3ff91d
			self.file_open(io.file_normpath(file))
1247
a207757f8451
1248
a207757f8451
		gtk.main()
1249
a207757f8451
1250
a207757f8451
1251
a207757f8451
	def undo(self):
1252
c9d89f3ff91d
		"Undoes the previous action"
1253
a207757f8451
1254
c9d89f3ff91d
		action = self.undoqueue.get_undo_action()
1255
c9d89f3ff91d
1256
c9d89f3ff91d
		if action is None:
1257
a207757f8451
			return
1258
a207757f8451
1259
c9d89f3ff91d
		self.undoqueue.undo()
1260
c9d89f3ff91d
		self.statusbar.set_status("%s undone" % action[1])
1261
a207757f8451
		self.__file_autosave()
1262
a207757f8451
1263
a207757f8451
1264
a207757f8451
1265
a207757f8451
if __name__ == "__main__":
1266
c9d89f3ff91d
	app = Revelation()
1267
c9d89f3ff91d
	app.run()
1268
a207757f8451