#!/usr/bin/env python # # Revelation 0.3.2 - a password manager for GNOME 2 # http://oss.codepoet.no/revelation/ # $Id$ # # 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): 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_toolbar() self.__init_menu() 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.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, ""), ("/File/_New", "N", "Create a new file", lambda w,d: self.file_new(), 0, "", gtk.STOCK_NEW), ("/File/_Open...", "O", "Open a file", lambda w,d: self.file_open(), 0, "", gtk.STOCK_OPEN), ("/File/sep1", None, None, None, 0, ""), ("/File/_Save", "S", "Save data to file", lambda w,d: self.file_save(self.data.file, self.data.password), 0, "", gtk.STOCK_SAVE), ("/File/Save _As...", "S", "Save data to different file", lambda w,d: self.file_save(), 0, "", gtk.STOCK_SAVE_AS), ("/File/_Revert", None, "Revert to the saved copy of the file", lambda w,d: self.file_revert(), 0, "", gtk.STOCK_REVERT_TO_SAVED), ("/File/sep2", None, None, None, 0, ""), ("/File/Change _Password...", None, "Change password of current file", lambda w,d: self.change_password(), 0, "", revelation.stock.STOCK_PASSWORD), ("/File/_Lock...", "L", "Lock the current data file", lambda w,d: self.file_lock(), 0, "", revelation.stock.STOCK_LOCK), ("/File/sep3", None, None, None, 0, ""), ("/File/_Import...", None, "Import data from a foreign file", lambda w,d: self.file_import(), 0, "", revelation.stock.STOCK_IMPORT), ("/File/_Export...", None, "Export data to a different format", lambda w,d: self.file_export(), 0, "", revelation.stock.STOCK_EXPORT), ("/File/sep4", None, None, None, 0, ""), ("/File/_Close", "W", "Close the application", lambda w,d: self.quit(), 0, "", gtk.STOCK_CLOSE), ("/File/_Quit", "Q", "Quit the application", lambda w,d: self.quit(), 0, "", gtk.STOCK_QUIT), ("/_Edit", None, None, None, 0, ""), ("/Edit/_Add Entry...", "Insert", "Create a new entry", lambda w,d: self.entry_add(), 0, "", revelation.stock.STOCK_ADD), ("/Edit/_Edit", "Return", "Edit the selected entry", lambda w,d: self.entry_edit(), 0, "", revelation.stock.STOCK_EDIT), ("/Edit/Re_move", "Delete", "Remove the selected entry", lambda w,d: self.entry_remove(), 0, "", revelation.stock.STOCK_REMOVE), ("/Edit/sep1", None, None, None, 0, ""), ("/Edit/_Undo", "Z", "Undo the last action", lambda w,d: self.undo(), 0, "", gtk.STOCK_UNDO), ("/Edit/_Redo", "Z", "Redo the previously undone action", lambda w,d: self.redo(), 0, "", gtk.STOCK_REDO), ("/Edit/sep2", None, None, None, 0, ""), ("/Edit/Cu_t", "X", "Cut the entry to the clipboard", lambda w,d: self.clip_cut(), 0, "", gtk.STOCK_CUT), ("/Edit/_Copy", "C", "Copy the entry to the clipboard", lambda w,d: self.clip_copy(), 0, "", gtk.STOCK_COPY), ("/Edit/_Paste", "V", "Paste entry from clipboard", lambda w,d: self.clip_paste(), 0, "", gtk.STOCK_PASTE), ("/Edit/sep3", None, None, None, 0, ""), ("/Edit/_Find...", "F", "Search for an entry", lambda w,d: self.entry_find(), 0, "", gtk.STOCK_FIND), ("/Edit/Find Ne_xt", "G", "Find the next search match", lambda w,d: self.__entry_find(self, revelation.data.SEARCH_NEXT), 0, ""), ("/Edit/Find Pre_vious", "G", "Find the previous search match", lambda w,d: self.__entry_find(self, revelation.data.SEARCH_PREV), 0, ""), ("/Edit/sep4", None, None, None, 0, ""), ("/Edit/_Select All", "A", "Select all entries", lambda w,d: self.tree.select_all(), 0, ""), ("/Edit/_Deselect All", "A", "Deselect all entries", lambda w,d: self.tree.unselect_all(), 0, ""), ("/Edit/sep5", None, None, None, 0, ""), ("/Edit/Prefere_nces", None, "Edit preferences", lambda w,d: revelation.dialog.Preferences(self, self.config).run(), 0, "", gtk.STOCK_PREFERENCES), ("/_View", None, None, None, 0, ""), ("/View/_Toolbar", None, "Toggle display of the toolbar", None, 0, ""), ("/View/_Statusbar", None, "Toggle display of the statusbar", None, 0, ""), ("/View/sep1", None, None, None, 0, ""), ("/View/Show _Passwords", "P", "Show passwords", None, 0, ""), ("/_Help", None, None, None, 0, ""), ("/Help/_Homepage", None, "Visit the Revelation homepage", lambda w,d: gnome.url_show(revelation.URL), 0, "", gtk.STOCK_HOME), ("/Help/_About", None, "Show info about this application", lambda w,d: revelation.dialog.About(self).run(), 0, "", "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("
/Edit/Find Next").set_sensitive(gtk.FALSE) self.if_menu.get_widget("
/Edit/Find Previous").set_sensitive(gtk.FALSE) self.if_menu.get_widget("
/Edit/Undo").set_sensitive(gtk.FALSE) self.if_menu.get_widget("
/Edit/Redo").set_sensitive(gtk.FALSE) self.show_all() self.config.bind_widget("view/passwords", self.if_menu.get_widget("
/View/Show Passwords")) self.config.bind_widget("view/statusbar", self.if_menu.get_widget("
/View/Statusbar")) self.config.bind_widget("view/toolbar", self.if_menu.get_widget("
/View/Toolbar")) 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()) # config callbacks 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("
/Edit/Paste").set_sensitive(self.clipboard.has_contents()) def __cb_state_entry(self, widget, data = None): "Sets state for entry-dependent ui items" selcount = len(self.tree.get_selected()) self.toolbar.button_entry_add.set_sensitive(selcount < 2) self.if_menu.get_widget("
/Edit/Add Entry...").set_sensitive(selcount < 2) self.if_menu.get_widget("
/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("
/Edit/Edit").set_sensitive(selcount == 1) self.toolbar.button_entry_remove.set_sensitive(selcount > 0) self.if_menu.get_widget("
/Edit/Remove").set_sensitive(selcount > 0) self.if_menu.get_widget("
/Edit/Cut").set_sensitive(selcount > 0) self.if_menu.get_widget("
/Edit/Copy").set_sensitive(selcount > 0) def __cb_state_file(self, widget, file = None): "Sets various states based on current file" self.if_menu.get_widget("
/File/Revert").set_sensitive(file is not None) self.if_menu.get_widget("
/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("
/Edit/Find Next").set_sensitive(self.finder.string != "") self.if_menu.get_widget("
/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("
/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("
/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_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 self.data.get_entry(iters[0]).type == revelation.entry.ENTRY_FOLDER): menuitems.append(("/_Add Entry...", None, "Create a new entry", lambda w,d: self.entry_add(), 0, "", revelation.stock.STOCK_ADD)) if len(iters) == 1: menuitems.append(("/_Edit", None, "Edit the selected entry", lambda w,d: self.entry_edit(), 0, "", revelation.stock.STOCK_EDIT)) if len(iters) > 0: menuitems.append(("/Re_move", None, "Remove the selected entry", lambda w,d: self.entry_remove(), 0, "", revelation.stock.STOCK_REMOVE)) clipboardmenu = [] if len(iters) > 0: clipboardmenu.append(("/Cu_t", "", "Cut the selected entry to the clipboard", lambda w,d: self.clip_cut(), 0, "", gtk.STOCK_CUT)) clipboardmenu.append(("/_Copy", "", "Copy the selected entry to the keyboard", lambda w,d: self.clip_copy(), 0, "", 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, "", gtk.STOCK_PASTE)) if len(clipboardmenu) > 0: menuitems.append(("/sep1", None, None, None, 0, "")) menuitems.extend(clipboardmenu) # various private methods def __entry_find(self, parent, direction = revelation.data.SEARCH_NEXT): "Searches for an entry" 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 entry.type == revelation.entry.ENTRY_FOLDER 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_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__": Revelation().run()