#!/usr/bin/env python

#
# Revelation 0.3.4 - a password manager for GNOME 2
# http://oss.codepoet.no/revelation/
# $Id: revelation 153 2004-09-27 17:29:40Z erikg $
#
# Copyright (c) 2003-2004 Erik Grinaker
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#

import pygtk
pygtk.require("2.0")

import gtk, gnome, revelation, os, os.path, sys, gobject


class Revelation(revelation.widget.App):
	"Main application class"

	def __init__(self):
		sys.excepthook = self.__cb_exception

		gnome.init(revelation.APPNAME, revelation.APPNAME)
		revelation.widget.App.__init__(self, revelation.APPNAME)

		os.umask(0077)

		gtk.window_set_default_icon_list(
			gtk.gdk.pixbuf_new_from_file(revelation.DATADIR + "/pixmaps/revelation.png"),
			gtk.gdk.pixbuf_new_from_file(revelation.DATADIR + "/pixmaps/revelation-16x16.png")
		)

		self.__init_facilities()
		self.__init_menu()
		self.__init_toolbar()
		self.__init_mainarea()
		self.__init_states()



	# init methods
	def __init_facilities(self):
		"Sets up various application facilities"

		try:
			self.config = revelation.data.Config()

		except revelation.data.ConfigError:
			revelation.dialog.Error(
				None, "Configuration error",
				"The Revelation configuration data could not be found in gconf. This indicates a fault in your installation, please re-install Revelation."
			).run()

			sys.exit(1)


		self.icons	= revelation.stock.IconFactory(self)
		self.data	= revelation.data.EntryStore()
		self.clipboard	= revelation.data.EntryClipboard()
		self.undoqueue	= revelation.data.UndoQueue(self.data)
		self.finder	= revelation.data.EntrySearch(self.data)

		self.data.connect("file-changed", self.__cb_state_file)

		self.clipboard.connect("copy", self.__cb_state_clipboard)
		self.clipboard.connect("cut", self.__cb_state_clipboard)

		self.undoqueue.connect("changed", self.__cb_state_undo)

		self.finder.connect("changed", self.__cb_state_find)


	def __init_mainarea(self):
		"Sets up the main application area"

		self.tree = revelation.widget.Tree(self.data)
		self.tree.connect("popup", self.__cb_popup_tree)
		self.tree.connect("doubleclick", lambda w,d: self.entry_edit())
		self.tree.connect("key-press-event", self.__cb_keypress_tree)
		self.tree.selection.connect("changed", lambda w: self.dataview.display_entry(self.data.get_entry(self.tree.get_active())))
		self.tree.selection.connect("changed", self.__cb_state_entry)
		scrolledwindow = revelation.widget.ScrolledWindow(self.tree)

		self.dataview = revelation.widget.DataView()
		alignment = gtk.Alignment(0.5, 0.4, 0, 0)
		alignment.add(self.dataview)

		self.hpaned = revelation.widget.HPaned(scrolledwindow, alignment, self.config.get("view/pane-position"))
		self.set_contents(self.hpaned)


	def __init_menu(self):
		"Sets up the application menu"

		self.create_menu((
			("/_File",		None,			None,					None,							0,	"<Branch>"),
			("/File/_New",		"<Control>N",		"Create a new file",			lambda w,d: self.file_new(),				0,	"<StockItem>",	gtk.STOCK_NEW),
			("/File/_Open...",	"<Control>O",		"Open a file",				lambda w,d: self.file_open(),				0,	"<StockItem>",	gtk.STOCK_OPEN),
			("/File/sep1",		None,			None,					None,							0,	"<Separator>"),
			("/File/_Save",		"<Control>S",		"Save data to file",			lambda w,d: self.file_save(self.data.file, self.data.password),	0,	"<StockItem>",	gtk.STOCK_SAVE),
			("/File/Save _As...",	"<Shift><Control>S",	"Save data to different file",		lambda w,d: self.file_save(),				0,	"<StockItem>",	gtk.STOCK_SAVE_AS),
			("/File/_Revert",	None,			"Revert to the saved copy of the file",	lambda w,d: self.file_revert(),				0,	"<StockItem>",	gtk.STOCK_REVERT_TO_SAVED),
			("/File/sep2",		None,			None,					None,							0,	"<Separator>"),
			("/File/Change _Password...",	None,		"Change password of current file",	lambda w,d: self.change_password(),			0,	"<StockItem>",	revelation.stock.STOCK_PASSWORD),
			("/File/_Lock...",	"<Shift><Control>L",	"Lock the current data file",		lambda w,d: self.file_lock(),				0,	"<StockItem>",	revelation.stock.STOCK_LOCK),
			("/File/sep3",		None,			None,					None,							0,	"<Separator>"),
			("/File/_Import...",	None,			"Import data from a foreign file",	lambda w,d: self.file_import(),				0,	"<StockItem>",	revelation.stock.STOCK_IMPORT),
			("/File/_Export...",	None,			"Export data to a different format",	lambda w,d: self.file_export(),				0,	"<StockItem>",	revelation.stock.STOCK_EXPORT),
			("/File/sep4",		None,			None,					None,							0,	"<Separator>"),
			("/File/_Close",	"<Control>W",		"Close the application",		lambda w,d: self.quit(),				0, 	"<StockItem>",	gtk.STOCK_CLOSE),
			("/File/_Quit",		"<Control>Q",		"Quit the application",			lambda w,d: self.quit(),				0, 	"<StockItem>",	gtk.STOCK_QUIT),

			("/_Edit",		None,			None,					None,							0,	"<Branch>"),
			("/Edit/_Add Entry...",	"<Control>Insert",	"Create a new entry",			lambda w,d: self.entry_add(),				0,	"<StockItem>",	revelation.stock.STOCK_ADD),
			("/Edit/_Edit",		"<Control>Return",	"Edit the selected entry",		lambda w,d: self.entry_edit(),				0,	"<StockItem>",	revelation.stock.STOCK_EDIT),
			("/Edit/Re_move",	"<Control>Delete",	"Remove the selected entry",		lambda w,d: self.entry_remove(),			0,	"<StockItem>",	revelation.stock.STOCK_REMOVE),
			("/Edit/_Launch",	"<Control>L",		"Launch the selected entry",		lambda w,d: self.entry_launch(),			0,	"<StockItem>",	revelation.stock.STOCK_LAUNCH),
			("/Edit/sep1",		None,			None,					None,							0,	"<Separator>"),
			("/Edit/_Undo",		"<Control>Z",		"Undo the last action",			lambda w,d: self.undo(),				0,	"<StockItem>",	gtk.STOCK_UNDO),
			("/Edit/_Redo",		"<Shift><Control>Z",	"Redo the previously undone action",	lambda w,d: self.redo(),				0,	"<StockItem>",	gtk.STOCK_REDO),
			("/Edit/sep2",		None,			None,					None,							0,	"<Separator>"),
			("/Edit/Cu_t",		"<Control>X",		"Cut the entry to the clipboard",	lambda w,d: self.clip_cut(),				0,	"<StockItem>",	gtk.STOCK_CUT),
			("/Edit/_Copy",		"<Control>C",		"Copy the entry to the clipboard",	lambda w,d: self.clip_copy(),				0,	"<StockItem>",	gtk.STOCK_COPY),
			("/Edit/_Paste",	"<Control>V",		"Paste entry from clipboard",		lambda w,d: self.clip_paste(),				0,	"<StockItem>",	gtk.STOCK_PASTE),
			("/Edit/sep3",		None,			None,					None,							0,	"<Separator>"),
			("/Edit/_Find...",	"<Control>F",		"Search for an entry",			lambda w,d: self.entry_find(),				0,	"<StockItem>",	gtk.STOCK_FIND),
			("/Edit/Find Ne_xt",	"<Control>G",		"Find the next search match",		lambda w,d: self.__entry_find(self, revelation.data.SEARCH_NEXT),	0,	"<Item>"),
			("/Edit/Find Pre_vious",	"<Shift><Control>G",		"Find the previous search match",	lambda w,d: self.__entry_find(self, revelation.data.SEARCH_PREV),	0,	"<Item>"),
			("/Edit/sep4",		None,			None,					None,							0,	"<Separator>"),
			("/Edit/_Select All",	"<Control>A",		"Select all entries",			lambda w,d: self.tree.select_all(),			0,	"<Item>"),
			("/Edit/_Deselect All",	"<Shift><Control>A",	"Deselect all entries",			lambda w,d: self.tree.unselect_all(),			0,	"<Item>"),
			("/Edit/sep5",		None,			None,					None,							0,	"<Separator>"),
			("/Edit/Prefere_nces",	None,			"Edit preferences",			lambda w,d: revelation.dialog.Preferences(self, self.config).run(),	0,	"<StockItem>",	gtk.STOCK_PREFERENCES),

			("/_View",		None,			None,					None,							0,	"<Branch>"),
			("/View/_Main Toolbar",	None,			"Toggle display of the main toolbar",	None,							0,	"<CheckItem>"),
			("/View/S_earch Toolbar",	None,		"Toggle display of the search toolbar",	None,							0,	"<CheckItem>"),
			("/View/_Statusbar",	None,			"Toggle display of the statusbar",	None,							0,	"<CheckItem>"),
			("/View/sep1",		None,			None,					None,							0,	"<Separator>"),
			("/View/Password _Generator", None,		"Open a password generator",		lambda w,d: revelation.dialog.PasswordGenerator(self, self.config).run(),	0,	"<StockItem>",	revelation.stock.STOCK_GENERATE),
			("/View/Show _Passwords",	"<Control>P",	"Show passwords",			None,							0,	"<CheckItem>"),

			("/_Help",		None,			None,					None,							0,	"<Branch>"),
			("/Help/_Homepage",	None,			"Visit the Revelation homepage",	lambda w,d: gnome.url_show(revelation.URL),		0,	"<StockItem>", gtk.STOCK_HOME),
			("/Help/_About",	None,			"Show info about this application",	lambda w,d: revelation.dialog.About(self).run(),	0,	"<StockItem>", "gnome-stock-about")
		))


	def __init_states(self):
		"Sets the initial application state"

		self.set_default_size(self.config.get("view/window-width"), self.config.get("view/window-height"))
		self.move(self.config.get("view/window-position-x"), self.config.get("view/window-position-y"))
		self.connect("delete_event", lambda w,d: gtk.TRUE ^ self.quit())

		self.tree.select(None)
		self.dataview.display_info()
		self.data.set_file(None)

		self.if_menu.get_widget("<main>/Edit/Find Next").set_sensitive(gtk.FALSE)
		self.if_menu.get_widget("<main>/Edit/Find Previous").set_sensitive(gtk.FALSE)
		self.if_menu.get_widget("<main>/Edit/Undo").set_sensitive(gtk.FALSE)
		self.if_menu.get_widget("<main>/Edit/Redo").set_sensitive(gtk.FALSE)

		self.show_all()

		self.config.bind_widget("view/passwords", self.if_menu.get_widget("<main>/View/Show Passwords"))
		self.config.bind_widget("view/searchbar", self.if_menu.get_widget("<main>/View/Search Toolbar"))
		self.config.bind_widget("view/statusbar", self.if_menu.get_widget("<main>/View/Statusbar"))
		self.config.bind_widget("view/toolbar", self.if_menu.get_widget("<main>/View/Main Toolbar"))

		self.config.notify_add("view/searchbar", self.__cb_config_searchbar)
		self.config.notify_add("view/statusbar", self.__cb_config_statusbar)
		self.config.notify_add("view/toolbar", self.__cb_config_toolbar)


	def __init_toolbar(self):
		"Sets up the application toolbar"

		self.toolbar.button_new		= self.toolbar.append_stock(gtk.STOCK_NEW, "New file", lambda w,d: self.file_new())
		self.toolbar.button_open	= self.toolbar.append_stock(gtk.STOCK_OPEN, "Open file", lambda w,d: self.file_open())
		self.toolbar.button_save	= self.toolbar.append_stock(gtk.STOCK_SAVE, "Save file", lambda w,d: self.file_save(self.data.file, self.data.password))

		self.toolbar.append_space()

		self.toolbar.button_entry_add	= self.toolbar.append_stock(revelation.stock.STOCK_ADD, "Add a new entry", lambda w,d: self.entry_add())
		self.toolbar.button_entry_edit	= self.toolbar.append_stock(revelation.stock.STOCK_EDIT, "Edit the selected entry", lambda w,d: self.entry_edit())
		self.toolbar.button_entry_remove = self.toolbar.append_stock(revelation.stock.STOCK_REMOVE, "Remove the selected entry", lambda w,d: self.entry_remove())
		self.toolbar.button_entry_launch = self.toolbar.append_stock(revelation.stock.STOCK_LAUNCH, "Launch the selected entry", lambda w,d: self.entry_launch())


		# search-bar
		self.searchbar = revelation.widget.Searchbar()
		self.add_toolbar(self.searchbar, "searchbar", 2)

		self.searchbar.button.connect("clicked", lambda w: self.__entry_find(self, string = self.searchbar.entry.get_text()))




	# exception callback
	def __cb_exception(self, type, value, trace):
		"Callback for unhandled exceptions"

		traceback = revelation.io.trace_exception(type, value, trace)
		sys.stderr.write(traceback)

		if revelation.dialog.Exception(self, traceback).run() == gtk.TRUE:
			self.run()

		else:
			sys.exit(1)


	# config callbacks
	def __cb_config_searchbar(self, config, value, data):
		"Config callback for searchbar changes"

		if value == gtk.TRUE:
			self.searchbar.show()

		else:
			self.searchbar.hide()

	def __cb_config_statusbar(self, config, value, data):
		"Config callback for statusbar changes"

		if value == gtk.TRUE:
			self.statusbar.show()

		else:
			self.statusbar.hide()


	def __cb_config_toolbar(self, config, value, data):
		"Config callback for toolbar changes"

		if value == gtk.TRUE:
			self.toolbar.show()

		else:
			self.toolbar.hide()



	# callbacks for handling ui states
	def __cb_state_clipboard(self, widget, data = None):
		"Sets clipboard item sensitivity as appropriate"

		self.if_menu.get_widget("<main>/Edit/Paste").set_sensitive(self.clipboard.has_contents())


	def __cb_state_entry(self, widget, data = None):
		"Sets state for entry-dependent ui items"

		selected = self.tree.get_selected()
		active = self.tree.get_active()

		self.toolbar.button_entry_add.set_sensitive(len(selected) < 2)
		self.if_menu.get_widget("<main>/Edit/Add Entry...").set_sensitive(len(selected) < 2)
		self.if_menu.get_widget("<main>/Edit/Paste").set_sensitive(len(selected) < 2 and self.clipboard.has_contents())

		self.toolbar.button_entry_edit.set_sensitive(len(selected) == 1)
		self.if_menu.get_widget("<main>/Edit/Edit").set_sensitive(len(selected) == 1)

		self.toolbar.button_entry_remove.set_sensitive(len(selected) > 0)
		self.if_menu.get_widget("<main>/Edit/Remove").set_sensitive(len(selected) > 0)
		self.if_menu.get_widget("<main>/Edit/Cut").set_sensitive(len(selected) > 0)
		self.if_menu.get_widget("<main>/Edit/Copy").set_sensitive(len(selected) > 0)


		for iter in selected:

			if self.data.get_entry(iter).can_launch() == gtk.TRUE:
				launch = gtk.TRUE
				break

		else:
			launch = gtk.FALSE

		self.toolbar.button_entry_launch.set_sensitive(launch)
		self.if_menu.get_widget("<main>/Edit/Launch").set_sensitive(launch)


	def __cb_state_file(self, widget, file = None):
		"Sets various states based on current file"

		self.if_menu.get_widget("<main>/File/Revert").set_sensitive(file is not None)
		self.if_menu.get_widget("<main>/File/Lock...").set_sensitive(file is not None)

		if file is None:
			self.set_title("[New file]")

		else:
			self.set_title(os.path.basename(file))
			os.chdir(os.path.dirname(file))


	def __cb_state_find(self, widget, data = None):
		"Sets ui item states for find-related items"

		self.if_menu.get_widget("<main>/Edit/Find Next").set_sensitive(self.finder.string != "")
		self.if_menu.get_widget("<main>/Edit/Find Previous").set_sensitive(self.finder.string != "")


	def __cb_state_undo(self, widget, data = None):
		"Sets states for undo-related ui items"

		# update undo widgets
		widget = self.if_menu.get_widget("<main>/Edit/Undo")
		action = "_Undo"

		if self.undoqueue.can_undo():
			widget.get_children()[0].set_label(action + " " + self.undoqueue.get_action(revelation.data.UNDO).name)
			widget.set_sensitive(gtk.TRUE)

		else:
			widget.get_children()[0].set_label(action)
			widget.set_sensitive(gtk.FALSE)

		# update redo widgets
		widget = self.if_menu.get_widget("<main>/Edit/Redo")
		action = "_Redo"

		if self.undoqueue.can_redo():
			widget.get_children()[0].set_label(action + " " + self.undoqueue.get_action(revelation.data.REDO).name)
			widget.set_sensitive(gtk.TRUE)

		else:
			widget.get_children()[0].set_label(action)
			widget.set_sensitive(gtk.FALSE)



	# other, normal callbacks
	def __cb_keypress_tree(self, widget, data = None):
		"Handles key presses for the tree"

		# return
		if data.keyval == 65293:
			self.entry_edit()

		# insert
		elif data.keyval == 65379:
			self.entry_add()

		# delete
		elif data.keyval == 65535:
			self.entry_remove()


	def __cb_popup_tree(self, widget, menuitems):
		"Create a popup-menu for the treeview"

		# create the popup menu
		iters = self.tree.get_selected()

		if len(iters) == 0 or (len(iters) == 1 and type(self.data.get_entry(iters[0])) == revelation.entry.FolderEntry):
			menuitems.append(("/_Add Entry...", None, "Create a new entry", lambda w,d: self.entry_add(), 0, "<StockItem>", revelation.stock.STOCK_ADD))

		if len(iters) == 1:
			menuitems.append(("/_Edit", None, "Edit the selected entry", lambda w,d: self.entry_edit(), 0, "<StockItem>", revelation.stock.STOCK_EDIT))

		if len(iters) > 0:
			menuitems.append(("/Re_move", None, "Remove the selected entry", lambda w,d: self.entry_remove(), 0, "<StockItem>", revelation.stock.STOCK_REMOVE))

		for iter in iters:
			if self.data.get_entry(iter).can_launch() == gtk.TRUE:
				menuitems.append(("/_Launch", None, "Launch the selected entry", lambda w,d: self.entry_launch(), 0, "<StockItem>", revelation.stock.STOCK_LAUNCH))
				break

		clipboardmenu = []

		if len(iters) > 0:
			clipboardmenu.append(("/Cu_t", "", "Cut the selected entry to the clipboard", lambda w,d: self.clip_cut(), 0, "<StockItem>", gtk.STOCK_CUT))
			clipboardmenu.append(("/_Copy", "", "Copy the selected entry to the keyboard", lambda w,d: self.clip_copy(), 0, "<StockItem>", gtk.STOCK_COPY))

		if len(iters) < 2 and self.clipboard.has_contents():
			clipboardmenu.append(("/_Paste", "", "Paste entry from clipboard", lambda w,d: self.clip_paste(), 0, "<StockItem>", gtk.STOCK_PASTE))


		if len(clipboardmenu) > 0:
			menuitems.append(("/sep1", None, None, None, 0, "<Separator>"))
			menuitems.extend(clipboardmenu)



	# various private methods
	def __entry_find(self, parent, direction = revelation.data.SEARCH_NEXT, string = None):
		"Searches for an entry"

		if string is not None:
			self.finder.string = string

		self.finder.folders = self.config.get("search/folders")
		self.finder.casesens = self.config.get("search/casesens")
		self.finder.namedesc = self.config.get("search/namedesc")

		match = self.finder.find(self.tree.get_active(), direction)

		if match is None:
			revelation.dialog.Error(parent, "No match found", "The string you searched for did not match any entries. Try using a different search-phrase.").run()

		else:
			self.tree.select(match)


	def __file_autosave(self):
		"Autosaves the current file, due to data modification"

		if self.data.file is None or self.data.password is None:
			return

		if not self.config.get("file/autosave"):
			return

		self.file_save(self.data.file, self.data.password)


	def __file_load(self, datafile):
		"Loads data from a file into an entrystore"

		try:
			if datafile.handler is None:
				datafile.detect_type()

			datafile.check_file()

			dialog = revelation.dialog.PasswordLoad(self, datafile.file)
			entrystore = None

			while 1:

				# load datafile, ask for password if needed
				try:
					if datafile.needs_password() and datafile.password is None:
						datafile.password = dialog.run()

					entrystore = datafile.load()

				except revelation.datahandler.PasswordError:
					datafile.password = None
					revelation.dialog.Error(
						self, "Incorrect password",
						"The password you entered for the file'" + datafile.file + "' was not correct."
					).run()

				except:
					dialog.destroy()
					raise

				else:
					dialog.destroy()
					break

			return entrystore

		except revelation.datahandler.FormatError:
			self.statusbar.set_status("Open failed")
			revelation.dialog.Error(
				self, "Invalid file format",
				"The file '" + datafile.file + "' contains invalid data."
			).run()

		except revelation.entry.EntryError:
			self.statusbar.set_status("Open failed")
			revelation.dialog.Error(
				self, "Unknown data",
				"The file '" + datafile.file + "' contains unknown data. It may have been created by a future version of Revelation, try upgrading to a newer version."
			).run()

		except revelation.datahandler.VersionError:
			self.statusbar.set_status("Open failed")
			revelation.dialog.Error(
				self, "Unknown data version",
				"The file '" + datafile.file + "' has a future version number - upgrade Revelation to a more recent version to open it."
			).run()

		except revelation.io.DetectError:
			self.statusbar.set_status("Open failed")
			revelation.dialog.Error(
				self, "Filetype autodetection failed",
				"The format of the file '" + datafile.file + "' could not be detected automatically. It may still be possible to open the file, try specifying the file type manually."
			).run()

		except IOError:
			self.statusbar.set_status("Open failed")
			revelation.dialog.Error(
				self, "Unable to open file",
				"The file '" + datafile.file + "' could not be opened. Make sure that the file exists, and that you have the proper permissions to open it."
			).run()

		except revelation.CancelError:
			dialog.destroy()
			raise


	def __file_save(self, datafile):
		"Saves data to a file"

		try:
			if datafile.file != self.data.file and revelation.io.file_exists(datafile.file):
				revelation.dialog.FileOverwrite(self, datafile.file).run()

			if not datafile.needs_password():
				revelation.dialog.FileExportInsecure(self).run()

			elif datafile.password is None:
				try:
					dialog = revelation.dialog.PasswordSave(self, datafile.file)
					datafile.password = dialog.run()
					dialog.destroy()

				except revelation.CancelError:
					dialog.destroy()
					raise


			datafile.save(self.data)

			return gtk.TRUE

		except IOError:
			revelation.dialog.Error(self, "Unable to write to file", "The file '" + datafile.file + "' could not be opened for writing. Make sure that you have the proper permissions to write to it.").run()
			self.statusbar.set_status("Save failed")

			return gtk.FALSE


	def __save_changes(self, dialog):
		"Asks the user if she wants to save her changes"

		try:
			if not self.data.changed:
				return gtk.TRUE

			if dialog(self).run() == gtk.TRUE:
				return self.file_save(self.data.file, self.data.password)

			return gtk.TRUE

		except revelation.CancelError:
			return gtk.FALSE



	# public methods
	def change_password(self):
		"Changes the password of the current data file"

		try:
			dialog = revelation.dialog.PasswordChange(self, self.data.password)
			self.data.password = dialog.run()

			self.__file_autosave()
			self.statusbar.set_status("Password changed")

		except revelation.CancelError:
			self.statusbar.set_status("Password change cancelled")

		dialog.destroy()


	def clip_copy(self):
		"Copies selected entries to the clipboard"

		iters = self.data.filter_parents(self.tree.get_selected())
		self.clipboard.copy(self.data, iters)


	def clip_cut(self):
		"Cuts selected entries to the clipboard"

		iters = self.data.filter_parents(self.tree.get_selected())
		self.undoqueue.add_action(revelation.data.UNDO_ACTION_CUT, iters)
		self.clipboard.cut(self.data, iters)
		self.__file_autosave()
		self.tree.unselect_all()


	def clip_paste(self):
		"Pastes entries from the clipboard"

		if not self.clipboard.has_contents():
			return

		iters = self.clipboard.paste(self.data, self.tree.get_active())
		self.undoqueue.add_action(revelation.data.UNDO_ACTION_PASTE, iters)
		self.__file_autosave()
		self.tree.select(iters[0])


	def entry_add(self):
		"Adds an entry"

		try:
			entry = revelation.dialog.EntryEdit(self, "Add entry").run()
			iter = self.data.add_entry(self.tree.get_active(), entry)

			self.undoqueue.add_action(revelation.data.UNDO_ACTION_ADD, iter)
			self.__file_autosave()

			self.tree.select(iter)
			self.statusbar.set_status("Added entry '" + entry.name + "'")

		except revelation.CancelError:
			self.statusbar.set_status("Add entry cancelled")


	def entry_edit(self):
		"Edits an entry"

		iter = self.tree.get_active()

		if iter is None:
			return

		try:
			entry = self.data.get_entry(iter)
			dialog = revelation.dialog.EntryEdit(self, "Edit entry", entry)

			if type(entry) == revelation.entry.FolderEntry and self.data.iter_n_children(iter) > 0:
				dialog.set_typechange_allowed(gtk.FALSE)

			newentry = dialog.run()
			self.data.update_entry(iter, newentry)
			self.undoqueue.add_action(revelation.data.UNDO_ACTION_EDIT, iter, entry)

			self.__file_autosave()
			self.tree.select(iter)
			self.statusbar.set_status("Updated entry '" + newentry.name + "'")

		except revelation.CancelError:
			self.statusbar.set_status("Update entry cancelled")


	def entry_find(self):
		"Searches for an entry"

		dialog = revelation.dialog.Find(self, self.config)
		dialog.entry_phrase.set_text(self.finder.string)
		dialog.dropdown.set_type(self.finder.type)

		while 1:
			response = dialog.run()
			self.finder.string = dialog.entry_phrase.get_text()
			self.finder.type = dialog.dropdown.get_type()

			if response == revelation.dialog.RESPONSE_NEXT:
				self.__entry_find(dialog, revelation.data.SEARCH_NEXT)

			elif response == revelation.dialog.RESPONSE_PREVIOUS:
				self.__entry_find(dialog, revelation.data.SEARCH_PREV)

			else:
				dialog.destroy()
				break



	def entry_launch(self):
		"Launches an entry"

		iters = self.tree.get_selected()

		for iter in iters:
			try:
				entry = self.data.get_entry(iter)

				if entry.can_launch():
					entry.launch()

				self.statusbar.set_status("Launched entry '" + entry.name + "'")

			except revelation.entry.LaunchDataError:
				revelation.dialog.Error(self, "Missing launcher data", "The entry '" + entry.name + "' does not have all the data required to launch it.").run()

			except revelation.entry.LaunchFormatError:
				revelation.dialog.Error(self, "Invalid launcher format", "The launch command for '" + entry.typename + "' entries is invalid, please correct this in the preferences.").run()



	def entry_remove(self):
		"Removes one or more entries"

		iters = self.tree.get_selected()

		if len(iters) == 0:
			return

		entries = self.data.get_entries(iters)


		try:
			if revelation.dialog.EntryRemove(self, entries).run() != gtk.TRUE:
				raise revelation.CancelError

			if len(iters) == 1:
				statustext = "Removed entry '" + entries[0].name + "'"

			else:
				statustext = "Removed " + str(len(entries)) + " entries"

			iters = self.data.filter_parents(iters)
			self.undoqueue.add_action(revelation.data.UNDO_ACTION_REMOVE, iters)

			for iter in iters:
				self.data.remove_entry(iter)

			self.__file_autosave()
			self.tree.unselect_all()
			self.statusbar.set_status(statustext)

		except revelation.CancelError:
			self.statusbar.set_status("Remove entry cancelled")



	def file_export(self):
		"Exports data to a foreign file format"

		try:
			file, handler = revelation.dialog.ExportFileSelector(self).run()
			datafile = revelation.io.DataFile(file, handler)
			self.__file_save(datafile)
			self.statusbar.set_status("Data exported to " + datafile.file)

		except revelation.CancelError:
			self.statusbar.set_status("Export cancelled")


	def file_import(self):
		"Imports data from a foreign file"

		try:
			file, handler = revelation.dialog.ImportFileSelector(self).run()
			datafile = revelation.io.DataFile(file, handler)
			entrystore = self.__file_load(datafile)

			if entrystore is None:
				return

			iters = self.data.import_entrystore(entrystore)
			self.undoqueue.add_action(revelation.data.UNDO_ACTION_IMPORT, iters)
			self.__file_autosave()
			self.statusbar.set_status("Data imported from " + datafile.file)

		except revelation.CancelError:
			self.statusbar.set_status("Import cancelled")


	def file_lock(self):
		"Locks the current data file"

		if self.data.password is None:
			return

		iter = self.tree.get_active()
		self.tree.set_model(None)
		self.dataview.clear()
		self.statusbar.set_status("File locked")

		dialog = revelation.dialog.PasswordLock(self)

		while 1:
			if dialog.run() == self.data.password:
				break

			else:
				revelation.dialog.Error(
					dialog, "Incorrect password",
					"The password you entered was not correct, please try again."
				).run()


		dialog.destroy()

		self.tree.set_model(self.data)
		self.tree.select(iter)
		self.statusbar.set_status("File unlocked")


	def file_new(self):
		"Opens a new file"

		if not self.__save_changes(revelation.dialog.FileChangedNew):
			self.statusbar.set_status("New file cancelled")
			return

		self.data.clear()
		self.statusbar.set_status("New file created")


	def file_open(self, file = None, password = None):
		"Opens a data file"

		try:
			if not self.__save_changes(revelation.dialog.FileChangedOpen):
				raise revelation.CancelError

			if file is None:
				file = revelation.dialog.FileSelector(self, "Select file to open").run()

			datafile = revelation.io.DataFile(file, revelation.datahandler.Revelation, password)
			entrystore = self.__file_load(datafile)

			if entrystore is None:
				return

			self.data.replace_entrystore(entrystore)
			self.statusbar.set_status("Opened file " + datafile.file)


		except revelation.CancelError:
			self.statusbar.set_status("Open cancelled")


	def file_revert(self):
		"Reverts to the saved version of the file"

		if not self.__save_changes(revelation.dialog.FileChangedRevert):
			self.statusbar.set_status("Revert cancelled")
			return gtk.FALSE

		self.data.changed = gtk.FALSE
		self.file_open(self.data.file, self.data.filepassword)


	def file_save(self, file = None, password = None):
		"Saves a data file"

		try:
			if file is None:
				file = revelation.dialog.FileSelector(self, "Select file to save data to").run()

			datafile = revelation.io.DataFile(file, revelation.datahandler.Revelation, password)

			if self.__file_save(datafile) == gtk.TRUE:
				self.data.set_file(file, password)
				self.statusbar.set_status("Data saved to file " + file)

				return gtk.TRUE

		except revelation.CancelError:
			self.statusbar.set_status("Save cancelled")
			return gtk.FALSE


	def quit(self):
		"Quits the application"

		if not self.__save_changes(revelation.dialog.FileChangedQuit):
			self.statusbar.set_status("Quit cancelled")
			return gtk.FALSE

		width, height = self.get_size()
		self.config.set("view/window-width", width)
		self.config.set("view/window-height", height)

		x, y = self.get_position()
		self.config.set("view/window-position-x", x)
		self.config.set("view/window-position-y", y)

		self.config.set("view/pane-position", self.hpaned.get_position())

		gtk.mainquit()
		return gtk.TRUE


	def redo(self):
		"Redo the previously undone operation"

		if not self.undoqueue.can_redo():
			return

		action = self.undoqueue.get_action(revelation.data.REDO)
		iters = self.undoqueue.redo()

		if len(iters) > 0:
			self.tree.select(iters[0])

		else:
			self.tree.unselect_all()

		self.__file_autosave()
		self.statusbar.set_status(action.name.capitalize() + " redone")


	def run(self):
		"Run the application"

		if len(sys.argv) > 1:
			self.file_open(os.path.abspath(sys.argv[1]))

		elif self.config.get("file/autoload") and self.config.get("file/autoload_file") != "":
			self.file_open(self.config.get("file/autoload_file"))

		gtk.main()


	def undo(self):
		"Attempts to undo the previous operation"

		if not self.undoqueue.can_undo():
			return

		action = self.undoqueue.get_action()
		iters = self.undoqueue.undo()

		if len(iters) > 0:
			self.tree.select(iters[0])

		else:
			self.tree.unselect_all()

		self.__file_autosave()
		self.statusbar.set_status(action.name.capitalize() + " undone")



if __name__ == "__main__":
	try:
		Revelation().run()

	except KeyboardInterrupt:
		sys.exit(1)

