#!/usr/bin/env python

#
# Revelation 0.1.2 - a password manager for GNOME 2
# http://oss.wired-networks.net/revelation/
#
# 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, gnome.ui, revelation, os, os.path, sys

class App(gnome.ui.App):

	def __init__(self):

		# set up application
		gnome.ui.App.__init__(self, revelation.APPNAME, revelation.APPNAME)
		self.set_default_size(600, 400)
		self.set_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.connect("delete_event", self.__cb_item_quit)

		self.password = None

		# set up various subsystems and ui components
		self.__init_facilities()
		self.__init_menu()
		self.__init_toolbar()
		self.__init_statusbar()
		self.__init_mainarea()

		# set some initial states
		self.set_current_file(None)
		self.dataview.display_info()
		self.tree.select(None)

		self.if_menu.get_widget("<main>/Edit/Undo").set_sensitive(self.undoqueue.can_undo())
		self.if_menu.get_widget("<main>/Edit/Redo").set_sensitive(self.undoqueue.can_undo(revelation.data.REDO))
		self.if_menu.get_widget("<main>/View/Toolbar").set_active(gtk.TRUE)
		self.if_menu.get_widget("<main>/View/Statusbar").set_active(gtk.TRUE)


	def __init_facilities(self):

		self.icons = revelation.ui.IconFactory(self)
		self.entrytypes = revelation.data.EntryTypes()
		self.data = revelation.data.DataStore(self.entrytypes)
		self.clipboard = revelation.data.EntryClipboard()
		self.undoqueue = revelation.data.UndoQueue()

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

		self.undoqueue.connect("undo", self.__cb_undo, revelation.data.UNDO)
		self.undoqueue.connect("redo", self.__cb_undo, revelation.data.REDO)
		self.undoqueue.connect("can-undo", self.__cb_state_undo, revelation.data.UNDO)
		self.undoqueue.connect("can-redo", self.__cb_state_undo, revelation.data.REDO)


	def __init_mainarea(self):

		# main hpaned
		hpaned = gtk.HPaned()
		hpaned.set_border_width(5)
		hpaned.set_position(300)
		self.set_contents(hpaned)

		# tree
		self.tree = revelation.ui.Tree(self.data)
		self.tree.get_selection().connect("changed", self.__cb_entry_display)
		self.tree.get_selection().connect("changed", self.__cb_state_entryitems)
		self.tree.connect("button_press_event", self.__cb_popup)

		scrolledwindow = gtk.ScrolledWindow()
		scrolledwindow.add(self.tree)
		hpaned.pack1(scrolledwindow, gtk.TRUE)

		# dataview
		self.dataview = revelation.ui.DataView(self.entrytypes)
		alignment = gtk.Alignment(0.5, 0.4, 0, 0)
		alignment.add(self.dataview)
		hpaned.pack2(alignment)


	def __init_menu(self):

		menuitems = (
			("/_File",		None,			None,					None,				0,	"<Branch>"),
			("/File/_New",		"<Control>N",		"Create a new file",			self.__cb_item_file_new,	0,	"<StockItem>", gtk.STOCK_NEW),
			("/File/_Open...",	"<Control>O",		"Open a file",				self.__cb_item_file_open,	0,	"<StockItem>", gtk.STOCK_OPEN),
			("/File/_Save",		"<Control>S",		"Save data to file",			self.__cb_item_file_save,	0,	"<StockItem>", gtk.STOCK_SAVE),
			("/File/Save _As...",	"<Shift><Control>S",	"Save data to different file",		self.__cb_item_file_save_as,	0,	"<StockItem>",	gtk.STOCK_SAVE_AS),
			("/File/sep1",		None,			None,					None,				0,	"<Separator>"),
			("/File/Change _Password...",	None,		"Change password of current file",	self.__cb_item_change_password,	0,	"<StockItem>",	revelation.ui.STOCK_PASSWORD),
			("/File/sep2",		None,			None,					None,				0,	"<Separator>"),
			("/File/_Quit",		"<Control>Q",		"Quit the application",			self.__cb_item_quit,		0, 	"<StockItem>", gtk.STOCK_QUIT),

			("/_Edit",		None,			None,					None,				0,	"<Branch>"),
			("/Edit/_Add Entry...",	"Insert",		"Create a new entry",			self.__cb_item_entry_add,	0,	"<StockItem>", revelation.ui.STOCK_ADD),
			("/Edit/_Edit",		"Return",		"Edit the selected entry",		self.__cb_item_entry_edit,	0,	"<StockItem>", revelation.ui.STOCK_EDIT),
			("/Edit/Re_move",	"Delete",		"Remove the selected entry",		self.__cb_item_entry_remove,	0,	"<StockItem>", revelation.ui.STOCK_REMOVE),
			("/Edit/sep1",		None,			None,					None,				0,	"<Separator>"),
			("/Edit/_Undo",		"<Control>Z",		"Undo the last action",			self.__cb_item_undo,		0,	"<StockItem>", gtk.STOCK_UNDO),
			("/Edit/_Redo",		"<Shift><Control>Z",	"Redo the previously undone action",	self.__cb_item_redo,		0,	"<StockItem>", gtk.STOCK_REDO),
			("/Edit/sep2",		None,			None,					None,				0,	"<Separator>"),
			("/Edit/Cu_t",		"<Control>X",		"Cut the entry to the clipboard",	self.__cb_item_cut,		0,	"<StockItem>", gtk.STOCK_CUT),
			("/Edit/_Copy",		"<Control>C",		"Copy the entry to the clipboard",	self.__cb_item_copy,		0,	"<StockItem>", gtk.STOCK_COPY),
			("/Edit/_Paste",	"<Control>V",		"Paste entry from clipboard",		self.__cb_item_paste,		0,	"<StockItem>", gtk.STOCK_PASTE),

			("/_View",		None,			None,					None,				0,	"<Branch>"),
			("/View/_Toolbar",	None,			"Toggle display of the toolbar",	self.__cb_item_toolbar,		0,	"<CheckItem>"),
			("/View/_Statusbar",	None,			"Toggle display of the statusbar",	self.__cb_item_statusbar,	0,	"<CheckItem>"),

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

		self.accelgroup = gtk.AccelGroup()
		self.add_accel_group(self.accelgroup)

		self.if_menu = self.__create_itemfactory(gtk.MenuBar, self.accelgroup, menuitems)
		self.set_menus(self.if_menu.get_widget("<main>"))


	def __init_statusbar(self):

		self.statusbar = gnome.ui.AppBar(gtk.FALSE, gtk.TRUE, gnome.ui.PREFERENCES_USER)
		self.set_statusbar(self.statusbar)


	def __init_toolbar(self):

		self.toolbar = gtk.Toolbar()
		self.set_toolbar(self.toolbar)

		self.toolbar.button_new = self.toolbar.insert_stock(gtk.STOCK_NEW, "New file", None, self.__cb_item_file_new, "", -1)
		self.toolbar.button_open = self.toolbar.insert_stock(gtk.STOCK_OPEN, "Open file", None, self.__cb_item_file_open, "", -1)
		self.toolbar.button_save = self.toolbar.insert_stock(gtk.STOCK_SAVE, "Save file", None, self.__cb_item_file_save, "", -1)

		self.toolbar.append_space()

		self.toolbar.button_entry_add = self.toolbar.insert_stock(revelation.ui.STOCK_ADD, "Add a new entry", None, self.__cb_item_entry_add, "", -1)
		self.toolbar.button_entry_edit = self.toolbar.insert_stock(revelation.ui.STOCK_EDIT, "Edit the selected entry", None, self.__cb_item_entry_edit, "", -1)
		self.toolbar.button_entry_remove = self.toolbar.insert_stock(revelation.ui.STOCK_REMOVE, "Remove the selected entry", None, self.__cb_item_entry_remove, "", -1)



	# private utility functions
	def __create_itemfactory(self, widget, accelgroup, items):

		itemfactory = revelation.ui.MenuFactory(widget, accelgroup)
		itemfactory.create_items(items)
		itemfactory.connect("item-selected", self.__cb_state_menudesc, gtk.TRUE)
		itemfactory.connect("item-deselected", self.__cb_state_menudesc, gtk.FALSE)

		return itemfactory


	def __ui_menu_popup(self, data):

		# get the selected entries
		path = self.tree.get_path_at_pos(int(data.x), int(data.y))

		if path == None:
			self.tree.unselect_all()
		elif self.tree.get_selection().iter_is_selected(self.data.get_iter(path[0])) == gtk.FALSE:
			self.tree.set_cursor(path[0], path[1], gtk.FALSE)

		iters = self.tree.get_selected()


		# set up entry menu
		entrymenu = []

		if len(iters) < 2:
			entrymenu.append(("/Add Entry...", None, "Create a new entry", self.__cb_item_entry_add, 0, "<StockItem>", revelation.ui.STOCK_ADD))

		if len(iters) == 1:
			entrymenu.append(("/Edit", None, "Edit the selected entry", self.__cb_item_entry_edit, 0, "<StockItem>", revelation.ui.STOCK_EDIT))

		if len(iters) > 0:
			entrymenu.append(("/Remove", None, "Remove the selected entry", self.__cb_item_entry_remove, 0, "<StockItem>", revelation.ui.STOCK_REMOVE))

		# set up clipboard menu
		clipboardmenu = []

		if len(iters) > 0:
			clipboardmenu.append(("/Cut", "", "Cut the selected entry to the clipboard", self.__cb_item_cut, 0, "<StockItem>", gtk.STOCK_CUT))
			clipboardmenu.append(("/Copy", "", "Copy the selected entry to the keyboard", self.__cb_item_copy, 0, "<StockItem>", gtk.STOCK_COPY))

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

		# create the menu
		menuitems = entrymenu

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

		itemfactory = self.__create_itemfactory(gtk.Menu, gtk.AccelGroup(), menuitems)
		itemfactory.popup(int(data.x_root), int(data.y_root), data.button, data.get_time())


	def __ui_save_changes(self, pritext, sectext):
		"""Checks for unsaved changes, and asks to save them"""

		if self.data.changed == gtk.TRUE:

			response = revelation.dialog.Hig(
				self, pritext, sectext, gtk.STOCK_DIALOG_WARNING,
				[ [ gtk.STOCK_DELETE, gtk.RESPONSE_CLOSE, "Discard" ], [ gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL ], [ gtk.STOCK_SAVE, gtk.RESPONSE_OK ] ], 2
			).run()

			if response == gtk.RESPONSE_OK and self.file_save(self.file, self.password) == gtk.FALSE:
				return gtk.FALSE
			elif response == gtk.RESPONSE_CANCEL:
				return gtk.FALSE
			elif response == gtk.RESPONSE_CLOSE:
				pass

		return gtk.TRUE



	# callbacks for items (buttons, menu items, etc)
	def __cb_item_change_password(self, object, data):
		self.change_password()

	def __cb_item_copy(self, object, data):
		self.clip_copy(self.tree.get_selected())

	def __cb_item_cut(self, object, data):
		self.clip_cut(self.tree.get_selected())

	def __cb_item_entry_add(self, object, data):
		self.entry_add(self.tree.get_active())

	def __cb_item_entry_edit(self, object, data):
		self.entry_edit(self.tree.get_active())

	def __cb_item_entry_remove(self, object, data):
		self.entry_remove(self.tree.get_selected())

	def __cb_item_file_new(self, object, data):
		self.file_new()

	def __cb_item_file_open(self, object, data):
		self.file_open()
	
	def __cb_item_file_save(self, object, data):
		self.file_save(self.file, self.password)

	def __cb_item_file_save_as(self, object, data):
		self.file_save(None, None)

	def __cb_item_paste(self, object, data):
		self.clip_paste(self.tree.get_active())

	def __cb_item_quit(self, object, data):
		return gtk.TRUE ^ self.quit()

	def __cb_item_redo(self, object, data):
		self.undoqueue.redo()

	def __cb_item_statusbar(self, object, data):
		if self.if_menu.get_widget("<main>/View/Statusbar").get_active() == gtk.TRUE:
			self.statusbar.show()
		else:
			self.statusbar.hide()

	def __cb_item_toolbar(self, object, data):
		dock = self.get_dock_item_by_name("Toolbar")
		if self.if_menu.get_widget("<main>/View/Toolbar").get_active() == gtk.TRUE:
			dock.show()
		else:
			dock.hide()

	def __cb_item_undo(self, object, data):
		self.undoqueue.undo()



	# callbacks for updating states
	def __cb_state_clipboard(self, object):
		self.if_menu.get_widget("<main>/Edit/Paste").set_sensitive(self.clipboard.has_contents())


	def __cb_state_menudesc(self, object, item, show):
		if show == gtk.TRUE:
			self.statusbar.push(item.get_data("description"))
		else:
			self.statusbar.pop()


	def __cb_state_undo(self, object, state, method):
		if method == revelation.data.UNDO:
			widget = self.if_menu.get_widget("<main>/Edit/Undo")
			action = "Undo"
		elif method == revelation.data.REDO:
			widget = self.if_menu.get_widget("<main>/Edit/Redo")
			action = "Redo"

		widget.set_sensitive(state)
		item = widget.get_children()[0]

		if state == gtk.TRUE:
			item.set_label(action + " " + self.undoqueue.get_data(method)["name"])
		else:
			item.set_label(action)


	def __cb_state_entryitems(self, object):
		selcount = len(self.tree.get_selected())

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

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

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



	# misc callbacks
	def __cb_entry_display(self, object):
		self.entry_display(self.tree.get_active())

	def __cb_popup(self, object, data):
		if data.button != 3:
			return

		self.__ui_menu_popup(data)
		return gtk.TRUE

	def __cb_undo(self, object, data, method):
		self.undo(data["name"], data["action"], data["data"], method)



	# main application methods
	def change_password(self):
		"""Changes the password of the current file"""

		password = revelation.dialog.Password(
			self, "Enter new password",
			"Enter a new password for the current data file. The file must be saved before the new password is applied.",
			gtk.TRUE, self.password
		).run()

		if password != None:
			self.password = password
			self.data.changed = gtk.TRUE
			self.statusbar.set_status("Password changed")
		else:
			self.statusbar.set_status("Password change cancelled")


	def clip_copy(self, iters):
		"""Copies data to the clipboard"""

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


	def clip_cut(self, iters):
		"""Cuts data to the clipboard"""

		iters = self.data.filter_parents(iters)
		self.undo_add_action(self.clip_cut, iters)
		self.clipboard.cut(self.data, iters)
		self.tree.unselect_all()


	def clip_paste(self, parent, sibling = None):
		"""Pastes data from the clipboard"""

		if self.clipboard.is_empty():
			return

		if self.data.get_entry_type(parent) not in [ revelation.data.ENTRY_FOLDER, None]:
			sibling = parent
			parent = self.data.iter_parent(parent)

		iters = self.clipboard.paste(self.data, parent, sibling)
		self.undo_add_action(self.clip_paste, iters)

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


	def entry_add(self, parent):
		"""Adds an entry"""

		data = revelation.dialog.EditEntry(self, "Add entry", self.data.entrytypes).run()

		if data != None:
			iter = self.data.add_entry(parent, data)
			self.undo_add_action(self.entry_add, iter)
			self.tree.select(iter)
			self.statusbar.set_status("Added entry '" + data["name"] + "'")


	def entry_display(self, iter):
		"""Displays an entry"""

		self.dataview.display_entry(self.data.get_entry(iter))


	def entry_edit(self, iter):
		"""Edits an entry"""

		if iter == None:
			return

		data = self.data.get_entry(iter)
		dialog = revelation.dialog.EditEntry(self, "Edit entry", self.data.entrytypes, data)

		if data["type"] == revelation.data.ENTRY_FOLDER and self.data.iter_n_children(iter) > 0:
			dialog.set_typechange_allowed(gtk.FALSE)

		newdata = dialog.run()

		if newdata != None:
			self.data.update_entry(iter, newdata)
			self.undo_add_action(self.entry_edit, iter, data)
			self.tree.select(iter)
			self.statusbar.set_status("Updated entry '" + newdata["name"] + "'")


	def entry_remove(self, iters):
		"""Removes entries"""

		if not isinstance(iters, list) and iters != None:
			iters = [iters]

		if len(iters) == 0:
			return

		elif len(iters) == 1:
			data = self.data.get_entry(iters[0])

			if data["type"] == revelation.data.ENTRY_FOLDER:
				pritext = "Really remove folder '" + data["name"] + "'?"
				sectext = "By removing this folder you will also remove all accounts and folders it contains."

			else:
				pritext = "Really remove account '" + data["name"] + "'?"
				sectext = "Please confirm that you wish to remove this account."

			statustext = "Removed entry '" + data["name"] + "'"

		else:
			pritext = "Really remove the " + str(len(iters)) + " selected entries?"
			sectext = "By removing these entries you will also remove any entries they may contain."
			statustext = "Removed " + str(len(iters)) + " entries"


		if revelation.dialog.Hig(
			self, pritext, sectext, gtk.STOCK_DIALOG_WARNING,
			[ [ gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL ], [ revelation.ui.STOCK_REMOVE, gtk.RESPONSE_OK ] ]
		).run() == gtk.RESPONSE_OK:

			iters = self.data.filter_parents(iters)
			self.undo_add_action(self.entry_remove, iters)

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

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


	def file_close(self):
		"""Closes the current file, without confirmation"""

		self.data.clear()
		self.dataview.clear()
		self.set_current_file(None)
		self.password = None
		self.undoqueue.clear()


	def file_new(self):
		"""Clears all data"""

		if self.__ui_save_changes("Save changes to current file?", "You have made changes which have not been saved. If you create a new file without saving, then these changes will be discarded.") == gtk.FALSE:
			self.statusbar.set_status("New file cancelled")
			return gtk.FALSE

		self.file_close()
		self.statusbar.set_status("New file created")


	def file_open(self, file = None):
		"""Loads data from a file"""

		if self.__ui_save_changes("Save changes before opening?", "You have made changes which have not been saved. If you open another file without saving, then these changes will be discarded.") == gtk.FALSE:
			self.statusbar.set_status("Open cancelled")
			return gtk.FALSE

		# get file to open
		if file == None:
			file = revelation.dialog.FileSelector("Select file to open").run()

			if file == None:
				self.statusbar.set_status("Open cancelled")
				return gtk.FALSE

		# attempt to read data
		data = revelation.data.file_read(file)
		if data == None:
			revelation.dialog.Error(self, "Unable to open file", "The file '" + file + "' could not be opened. Make sure that the file exists, and that you have the proper permissions to open it.").run()
			self.statusbar.set_status("Open failed")
			return gtk.FALSE

		# fetch password
		password = revelation.dialog.Password(self, "Enter file password", "This file is encrypted. Please enter the file password to open it.").run()
		if password == None:
			self.statusbar.set_status("Open cancelled")
			return gtk.FALSE

		# decrypt data
		data = revelation.data.decrypt(data, password)
		if data[0:5] != "<?xml":
			revelation.dialog.Error(self, "Wrong password", "The password you entered is wrong, or the file is invalid.").run()
			self.statusbar.set_status("Open failed")
			return gtk.FALSE

		# import xml
		self.file_close()
		if self.data.import_xml(data) == gtk.FALSE:
			self.statusbar.set_status("Open failed")
			revelation.dialog.Error(self, "Invalid data", "The file contained unknown data and could not be loaded.").run()
			return gtk.FALSE

		# update misc data
		self.data.changed = gtk.FALSE
		self.set_current_file(file)
		self.password = password
		self.statusbar.set_status("Opened file " + file)

		return gtk.TRUE


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

		# get file to write to
		if file == None:
			file = revelation.dialog.FileSelector("Select file to save data to").run()

			if file == None:
				self.statusbar.set_status("Save cancelled")
				return gtk.FALSE

			# check if file exists, and ask for overwrite confirmation
			if os.access(file, os.F_OK) == 1:
				if revelation.dialog.Hig(
					self, "Overwrite existing file?",
					"The file '" + file + "' already exists. If you choose to overwrite the file, its contents will be lost.",
					gtk.STOCK_DIALOG_WARNING, [ [ gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL ], [ gtk.STOCK_SAVE_AS, gtk.RESPONSE_OK, "Overwrite" ] ]
				).run() == gtk.RESPONSE_CANCEL:
					self.statusbar.set_status("Save cancelled")
					return gtk.FALSE
				

		# get password if not given
		if password == None:
			password = revelation.dialog.Password(
				self, "Enter file password",
				"Please enter a password which will be used to encrypt the file. You will need this password to open the file at a later time.",
				gtk.TRUE
			).run()

			if password == None:
				self.statusbar.set_status("Save cancelled")
				return gtk.FALSE


		# write data to file
		xml = self.data.export_xml()
		cipher = revelation.data.encrypt(xml, password)

		if revelation.data.file_write(file, cipher) == gtk.FALSE:
			self.statusbar.set_status("Unable to save data to file " + file)
			revelation.dialog.Error(self, "Unable to save data", "Could not save data to file '" + file + "', make sure you have proper permissions to write to it").run()
			return gtk.FALSE

		# update misc data
		self.data.changed = gtk.FALSE
		self.set_current_file(file)
		self.password = password

		self.statusbar.set_status("Data saved to file " + file)

		return gtk.TRUE


	def quit(self):
		"""Quits the application"""

		if self.__ui_save_changes("Save changes before quitting?", "You have made changes which have not been saved. If you quit without saving, then these changes will be discarded.") == gtk.FALSE:
			return gtk.FALSE

		gtk.mainquit()
		return gtk.TRUE


	def set_current_file(self, file):
		"""stores the name of the current file"""

		self.file = file

		if file == None:
			self.set_title("[New file] - " + revelation.APPNAME)
		else:
			self.set_title(os.path.basename(file) + " - " + revelation.APPNAME)
			os.chdir(os.path.dirname(file))


	def undo(self, name, action, data, method = revelation.data.UNDO):
		"""Undoes the previous action"""

		iters = []

		# handle add and paste actions
		if action == self.entry_add or action == self.clip_paste:

			if method == revelation.data.UNDO:
				for item in data:
					item["iter"] = self.data.get_iter(item["path"])

				for item in data:
					self.data.remove_entry(item["iter"])

			elif method == revelation.data.REDO:
				for item in data:
					newiters = self.data.import_entrystore_before(item["data"], self.data.get_iter(item["parent"]), self.data.get_iter(item["path"]))
					iters.extend(newiters)


		# handle remove and cut actions
		elif action == self.entry_remove or action == self.clip_cut:

			if method == revelation.data.UNDO:
				for item in data:
					newiters = self.data.import_entrystore_before(item["data"], self.data.get_iter(item["parent"]), self.data.get_iter(item["path"]))
					iters.extend(newiters)


			elif method == revelation.data.REDO:
				for item in data:
					item["iter"] = self.data.get_iter(item["path"])

				for item in data:
					self.data.remove_entry(item["iter"])


		# handle edit action
		elif action == self.entry_edit:

			iter = self.data.get_iter(data[0]["path"])
			iters.append(iter)

			if method == revelation.data.UNDO:
				self.data.update_entry(iter, data[0]["predata"])

			elif method == revelation.data.REDO:
				self.data.update_entry(iter, data[0]["data"])


		# update status
		if method == revelation.data.UNDO:
			self.statusbar.set_status(name.capitalize() + " undone")
		elif method == revelation.data.REDO:
			self.statusbar.set_status(name.capitalize() + " redone")

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


	def undo_add_action(self, action, iters, extradata = None):
		"""Adds action data to the undo queue"""

		if not isinstance(iters, list) and iters != None:
			iters = [iters]

		data = []
		name = {
			self.entry_add		: "Add Entry",
			self.entry_remove	: "Remove Entry",
			self.entry_edit		: "Edit Entry",
			self.clip_cut		: "Cut",
			self.clip_paste		: "Paste"
		}[action]


		for iter in iters:

			item = ({
				"path"		: self.data.get_path(iter),
				"parent"	: self.data.get_path(self.data.iter_parent(iter))
			})

			if action in [ self.entry_add, self.entry_remove, self.clip_cut, self.clip_paste ]:
				item["data"] = self.data.export_entrystore([iter])

			elif action == self.entry_edit:
				item["data"] = self.data.get_entry(iter)
				item["predata"] = extradata

			data.append(item)

		self.undoqueue.add_action(name, action, data)



if __name__ == "__main__":
	program = gnome.init(revelation.APPNAME, revelation.VERSION)
	app = App()
	app.show_all()

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

	gtk.main()

