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
75a53bdb5d20
parent
cf608f40f65b
parent
8a2b7cfa374a
branch
default

Automated merge with https://bitbucket.org/urmas/revelation

1
a207757f8451
#!/usr/bin/env python
2
a207757f8451
3
a207757f8451
#
4
78fb3436ec03
# Revelation - 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
f7e311e828af
import gettext, 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
58c0d7a4dffe
from revelation import config, data, datahandler, dialog, entry, io, ui, util
31
a207757f8451
32
1514c7bc4bdc
tran = gettext.translation(config.PACKAGE,config.DIR_LOCALE) 
33
1514c7bc4bdc
_ = tran.ugettext
34
a207757f8451
35
c9d89f3ff91d
class Revelation(ui.App):
36
c9d89f3ff91d
	"The Revelation application"
37
a207757f8451
38
a207757f8451
	def __init__(self):
39
a207757f8451
		sys.excepthook = self.__cb_exception
40
086abbb6fc3c
		os.umask(0077)
41
a207757f8451
42
f7e311e828af
		gettext.bindtextdomain(config.PACKAGE, config.DIR_LOCALE)
43
f7e311e828af
		gettext.bind_textdomain_codeset(config.PACKAGE, "UTF-8")
44
f7e311e828af
		gettext.textdomain(config.PACKAGE)
45
f7e311e828af
46
59be49691b29
		self.program = gnome.init(
47
59be49691b29
			config.APPNAME, config.APPNAME,
48
59be49691b29
			gnome.libgnome_module_info_get(), sys.argv, []
49
59be49691b29
		)
50
59be49691b29
51
abbd35ed4b61
		gnome.ui.authentication_manager_init()
52
c9d89f3ff91d
		ui.App.__init__(self, config.APPNAME)
53
a207757f8451
54
693fe3e0223f
		self.connect("delete-event", self.__cb_quit)
55
a207757f8451
56
c9d89f3ff91d
		try:
57
0b304787c6ff
			self.__init_actions()
58
c9d89f3ff91d
			self.__init_facilities()
59
c9d89f3ff91d
			self.__init_ui()
60
c9d89f3ff91d
			self.__init_states()
61
a207757f8451
62
c9d89f3ff91d
		except IOError:
63
e1117203c473
			dialog.Error(self, _('Missing data files'), _('Some of Revelations system files could not be found, please reinstall Revelation.')).run()
64
c9d89f3ff91d
			sys.exit(1)
65
c9d89f3ff91d
66
c9d89f3ff91d
		except config.ConfigError:
67
e1117203c473
			dialog.Error(self, _('Missing configuration data'), _('Revelation could not find its configuration data, please reinstall Revelation.')).run()
68
c9d89f3ff91d
			sys.exit(1)
69
c9d89f3ff91d
70
c9d89f3ff91d
		except ui.DataError:
71
e1117203c473
			dialog.Error(self, _('Invalid data files'), _('Some of Revelations system files contain invalid data, please reinstall Revelation.')).run()
72
c9d89f3ff91d
			sys.exit(1)
73
c9d89f3ff91d
74
c9d89f3ff91d
75
0b304787c6ff
	def __init_actions(self):
76
0b304787c6ff
		"Sets up actions"
77
0b304787c6ff
78
0b304787c6ff
		# set up placeholders
79
0b304787c6ff
		group	= ui.ActionGroup("placeholder")
80
0b304787c6ff
		self.uimanager.append_action_group(group)
81
0b304787c6ff
82
0b304787c6ff
		group.add_action(ui.Action("menu-edit",		_('_Edit')))
83
0b304787c6ff
		group.add_action(ui.Action("menu-entry",	_('E_ntry')))
84
0b304787c6ff
		group.add_action(ui.Action("menu-file",		_('_File')))
85
0b304787c6ff
		group.add_action(ui.Action("menu-help",		_('_Help')))
86
0b304787c6ff
		group.add_action(ui.Action("menu-view",		_('_View')))
87
0b304787c6ff
		group.add_action(ui.Action("popup-tree"))
88
0b304787c6ff
89
0b304787c6ff
		# set up dynamic actions
90
0b304787c6ff
		group	= ui.ActionGroup("dynamic")
91
0b304787c6ff
		self.uimanager.append_action_group(group)
92
0b304787c6ff
93
0b304787c6ff
		action	= ui.Action("clip-paste",	_('_Paste'),		_('Paste entry from clipboard'),		"gtk-paste")
94
0b304787c6ff
		action.connect("activate",		self.__cb_clip_paste)
95
0b304787c6ff
		group.add_action(action, "<Control>V")
96
0b304787c6ff
97
0b304787c6ff
		action	= ui.Action("entry-goto",	_('_Go to'),		_('Go to the selected entries'),		"revelation-goto",	True)
98
0b304787c6ff
		action.connect("activate",		lambda w: self.entry_goto(self.tree.get_selected()))
99
0b304787c6ff
		group.add_action(action, "<Shift><Control>Return")
100
0b304787c6ff
101
0b304787c6ff
		action	= ui.Action("redo",		_('_Redo'),		_('Redo the previously undone action'),		"gtk-redo")
102
0b304787c6ff
		action.connect("activate",		lambda w: self.redo())
103
0b304787c6ff
		group.add_action(action, "<Shift><Control>Z")
104
0b304787c6ff
105
0b304787c6ff
		action	= ui.Action("undo",		_('_Undo'),		_('Undo the last action'),			"gtk-undo")
106
0b304787c6ff
		action.connect("activate",		lambda w: self.undo())
107
0b304787c6ff
		group.add_action(action, "<Control>Z")
108
0b304787c6ff
109
0b304787c6ff
		# set up group for multiple entries
110
0b304787c6ff
		group	= ui.ActionGroup("entry-multiple")
111
0b304787c6ff
		self.uimanager.append_action_group(group)
112
0b304787c6ff
113
0b304787c6ff
		action	= ui.Action("clip-copy",	_('_Copy'),		_('Copy selected entries to the clipboard'),	"gtk-copy")
114
0b304787c6ff
		action.connect("activate",		self.__cb_clip_copy)
115
0b304787c6ff
		group.add_action(action, "<Control>C")
116
0b304787c6ff
117
0b304787c6ff
		action	= ui.Action("clip-chain",	_('_Copy Pass_word'),	_('Copy password to the clipboard'))
118
0b304787c6ff
		action.connect("activate",		lambda w: self.clip_chain(self.entrystore.get_entry(self.tree.get_active())))
119
0b304787c6ff
		group.add_action(action, "<Shift><Control>C")
120
0b304787c6ff
121
0b304787c6ff
		action	= ui.Action("clip-cut",		_('Cu_t'),		_('Cut selected entries to the clipboard'),	"gtk-cut")
122
0b304787c6ff
		action.connect("activate",		self.__cb_clip_cut)
123
0b304787c6ff
		group.add_action(action, "<Control>X")
124
0b304787c6ff
125
0b304787c6ff
		action	= ui.Action("entry-remove",	_('Re_move'),		_('Remove the selected entries'),		"revelation-remove")
126
0b304787c6ff
		action.connect("activate",		lambda w: self.entry_remove(self.tree.get_selected()))
127
0b304787c6ff
		group.add_action(action, "<Control>Delete")
128
0b304787c6ff
129
0b304787c6ff
		# action group for "optional" entries
130
0b304787c6ff
		group	= ui.ActionGroup("entry-optional")
131
0b304787c6ff
		self.uimanager.append_action_group(group)
132
0b304787c6ff
133
0b304787c6ff
		action	= ui.Action("entry-add",	_('_Add Entry...'),	_('Create a new entry'),			"revelation-new-entry",		True)
134
0b304787c6ff
		action.connect("activate",		lambda w: self.entry_add(None, self.tree.get_active()))
135
0b304787c6ff
		group.add_action(action, "<Control>Insert")
136
0b304787c6ff
137
0b304787c6ff
		action	= ui.Action("entry-folder",	_('Add _Folder...'),	_('Create a new folder'),			"revelation-new-folder")
138
0b304787c6ff
		action.connect("activate",		lambda w: self.entry_folder(None, self.tree.get_active()))
139
0b304787c6ff
		group.add_action(action, "<Shift><Control>Insert")
140
0b304787c6ff
141
0b304787c6ff
		# action group for single entries
142
0b304787c6ff
		group	= ui.ActionGroup("entry-single")
143
0b304787c6ff
		self.uimanager.append_action_group(group)
144
0b304787c6ff
145
0b304787c6ff
		action	= ui.Action("entry-edit",	_('_Edit...'),		_('Edit the selected entry'),			"revelation-edit")
146
0b304787c6ff
		action.connect("activate",		lambda w: self.entry_edit(self.tree.get_active()))
147
0b304787c6ff
		group.add_action(action, "<Control>Return")
148
0b304787c6ff
149
0b304787c6ff
		# action group for existing file
150
0b304787c6ff
		group	= ui.ActionGroup("file-exists")
151
0b304787c6ff
		self.uimanager.append_action_group(group)
152
0b304787c6ff
153
0b304787c6ff
		action	= ui.Action("file-lock",	_('_Lock'),		_('Lock the current data file'),		"revelation-lock")
154
0b304787c6ff
		action.connect("activate",		lambda w: self.file_lock())
155
0b304787c6ff
		group.add_action(action, "<Control>L")
156
0b304787c6ff
157
0b304787c6ff
		# action group for searching
158
0b304787c6ff
		group	= ui.ActionGroup("find")
159
0b304787c6ff
		self.uimanager.append_action_group(group)
160
0b304787c6ff
161
0b304787c6ff
		action	= ui.Action("find-next",	_('Find Ne_xt'),	_('Find the next search match'),		"find-next")
162
0b304787c6ff
		action.connect("activate",		lambda w: self.__entry_find(self, self.searchbar.entry.get_text(), self.searchbar.dropdown.get_active_type(), data.SEARCH_NEXT))
163
0b304787c6ff
		group.add_action(action, "<Control>G")
164
0b304787c6ff
165
0b304787c6ff
		action	= ui.Action("find-previous",	_('Find Pre_vious'),	_('Find the previous search match'),		"find-previous")
166
0b304787c6ff
		action.connect("activate",		lambda w: self.__entry_find(self, self.searchbar.entry.get_text(), self.searchbar.dropdown.get_active_type(), data.SEARCH_PREVIOUS))
167
0b304787c6ff
		group.add_action(action, "<Shift><Control>G")
168
0b304787c6ff
169
0b304787c6ff
		# global action group
170
0b304787c6ff
		group	= ui.ActionGroup("find")
171
0b304787c6ff
		self.uimanager.append_action_group(group)
172
0b304787c6ff
173
0b304787c6ff
		action	= ui.Action("file-change-password",	_('Change _Password...'),	_('Change password of current file'),		"revelation-password-change")
174
0b304787c6ff
		action.connect("activate",		lambda w: self.file_change_password())
175
0b304787c6ff
		group.add_action(action)
176
0b304787c6ff
177
0b304787c6ff
		action	= ui.Action("file-close",	_('_Close'),		_('Close the application'),			"gtk-close")
178
0b304787c6ff
		action.connect("activate",		self.__cb_quit)
179
0b304787c6ff
		group.add_action(action, "<Control>W")
180
0b304787c6ff
181
0b304787c6ff
		action	= ui.Action("file-export",	_('_Export...'),	_('Export data to a different file format'),	"revelation-export")
182
0b304787c6ff
		action.connect("activate",		lambda w: self.file_export())
183
0b304787c6ff
		group.add_action(action)
184
0b304787c6ff
185
0b304787c6ff
		action	= ui.Action("file-import",	_('_Import...'),	_('Import data from a foreign file'),		"revelation-import")
186
0b304787c6ff
		action.connect("activate",		lambda w: self.file_import())
187
0b304787c6ff
		group.add_action(action)
188
0b304787c6ff
189
0b304787c6ff
		action	= ui.Action("file-new",		_('_New'),		_('Create a new file'),				"gtk-new")
190
0b304787c6ff
		action.connect("activate",		lambda w: self.file_new())
191
0b304787c6ff
		group.add_action(action, "<Control>N")
192
0b304787c6ff
193
0b304787c6ff
		action	= ui.Action("file-open",	_('_Open'),		_('Open a file'),				"gtk-open")
194
0b304787c6ff
		action.connect("activate",		lambda w: self.file_open())
195
0b304787c6ff
		group.add_action(action, "<Control>O")
196
0b304787c6ff
197
0b304787c6ff
		action	= ui.Action("file-save",	_('_Save'),		_('Save data to a file'),			"gtk-save",		True)
198
0b304787c6ff
		action.connect("activate",		lambda w: self.file_save(self.datafile.get_file(), self.datafile.get_password()))
199
0b304787c6ff
		group.add_action(action, "<Control>S")
200
0b304787c6ff
201
0b304787c6ff
		action	= ui.Action("file-save-as",	_('Save _as...'),	_('Save data to a different file'),		"gtk-save-as")
202
0b304787c6ff
		action.connect("activate",		lambda w: self.file_save(None, None))
203
0b304787c6ff
		group.add_action(action, "<Shift><Control>S")
204
0b304787c6ff
205
0b304787c6ff
		action	= ui.Action("find",		_('_Find...'),		_('Search for an entry'),			"gtk-find")
206
0b304787c6ff
		action.connect("activate",		lambda w: self.entry_find())
207
0b304787c6ff
		group.add_action(action, "<Control>F")
208
0b304787c6ff
209
0b304787c6ff
		action	= ui.Action("help-about",	_('_About'),		_('About this application'),			"gnome-stock-about")
210
0b304787c6ff
		action.connect("activate",		lambda w: self.about())
211
0b304787c6ff
		group.add_action(action)
212
0b304787c6ff
213
0b304787c6ff
		action	= ui.Action("prefs",		_('Prefere_nces'),	_('Edit preferences'),				"gtk-preferences")
214
0b304787c6ff
		action.connect("activate",		lambda w: self.prefs())
215
0b304787c6ff
		group.add_action(action)
216
0b304787c6ff
217
0b304787c6ff
		action	= ui.Action("pwchecker",	_('Password _Checker'),	_('Opens a password checker'),			"revelation-password-check")
218
0b304787c6ff
		action.connect("activate",		lambda w: self.pwcheck())
219
0b304787c6ff
		group.add_action(action)
220
0b304787c6ff
221
0b304787c6ff
		action	= ui.Action("pwgenerator",	_('Password _Generator'),	_('Opens a password generator'),	"revelation-generate")
222
0b304787c6ff
		action.connect("activate",		lambda w: self.pwgen())
223
0b304787c6ff
		group.add_action(action)
224
0b304787c6ff
225
0b304787c6ff
		action	= ui.Action("quit",		_('_Quit'),		_('Quit the application'),			"gtk-quit")
226
0b304787c6ff
		action.connect("activate",		self.__cb_quit)
227
0b304787c6ff
		group.add_action(action, "<Control>Q")
228
0b304787c6ff
229
0b304787c6ff
		action	= ui.Action("select-all",	_('_Select All'),	_('Selects all entries'))
230
0b304787c6ff
		action.connect("activate",		lambda w: self.tree.select_all())
231
0b304787c6ff
		group.add_action(action, "<Control>A")
232
0b304787c6ff
233
0b304787c6ff
		action	= ui.Action("select-none",	_('_Deselect All'),	_('Deselects all entries'))
234
0b304787c6ff
		action.connect("activate",		lambda w: self.tree.unselect_all())
235
0b304787c6ff
		group.add_action(action, "<Shift><Control>A")
236
0b304787c6ff
237
0b304787c6ff
		action	= ui.ToggleAction("view-passwords",	_('Show _Passwords'),	_('Toggle display of passwords'))
238
0b304787c6ff
		group.add_action(action, "<Control>P")
239
0b304787c6ff
240
0b304787c6ff
		action	= ui.ToggleAction("view-searchbar",	_('S_earch Toolbar'),	_('Toggle the search toolbar'))
241
0b304787c6ff
		group.add_action(action)
242
0b304787c6ff
243
0b304787c6ff
		action	= ui.ToggleAction("view-statusbar",	_('_Statusbar'),	_('Toggle the statusbar'))
244
0b304787c6ff
		group.add_action(action)
245
0b304787c6ff
246
0b304787c6ff
		action	= ui.ToggleAction("view-toolbar",	_('_Main Toolbar'),	_('Toggle the main toolbar'))
247
0b304787c6ff
		group.add_action(action)
248
0b304787c6ff
249
0b304787c6ff
250
c9d89f3ff91d
	def __init_facilities(self):
251
c9d89f3ff91d
		"Sets up various facilities"
252
c9d89f3ff91d
253
c9d89f3ff91d
		self.clipboard		= data.Clipboard()
254
c9d89f3ff91d
		self.config		= config.Config()
255
c9d89f3ff91d
		self.datafile		= io.DataFile(datahandler.Revelation)
256
c9d89f3ff91d
		self.entryclipboard	= data.EntryClipboard()
257
c9d89f3ff91d
		self.entrystore		= data.EntryStore()
258
c9d89f3ff91d
		self.entrysearch	= data.EntrySearch(self.entrystore)
259
c9d89f3ff91d
		self.items		= ui.ItemFactory(self)
260
89c3dd775abc
		self.locktimer		= data.Timer()
261
59be49691b29
		self.sessionclient	= gnome.ui.master_client()
262
c9d89f3ff91d
		self.undoqueue		= data.UndoQueue()
263
c9d89f3ff91d
264
c9d89f3ff91d
		self.datafile.connect("changed", lambda w,f: self.__state_file(f))
265
bffe619c6cec
		self.datafile.connect("content-changed", self.__cb_file_content_changed)
266
c9d89f3ff91d
		self.entryclipboard.connect("content-toggled", lambda w,d: self.__state_clipboard(d))
267
bffe619c6cec
		self.locktimer.connect("ring", self.__cb_file_autolock)
268
8b171bec8d4b
		self.sessionclient.connect("die", lambda w: self.quit())
269
8b171bec8d4b
		self.sessionclient.connect("save-yourself", self.__cb_session_save)
270
c9d89f3ff91d
		self.undoqueue.connect("changed", lambda w: self.__state_undo(self.undoqueue.get_undo_action(), self.undoqueue.get_redo_action()))
271
c9d89f3ff91d
272
e5681e842641
		# check if configuration is updated, install schema if not
273
e5681e842641
		if self.__check_config() == False:
274
e5681e842641
275
e5681e842641
			if config.install_schema("%s/revelation.schemas" % config.DIR_GCONFSCHEMAS) == False:
276
e5681e842641
				raise config.ConfigError
277
e5681e842641
278
92c45e701471
			self.config.client.clear_cache()
279
92c45e701471
280
e5681e842641
			if self.__check_config() == False:
281
e5681e842641
				raise config.ConfigError
282
e5681e842641
283
bffe619c6cec
		self.config.monitor("file/autolock_timeout",	lambda k,v,d: self.locktimer.start(v * 60))
284
bffe619c6cec
285
bffe619c6cec
		dialog.EVENT_FILTER = self.__cb_event_filter
286
c9d89f3ff91d
287
c9d89f3ff91d
288
c9d89f3ff91d
	def __init_states(self):
289
c9d89f3ff91d
		"Sets the initial application state"
290
c9d89f3ff91d
291
c9d89f3ff91d
		# set window states
292
c9d89f3ff91d
		self.set_default_size(
293
c9d89f3ff91d
			self.config.get("view/window-width"),
294
c9d89f3ff91d
			self.config.get("view/window-height")
295
c9d89f3ff91d
		)
296
c9d89f3ff91d
297
c9d89f3ff91d
		self.move(
298
c9d89f3ff91d
			self.config.get("view/window-position-x"),
299
c9d89f3ff91d
			self.config.get("view/window-position-y")
300
c9d89f3ff91d
		)
301
c9d89f3ff91d
302
c9d89f3ff91d
		self.hpaned.set_position(
303
c9d89f3ff91d
			self.config.get("view/pane-position")
304
c9d89f3ff91d
		)
305
c9d89f3ff91d
306
c9d89f3ff91d
		# bind ui widgets to config keys
307
c9d89f3ff91d
		bind = {
308
c9d89f3ff91d
			"view/passwords"	: "/menubar/menu-view/view-passwords",
309
c9d89f3ff91d
			"view/searchbar"	: "/menubar/menu-view/view-searchbar",
310
c9d89f3ff91d
			"view/statusbar"	: "/menubar/menu-view/view-statusbar",
311
c9d89f3ff91d
			"view/toolbar"		: "/menubar/menu-view/view-toolbar"
312
c9d89f3ff91d
		}
313
c9d89f3ff91d
314
c9d89f3ff91d
		for key, path in bind.items():
315
c9d89f3ff91d
			ui.config_bind(self.config, key, self.uimanager.get_widget(path))
316
c9d89f3ff91d
317
c9d89f3ff91d
		self.show_all()
318
c9d89f3ff91d
319
bffe619c6cec
		self.window.add_filter(self.__cb_event_filter)
320
bffe619c6cec
321
bffe619c6cec
322
c9d89f3ff91d
		# set some variables
323
e1117203c473
		self.entrysearch.string	= ''
324
c9d89f3ff91d
		self.entrysearch.type	= None
325
c9d89f3ff91d
326
c9d89f3ff91d
		# set ui widget states
327
c9d89f3ff91d
		self.__state_clipboard(self.entryclipboard.has_contents())
328
c9d89f3ff91d
		self.__state_entry([])
329
c9d89f3ff91d
		self.__state_file(None)
330
f80290f7e569
		self.__state_find(self.searchbar.entry.get_text())
331
c9d89f3ff91d
		self.__state_undo(None, None)
332
c9d89f3ff91d
333
c9d89f3ff91d
		# set states from config
334
c9d89f3ff91d
		self.config.monitor("view/searchbar", self.__cb_config_toolbar, self.searchbar)
335
c9d89f3ff91d
		self.config.monitor("view/statusbar", self.__cb_config_toolbar, self.statusbar)
336
c9d89f3ff91d
		self.config.monitor("view/toolbar", self.__cb_config_toolbar, self.toolbar)
337
3fdc3c8552be
		self.config.monitor("view/toolbar_style", self.__cb_config_toolbar_style)
338
c9d89f3ff91d
339
167384ef2e8d
		# give focus to searchbar entry if shown
340
167384ef2e8d
		if self.searchbar.get_property("visible") == True:
341
167384ef2e8d
			self.searchbar.entry.grab_focus()
342
167384ef2e8d
343
c9d89f3ff91d
344
086abbb6fc3c
	def __init_ui(self):
345
c9d89f3ff91d
		"Sets up the UI"
346
a207757f8451
347
253760f7a205
		gtk.about_dialog_set_url_hook(lambda d,l: gnome.url_show(l))
348
253760f7a205
		gtk.about_dialog_set_email_hook(lambda d,l: gnome.url_show("mailto:" + l))
349
253760f7a205
350
c9d89f3ff91d
		# set window icons
351
6d8a1a08e17f
		pixbufs = [ self.items.get_pixbuf("revelation", size) for size in ( 48, 32, 24, 16) ]
352
c70268e6a3c3
		pixbufs = [ pixbuf for pixbuf in pixbufs if pixbuf != None ]
353
c70268e6a3c3
354
c70268e6a3c3
		if len(pixbufs) > 0:
355
c70268e6a3c3
			gtk.window_set_default_icon_list(*pixbufs)
356
3dcf95408d8d
357
c9d89f3ff91d
		# load UI definitions
358
c9d89f3ff91d
		self.uimanager.add_ui_from_file(config.DIR_UI + "/menubar.xml")
359
c9d89f3ff91d
		self.uimanager.add_ui_from_file(config.DIR_UI + "/popup-tree.xml")
360
c9d89f3ff91d
		self.uimanager.add_ui_from_file(config.DIR_UI + "/toolbar.xml")
361
a207757f8451
362
c9d89f3ff91d
		# set up toolbar and menus
363
c9d89f3ff91d
		self.set_menus(self.uimanager.get_widget("/menubar"))
364
a207757f8451
365
c9d89f3ff91d
		self.toolbar = self.uimanager.get_widget("/toolbar")
366
3fdc3c8552be
		self.toolbar.connect("popup-context-menu", lambda w,x,y,b: True)
367
c9d89f3ff91d
		self.set_toolbar(self.toolbar)
368
a207757f8451
369
47caac1545f2
		try:
370
47caac1545f2
			detachable = self.config.get("/desktop/gnome/interface/toolbar_detachable")
371
47caac1545f2
372
47caac1545f2
		except config.ConfigError:
373
47caac1545f2
			detachable = False
374
47caac1545f2
375
c9d89f3ff91d
		self.searchbar = ui.Searchbar()
376
47caac1545f2
		self.add_toolbar(self.searchbar, "searchbar", 2, detachable)
377
a207757f8451
378
c9d89f3ff91d
		# set up main application widgets
379
c9d89f3ff91d
		self.tree = ui.EntryTree(self.entrystore)
380
c9d89f3ff91d
		self.scrolledwindow = ui.ScrolledWindow(self.tree)
381
a207757f8451
382
8812d70e39d9
		self.entryview = ui.EntryView(self.config, self.clipboard)
383
f0a9532c50da
		alignment = ui.Alignment(self.entryview, 0.5, 0.5, 1, 0)
384
a7c01d3b4b22
385
c9d89f3ff91d
		self.hpaned = ui.HPaned(self.scrolledwindow, alignment)
386
c9d89f3ff91d
		self.set_contents(self.hpaned)
387
c9d89f3ff91d
388
8077271f872d
		# set up drag-and-drop
389
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 )
390
f5f9aadf2e19
		self.connect("drag_data_received", self.__cb_drag_dest)
391
f5f9aadf2e19
392
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)
393
8077271f872d
		self.tree.enable_model_drag_dest(( ( "revelation/treerow", gtk.TARGET_SAME_APP | gtk.TARGET_SAME_WIDGET, 0), ), gtk.gdk.ACTION_MOVE)
394
8077271f872d
		self.tree.connect("drag_data_received", self.__cb_tree_drag_received)
395
8077271f872d
396
c9d89f3ff91d
		# set up callbacks
397
f80290f7e569
		self.searchbar.connect("key-press-event", self.__cb_searchbar_key_press)
398
f80290f7e569
		self.searchbar.button_next.connect("clicked", self.__cb_searchbar_button_clicked, data.SEARCH_NEXT)
399
f80290f7e569
		self.searchbar.button_prev.connect("clicked", self.__cb_searchbar_button_clicked, data.SEARCH_PREVIOUS)
400
f80290f7e569
		self.searchbar.entry.connect("changed", lambda w: self.__state_find(self.searchbar.entry.get_text()))
401
c9d89f3ff91d
402
c9d89f3ff91d
		self.tree.connect("popup", lambda w,d: self.popup(self.uimanager.get_widget("/popup-tree"), d.button, d.time))
403
6cf6849eed72
		self.tree.connect("doubleclick", self.__cb_tree_doubleclick)
404
c9d89f3ff91d
		self.tree.connect("key-press-event", self.__cb_tree_keypress)
405
c9d89f3ff91d
		self.tree.selection.connect("changed", lambda w: self.entryview.display_entry(self.entrystore.get_entry(self.tree.get_active())))
406
c9d89f3ff91d
		self.tree.selection.connect("changed", lambda w: self.__state_entry(self.tree.get_selected()))
407
c9d89f3ff91d
408
c9d89f3ff91d
409
c9d89f3ff91d
410
c9d89f3ff91d
	##### STATE HANDLERS #####
411
c9d89f3ff91d
412
59be49691b29
	def __save_state(self):
413
59be49691b29
		"Saves the current application state"
414
59be49691b29
415
59be49691b29
		width, height = self.get_size()
416
59be49691b29
		self.config.set("view/window-width", width)
417
59be49691b29
		self.config.set("view/window-height", height)
418
59be49691b29
419
59be49691b29
		x, y = self.get_position()
420
59be49691b29
		self.config.set("view/window-position-x", x)
421
59be49691b29
		self.config.set("view/window-position-y", y)
422
59be49691b29
423
59be49691b29
		self.config.set("view/pane-position", self.hpaned.get_position())
424
59be49691b29
425
59be49691b29
426
c9d89f3ff91d
	def __state_clipboard(self, has_contents):
427
c9d89f3ff91d
		"Sets states based on the clipboard contents"
428
c9d89f3ff91d
429
c9d89f3ff91d
		self.uimanager.get_action("clip-paste").set_property("sensitive", has_contents)
430
c9d89f3ff91d
431
c9d89f3ff91d
432
c9d89f3ff91d
	def __state_entry(self, iters):
433
c9d89f3ff91d
		"Sets states for entry-dependant ui items"
434
c9d89f3ff91d
435
c9d89f3ff91d
		# widget sensitivity based on number of entries
436
c9d89f3ff91d
		self.uimanager.get_action_group("entry-multiple").set_sensitive(len(iters) > 0)
437
c9d89f3ff91d
		self.uimanager.get_action_group("entry-single").set_sensitive(len(iters) == 1)
438
c9d89f3ff91d
		self.uimanager.get_action_group("entry-optional").set_sensitive(len(iters) < 2)
439
c9d89f3ff91d
440
260e1dfe3e88
441
260e1dfe3e88
		# copy password sensitivity
442
260e1dfe3e88
		s = False
443
260e1dfe3e88
444
260e1dfe3e88
		for iter in iters:
445
260e1dfe3e88
			e = self.entrystore.get_entry(iter)
446
260e1dfe3e88
447
260e1dfe3e88
			for f in e.fields:
448
260e1dfe3e88
				if f.datatype == entry.DATATYPE_PASSWORD and f.value != "":
449
260e1dfe3e88
					s = True
450
260e1dfe3e88
451
deb6e8bed5dc
		self.uimanager.get_action("clip-chain").set_property("sensitive", s)
452
260e1dfe3e88
453
260e1dfe3e88
454
095f4008dc7f
		# goto sensitivity
455
c9d89f3ff91d
		try:
456
c9d89f3ff91d
			for iter in iters:
457
c9d89f3ff91d
				e = self.entrystore.get_entry(iter)
458
c9d89f3ff91d
459
c9d89f3ff91d
				if self.config.get("launcher/%s" % e.id) not in ( "", None ):
460
c9d89f3ff91d
					s = True
461
c9d89f3ff91d
					break
462
c9d89f3ff91d
463
c9d89f3ff91d
			else:
464
c9d89f3ff91d
				s = False
465
c9d89f3ff91d
466
c9d89f3ff91d
		except config.ConfigError:
467
c9d89f3ff91d
			s = False
468
c9d89f3ff91d
469
0b304787c6ff
		self.uimanager.get_action("entry-goto").set_sensitive(s)
470
dad9b7b301d1
471
a207757f8451
472
c9d89f3ff91d
	def __state_file(self, file):
473
c9d89f3ff91d
		"Sets states based on file"
474
a207757f8451
475
c9d89f3ff91d
		self.uimanager.get_action_group("file-exists").set_sensitive(file is not None)
476
a207757f8451
477
c9d89f3ff91d
		if file is not None:
478
c9d89f3ff91d
			self.set_title(os.path.basename(file))
479
a207757f8451
480
c9d89f3ff91d
			if io.file_is_local(file):
481
c9d89f3ff91d
				os.chdir(os.path.dirname(file))
482
a7c01d3b4b22
483
c9d89f3ff91d
		else:
484
e1117203c473
			self.set_title('[' + _('New file') + ']')
485
a207757f8451
486
086abbb6fc3c
487
c9d89f3ff91d
	def __state_find(self, string):
488
c9d89f3ff91d
		"Sets states based on the current search string"
489
086abbb6fc3c
490
c9d89f3ff91d
		self.uimanager.get_action_group("find").set_sensitive(string != "")
491
086abbb6fc3c
492
086abbb6fc3c
493
c9d89f3ff91d
	def __state_undo(self, undoaction, redoaction):
494
c9d89f3ff91d
		"Sets states based on undoqueue actions"
495
086abbb6fc3c
496
c9d89f3ff91d
		if undoaction is None:
497
e1117203c473
			s, l = False, _('_Undo')
498
086abbb6fc3c
499
c9d89f3ff91d
		else:
500
e1117203c473
			s, l = True, _('_Undo %s') % undoaction[1].lower()
501
086abbb6fc3c
502
c9d89f3ff91d
		action = self.uimanager.get_action("undo")
503
c9d89f3ff91d
		action.set_property("sensitive", s)
504
c9d89f3ff91d
		action.set_property("label", l)
505
a207757f8451
506
a207757f8451
507
c9d89f3ff91d
		if redoaction is None:
508
e1117203c473
			s, l = False, _('_Redo')
509
a207757f8451
510
c9d89f3ff91d
		else:
511
e1117203c473
			s, l = True, _('_Redo %s') % redoaction[1].lower()
512
a207757f8451
513
c9d89f3ff91d
		action = self.uimanager.get_action("redo")
514
c9d89f3ff91d
		action.set_property("sensitive", s)
515
c9d89f3ff91d
		action.set_property("label", l)
516
a207757f8451
517
a207757f8451
518
a207757f8451
519
a207757f8451
520
c9d89f3ff91d
	##### MISC CALLBACKS #####
521
086abbb6fc3c
522
5a85d838a653
	def __cb_clip_copy(self, widget, data = None):
523
5a85d838a653
		"Handles copying to the clipboard"
524
5a85d838a653
525
5a85d838a653
		focuswidget = self.get_focus()
526
5a85d838a653
527
5a85d838a653
		if focuswidget is self.tree:
528
5a85d838a653
			self.clip_copy(self.tree.get_selected())
529
5a85d838a653
530
5a85d838a653
		elif isinstance(focuswidget, gtk.Label) or isinstance(focuswidget, gtk.Entry):
531
5a85d838a653
			focuswidget.emit("copy-clipboard")
532
5a85d838a653
533
5a85d838a653
534
5a85d838a653
	def __cb_clip_cut(self, widget, data = None):
535
5a85d838a653
		"Handles cutting to clipboard"
536
5a85d838a653
537
5a85d838a653
		focuswidget = self.get_focus()
538
5a85d838a653
539
5a85d838a653
		if focuswidget is self.tree:
540
5a85d838a653
			self.clip_cut(self.tree.get_selected())
541
5a85d838a653
542
5a85d838a653
		elif isinstance(focuswidget, gtk.Entry):
543
5a85d838a653
			focuswidget.emit("cut-clipboard")
544
5a85d838a653
545
5a85d838a653
546
5a85d838a653
	def __cb_clip_paste(self, widget, data = None):
547
5a85d838a653
		"Handles pasting from clipboard"
548
5a85d838a653
549
5a85d838a653
		focuswidget = self.get_focus()
550
5a85d838a653
551
5a85d838a653
		if focuswidget is self.tree:
552
5a85d838a653
			self.clip_paste(self.entryclipboard.get(), self.tree.get_active())
553
5a85d838a653
554
5a85d838a653
		elif isinstance(focuswidget, gtk.Entry):
555
5a85d838a653
			focuswidget.emit("paste-clipboard")
556
5a85d838a653
557
5a85d838a653
558
f5f9aadf2e19
	def __cb_drag_dest(self, widget, context, x, y, seldata, info, time, userdata = None):
559
f5f9aadf2e19
		"Handles file drops"
560
f5f9aadf2e19
561
f5f9aadf2e19
		if seldata.data is None:
562
f5f9aadf2e19
			return
563
f5f9aadf2e19
564
f5f9aadf2e19
		files = [ file.strip() for file in seldata.data.split("\n") if file.strip() != "" ]
565
f5f9aadf2e19
566
b57efdd083f5
		if len(files) > 0:
567
b57efdd083f5
			self.file_open(files[0])
568
f5f9aadf2e19
569
f5f9aadf2e19
570
bffe619c6cec
	def __cb_event_filter(self, event):
571
bffe619c6cec
		"Event filter for gdk window"
572
bffe619c6cec
573
bffe619c6cec
		self.locktimer.reset()
574
bffe619c6cec
		return gtk.gdk.FILTER_CONTINUE
575
bffe619c6cec
576
bffe619c6cec
577
a207757f8451
	def __cb_exception(self, type, value, trace):
578
a207757f8451
		"Callback for unhandled exceptions"
579
a207757f8451
580
9e161d94bffc
		if type == KeyboardInterrupt:
581
9e161d94bffc
			sys.exit(1)
582
9e161d94bffc
583
c9d89f3ff91d
		traceback = util.trace_exception(type, value, trace)
584
a207757f8451
		sys.stderr.write(traceback)
585
4db37324fd05
586
c9d89f3ff91d
		if dialog.Exception(self, traceback).run() == True:
587
c9d89f3ff91d
			gtk.main()
588
4db37324fd05
589
4db37324fd05
		else:
590
4db37324fd05
			sys.exit(1)
591
a207757f8451
592
a207757f8451
593
bffe619c6cec
	def __cb_file_content_changed(self, widget, file):
594
bffe619c6cec
		"Callback for changed file"
595
bffe619c6cec
596
bffe619c6cec
		try:
597
bffe619c6cec
			if dialog.FileChanged(self, file).run() == True:
598
bffe619c6cec
				self.file_open(self.datafile.get_file(), self.datafile.get_password())
599
bffe619c6cec
600
bffe619c6cec
		except dialog.CancelError:
601
e1117203c473
			self.statusbar.set_status(_('Open cancelled'))
602
bffe619c6cec
603
bffe619c6cec
604
bffe619c6cec
	def __cb_file_autolock(self, widget, data = None):
605
bffe619c6cec
		"Callback for locking the file"
606
bffe619c6cec
607
bffe619c6cec
		if self.config.get("file/autolock") == True:
608
bffe619c6cec
			self.file_lock()
609
bffe619c6cec
610
bffe619c6cec
611
693fe3e0223f
	def __cb_quit(self, widget, data = None):
612
693fe3e0223f
		"Callback for quit"
613
693fe3e0223f
614
693fe3e0223f
		if self.quit() == False:
615
693fe3e0223f
			return True
616
693fe3e0223f
617
693fe3e0223f
		else:
618
693fe3e0223f
			return False
619
693fe3e0223f
620
693fe3e0223f
621
f80290f7e569
	def __cb_searchbar_button_clicked(self, widget, direction = data.SEARCH_NEXT):
622
bd0aa2a05f0f
		"Callback for searchbar button clicks"
623
bd0aa2a05f0f
624
f80290f7e569
		self.__entry_find(self, self.searchbar.entry.get_text(), self.searchbar.dropdown.get_active_type(), direction)
625
bd0aa2a05f0f
		self.searchbar.entry.select_region(0, -1)
626
bd0aa2a05f0f
627
bd0aa2a05f0f
628
f80290f7e569
	def __cb_searchbar_key_press(self, widget, data):
629
f80290f7e569
		"Callback for searchbar key presses"
630
f80290f7e569
631
f80290f7e569
		# escape
632
f80290f7e569
		if data.keyval == 65307:
633
f80290f7e569
			self.config.set("view/searchbar", False)
634
f80290f7e569
635
f80290f7e569
636
59be49691b29
	def __cb_session_save(self, widget, phase, what, end, interaction, fast, data = None):
637
59be49691b29
		"Handles session saves"
638
59be49691b29
639
59be49691b29
		self.sessionclient.set_current_directory(os.getcwd())
640
59be49691b29
		self.sessionclient.set_clone_command(sys.argv[0])
641
59be49691b29
642
59be49691b29
		if self.datafile.get_file() == None:
643
59be49691b29
			self.sessionclient.set_restart_command(1, [ sys.argv[0] ])
644
59be49691b29
645
59be49691b29
		else:
646
59be49691b29
			self.sessionclient.set_restart_command(2, [ sys.argv[0], self.datafile.get_file() ] )
647
59be49691b29
648
59be49691b29
		self.__save_state()
649
59be49691b29
650
59be49691b29
		return True
651
59be49691b29
652
59be49691b29
653
1d846e1296fb
	def __cb_tree_doubleclick(self, widget, iter):
654
6cf6849eed72
		"Handles doubleclicks on the tree"
655
6cf6849eed72
656
7f9cbee4434c
		if self.config.get("behavior/doubleclick") == "edit":
657
1d846e1296fb
			self.entry_edit(iter)
658
7f9cbee4434c
659
bb1810f15e75
		elif self.config.get("behavior/doubleclick") == "copy":
660
1d846e1296fb
			self.clip_chain(self.entrystore.get_entry(iter))
661
bb1810f15e75
662
7f9cbee4434c
		else:
663
1d846e1296fb
			self.entry_goto((iter,))
664
6cf6849eed72
665
6cf6849eed72
666
8077271f872d
	def __cb_tree_drag_received(self, tree, context, x, y, seldata, info, time):
667
8077271f872d
		"Callback for drag drops on the treeview"
668
8077271f872d
669
8077271f872d
		# get source and destination data
670
8077271f872d
		sourceiters = self.entrystore.filter_parents(self.tree.get_selected())
671
8077271f872d
		destrow = self.tree.get_dest_row_at_pos(x, y)
672
8077271f872d
673
8077271f872d
		if destrow is None:
674
8077271f872d
			destpath = ( self.entrystore.iter_n_children(None) - 1, )
675
8077271f872d
			pos = gtk.TREE_VIEW_DROP_AFTER
676
8077271f872d
677
8077271f872d
		else:
678
8077271f872d
			destpath, pos = destrow
679
8077271f872d
680
8077271f872d
		destiter = self.entrystore.get_iter(destpath)
681
9e1db933e6ed
		destpath = self.entrystore.get_path(destiter)
682
8077271f872d
683
9e1db933e6ed
		# avoid drops to current iter or descentants
684
8077271f872d
		for sourceiter in sourceiters:
685
9e1db933e6ed
			sourcepath = self.entrystore.get_path(sourceiter)
686
9e1db933e6ed
687
9e1db933e6ed
			if self.entrystore.is_ancestor(sourceiter, destiter) == True or sourcepath == destpath:
688
8b171bec8d4b
				context.finish(False, False, long(time))
689
8077271f872d
				return
690
8077271f872d
691
8650886bcc92
			elif pos == gtk.TREE_VIEW_DROP_BEFORE and sourcepath[:-1] == destpath[:-1] and sourcepath[-1] == destpath[-1] - 1:
692
8b171bec8d4b
				context.finish(False, False, long(time))
693
9e1db933e6ed
				return
694
9e1db933e6ed
695
8650886bcc92
			elif pos == gtk.TREE_VIEW_DROP_AFTER and sourcepath[:-1] == destpath[:-1] and sourcepath[-1] == destpath[-1] + 1:
696
8b171bec8d4b
				context.finish(False, False, long(time))
697
9e1db933e6ed
				return
698
9e1db933e6ed
699
9e1db933e6ed
700
8077271f872d
		# move the entries
701
8077271f872d
		if pos in ( gtk.TREE_VIEW_DROP_INTO_OR_BEFORE, gtk.TREE_VIEW_DROP_INTO_OR_AFTER):
702
8077271f872d
			parent = destiter
703
8077271f872d
			sibling = None
704
8077271f872d
705
8077271f872d
		elif pos == gtk.TREE_VIEW_DROP_BEFORE:
706
8077271f872d
			parent = self.entrystore.iter_parent(destiter)
707
8077271f872d
			sibling = destiter
708
8077271f872d
709
8077271f872d
		elif pos == gtk.TREE_VIEW_DROP_AFTER:
710
8077271f872d
			parent = self.entrystore.iter_parent(destiter)
711
8077271f872d
712
8077271f872d
			sibpath = list(destpath)
713
8077271f872d
			sibpath[-1] += 1
714
8077271f872d
			sibling = self.entrystore.get_iter(sibpath)
715
8077271f872d
716
8077271f872d
		self.entry_move(sourceiters, parent, sibling)
717
fe71b6d3fc2a
718
8b171bec8d4b
		context.finish(False, False, long(time))
719
8077271f872d
720
8077271f872d
721
c9d89f3ff91d
	def __cb_tree_keypress(self, widget, data = None):
722
a207757f8451
		"Handles key presses for the tree"
723
a207757f8451
724
a207757f8451
		# return
725
a207757f8451
		if data.keyval == 65293:
726
5f46bd4ac49c
			self.entry_edit(self.tree.get_active())
727
a207757f8451
728
a207757f8451
		# insert
729
a207757f8451
		elif data.keyval == 65379:
730
5f46bd4ac49c
			self.entry_add(None, self.tree.get_active())
731
a207757f8451
732
a207757f8451
		# delete
733
a207757f8451
		elif data.keyval == 65535:
734
5f46bd4ac49c
			self.entry_remove(self.tree.get_selected())
735
a207757f8451
736
a207757f8451
737
a207757f8451
738
c9d89f3ff91d
	##### CONFIG CALLBACKS #####
739
c9d89f3ff91d
740
c9d89f3ff91d
	def __cb_config_toolbar(self, config, value, toolbar):
741
c9d89f3ff91d
		"Config callback for showing toolbars"
742
c9d89f3ff91d
743
c9d89f3ff91d
		if value == True:
744
c9d89f3ff91d
			toolbar.show()
745
c9d89f3ff91d
746
c9d89f3ff91d
		else:
747
c9d89f3ff91d
			toolbar.hide()
748
c9d89f3ff91d
749
c9d89f3ff91d
750
3fdc3c8552be
	def __cb_config_toolbar_style(self, config, value, data = None):
751
3fdc3c8552be
		"Config callback for setting toolbar style"
752
3fdc3c8552be
753
3fdc3c8552be
		if value == "both":
754
3fdc3c8552be
			self.toolbar.set_style(gtk.TOOLBAR_BOTH)
755
3fdc3c8552be
756
3fdc3c8552be
		elif value == "both-horiz":
757
3fdc3c8552be
			self.toolbar.set_style(gtk.TOOLBAR_BOTH_HORIZ)
758
3fdc3c8552be
759
3fdc3c8552be
		elif value == "icons":
760
3fdc3c8552be
			self.toolbar.set_style(gtk.TOOLBAR_ICONS)
761
3fdc3c8552be
762
3fdc3c8552be
		elif value == "text":
763
3fdc3c8552be
			self.toolbar.set_style(gtk.TOOLBAR_TEXT)
764
3fdc3c8552be
765
3fdc3c8552be
		else:
766
3fdc3c8552be
			self.toolbar.unset_style()
767
3fdc3c8552be
768
c9d89f3ff91d
769
c9d89f3ff91d
	#### UNDO / REDO CALLBACKS #####
770
c9d89f3ff91d
771
c9d89f3ff91d
	def __cb_redo_add(self, name, actiondata):
772
c9d89f3ff91d
		"Redoes an add action"
773
c9d89f3ff91d
774
c9d89f3ff91d
		path, e = actiondata
775
c9d89f3ff91d
		parent = self.entrystore.get_iter(path[:-1])
776
c9d89f3ff91d
		sibling = self.entrystore.get_iter(path)
777
c9d89f3ff91d
778
c9d89f3ff91d
		iter = self.entrystore.add_entry(e, parent, sibling)
779
c9d89f3ff91d
		self.tree.select(iter)
780
c9d89f3ff91d
781
c9d89f3ff91d
782
c9d89f3ff91d
	def __cb_redo_edit(self, name, actiondata):
783
c9d89f3ff91d
		"Redoes an edit action"
784
c9d89f3ff91d
785
c9d89f3ff91d
		path, preentry, postentry = actiondata
786
c9d89f3ff91d
		iter = self.entrystore.get_iter(path)
787
c9d89f3ff91d
788
bffe619c6cec
		self.entrystore.update_entry(iter, postentry)
789
c9d89f3ff91d
		self.tree.select(iter)
790
c9d89f3ff91d
791
c9d89f3ff91d
792
c9d89f3ff91d
	def __cb_redo_import(self, name, actiondata):
793
c9d89f3ff91d
		"Redoes an import action"
794
c9d89f3ff91d
795
c9d89f3ff91d
		paths, entrystore = actiondata
796
c9d89f3ff91d
		self.entrystore.import_entry(entrystore, None)
797
c9d89f3ff91d
798
c9d89f3ff91d
799
8077271f872d
	def __cb_redo_move(self, name, actiondata):
800
8077271f872d
		"Redoes a move action"
801
8077271f872d
802
8077271f872d
		newiters = []
803
8077271f872d
804
8077271f872d
		for prepath, postpath in actiondata:
805
8077271f872d
			prepath, postpath = list(prepath), list(postpath)
806
8077271f872d
807
8077271f872d
			# adjust path if necessary
808
de53973fd104
			if len(prepath) <= len(postpath):
809
de53973fd104
				if prepath[:-1] == postpath[:len(prepath) - 1]:
810
de53973fd104
					if prepath[-1] <= postpath[len(prepath) - 1]:
811
de53973fd104
						postpath[len(prepath) - 1] += 1
812
8077271f872d
813
8077271f872d
			newiter = self.entrystore.move_entry(
814
8077271f872d
				self.entrystore.get_iter(prepath),
815
8077271f872d
				self.entrystore.get_iter(postpath[:-1]),
816
8077271f872d
				self.entrystore.get_iter(postpath)
817
8077271f872d
			)
818
8077271f872d
819
8077271f872d
			newiters.append(newiter)
820
8077271f872d
821
8077271f872d
		if len(newiters) > 0:
822
8077271f872d
			self.tree.select(newiters[0])
823
8077271f872d
824
8077271f872d
825
c9d89f3ff91d
	def __cb_redo_paste(self, name, actiondata):
826
c9d89f3ff91d
		"Redoes a paste action"
827
c9d89f3ff91d
828
c9d89f3ff91d
		entrystore, parentpath, paths = actiondata
829
c9d89f3ff91d
		iters = self.entrystore.import_entry(entrystore, None, self.entrystore.get_iter(parentpath))
830
c9d89f3ff91d
831
c9d89f3ff91d
		if len(iters) > 0:
832
c9d89f3ff91d
			self.tree.select(iters[0])
833
c9d89f3ff91d
834
c9d89f3ff91d
835
c9d89f3ff91d
	def __cb_redo_remove(self, name, actiondata):
836
c9d89f3ff91d
		"Redoes a remove action"
837
c9d89f3ff91d
838
c9d89f3ff91d
		iters = []
839
c9d89f3ff91d
		for path, entrystore in actiondata:
840
c9d89f3ff91d
			iters.append(self.entrystore.get_iter(path))
841
c9d89f3ff91d
842
c9d89f3ff91d
		for iter in iters:
843
c9d89f3ff91d
			self.entrystore.remove_entry(iter)
844
c9d89f3ff91d
845
c9d89f3ff91d
		self.tree.unselect_all()
846
c9d89f3ff91d
847
c9d89f3ff91d
848
c9d89f3ff91d
	def __cb_undo_add(self, name, actiondata):
849
c9d89f3ff91d
		"Undoes an add action"
850
c9d89f3ff91d
851
c9d89f3ff91d
		path, e = actiondata
852
c9d89f3ff91d
853
c9d89f3ff91d
		self.entrystore.remove_entry(self.entrystore.get_iter(path))
854
c9d89f3ff91d
		self.tree.unselect_all()
855
c9d89f3ff91d
856
c9d89f3ff91d
857
c9d89f3ff91d
	def __cb_undo_edit(self, name, actiondata):
858
c9d89f3ff91d
		"Undoes an edit action"
859
c9d89f3ff91d
860
c9d89f3ff91d
		path, preentry, postentry = actiondata
861
c9d89f3ff91d
		iter = self.entrystore.get_iter(path)
862
c9d89f3ff91d
863
bffe619c6cec
		self.entrystore.update_entry(iter, preentry)
864
c9d89f3ff91d
		self.tree.select(iter)
865
c9d89f3ff91d
866
c9d89f3ff91d
867
c9d89f3ff91d
	def __cb_undo_import(self, name, actiondata):
868
c9d89f3ff91d
		"Undoes an import action"
869
c9d89f3ff91d
870
c9d89f3ff91d
		paths, entrystore = actiondata
871
c9d89f3ff91d
		iters = [ self.entrystore.get_iter(path) for path in paths ]
872
c9d89f3ff91d
873
c9d89f3ff91d
		for iter in iters:
874
c9d89f3ff91d
			self.entrystore.remove_entry(iter)
875
c9d89f3ff91d
876
c9d89f3ff91d
		self.tree.unselect_all()
877
c9d89f3ff91d
878
c9d89f3ff91d
879
8077271f872d
	def __cb_undo_move(self, name, actiondata):
880
8077271f872d
		"Undoes a move action"
881
8077271f872d
882
6bb7821fddaf
		actiondata = actiondata[:]
883
6bb7821fddaf
		actiondata.reverse()
884
6bb7821fddaf
885
8077271f872d
		newiters = []
886
8077271f872d
887
8077271f872d
		for prepath, postpath in actiondata:
888
8077271f872d
			prepath, postpath = list(prepath), list(postpath)
889
8077271f872d
890
8077271f872d
			# adjust path if necessary
891
de53973fd104
			if len(postpath) <= len(prepath):
892
de53973fd104
				if postpath[:-1] == prepath[:len(postpath) - 1]:
893
de53973fd104
					if postpath[-1] <= prepath[len(postpath) - 1]:
894
de53973fd104
						prepath[len(postpath) - 1] += 1
895
8077271f872d
896
8077271f872d
			newiter = self.entrystore.move_entry(
897
8077271f872d
				self.entrystore.get_iter(postpath),
898
8077271f872d
				self.entrystore.get_iter(prepath[:-1]),
899
8077271f872d
				self.entrystore.get_iter(prepath)
900
8077271f872d
			)
901
8077271f872d
902
8077271f872d
			newiters.append(newiter)
903
8077271f872d
904
8077271f872d
		if len(newiters) > 0:
905
6bb7821fddaf
			self.tree.select(newiters[-1])
906
8077271f872d
907
8077271f872d
908
c9d89f3ff91d
	def __cb_undo_paste(self, name, actiondata):
909
c9d89f3ff91d
		"Undoes a paste action"
910
c9d89f3ff91d
911
c9d89f3ff91d
		entrystore, parentpath, paths = actiondata
912
c9d89f3ff91d
		iters = [ self.entrystore.get_iter(path) for path in paths ]
913
c9d89f3ff91d
914
c9d89f3ff91d
		for iter in iters:
915
c9d89f3ff91d
			self.entrystore.remove_entry(iter)
916
c9d89f3ff91d
917
c9d89f3ff91d
		self.tree.unselect_all()
918
c9d89f3ff91d
919
c9d89f3ff91d
920
c9d89f3ff91d
	def __cb_undo_remove(self, name, actiondata):
921
c9d89f3ff91d
		"Undoes a remove action"
922
c9d89f3ff91d
923
c9d89f3ff91d
		iters = []
924
c9d89f3ff91d
		for path, entrystore in actiondata:
925
c9d89f3ff91d
			parent = self.entrystore.get_iter(path[:-1])
926
c9d89f3ff91d
			sibling = self.entrystore.get_iter(path)
927
c9d89f3ff91d
928
c9d89f3ff91d
			iter = self.entrystore.import_entry(entrystore, entrystore.iter_nth_child(None, 0), parent, sibling)
929
c9d89f3ff91d
			iters.append(iter)
930
c9d89f3ff91d
931
c9d89f3ff91d
		self.tree.select(iters[0])
932
c9d89f3ff91d
933
c9d89f3ff91d
934
c9d89f3ff91d
935
c9d89f3ff91d
	##### PRIVATE METHODS #####
936
c9d89f3ff91d
937
e5681e842641
	def __check_config(self):
938
e5681e842641
		"Checks if the configuration is correct"
939
e5681e842641
940
e5681e842641
		try:
941
e5681e842641
			self.config.get("launcher/website")
942
e5681e842641
			self.config.get("view/searchbar")
943
e5681e842641
			self.config.get("clipboard/chain_username")
944
e5681e842641
			self.config.get("behavior/doubleclick")
945
3fdc3c8552be
			self.config.get("view/toolbar_style")
946
e5681e842641
947
e5681e842641
			return True
948
e5681e842641
949
e5681e842641
		except config.ConfigError:
950
e5681e842641
			return False
951
e5681e842641
952
e5681e842641
953
c9d89f3ff91d
	def __entry_find(self, parent, string, entrytype, direction = data.SEARCH_NEXT):
954
a207757f8451
		"Searches for an entry"
955
a207757f8451
956
c9d89f3ff91d
		match = self.entrysearch.find(string, entrytype, self.tree.get_active(), direction)
957
a207757f8451
958
f80290f7e569
		if match != None:
959
c9d89f3ff91d
			self.tree.select(match)
960
e1117203c473
			self.statusbar.set_status(_('Match found for \'%s\'') % string)
961
a207757f8451
962
a207757f8451
		else:
963
e1117203c473
			self.statusbar.set_status(_('No match found for \'%s\'') % string)
964
e1117203c473
			dialog.Error(parent, _('No match found'), _('The string \'%s\' does not match any entries. Try searching for a different phrase.') % string).run()
965
a207757f8451
966
a207757f8451
967
a207757f8451
	def __file_autosave(self):
968
c9d89f3ff91d
		"Autosaves the current file if needed"
969
a207757f8451
970
99200bf77234
		try:
971
99200bf77234
			if self.datafile.get_file() is None or self.datafile.get_password() is None:
972
99200bf77234
				return
973
a207757f8451
974
99200bf77234
			if self.config.get("file/autosave") == False:
975
99200bf77234
				return
976
a207757f8451
977
99200bf77234
			self.datafile.save(self.entrystore, self.datafile.get_file(), self.datafile.get_password())
978
99200bf77234
			self.entrystore.changed = False
979
99200bf77234
980
99200bf77234
		except IOError:
981
99200bf77234
			pass
982
a207757f8451
983
a207757f8451
984
9e161d94bffc
	def __file_load(self, file, password, datafile = None):
985
c9d89f3ff91d
		"Loads data from a data file into an entrystore"
986
9e161d94bffc
987
a207757f8451
		try:
988
c9d89f3ff91d
			if datafile is None:
989
c9d89f3ff91d
				datafile = self.datafile
990
c9d89f3ff91d
991
9e161d94bffc
			while 1:
992
9e161d94bffc
				try:
993
c9d89f3ff91d
					return datafile.load(file, password, lambda: dialog.PasswordOpen(self, os.path.basename(file)).run())
994
a207757f8451
995
c9d89f3ff91d
				except datahandler.PasswordError:
996
e1117203c473
					dialog.Error(self, _('Incorrect password'), _('The password you entered for the file \'%s\' was not correct.') % file).run()
997
a207757f8451
998
c9d89f3ff91d
		except datahandler.FormatError:
999
e1117203c473
			self.statusbar.set_status(_('Open failed'))
1000
e1117203c473
			dialog.Error(self, _('Invalid file format'), _('The file \'%s\' contains invalid data.') % file).run()
1001
a207757f8451
1002
f75bf9f5bc0d
		except ( datahandler.DataError, entry.EntryTypeError, entry.EntryFieldError ):
1003
e1117203c473
			self.statusbar.set_status(_('Open failed'))
1004
e1117203c473
			dialog.Error(self, _('Unknown data'), _('The file \'%s\' contains unknown data. It may have been created by a newer version of Revelation.') % file).run()
1005
a207757f8451
1006
c9d89f3ff91d
		except datahandler.VersionError:
1007
e1117203c473
			self.statusbar.set_status(_('Open failed'))
1008
e1117203c473
			dialog.Error(self, _('Unknown data version'), _('The file \'%s\' has a future version number, please upgrade Revelation to open it.') % file).run()
1009
a207757f8451
1010
c9d89f3ff91d
		except datahandler.DetectError:
1011
e1117203c473
			self.statusbar.set_status(_('Open failed'))
1012
e1117203c473
			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()
1013
a207757f8451
1014
a207757f8451
		except IOError:
1015
e1117203c473
			self.statusbar.set_status(_('Open failed'))
1016
e1117203c473
			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()
1017
a207757f8451
1018
a207757f8451
1019
c9d89f3ff91d
	def __get_common_usernames(self, e = None):
1020
c9d89f3ff91d
		"Returns a list of possibly relevant usernames"
1021
a207757f8451
1022
c9d89f3ff91d
		list = []
1023
c9d89f3ff91d
1024
c9d89f3ff91d
		if e is not None and e.has_field(entry.UsernameField):
1025
c9d89f3ff91d
			list.append(e[entry.UsernameField])
1026
c9d89f3ff91d
1027
c9d89f3ff91d
		list.append(pwd.getpwuid(os.getuid())[0])
1028
c9d89f3ff91d
		list.extend(self.entrystore.get_popular_values(entry.UsernameField, 3))
1029
c9d89f3ff91d
1030
c9d89f3ff91d
		list = {}.fromkeys(list).keys()
1031
c9d89f3ff91d
		list.sort()
1032
c9d89f3ff91d
1033
c9d89f3ff91d
		return list
1034
c9d89f3ff91d
1035
c9d89f3ff91d
1036
a207757f8451
1037
c9d89f3ff91d
	##### PUBLIC METHODS #####
1038
a207757f8451
1039
e5681e842641
	def about(self):
1040
e5681e842641
		"Displays the about dialog"
1041
e5681e842641
1042
dce7200431f0
		dialog.run_unique(dialog.About, self)
1043
e5681e842641
1044
e5681e842641
1045
4674492151c3
	def clip_chain(self, e):
1046
260e1dfe3e88
		"Copies all passwords from an entry as a chain"
1047
b57efdd083f5
1048
4674492151c3
		if e == None:
1049
b57efdd083f5
			return
1050
b57efdd083f5
1051
b57efdd083f5
		secrets = [ field.value for field in e.fields if field.datatype == entry.DATATYPE_PASSWORD and field.value != "" ]
1052
b57efdd083f5
1053
260e1dfe3e88
		if self.config.get("clipboard/chain_username") == True and len(secrets) > 0 and e.has_field(entry.UsernameField) and e[entry.UsernameField] != "":
1054
b57efdd083f5
			secrets.insert(0, e[entry.UsernameField])
1055
b57efdd083f5
1056
bb1810f15e75
		if len(secrets) == 0:
1057
e1117203c473
			self.statusbar.set_status(_('Entry has no password to copy'))
1058
bb1810f15e75
1059
bb1810f15e75
		else:
1060
8812d70e39d9
			self.clipboard.set(secrets, True)
1061
e1117203c473
			self.statusbar.set_status(_('Password copied to clipboard'))
1062
b57efdd083f5
1063
b57efdd083f5
1064
4674492151c3
	def clip_copy(self, iters):
1065
c9d89f3ff91d
		"Copies entries to the clipboard"
1066
a207757f8451
1067
4674492151c3
		self.entryclipboard.set(self.entrystore, iters)
1068
e1117203c473
		self.statusbar.set_status(_('Entries copied'))
1069
a207757f8451
1070
a207757f8451
1071
4674492151c3
	def clip_cut(self, iters):
1072
c9d89f3ff91d
		"Cuts entries to the clipboard"
1073
a207757f8451
1074
c9d89f3ff91d
		iters = self.entrystore.filter_parents(iters)
1075
c9d89f3ff91d
		self.entryclipboard.set(self.entrystore, iters)
1076
c9d89f3ff91d
1077
c9d89f3ff91d
		# store undo data (need paths)
1078
c9d89f3ff91d
		undoactions = []
1079
c9d89f3ff91d
		for iter in iters:
1080
c9d89f3ff91d
			undostore = data.EntryStore()
1081
c9d89f3ff91d
			undostore.import_entry(self.entrystore, iter)
1082
c9d89f3ff91d
			path = self.entrystore.get_path(iter)
1083
c9d89f3ff91d
			undoactions.append( ( path, undostore ) )
1084
c9d89f3ff91d
1085
c9d89f3ff91d
		# remove data
1086
c9d89f3ff91d
		for iter in iters:
1087
c9d89f3ff91d
			self.entrystore.remove_entry(iter)
1088
c9d89f3ff91d
1089
c9d89f3ff91d
		self.undoqueue.add_action(
1090
e1117203c473
			_('Cut entries'), self.__cb_undo_remove, self.__cb_redo_remove,
1091
c9d89f3ff91d
			undoactions
1092
c9d89f3ff91d
		)
1093
c9d89f3ff91d
1094
a207757f8451
		self.__file_autosave()
1095
c9d89f3ff91d
1096
a207757f8451
		self.tree.unselect_all()
1097
e1117203c473
		self.statusbar.set_status(_('Entries cut'))
1098
a207757f8451
1099
a207757f8451
1100
4674492151c3
	def clip_paste(self, entrystore, parent):
1101
a207757f8451
		"Pastes entries from the clipboard"
1102
a207757f8451
1103
4674492151c3
		if entrystore == None:
1104
a207757f8451
			return
1105
a207757f8451
1106
c9d89f3ff91d
		parent = self.tree.get_active()
1107
c9d89f3ff91d
		iters = self.entrystore.import_entry(entrystore, None, parent)
1108
c9d89f3ff91d
1109
c9d89f3ff91d
		paths = [ self.entrystore.get_path(iter) for iter in iters ]
1110
c9d89f3ff91d
1111
c9d89f3ff91d
		self.undoqueue.add_action(
1112
e1117203c473
			_('Paste entries'), self.__cb_undo_paste, self.__cb_redo_paste,
1113
c9d89f3ff91d
			( entrystore, self.entrystore.get_path(parent), paths )
1114
c9d89f3ff91d
		)
1115
c9d89f3ff91d
1116
c9d89f3ff91d
		if len(iters) > 0:
1117
c9d89f3ff91d
			self.tree.select(iters[0])
1118
c9d89f3ff91d
1119
e1117203c473
		self.statusbar.set_status(_('Entries pasted'))
1120
a207757f8451
1121
a207757f8451
1122
4674492151c3
	def entry_add(self, e = None, parent = None, sibling = None):
1123
a207757f8451
		"Adds an entry"
1124
a207757f8451
1125
a207757f8451
		try:
1126
4674492151c3
			if e == None:
1127
e1117203c473
				d = dialog.EntryEdit(self, _('Add Entry'), None, self.config, self.clipboard)
1128
4674492151c3
				d.set_fieldwidget_data(entry.UsernameField, self.__get_common_usernames())
1129
4674492151c3
				e = d.run()
1130
a207757f8451
1131
4674492151c3
			iter = self.entrystore.add_entry(e, parent, sibling)
1132
c9d89f3ff91d
1133
c9d89f3ff91d
			self.undoqueue.add_action(
1134
e1117203c473
				_('Add entry'), self.__cb_undo_add, self.__cb_redo_add,
1135
c9d89f3ff91d
				( self.entrystore.get_path(iter), e.copy() )
1136
c9d89f3ff91d
			)
1137
c9d89f3ff91d
1138
a207757f8451
			self.__file_autosave()
1139
4674492151c3
			self.tree.select(iter)
1140
e1117203c473
			self.statusbar.set_status(_('Entry added'))
1141
a207757f8451
1142
c9d89f3ff91d
		except dialog.CancelError:
1143
e1117203c473
			self.statusbar.set_status(_('Add entry cancelled'))
1144
a207757f8451
1145
a207757f8451
1146
4674492151c3
	def entry_edit(self, iter):
1147
a207757f8451
		"Edits an entry"
1148
a207757f8451
1149
c9d89f3ff91d
		try:
1150
4674492151c3
			if iter == None:
1151
4674492151c3
				return
1152
4674492151c3
1153
c9d89f3ff91d
			e = self.entrystore.get_entry(iter)
1154
a207757f8451
1155
a1eabf1a2e68
			if type(e) == entry.FolderEntry:
1156
e1117203c473
				d = dialog.FolderEdit(self, _('Edit Folder'), e)
1157
a1eabf1a2e68
1158
a1eabf1a2e68
			else:
1159
e1117203c473
				d = dialog.EntryEdit(self, _('Edit Entry'), e, self.config, self.clipboard)
1160
a1eabf1a2e68
				d.set_fieldwidget_data(entry.UsernameField, self.__get_common_usernames(e))
1161
a207757f8451
1162
e5681e842641
1163
c9d89f3ff91d
			n = d.run()
1164
c9d89f3ff91d
			self.entrystore.update_entry(iter, n)
1165
c9d89f3ff91d
			self.tree.select(iter)
1166
a207757f8451
1167
c9d89f3ff91d
			self.undoqueue.add_action(
1168
e1117203c473
				_('Update entry'), self.__cb_undo_edit, self.__cb_redo_edit,
1169
c9d89f3ff91d
				( self.entrystore.get_path(iter), e.copy(), n.copy() )
1170
c9d89f3ff91d
			)
1171
a207757f8451
1172
a207757f8451
			self.__file_autosave()
1173
e1117203c473
			self.statusbar.set_status(_('Entry updated'))
1174
c9d89f3ff91d
1175
c9d89f3ff91d
		except dialog.CancelError:
1176
e1117203c473
			self.statusbar.set_status(_('Edit entry cancelled'))
1177
a207757f8451
1178
a207757f8451
1179
a207757f8451
	def entry_find(self):
1180
a207757f8451
		"Searches for an entry"
1181
a207757f8451
1182
f80290f7e569
		self.config.set("view/searchbar", True)
1183
f80290f7e569
		self.searchbar.entry.select_region(0, -1)
1184
f80290f7e569
		self.searchbar.entry.grab_focus()
1185
a207757f8451
1186
a207757f8451
1187
a1eabf1a2e68
	def entry_folder(self, e = None, parent = None, sibling = None):
1188
a1eabf1a2e68
		"Adds a folder"
1189
a1eabf1a2e68
1190
a1eabf1a2e68
		try:
1191
a1eabf1a2e68
			if e == None:
1192
e1117203c473
				e = dialog.FolderEdit(self, _('Add Folder')).run()
1193
a1eabf1a2e68
1194
a1eabf1a2e68
			iter = self.entrystore.add_entry(e, parent, sibling)
1195
a1eabf1a2e68
1196
a1eabf1a2e68
			self.undoqueue.add_action(
1197
e1117203c473
				_('Add folder'), self.__cb_undo_add, self.__cb_redo_add,
1198
a1eabf1a2e68
				( self.entrystore.get_path(iter), e.copy() )
1199
a1eabf1a2e68
			)
1200
a1eabf1a2e68
1201
a1eabf1a2e68
			self.__file_autosave()
1202
a1eabf1a2e68
			self.tree.select(iter)
1203
e1117203c473
			self.statusbar.set_status(_('Folder added'))
1204
a1eabf1a2e68
1205
a1eabf1a2e68
		except dialog.CancelError:
1206
e1117203c473
			self.statusbar.set_status(_('Add folder cancelled'))
1207
a1eabf1a2e68
1208
a1eabf1a2e68
1209
095f4008dc7f
	def entry_goto(self, iters):
1210
095f4008dc7f
		"Goes to an entry"
1211
a207757f8451
1212
a207757f8451
		for iter in iters:
1213
a207757f8451
			try:
1214
e5330daca1cd
1215
095f4008dc7f
				# get goto data for entry
1216
c9d89f3ff91d
				e = self.entrystore.get_entry(iter)
1217
6cf6849eed72
				command = self.config.get("launcher/%s" % e.id)
1218
a207757f8451
1219
c9d89f3ff91d
				if command in ( "", None ):
1220
e1117203c473
					self.statusbar.set_status(_('No goto command found for %s entries') % e.typename)
1221
c9d89f3ff91d
					return
1222
c371b70d69d8
1223
c9d89f3ff91d
				subst = {}
1224
c9d89f3ff91d
				for field in e.fields:
1225
c9d89f3ff91d
					subst[field.symbol] = field.value
1226
c371b70d69d8
1227
e5681e842641
				# copy passwords to clipboard
1228
095f4008dc7f
				chain = []
1229
e5330daca1cd
1230
095f4008dc7f
				for field in e.fields:
1231
095f4008dc7f
					if field.datatype == entry.DATATYPE_PASSWORD and field.value != "":
1232
095f4008dc7f
						chain.append(field.value)
1233
095f4008dc7f
1234
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:
1235
260e1dfe3e88
					chain.insert(0, e[entry.UsernameField])
1236
260e1dfe3e88
1237
8812d70e39d9
				self.clipboard.set(chain, True)
1238
095f4008dc7f
1239
095f4008dc7f
				# generate and run goto command
1240
c9d89f3ff91d
				command = util.parse_subst(command, subst)
1241
c9d89f3ff91d
				util.execute_child(command)
1242
a207757f8451
1243
e1117203c473
				self.statusbar.set_status(_('Entry opened'))
1244
e5330daca1cd
1245
c9d89f3ff91d
			except ( util.SubstFormatError, config.ConfigError ):
1246
e1117203c473
				dialog.Error(self, _('Invalid goto command format'), _('The goto command for \'%s\' entries is invalid, please correct it in the preferences.') % e.typename).run()
1247
a207757f8451
1248
c9d89f3ff91d
			except util.SubstValueError:
1249
e1117203c473
				dialog.Error(self, _('Missing entry data'), _('The entry \'%s\' does not have all the data required to open it.') % e.name).run()
1250
8077271f872d
1251
8077271f872d
1252
8077271f872d
	def entry_move(self, sourceiters, parent = None, sibling = None):
1253
8077271f872d
		"Moves a set of entries"
1254
8077271f872d
1255
8077271f872d
		if type(sourceiters) != list:
1256
8077271f872d
			sourceiters = [ sourceiters ]
1257
8077271f872d
1258
8077271f872d
		newiters = []
1259
8077271f872d
		undoactions = []
1260
8077271f872d
1261
8077271f872d
		for sourceiter in sourceiters:
1262
8077271f872d
			sourcepath = self.entrystore.get_path(sourceiter)
1263
8077271f872d
			newiter = self.entrystore.move_entry(sourceiter, parent, sibling)
1264
8077271f872d
			newpath = self.entrystore.get_path(newiter)
1265
8077271f872d
1266
8077271f872d
			undoactions.append( ( sourcepath, newpath ) )
1267
8077271f872d
			newiters.append(newiter)
1268
8077271f872d
1269
8077271f872d
		self.undoqueue.add_action(
1270
e1117203c473
			_('Move entry'), self.__cb_undo_move, self.__cb_redo_move,
1271
8077271f872d
			undoactions
1272
8077271f872d
		)
1273
8077271f872d
1274
8077271f872d
		if len(newiters) > 0:
1275
8077271f872d
			self.tree.select(newiters[0])
1276
8077271f872d
1277
249de64f5d68
		self.__file_autosave()
1278
e1117203c473
		self.statusbar.set_status(_('Entries moved'))
1279
a207757f8451
1280
a207757f8451
1281
4674492151c3
	def entry_remove(self, iters):
1282
c9d89f3ff91d
		"Removes the selected entries"
1283
a207757f8451
1284
a207757f8451
		try:
1285
c9d89f3ff91d
			if len(iters) == 0:
1286
c9d89f3ff91d
				return
1287
a207757f8451
1288
c9d89f3ff91d
			entries = [ self.entrystore.get_entry(iter) for iter in iters ]
1289
c9d89f3ff91d
			dialog.EntryRemove(self, entries).run()
1290
c9d89f3ff91d
			iters = self.entrystore.filter_parents(iters)
1291
a207757f8451
1292
c9d89f3ff91d
			# store undo data (need paths)
1293
c9d89f3ff91d
			undoactions = []
1294
c9d89f3ff91d
			for iter in iters:
1295
c9d89f3ff91d
				undostore = data.EntryStore()
1296
c9d89f3ff91d
				undostore.import_entry(self.entrystore, iter)
1297
c9d89f3ff91d
				path = self.entrystore.get_path(iter)
1298
c9d89f3ff91d
				undoactions.append( ( path, undostore ) )
1299
a207757f8451
1300
c9d89f3ff91d
			# remove data
1301
a207757f8451
			for iter in iters:
1302
c9d89f3ff91d
				self.entrystore.remove_entry(iter)
1303
c9d89f3ff91d
1304
c9d89f3ff91d
			self.undoqueue.add_action(
1305
e1117203c473
				_('Remove entry'), self.__cb_undo_remove, self.__cb_redo_remove,
1306
c9d89f3ff91d
				undoactions
1307
c9d89f3ff91d
			)
1308
a207757f8451
1309
4674492151c3
			self.tree.unselect_all()
1310
a207757f8451
			self.__file_autosave()
1311
e1117203c473
			self.statusbar.set_status(_('Entries removed'))
1312
a207757f8451
1313
c9d89f3ff91d
		except dialog.CancelError:
1314
e1117203c473
			self.statusbar.set_status(_('Entry removal cancelled'))
1315
c9d89f3ff91d
1316
c9d89f3ff91d
1317
4674492151c3
	def file_change_password(self, password = None):
1318
c9d89f3ff91d
		"Changes the password of the current data file"
1319
c9d89f3ff91d
1320
c9d89f3ff91d
		try:
1321
4674492151c3
			if password == None:
1322
4674492151c3
				password = dialog.PasswordChange(self, self.datafile.get_password()).run()
1323
4674492151c3
1324
c9d89f3ff91d
			self.datafile.set_password(password)
1325
c9d89f3ff91d
			self.entrystore.changed = True
1326
c9d89f3ff91d
1327
c9d89f3ff91d
			self.__file_autosave()
1328
e1117203c473
			self.statusbar.set_status(_('Password changed'))
1329
c9d89f3ff91d
1330
c9d89f3ff91d
		except dialog.CancelError:
1331
e1117203c473
			self.statusbar.set_status(_('Password change cancelled'))
1332
a207757f8451
1333
a207757f8451
1334
a207757f8451
	def file_export(self):
1335
a207757f8451
		"Exports data to a foreign file format"
1336
a207757f8451
1337
a207757f8451
		try:
1338
c9d89f3ff91d
			file, handler = dialog.ExportFileSelector(self).run()
1339
c9d89f3ff91d
			datafile = io.DataFile(handler)
1340
a207757f8451
1341
99200bf77234
			if datafile.get_handler().encryption == True:
1342
da124919d5d3
				password = dialog.PasswordSave(self, file).run()
1343
99200bf77234
1344
99200bf77234
			else:
1345
99200bf77234
				dialog.FileSaveInsecure(self).run()
1346
da124919d5d3
				password = None
1347
99200bf77234
1348
99200bf77234
			datafile.save(self.entrystore, file, password)
1349
e1117203c473
			self.statusbar.set_status(_('Data exported to %s') % datafile.get_file())
1350
c9d89f3ff91d
1351
c9d89f3ff91d
		except dialog.CancelError:
1352
e1117203c473
			self.statusbar.set_status(_('Export cancelled'))
1353
a207757f8451
1354
99200bf77234
		except IOError:
1355
e1117203c473
			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()
1356
e1117203c473
			self.statusbar.set_status(_('Export failed'))
1357
99200bf77234
1358
a207757f8451
1359
a207757f8451
	def file_import(self):
1360
a207757f8451
		"Imports data from a foreign file"
1361
a207757f8451
1362
a207757f8451
		try:
1363
c9d89f3ff91d
			file, handler = dialog.ImportFileSelector(self).run()
1364
c9d89f3ff91d
			datafile = io.DataFile(handler)
1365
9e161d94bffc
			entrystore = self.__file_load(file, None, datafile)
1366
a207757f8451
1367
c9d89f3ff91d
			if entrystore is not None:
1368
c9d89f3ff91d
				newiters = self.entrystore.import_entry(entrystore, None)
1369
c9d89f3ff91d
				paths = [ self.entrystore.get_path(iter) for iter in newiters ]
1370
a207757f8451
1371
c9d89f3ff91d
				self.undoqueue.add_action(
1372
e1117203c473
					_('Import data'), self.__cb_undo_import, self.__cb_redo_import,
1373
c9d89f3ff91d
					( paths, entrystore )
1374
c9d89f3ff91d
				)
1375
c9d89f3ff91d
1376
e1117203c473
				self.statusbar.set_status(_('Data imported from %s') % datafile.get_file())
1377
c9d89f3ff91d
1378
a207757f8451
			self.__file_autosave()
1379
a207757f8451
1380
c9d89f3ff91d
		except dialog.CancelError:
1381
e1117203c473
			self.statusbar.set_status(_('Import cancelled'))
1382
a207757f8451
1383
a207757f8451
1384
a207757f8451
	def file_lock(self):
1385
c9d89f3ff91d
		"Locks the current file"
1386
a207757f8451
1387
c9d89f3ff91d
		password = self.datafile.get_password()
1388
c9d89f3ff91d
1389
c9d89f3ff91d
		if password is None:
1390
a207757f8451
			return
1391
a207757f8451
1392
72bff8816fca
		self.locktimer.stop()
1393
72bff8816fca
1394
c8f7da645a99
		# TODO can this be done more elegantly?
1395
c8f7da645a99
		transients = [ window for window in gtk.window_list_toplevels() if window.get_transient_for() == self ]
1396
c8f7da645a99
1397
c9d89f3ff91d
		# store current state
1398
c9d89f3ff91d
		activeiter = self.tree.get_active()
1399
c9d89f3ff91d
		oldtitle = self.get_title()
1400
c9d89f3ff91d
1401
c8f7da645a99
		# clear application contents
1402
a207757f8451
		self.tree.set_model(None)
1403
c9d89f3ff91d
		self.entryview.clear()
1404
e1117203c473
		self.set_title('[' + _('Locked') + ']')
1405
e1117203c473
		self.statusbar.set_status(_('File locked'))
1406
a207757f8451
1407
c8f7da645a99
		# hide any dialogs
1408
c8f7da645a99
		for window in transients:
1409
c8f7da645a99
			window.hide()
1410
c8f7da645a99
1411
c8f7da645a99
		# lock file
1412
5411d685ecc6
		try:
1413
5411d685ecc6
			d = dialog.PasswordLock(self, password)
1414
5411d685ecc6
1415
5411d685ecc6
			if self.entrystore.changed == True:
1416
e1117203c473
				l = ui.ImageLabel(_('Quit disabled due to unsaved changes'), ui.STOCK_WARNING)
1417
c9459c069133
				d.contents.pack_start(l)
1418
5411d685ecc6
				d.get_button(1).set_sensitive(False)
1419
5411d685ecc6
1420
5411d685ecc6
			d.run()
1421
5411d685ecc6
1422
5411d685ecc6
		except dialog.CancelError:
1423
5411d685ecc6
			self.quit()
1424
a207757f8451
1425
c9d89f3ff91d
		# unlock the file and restore state
1426
c9d89f3ff91d
		self.tree.set_model(self.entrystore)
1427
c9d89f3ff91d
		self.tree.select(activeiter)
1428
c9d89f3ff91d
		self.set_title(oldtitle)
1429
e1117203c473
		self.statusbar.set_status(_('File unlocked'))
1430
a207757f8451
1431
c8f7da645a99
		for window in transients:
1432
c8f7da645a99
			window.show()
1433
c8f7da645a99
1434
72bff8816fca
		self.locktimer.start(self.config.get("file/autolock_timeout") * 60)
1435
72bff8816fca
1436
a207757f8451
1437
a207757f8451
	def file_new(self):
1438
a207757f8451
		"Opens a new file"
1439
a207757f8451
1440
c9d89f3ff91d
		try:
1441
99200bf77234
			if self.entrystore.changed == True and dialog.FileChangesNew(self).run() == True:
1442
99200bf77234
				if self.file_save(self.datafile.get_file(), self.datafile.get_password()) == False:
1443
99200bf77234
					raise dialog.CancelError
1444
c9d89f3ff91d
1445
c9d89f3ff91d
			self.entrystore.clear()
1446
c9d89f3ff91d
			self.datafile.close()
1447
8eba4e20d67b
			self.undoqueue.clear()
1448
e1117203c473
			self.statusbar.set_status(_('New file created'))
1449
c9d89f3ff91d
1450
c9d89f3ff91d
		except dialog.CancelError:
1451
e1117203c473
			self.statusbar.set_status(_('New file cancelled'))
1452
a207757f8451
1453
a207757f8451
1454
a207757f8451
	def file_open(self, file = None, password = None):
1455
a207757f8451
		"Opens a data file"
1456
a207757f8451
1457
a207757f8451
		try:
1458
99200bf77234
			if self.entrystore.changed == True and dialog.FileChangesOpen(self).run() == True:
1459
99200bf77234
				if self.file_save(self.datafile.get_file(), self.datafile.get_password()) == False:
1460
99200bf77234
					raise dialog.CancelError
1461
a207757f8451
1462
a207757f8451
			if file is None:
1463
c9d89f3ff91d
				file = dialog.OpenFileSelector(self).run()
1464
a207757f8451
1465
9e161d94bffc
			entrystore = self.__file_load(file, password)
1466
a207757f8451
1467
a207757f8451
			if entrystore is None:
1468
a207757f8451
				return
1469
a207757f8451
1470
c9d89f3ff91d
			self.entrystore.clear()
1471
c9d89f3ff91d
			self.entrystore.import_entry(entrystore, None)
1472
c9d89f3ff91d
			self.entrystore.changed = False
1473
8eba4e20d67b
			self.undoqueue.clear()
1474
a207757f8451
1475
e1117203c473
			self.statusbar.set_status(_('Opened file %s') % self.datafile.get_file())
1476
a207757f8451
1477
c9d89f3ff91d
		except dialog.CancelError:
1478
e1117203c473
			self.statusbar.set_status(_('Open cancelled'))
1479
a207757f8451
1480
a207757f8451
1481
a207757f8451
	def file_save(self, file = None, password = None):
1482
c9d89f3ff91d
		"Saves data to a file"
1483
a207757f8451
1484
a207757f8451
		try:
1485
a207757f8451
			if file is None:
1486
c9d89f3ff91d
				file = dialog.SaveFileSelector(self).run()
1487
a207757f8451
1488
99200bf77234
			if password == None:
1489
99200bf77234
				password = dialog.PasswordSave(self, file).run()
1490
99200bf77234
1491
99200bf77234
			self.datafile.save(self.entrystore, file, password)
1492
c9d89f3ff91d
			self.entrystore.changed = False
1493
e1117203c473
			self.statusbar.set_status(_('Data saved to file %s') % file)
1494
a207757f8451
1495
c9d89f3ff91d
			return True
1496
c9d89f3ff91d
1497
c9d89f3ff91d
		except dialog.CancelError:
1498
e1117203c473
			self.statusbar.set_status(_('Save cancelled'))
1499
9e161d94bffc
			return False
1500
a207757f8451
1501
99200bf77234
		except IOError:
1502
e1117203c473
			dialog.Error(self, _('Unable to save file'), _('The file \'%s\' could not be opened for writing. Make sure that you have the proper permissions to write to it.') % file).run()
1503
f7e311e828af
			self.statusbar.set_status(_('Save failed'))
1504
99200bf77234
			return False
1505
99200bf77234
1506
a207757f8451
1507
e5681e842641
	def prefs(self):
1508
e5681e842641
		"Displays the application preferences"
1509
e5681e842641
1510
a5397c4e7ddd
		dialog.run_unique(Preferences, self, self.config)
1511
e5681e842641
1512
e5681e842641
1513
e5681e842641
	def pwcheck(self):
1514
e5681e842641
		"Displays the password checking dialog"
1515
e5681e842641
1516
8047278c3ce6
		dialog.run_unique(dialog.PasswordChecker, self, self.config, self.clipboard)
1517
e5681e842641
1518
e5681e842641
1519
e5681e842641
	def pwgen(self):
1520
e5681e842641
		"Displays the password generator dialog"
1521
e5681e842641
1522
8047278c3ce6
		dialog.run_unique(dialog.PasswordGenerator, self, self.config, self.clipboard)
1523
e5681e842641
1524
e5681e842641
1525
a207757f8451
	def quit(self):
1526
a207757f8451
		"Quits the application"
1527
a207757f8451
1528
c9d89f3ff91d
		try:
1529
99200bf77234
			if self.entrystore.changed == True and dialog.FileChangesQuit(self).run() == True:
1530
99200bf77234
				if self.file_save(self.datafile.get_file(), self.datafile.get_password()) == False:
1531
99200bf77234
					raise dialog.CancelError
1532
c9d89f3ff91d
1533
c9d89f3ff91d
			self.clipboard.clear()
1534
c9d89f3ff91d
			self.entryclipboard.clear()
1535
c9d89f3ff91d
1536
59be49691b29
			self.__save_state()
1537
c9d89f3ff91d
1538
c9d89f3ff91d
			gtk.main_quit()
1539
c9d89f3ff91d
			sys.exit(0)
1540
693fe3e0223f
			return True
1541
c9d89f3ff91d
1542
c9d89f3ff91d
		except dialog.CancelError:
1543
e1117203c473
			self.statusbar.set_status(_('Quit cancelled'))
1544
c9d89f3ff91d
			return False
1545
a207757f8451
1546
a207757f8451
1547
a207757f8451
	def redo(self):
1548
c9d89f3ff91d
		"Redoes the previous action"
1549
a207757f8451
1550
c9d89f3ff91d
		action = self.undoqueue.get_redo_action()
1551
c9d89f3ff91d
1552
c9d89f3ff91d
		if action is None:
1553
a207757f8451
			return
1554
a207757f8451
1555
c9d89f3ff91d
		self.undoqueue.redo()
1556
e1117203c473
		self.statusbar.set_status(_('%s redone') % action[1])
1557
c9d89f3ff91d
		self.__file_autosave()
1558
a207757f8451
1559
c9d89f3ff91d
1560
c9d89f3ff91d
	def run(self):
1561
c9d89f3ff91d
		"Runs the application"
1562
c9d89f3ff91d
1563
59be49691b29
		args, argdict = self.program.get_popt_args()
1564
59be49691b29
1565
59be49691b29
		if len(args) > 0:
1566
59be49691b29
			file = args[0]
1567
c9d89f3ff91d
1568
c9d89f3ff91d
		elif self.config.get("file/autoload") == True:
1569
c9d89f3ff91d
			file = self.config.get("file/autoload_file")
1570
a207757f8451
1571
a207757f8451
		else:
1572
c9d89f3ff91d
			file = ""
1573
a207757f8451
1574
a207757f8451
1575
c9d89f3ff91d
		if file != "":
1576
c9d89f3ff91d
			self.file_open(io.file_normpath(file))
1577
a207757f8451
1578
a207757f8451
		gtk.main()
1579
a207757f8451
1580
a207757f8451
1581
a207757f8451
	def undo(self):
1582
c9d89f3ff91d
		"Undoes the previous action"
1583
a207757f8451
1584
c9d89f3ff91d
		action = self.undoqueue.get_undo_action()
1585
c9d89f3ff91d
1586
c9d89f3ff91d
		if action is None:
1587
a207757f8451
			return
1588
a207757f8451
1589
c9d89f3ff91d
		self.undoqueue.undo()
1590
e1117203c473
		self.statusbar.set_status(_('%s undone') % action[1])
1591
a207757f8451
		self.__file_autosave()
1592
a207757f8451
1593
a207757f8451
1594
a207757f8451
1595
253760f7a205
class Preferences(dialog.Utility):
1596
a5397c4e7ddd
	"A preference dialog"
1597
a5397c4e7ddd
1598
a5397c4e7ddd
	def __init__(self, parent, cfg):
1599
e1117203c473
		dialog.Utility.__init__(self, parent, _('Preferences'))
1600
a5397c4e7ddd
		self.config = cfg
1601
a5397c4e7ddd
		self.set_modal(False)
1602
a5397c4e7ddd
1603
a5397c4e7ddd
		self.notebook = ui.Notebook()
1604
a5397c4e7ddd
		self.vbox.pack_start(self.notebook)
1605
a5397c4e7ddd
1606
e1117203c473
		self.page_general = self.notebook.create_page(_('General'))
1607
7df21750b8cf
		self.__init_section_files(self.page_general)
1608
7df21750b8cf
		self.__init_section_password(self.page_general)
1609
a5397c4e7ddd
1610
e1117203c473
		self.page_interface = self.notebook.create_page(_('Interface'))
1611
a5397c4e7ddd
		self.__init_section_doubleclick(self.page_interface)
1612
a5397c4e7ddd
		self.__init_section_toolbar(self.page_interface)
1613
a5397c4e7ddd
1614
e1117203c473
		self.page_gotocmd = self.notebook.create_page(_('Goto Commands'))
1615
a5397c4e7ddd
		self.__init_section_gotocmd(self.page_gotocmd)
1616
a5397c4e7ddd
1617
a5397c4e7ddd
		self.connect("response", lambda w,d: self.destroy())
1618
a5397c4e7ddd
1619
a5397c4e7ddd
1620
a5397c4e7ddd
	def __init_section_doubleclick(self, page):
1621
a5397c4e7ddd
		"Sets up the doubleclick section"
1622
a5397c4e7ddd
1623
e1117203c473
		self.section_doubleclick = page.add_section(_('Doubleclick Action'))
1624
a5397c4e7ddd
1625
a5397c4e7ddd
		# radio-button for go to
1626
e1117203c473
		self.radio_doubleclick_goto = ui.RadioButton(None, _('Go to account, if possible'))
1627
a5397c4e7ddd
		ui.config_bind(self.config, "behavior/doubleclick", self.radio_doubleclick_goto, "goto")
1628
a5397c4e7ddd
1629
e1117203c473
		self.tooltips.set_tip(self.radio_doubleclick_goto, _('Go to the account (open in external application) on doubleclick, if required data is filled in'))
1630
a5397c4e7ddd
		self.section_doubleclick.append_widget(None, self.radio_doubleclick_goto)
1631
a5397c4e7ddd
1632
a5397c4e7ddd
		# radio-button for edit
1633
e1117203c473
		self.radio_doubleclick_edit = ui.RadioButton(self.radio_doubleclick_goto, _('Edit account'))
1634
a5397c4e7ddd
		ui.config_bind(self.config, "behavior/doubleclick", self.radio_doubleclick_edit, "edit")
1635
a5397c4e7ddd
1636
e1117203c473
		self.tooltips.set_tip(self.radio_doubleclick_edit, _('Edit the account on doubleclick'))
1637
a5397c4e7ddd
		self.section_doubleclick.append_widget(None, self.radio_doubleclick_edit)
1638
a5397c4e7ddd
1639
a5397c4e7ddd
		# radio-button for copy
1640
e1117203c473
		self.radio_doubleclick_copy = ui.RadioButton(self.radio_doubleclick_goto, _('Copy password to clipboard'))
1641
a5397c4e7ddd
		ui.config_bind(self.config, "behavior/doubleclick", self.radio_doubleclick_copy, "copy")
1642
a5397c4e7ddd
1643
e1117203c473
		self.tooltips.set_tip(self.radio_doubleclick_copy, _('Copy the account password to clipboard on doubleclick'))
1644
a5397c4e7ddd
		self.section_doubleclick.append_widget(None, self.radio_doubleclick_copy)
1645
a5397c4e7ddd
1646
a5397c4e7ddd
1647
7df21750b8cf
	def __init_section_files(self, page):
1648
7df21750b8cf
		"Sets up the files section"
1649
a5397c4e7ddd
1650
e1117203c473
		self.section_files = page.add_section(_('Files'))
1651
7df21750b8cf
1652
7df21750b8cf
		# checkbutton and file button for autoloading a file
1653
e1117203c473
		self.check_autoload = ui.CheckButton(_('Open file on startup:'))
1654
7df21750b8cf
		ui.config_bind(self.config, "file/autoload", self.check_autoload)
1655
7df21750b8cf
1656
7df21750b8cf
		self.check_autoload.connect("toggled", lambda w: self.button_autoload_file.set_sensitive(w.get_active()))
1657
e1117203c473
		self.tooltips.set_tip(self.check_autoload, _('When enabled, this file will be opened when the program is started'))
1658
7df21750b8cf
1659
e1117203c473
		self.button_autoload_file = ui.FileButton(_('Select File to Automatically Open'))
1660
7df21750b8cf
		ui.config_bind(self.config, "file/autoload_file", self.button_autoload_file)
1661
7df21750b8cf
		self.button_autoload_file.set_sensitive(self.check_autoload.get_active())
1662
7df21750b8cf
1663
7df21750b8cf
		eventbox = ui.EventBox(self.button_autoload_file)
1664
e1117203c473
		self.tooltips.set_tip(eventbox, _('File to open when Revelation is started'))
1665
7df21750b8cf
1666
7df21750b8cf
		hbox = ui.HBox()
1667
7df21750b8cf
		hbox.pack_start(self.check_autoload, False, False)
1668
7df21750b8cf
		hbox.pack_start(eventbox)
1669
7df21750b8cf
		self.section_files.append_widget(None, hbox)
1670
a5397c4e7ddd
1671
a5397c4e7ddd
		# check-button for autosave
1672
e1117203c473
		self.check_autosave = ui.CheckButton(_('Automatically save data when changed'))
1673
a5397c4e7ddd
		ui.config_bind(self.config, "file/autosave", self.check_autosave)
1674
a5397c4e7ddd
1675
e1117203c473
		self.tooltips.set_tip(self.check_autosave, _('Automatically save the data file when an entry is added, modified or removed'))
1676
7df21750b8cf
		self.section_files.append_widget(None, self.check_autosave)
1677
a5397c4e7ddd
1678
a5397c4e7ddd
		# autolock file
1679
e1117203c473
		self.check_autolock = ui.CheckButton(_('Lock file when inactive for'))
1680
a5397c4e7ddd
		ui.config_bind(self.config, "file/autolock", self.check_autolock)
1681
a5397c4e7ddd
		self.check_autolock.connect("toggled", lambda w: self.spin_autolock_timeout.set_sensitive(w.get_active()))
1682
e1117203c473
		self.tooltips.set_tip(self.check_autolock, _('Automatically lock the data file after a period of inactivity'))
1683
a5397c4e7ddd
1684
a5397c4e7ddd
		self.spin_autolock_timeout = ui.SpinEntry()
1685
a5397c4e7ddd
		self.spin_autolock_timeout.set_range(1, 120)
1686
a5397c4e7ddd
		self.spin_autolock_timeout.set_sensitive(self.check_autolock.get_active())
1687
a5397c4e7ddd
		ui.config_bind(self.config, "file/autolock_timeout", self.spin_autolock_timeout)
1688
e1117203c473
		self.tooltips.set_tip(self.spin_autolock_timeout, _('The period of inactivity before locking the file, in minutes'))
1689
a5397c4e7ddd
1690
a5397c4e7ddd
		hbox = ui.HBox()
1691
a5397c4e7ddd
		hbox.set_spacing(3)
1692
7df21750b8cf
		hbox.pack_start(self.check_autolock, False, False)
1693
7df21750b8cf
		hbox.pack_start(self.spin_autolock_timeout, False, False)
1694
e1117203c473
		hbox.pack_start(ui.Label(_('minutes')))
1695
7df21750b8cf
		self.section_files.append_widget(None, hbox)
1696
a5397c4e7ddd
1697
a5397c4e7ddd
1698
a5397c4e7ddd
	def __init_section_gotocmd(self, page):
1699
a5397c4e7ddd
		"Sets up the goto command section"
1700
a5397c4e7ddd
1701
e1117203c473
		self.section_gotocmd = page.add_section(_('Goto Commands'))
1702
a5397c4e7ddd
1703
a5397c4e7ddd
		for entrytype in entry.ENTRYLIST:
1704
a5397c4e7ddd
			if entrytype == entry.FolderEntry:
1705
a5397c4e7ddd
				continue
1706
a5397c4e7ddd
1707
a5397c4e7ddd
			e = entrytype()
1708
a5397c4e7ddd
1709
a5397c4e7ddd
			widget = ui.Entry()
1710
a5397c4e7ddd
			ui.config_bind(self.config, "launcher/%s" % e.id, widget)
1711
a5397c4e7ddd
1712
e1117203c473
			tooltip = _('Goto command for %s accounts. The following expansion variables can be used:') % e.typename + "\n\n"
1713
a5397c4e7ddd
1714
a5397c4e7ddd
			for field in e.fields:
1715
a5397c4e7ddd
				tooltip += "%%%s: %s\n" % ( field.symbol, field.name )
1716
a5397c4e7ddd
1717
a5397c4e7ddd
			tooltip += "\n"
1718
e1117203c473
			tooltip += _('%%: a % sign') + "\n"
1719
e1117203c473
			tooltip += _('%?x: optional expansion variable') + "\n"
1720
e1117203c473
			tooltip += _('%(...%): optional substring expansion')
1721
a5397c4e7ddd
1722
a5397c4e7ddd
			self.tooltips.set_tip(widget, tooltip)
1723
a5397c4e7ddd
			self.section_gotocmd.append_widget(e.typename, widget)
1724
a5397c4e7ddd
1725
a5397c4e7ddd
1726
7df21750b8cf
	def __init_section_password(self, page):
1727
7df21750b8cf
		"Sets up the password section"
1728
a5397c4e7ddd
1729
e1117203c473
		self.section_password = page.add_section(_('Passwords'))
1730
a5397c4e7ddd
1731
a5397c4e7ddd
		# show passwords checkbutton
1732
e1117203c473
		self.check_show_passwords = ui.CheckButton(_('Display passwords and other secrets'))
1733
a5397c4e7ddd
		ui.config_bind(self.config, "view/passwords", self.check_show_passwords)
1734
a5397c4e7ddd
1735
e1117203c473
		self.tooltips.set_tip(self.check_show_passwords, _('Display passwords and other secrets, such as PIN codes (otherwise, hide with ******)'))
1736
7df21750b8cf
		self.section_password.append_widget(None, self.check_show_passwords)
1737
a5397c4e7ddd
1738
7df21750b8cf
		# chain username checkbutton
1739
e1117203c473
		self.check_chain_username = ui.CheckButton(_('Also copy username when copying password'))
1740
7df21750b8cf
		ui.config_bind(self.config, "clipboard/chain_username", self.check_chain_username)
1741
a5397c4e7ddd
1742
e1117203c473
		self.tooltips.set_tip(self.check_chain_username, _('When the password is copied to clipboard, put the username before the password as a clipboard "chain"'))
1743
7df21750b8cf
		self.section_password.append_widget(None, self.check_chain_username)
1744
a5397c4e7ddd
1745
a5397c4e7ddd
		# password length spinbutton
1746
a5397c4e7ddd
		self.spin_pwlen = ui.SpinEntry()
1747
a5397c4e7ddd
		self.spin_pwlen.set_range(4, 32)
1748
a5397c4e7ddd
		ui.config_bind(self.config, "passwordgen/length", self.spin_pwlen)
1749
a5397c4e7ddd
1750
e1117203c473
		self.tooltips.set_tip(self.spin_pwlen, _('The number of characters in generated passwords - 8 or more are recommended'))
1751
e1117203c473
		self.section_password.append_widget(_('Length of generated passwords'), self.spin_pwlen)
1752
a5397c4e7ddd
1753
a5397c4e7ddd
1754
a5397c4e7ddd
	def __init_section_toolbar(self, page):
1755
a5397c4e7ddd
		"Sets up the toolbar section"
1756
a5397c4e7ddd
1757
e1117203c473
		self.section_toolbar = page.add_section(_('Toolbar Style'))
1758
a5397c4e7ddd
1759
a5397c4e7ddd
		# radio-button for desktop default
1760
e1117203c473
		self.radio_toolbar_desktop = ui.RadioButton(None, _('Use desktop default'))
1761
a5397c4e7ddd
		ui.config_bind(self.config, "view/toolbar_style", self.radio_toolbar_desktop, "desktop")
1762
a5397c4e7ddd
1763
e1117203c473
		self.tooltips.set_tip(self.radio_toolbar_desktop, _('Show toolbar items with default style'))
1764
a5397c4e7ddd
		self.section_toolbar.append_widget(None, self.radio_toolbar_desktop)
1765
a5397c4e7ddd
1766
a5397c4e7ddd
		# radio-button for icons and text
1767
e1117203c473
		self.radio_toolbar_both = ui.RadioButton(self.radio_toolbar_desktop, _('Show icons and text'))
1768
a5397c4e7ddd
		ui.config_bind(self.config, "view/toolbar_style", self.radio_toolbar_both, "both")
1769
a5397c4e7ddd
1770
e1117203c473
		self.tooltips.set_tip(self.radio_toolbar_both, _('Show toolbar items with both icons and text'))
1771
a5397c4e7ddd
		self.section_toolbar.append_widget(None, self.radio_toolbar_both)
1772
a5397c4e7ddd
1773
a5397c4e7ddd
		# radio-button for icons and important text
1774
e1117203c473
		self.radio_toolbar_bothhoriz = ui.RadioButton(self.radio_toolbar_desktop, _('Show icons and important text'))
1775
a5397c4e7ddd
		ui.config_bind(self.config, "view/toolbar_style", self.radio_toolbar_bothhoriz, "both-horiz")
1776
a5397c4e7ddd
1777
e1117203c473
		self.tooltips.set_tip(self.radio_toolbar_bothhoriz, _('Show toolbar items with text beside important icons'))
1778
a5397c4e7ddd
		self.section_toolbar.append_widget(None, self.radio_toolbar_bothhoriz)
1779
a5397c4e7ddd
1780
a5397c4e7ddd
		# radio-button for icons only
1781
e1117203c473
		self.radio_toolbar_icons = ui.RadioButton(self.radio_toolbar_desktop, _('Show icons only'))
1782
a5397c4e7ddd
		ui.config_bind(self.config, "view/toolbar_style", self.radio_toolbar_icons, "icons")
1783
a5397c4e7ddd
1784
e1117203c473
		self.tooltips.set_tip(self.radio_toolbar_icons, _('Show toolbar items with icons only'))
1785
a5397c4e7ddd
		self.section_toolbar.append_widget(None, self.radio_toolbar_icons)
1786
a5397c4e7ddd
1787
a5397c4e7ddd
		# radio-button for text only
1788
e1117203c473
		self.radio_toolbar_text = ui.RadioButton(self.radio_toolbar_desktop, _('Show text only'))
1789
a5397c4e7ddd
		ui.config_bind(self.config, "view/toolbar_style", self.radio_toolbar_text, "text")
1790
a5397c4e7ddd
1791
e1117203c473
		self.tooltips.set_tip(self.radio_toolbar_text, _('Show toolbar items with text only'))
1792
a5397c4e7ddd
		self.section_toolbar.append_widget(None, self.radio_toolbar_text)
1793
a5397c4e7ddd
1794
a5397c4e7ddd
1795
a5397c4e7ddd
	def run(self):
1796
a5397c4e7ddd
		"Runs the preference dialog"
1797
a5397c4e7ddd
1798
a5397c4e7ddd
		self.show_all()
1799
a5397c4e7ddd
1800
f3b5ac5a54f5
		if dialog.EVENT_FILTER != None:
1801
f3b5ac5a54f5
			self.window.add_filter(dialog.EVENT_FILTER)
1802
a5397c4e7ddd
1803
7df21750b8cf
		# for some reason, gtk crashes on close-by-escape unless we do this
1804
a5397c4e7ddd
		self.get_button(0).grab_focus()
1805
a5397c4e7ddd
		self.notebook.grab_focus()
1806
a5397c4e7ddd
1807
a5397c4e7ddd
1808
a5397c4e7ddd
1809
a207757f8451
if __name__ == "__main__":
1810
c9d89f3ff91d
	app = Revelation()
1811
c9d89f3ff91d
	app.run()
1812
a207757f8451