diff options
Diffstat (limited to 'lang/py3-pyme/examples/pygpa.py')
-rwxr-xr-x | lang/py3-pyme/examples/pygpa.py | 1457 |
1 files changed, 1457 insertions, 0 deletions
diff --git a/lang/py3-pyme/examples/pygpa.py b/lang/py3-pyme/examples/pygpa.py new file mode 100755 index 00000000..341a87cb --- /dev/null +++ b/lang/py3-pyme/examples/pygpa.py @@ -0,0 +1,1457 @@ +#!/usr/bin/env python3 +# $Id$ +# Copyright (C) 2005,2008 Igor Belyi <[email protected]> +# +# 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 gtk, gobject, gtk.glade +import gettext +import time, sys, os +from pyme import errors, core +from pyme.core import Context, Data, pubkey_algo_name +from pyme.constants import validity, status, keylist, sig, sigsum + +# Enable internationalization using gpa translation pages. +gettext.install('gpa', None, 1) +gtk.glade.bindtextdomain('gpa') +gtk.glade.textdomain('gpa') + +# Thanks to Bernhard Reiter for pointing out the following: +# gpgme_check_version() necessary for initialisation according to +# gpgme 1.1.6 and this is not done automatically in pyme-0.7.0 +print("gpgme version:", core.check_version()) + +# Helper functions to convert non-string data into printable strings +def sec2str(secs, empty="_(Unknown)"): + "Convert seconds since 1970 into mm/dd/yy string" + if secs > 0: return time.strftime("%m/%d/%y", time.localtime(secs)) + elif secs == 0: return empty + else: return "" + +trusts = { + validity.UNDEFINED: "Unknown", + validity.NEVER: "Never", + validity.MARGINAL: "Marginal", + validity.FULL: "Full", + validity.ULTIMATE: "Ultimate" + } + +def validity2str(valid): + "Convert trust integer into a human understandable string" + if valid in trusts: return _("%s" % trusts[valid]) + else: return _("Unknown") + +def keyvalid2str(key): + "Create a string representing validity of a key" + if key.owner_trust==validity.ULTIMATE: + return _("Fully Valid") + if key.revoked: return _("Revoked") + if key.expired: return _("Expired") + if key.disabled: return _("Disabled") + if not key.uids or key.uids[0].invalid: return _("Incomplete") + return _("Unknown") + +def subvalid2str(subkey): + "Create a string representing validity of a subkey" + if subkey.revoked: return _("Revoked") + if subkey.expired: return _("Expired") + if subkey.disabled: return _("Disabled") + if subkey.invalid: return _("Unsigned") + return _("Valid") + +def signstat2str(sig): + "Create a string representing validity of a signature" + status = _("Unknown") + if sig.status == 1: status = _("Valid") + elif sig.status == 2: status = _("Bad") + if sig.expired: status = _("Expired") + elif sig.revoked: status = _("Revoked") + return status + +def sigsum2str(summary): + if summary & sigsum.VALID: return _("Valid") + if summary & sigsum.RED: return _("Bad") + if summary & sigsum.KEY_MISSING: return _("Unknown Key") + if summary & sigsum.KEY_REVOKED: return _("Revoked Key") + if summary & sigsum.KEY_EXPIRED: return _("Expired Key") + return _("Key NOT valid") + +def class2str(cls): + "Convert signature class integer into a human understandable string" + if cls==0x10: return _("Generic") + if cls==0x11: return _("Persona") + if cls==0x12: return _("Casual") + if cls==0x13: return _("Positive") + return _("Unknown") + +def algo2str(algo): + "Convert algorithm integer into a human understandable string" + return pubkey_algo_name(algo) + +def fpr2str(fpr): + "Convert fpr string in a sparsely spaced string for easy reading" + result = [] + while fpr: + result.append(fpr[:4]) + fpr = fpr[4:] + return " ".join(result) + +# Helper functions for tasks unrelated to any class +def obj2model(objs, columns): + "Create a model from the obj (key, subkey, or signature) using columns" + model = gtk.ListStore(*[x.ctype for x in columns]) + for obj in objs: + model.append([x.cfunc(obj) for x in columns]) + return model + +def labels2table(key_labels): + "Create a gtk.Table from an array of 2-tuples of strings" + table = gtk.Table(len(key_labels), 2) + for i, row in enumerate(key_labels): + if len(row) != 2: + raise ValueError("Unexpected number of rows in labels2table call") + label1 = gtk.Label(row[0]) + label1.set_alignment(1.0, 0.5) + label2 = gtk.Label(row[1]) + label2.set_alignment(0.0, 0.5) + label2.set_padding(5, 0) + table.attach(label1, 0, 1, i, i+1, gtk.FILL, gtk.FILL) + table.attach(label2, 1, 2, i, i+1, gtk.FILL, gtk.FILL) + return table + +status2str = {} +for name in dir(status): + if not name.startswith('__') and name != "util": + status2str[getattr(status, name)] = name + +def editor_func(status, args, val_dict): + prompt = "%s %s" % (val_dict["state"], args) + if prompt in val_dict: + val_dict["state"] = val_dict[prompt][0] + return val_dict[prompt][1] + elif args and "ignore %s" % status2str[status] not in val_dict: + for error in ["error %s" % status2str[status], "error %s" % prompt]: + if error in val_dict: + raise errors.GPGMEError(val_dict[error]) + sys.stderr.write(_("Unexpected status and prompt in editor_func: " + + "%s %s\n") % (status2str[status], prompt)) + raise EOFError() + return "" + +common_dict = { + "state": "start", + "quit keyedit.save.okay": ("save", "Y"), + "ignore NEED_PASSPHRASE": None, + "ignore NEED_PASSPHRASE_SYM": None, + "ignore BAD_PASSPHRASE": None, + "ignore USERID_HINT": None + } + +def change_key_expire(context, key, date): + "Change key's expiration date to date" + val_dict = common_dict.copy() + val_dict.update({ + "start keyedit.prompt": ("expire", "expire"), + "expire keygen.valid": ("date", date), + "date keyedit.prompt": ("quit", "quit") + }) + out = Data() + context.op_edit(key, editor_func, val_dict, out) + +def change_key_trust(context, key, new_trust): + "Change key's trust to new_trust" + val_dict = common_dict.copy() + val_dict.update({ + "start keyedit.prompt": ("trust", "trust"), + "trust edit_ownertrust.value": ("value", "%d" % new_trust), + "value edit_ownertrust.set_ultimate.okay": ("value", "Y"), + "value keyedit.prompt": ("quit", "quit") + }) + out = Data() + context.op_edit(key, editor_func, val_dict, out) + +def sign_key(context, key, sign_key, local): + "Sign key using sign_key. Signature is exportable if local is False" + # Values copied from <gpg-error.h> + GPG_ERR_CONFLICT = 70 + GPG_ERR_UNUSABLE_PUBKEY = 53 + val_dict = common_dict.copy() + val_dict.update({ + "start keyedit.prompt": ("sign", (local and "lsign") or "sign"), + "sign keyedit.sign_all.okay": ("sign", "Y"), + "sign sign_uid.expire": ("sign", "Y"), + "sign sign_uid.class": ("sign", "0"), + "sign sign_uid.okay": ("okay", "Y"), + "okay keyedit.prompt": ("quit", "quit"), + "error ALREADY_SIGNED": GPG_ERR_CONFLICT, + "error sign keyedit.prompt": GPG_ERR_UNUSABLE_PUBKEY + }) + out = Data() + context.signers_clear() + context.signers_add(sign_key) + context.op_edit(key, editor_func, val_dict, out) + +def trigger_change_password(context, key): + "Trigger sequence of passphrase_cb callbacks to change password of the key" + val_dict = common_dict.copy() + val_dict.update({ + "start keyedit.prompt": ("passwd", "passwd"), + "passwd keyedit.prompt": ("quit", "quit") + }) + out = Data() + context.op_edit(key, editor_func, val_dict, out) + +# Helper classes whose instances are used in the major PyGpa class +class KeyInfo: + """Helper class to represent key information in different views. + If KeyInfo instance is initialized with an integer as a key the views + correspond to a state when multiple or no keys are selected""" + def __init__(self, key, secret=None): + self.key = key + self.secret = secret + + def key_print_labels(self, fpr=False): + "Create an array of 2-tuples for 'User Name' and 'Key ID' fields" + labels = [] + if type(self.key) != int: + if self.key.uids: + labels.append((_("User Name:"), self.key.uids[0].uid)) + for uid in self.key.uids[1:]: + labels.append(("", uid.uid)) + if fpr: + labels += [(_("Fingerprint:"), fpr2str(self.key.subkeys[0].fpr))] + else: + labels += [(_("Key ID:"), self.key.subkeys[0].keyid[-8:])] + return labels + + def key_expires_label(self): + return sec2str(self.key.subkeys[0].expires,_("never expires")) + + def details(self): + "Create a widget for 'Details' notebook tab" + if type(self.key) == int: + if self.key: + details=gtk.Label(_("%d keys selected") % self.key) + else: + details=gtk.Label(_("No keys selected")) + details.set_alignment(0.5, 0) + return details + + if self.secret: + header = _("The key has both a private and a public part") + else: + header = _("The key has only a public part") + key_info_labels = [("", header)] + + if self.key.can_certify: + if self.key.can_sign: + if self.key.can_encrypt: + ability = _("The key can be used for certification, " + + "signing and encryption.") + else: + ability = _("The key can be used for certification and " + + "signing, but not for encryption.") + else: + if self.key.can_encrypt: + ability = _("The key can be used for certification and " + + "encryption.") + else: + ability = _("The key can be used only for certification.") + else: + if self.key.can_sign: + if self.key.can_encrypt: + ability = _("The key can be used only for signing and " + + "encryption, but not for certification.") + else: + ability = _("The key can be used only for signing.") + else: + if self.key.can_encrypt: + ability = _("The key can be used only for encryption.") + else: + ability = _("This key is useless.") + key_info_labels.append(("", ability)) + + key_info_labels += self.key_print_labels() + [ + (_("Fingerprint:"), fpr2str(self.key.subkeys[0].fpr)), + (_("Expires at:"), self.key_expires_label()), + (_("Owner Trust:"), validity2str(self.key.owner_trust)), + (_("Key Validity:"), keyvalid2str(self.key)), + (_("Key Type:"), _("%s %u bits") % \ + (algo2str(self.key.subkeys[0].pubkey_algo),self.key.subkeys[0].length)), + (_("Created at:"), sec2str(self.key.subkeys[0].timestamp)) + ] + + return labels2table(key_info_labels) + + def sign_model(self): + "Create a model for ComboBox of uids in 'Signatures' notebook tab" + model = gtk.ListStore(str, gtk.ListStore) + if type(self.key) != int: + for uid in self.key.uids: + model.append([uid.uid, obj2model(uid.signatures,sign_columns)]) + return model + + def subkey_model(self): + "Create a model for TreeView in 'Subkeys' notebook tab" + if type(self.key) == int: + return gtk.ListStore(*[x.ctype for x in subkey_columns]) + else: + return obj2model(self.key.subkeys, subkey_columns) + +class Column: + "Helper class to represent a column in a TreeView" + def __init__(self, name, ctype, cfunc, detail=False): + """Column(name, ctype, cfunc): + name - Name to use as a column header + ctype - type to use in a model definition for this column + cfunc - function retrieving column's infromation from an object + detail- indicate if it's a detail visible only in detailed view""" + self.name = name + self.ctype = ctype + self.cfunc = cfunc + self.detail = detail + +# Columns for the list of keys which can be used as default +def_keys_columns = [ + Column(_("Key ID"), str, lambda x,y: x.subkeys[0].keyid[-8:]), + Column(_("User Name"), str, + lambda x,y: (x.uids and x.uids[0].uid) or _("[Unknown user ID]")), + Column(None, gobject.TYPE_PYOBJECT, lambda x,y: KeyInfo(x,y)) + ] + +# Columns for the list of all keys in the keyring +keys_columns = [ + Column("", str, lambda x,y: (y and "sec") or "pub"), + def_keys_columns[0], + Column(_("Expiry Date"), str, + lambda x,y: sec2str(x.subkeys[0].expires, _("never expires")), True), + Column(_("Owner Trust"),str,lambda x,y:validity2str(x.owner_trust),True), + Column(_("Key Validity"), str, lambda x,y: keyvalid2str(x), True) + ] + def_keys_columns[1:] + +# Columns for the list of signatures on a uid +sign_columns = [ + Column(_("Key ID"), str, lambda x: x.keyid[-8:]), + Column(_("Status"), str, lambda x: signstat2str(x)), + Column(_("Level"), str, lambda x: class2str(x.sig_class)), + Column(_("Local"), type(True), lambda x: x.exportable==0), + Column(_("User Name"), str, lambda x: x.uid or _("[Unknown user ID]")) + ] + +# Columns for the list of subkeys +subkey_columns = [ + Column(_("Subkey ID"), str, lambda x: x.keyid[-8:]), + Column(_("Status"), str, lambda x: subvalid2str(x)), + Column(_("Algorithm"), str, lambda x: algo2str(x.pubkey_algo)), + Column(_("Size"), str, lambda x: _("%u bits") % x.length), + Column(_("Expiry Date"), str, + lambda x: sec2str(x.expires, _("never expires"))), + Column(_("Can sign"), type(True), lambda x: x.can_sign), + Column(_("Can certify"), type(True), lambda x: x.can_certify), + Column(_("Can encrypt"), type(True), lambda x: x.can_encrypt), + Column(_("Can authenticate"), type(True), lambda x: x.can_authenticate) + ] + +file_columns = [ + Column(_("File"), str, lambda x: x) + ] + +class PyGpa: + "Major class representing PyGpa application" + def popup(self, dialog, parent=None, title=None): + "Common way to popup a dialog defined in Glade" + dialog.set_transient_for(parent or self.main_window) + if title: dialog.set_title(title) + result = dialog.run() + dialog.hide() + return result + + def file_popup(self, dialog, parent=None, title=None): + return self.popup(dialog, parent or self.filemanager_window, title) + + def error_message(self, text, parent=None, title=_("Warning")): + "Pop up an error message dialog" + if type(text) == int: + text = errors.GPGMEError(text).getstring() + title = "GPGME error" + elif isinstance(text, errors.GPGMEError): + text = text.getstring() + title = "GPGME error" + self.error_label.set_text(text) + self.popup(self.error_dialog, parent, title) + + def file_error_message(self, text, parent=None, title=_("Warning")): + self.error_message(text, parent or self.filemanager_window, title) + + def info_message(self, text, parent=None, title=_("Information")): + "Pop up an information dialog" + self.info_label.set_text(text) + self.popup(self.info_dialog, parent, title) + + def yesno_message(self, text, parent=None): + "Pop up a dialog requiring yes/no answer" + self.yesno_label.set_text(text) + return self.popup(self.yesno_dialog, parent, + _("Warning")) == gtk.RESPONSE_YES + + def on_uid_list_changed(self, uid_list): + "this callback is called when uid selection is changed" + index = uid_list.get_active() + if index == -1: + self.sign_treeview.set_model(KeyInfo(0).sign_model()) + else: + self.sign_treeview.set_model(uid_list.get_model()[index][1]) + + def get_selected_keys(self, treeview=None): + "Helper function to get all selected rows in a treeview" + if not treeview: + treeview = self.keys_treeview + model, rows = treeview.get_selection().get_selected_rows() + return [model[path] for path in rows] + + def on_keys_changed(self, keys_treeview): + "this callback is called when key selection is changed" + selection = keys_treeview.get_selection() + count = selection.count_selected_rows() + if count == 1: + key_info = self.get_selected_keys()[0][-1] + else: + key_info = KeyInfo(count) + + self.update_menu(key_info) + + # Update Details tab of the notebook + old_child = self.details_view.get_child() + if old_child: self.details_view.remove(old_child) + self.details_view.add(key_info.details()) + self.details_view.show_all() + + # Update Subkeys tab of the notebook + self.subkeys_treeview.set_model(key_info.subkey_model()) + + # Update Signatures tab of the notebook + sign_model = key_info.sign_model() + self.uid_list.set_model(sign_model) + if len(sign_model) < 2: + self.uid_list_box.hide() + else: + self.uid_list_box.show_all() + self.uid_list.set_active(0) + self.on_uid_list_changed(self.uid_list) + + def on_keys_button_press(self, obj, event): + "callback on a mouse press in the keys_treeview" + if event.button == 3: + self.popup_menu.popup(None, None, None, event.button, event.time) + return True + return False + + def create_popup_menu(self): + "create the popup menu shown on right mouse click" + self.items = [ + (gtk.ImageMenuItem(gtk.STOCK_COPY), self.on_copy_activate), + (gtk.ImageMenuItem(gtk.STOCK_PASTE), self.on_paste_activate), + (gtk.ImageMenuItem(gtk.STOCK_DELETE), self.on_delete_activate), + (gtk.SeparatorMenuItem(), None), + (gtk.MenuItem(_("_Sign Keys...")), self.on_sign_keys_activate), + (gtk.MenuItem(_("Set _Owner Trust...")), + self.on_set_owner_trust_activate), + (gtk.MenuItem(_("_Edit Private Key...")), + self.on_edit_private_key_activate), + (gtk.SeparatorMenuItem(), None), + (gtk.MenuItem(_("E_xport Keys...")), self.on_export_keys_activate) + ] + self.popup_menu = gtk.Menu() + for item, callback in self.items: + if callback: item.connect("activate", callback) + self.popup_menu.append(item) + self.popup_menu.show_all() + + def update_menu(self, key_info): + "update sensitivity of menu items depending on what keys are selected" + # copy, delete, sign, trust, edit, export + if key_info.secret == None: + if key_info.key: # more than one key selected + values = ( True, True, True, False, False, True) + else: # no keys selected + values = (False, False, False, False, False, False) + elif key_info.secret: + if key_info.key == self.default_key: # default key seleted + values = ( True, True, False, True, True, True) + else: # secret (not default) key selected + values = ( True, True, True, True, True, True) + else: # public key selected + values = ( True, True, True, True, False, True) + + for w,v in zip((self.copy, self.delete, self.sign_keys, + self.set_owner_trust, self.edit_private_key, + self.export_keys), values): + w.set_sensitive(v) + for w,v in zip((self.items[0][0], self.items[2][0], self.items[4][0], + self.items[5][0], self.items[6][0], self.items[8][0]), + values): + w.set_sensitive(v) + + def setup_columns(self): + "Helper function to setup columns of different treeviews" + for treeview, columns in \ + [(self.keys_treeview, keys_columns), + (self.sign_treeview, sign_columns), + (self.subkeys_treeview, subkey_columns), + (self.def_keys_treeview, def_keys_columns), + (self.sign_with_keys_treeview, def_keys_columns), + (self.encrypt_with_keys_treeview, def_keys_columns), + (self.files_treeview, file_columns)]: + for index, item in enumerate([x for x in columns if x.name!=None]): + if item.ctype == str: + renderer = gtk.CellRendererText() + attrs = {"text": index} + else: + renderer = gtk.CellRendererToggle() + attrs = {"active": index} + column = treeview.insert_column_with_attributes( + index, item.name, renderer, **attrs) + column.set_sort_column_id(index) + column.set_visible(not item.detail) + + for index,item in enumerate([x for x in keys_columns if x.name!=None]): + if item.name and not item.detail: + renderer = gtk.CellRendererText() + column = gtk.TreeViewColumn(item.name, renderer, text=index) + column.set_sort_column_id(index) + self.encrypt_for_keys_treeview.append_column(column) + + for treeview in [self.encrypt_with_keys_treeview, self.keys_treeview, + self.encrypt_for_keys_treeview, self.files_treeview, + self.sign_with_keys_treeview]: + treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) + self.def_keys_treeview.get_selection().set_mode(gtk.SELECTION_SINGLE) + + cell = gtk.CellRendererText() + self.uid_list.pack_start(cell, True) + self.uid_list.add_attribute(cell, 'text', 0) + + model = gtk.ListStore(str, str) + for lines in [(_("days"), "d"), (_("weeks"), "w"), + (_("months"), "m"), (_("years"), "y")]: + model.append(lines) + self.new_expire_unit_combo.set_model(model) + self.new_expire_unit_combo.child.set_editable(False) + self.new_algorithm_combo.child.set_editable(False) + + self.files_treeview.set_model(gtk.ListStore(str)) + + def setup_default_views(self): + "Setup initial values for different views" + self.update_default_keys() + self.on_advanced_mode_toggled(self.advanced_mode_rb) + self.create_popup_menu() + self.on_keys_changed(self.keys_treeview) + + def load_keys(self): + "Download keys from the keyring" + context = Context() + sec_keys = {} + for key in context.op_keylist_all(None, 1): + sec_keys[key.subkeys[0].fpr] = 1 + model = gtk.ListStore(*[x.ctype for x in keys_columns]) + encrypt_model = gtk.ListStore(*[x.ctype for x in keys_columns]) + context.set_keylist_mode(keylist.mode.SIGS) + for key in context.op_keylist_all(None, 0): + secret = key.subkeys[0].fpr in sec_keys + data = [x.cfunc(key, secret) for x in keys_columns] + if key.can_encrypt: encrypt_model.append(data) + model.append(data) + self.keys_treeview.set_model(model) + self.encrypt_for_keys_treeview.set_model(encrypt_model) + + def set_default_key(self, key): + "Setup default key and update status bar with it" + self.default_key = key + self.status_uid.set_text((key.uids and key.uids[0].uid) or \ + _("[Unknown user ID]")) + self.status_keyid.set_text(key.subkeys[0].keyid[-8:]) + + def on_default_keys_changed(self, treeview): + "This callback is called when default key is changed in Preferences" + model, rows = treeview.get_selection().get_selected_rows() + if model and rows: + self.set_default_key(model[rows[0]][-1].key) + + def add_default_key(self, model, path, iter, def_model): + "Helper function to add secret keys to the list of possible defaults" + key = model[path][-1] + if key.secret: + def_model.append([x.cfunc(key.key,True) for x in def_keys_columns]) + + def add_sig_key(self, model, path, iter, sign_model): + "Helper function to add secret keys to the list of possible defaults" + key = model[path][-1].key + if key.can_sign: + sign_model.append([x.cfunc(key,True) for x in def_keys_columns]) + + def select_default_key(self, model, path, iter): + "Helper function to select current default key from the available list" + if model[path][-1].key == self.default_key: + self.def_keys_treeview.get_selection().select_path(path) + + def update_default_keys(self): + "Update list of default keys" + model = gtk.ListStore(*[x.ctype for x in def_keys_columns]) + self.keys_treeview.get_model().foreach(self.add_default_key, model) + self.def_keys_treeview.set_model(model) + model.foreach(self.select_default_key) + selection = self.def_keys_treeview.get_selection() + if selection.count_selected_rows() != 1: + selection.select_path((0,)) + self.on_default_keys_changed(self.def_keys_treeview) + model = gtk.ListStore(*[x.ctype for x in def_keys_columns]) + self.def_keys_treeview.get_model().foreach(self.add_sig_key, model) + self.sign_with_keys_treeview.set_model(model) + self.encrypt_with_keys_treeview.set_model(model) + + def on_select_all_activate(self, obj): + "This callback is called when SelectAll menu item is selected" + self.keys_treeview.get_selection().select_all() + + def on_file_preferences_activate(self, obj): + "Callback called when Preferences menu item is selected in filemanager" + self.show_preferences(self.filemanager_window) + + def on_preferences_activate(self, obj): + "Callback called when Preferences menu item is selected in key editor" + self.show_preferences(None) + + def show_preferences(self, parent): + "Show preferences positioning its window in the middle of the parent" + self.popup(self.preferences_dialog, parent) + self.def_keyserver = self.default_keyserver_combox.child.get_text() + + def on_advanced_mode_toggled(self, radiobutton): + "This callback is called when Advanced Mode selection is changed" + if radiobutton.get_active(): + self.subkeys_notebook_tab.show() + self.get_generate_params = self.get_advanced_generate_params + else: + self.subkeys_notebook_tab.hide() + self.get_generate_params = self.get_novice_generate_params + + def popup_progress_dialog(self, label, parent): + self.progress_dialog.set_transient_for(parent) + self.progress_label.set_text(label) + self.progress_dialog.show_all() + gobject.timeout_add(100, self.update_progress) + + def on_progress_cancel_clicked(self, obj): + self.progress_context.cancel() + + def update_progress(self): + "Helper function to show progress while a work on a key is being done" + try: + status = self.progress_context.wait(0) + if status == None or self.progress_func(status): + self.new_progressbar.pulse() + return True + except errors.GPGMEError as exc: + self.error_message(exc) + + self.progress_context = None + self.progress_func = None + self.progress_dialog.hide() + + # Let callback to be removed. + return False + + def key_generate_done(self, status): + "Helper function called on the completion of a key generation" + if status == 0: + fpr = self.progress_context.op_genkey_result().fpr + self.progress_context.set_keylist_mode(keylist.mode.SIGS) + key = self.progress_context.get_key(fpr, 0) + data = [x.cfunc(key, True) for x in keys_columns] + self.keys_treeview.get_model().append(data) + if key.can_encrypt: + self.encrypt_for_keys_treeview.get_model().append(data) + self.update_default_keys() + else: + self.error_message(status) + return False + + def on_new_activate(self, obj): + "Callback for 'New Key' menu item" + params = self.get_generate_params() + if params == None: + return + + (key_algo, subkeys, size, userid, email, + comment, expire, password, backup) = params + + gen_string = "<GnupgKeyParms format=\"internal\">\n" + \ + "Key-Type: %s\n" % key_algo + \ + "Key-Length: %d\n" % size + if subkeys: + gen_string += "Subkey-Type: %s\n" % subkeys + \ + "Subkey-Length: %d\n" % size + gen_string += "Name-Real: %s\n" % userid + if email: + gen_string += "Name-Email: %s\n" % email + if comment: + gen_string += "Name-Comment: %s\n" % comment + if expire: + gen_string += "Expire-Date: %s\n" % expire + if password: + gen_string += "Passphrase: %s\n" % password + gen_string += "</GnupgKeyParms>\n" + + self.progress_context = Context() + self.progress_context.op_genkey_start(gen_string, None, None) + self.progress_func = self.key_generate_done + self.popup_progress_dialog(_("Generating Key..."), self.main_window) + + def check_passphrase(self, passphrase, repeat_passphrase, parent): + """Helper function to check that enetered password satisfies our + requirements""" + if not passphrase: + self.error_message(_('You did not enter a passphrase.\n' + + 'It is needed to protect your private key.'), + parent) + elif repeat_passphrase != passphrase: + self.error_message(_('In "Passphrase" and "Repeat passphrase",\n' + + 'you must enter the same passphrase.'), + parent) + else: + return True + return False + + def get_novice_generate_params(self): + "Helper function to get generate key parameter in Novice mode" + dialogs = [self.generate_userid_dialog, + self.generate_email_dialog, + self.generate_comment_dialog, + self.generate_passphrase_dialog, + self.generate_backup_dialog] + step = 0 + params = None + while step>=0: + dialog = dialogs[step] + dialog.set_transient_for(self.main_window) + result = dialog.run() + newstep = step + if result == 2: + if step == 0: + userid = self.generate_novice_userid_entry.get_text() + if userid: newstep = step + 1 + else: self.error_message(_("Please insert your name.")) + elif step == 1: + email = self.generate_novice_email_entry.get_text() + if email: newstep = step + 1 + else: + self.error_message(_("Please insert your email address")) + elif step == 2: + comment = self.generate_novice_comment_entry.get_text() + newstep = step + 1 + elif step == 3: + passphrase=self.generate_novice_passphrase_entry.get_text() + if self.check_passphrase( + passphrase, + self.generate_novice_repeat_passphrase_entry.get_text(), + dialog): + newstep = step + 1 + elif step == 4: + backup = self.generate_novice_backup_rb.get_active() + params = ("DSA", "ELG-E", 1024, userid, email, + comment, "", passphrase, backup) + newstep = -1 + elif result == 1: + newstep = step - 1 + else: + newstep = -1 + + if newstep != step: + dialog.hide() + step = newstep + + self.generate_novice_userid_entry.set_text("") + self.generate_novice_email_entry.set_text("") + self.generate_novice_comment_entry.set_text("") + self.generate_novice_passphrase_entry.set_text("") + self.generate_novice_repeat_passphrase_entry.set_text("") + return params + + def on_new_expire_on_rb_toggled(self, expireon_rb): + self.new_expire_calendar.set_sensitive(expireon_rb.get_active()) + + def on_new_expire_after_rb_toggled(self, expireafter_rb): + active = expireafter_rb.get_active() + self.new_expire_count_entry.set_sensitive(active) + self.new_expire_unit_combo.set_sensitive(active) + + def get_advanced_generate_params(self): + "Helper function to get generate key parameter in Advanced mode" + params = None + self.new_expire_unit_combo.set_active(0) + self.new_algorithm_combo.set_active(0) + self.new_key_size_combo.set_active(1) + self.generate_dialog.set_transient_for(self.main_window) + while params == None and self.generate_dialog.run() == gtk.RESPONSE_OK: + passphrase = self.new_passphrase_entry.get_text() + if not self.check_passphrase( + passphrase, + self.new_repeat_passphrase_entry.get_text(), + self.generate_dialog): + continue + key_algo, subkeys = { + 'DSA and ElGamal (default)': ("DSA", "ELG-E"), + 'DSA (sign only)': ("DSA", ""), + 'RSA (sign only)': ("RSA", "") + }[self.new_algorithm_combo.child.get_text()] + try: + size = int(self.new_key_size_combo.child.get_text()) + except ValueError: + self.new_key_size_combo.child.grab_focus() + continue + userid = self.new_userid_entry.get_text() + email = self.new_email_entry.get_text() + comment = self.new_comment_entry.get_text() + expire = "" + if self.new_expire_after_rb.get_active(): + model = self.new_expire_unit_combo.get_model() + unit = model[(self.new_expire_unit_combo.get_active(),)][1] + try: + value = int(self.new_expire_count_entry.get_text()) + except ValueError: + self.new_expire_count_entry.grab_focus() + continue + expire = "%d%s" % (value, unit) + elif self.new_expire_on_rb.get_active(): + (year, month, day) = self.new_expire_calendar.get_date() + expire = "%04d-%02d-%02d" % (year, month+1, day) + params = (key_algo, subkeys, size, userid, email, + comment, expire, passphrase, False) + self.generate_dialog.hide() + self.new_passphrase_entry.set_text("") + self.new_repeat_passphrase_entry.set_text("") + return params + + def del_key(self, key, treeview): + "Helper function to delete a key from a treeview list" + row_list = [] + treeview.get_model().foreach(lambda m,p,i,l: l.append(m[p]), row_list) + for row in row_list: + if row[-1].key.subkeys[0].fpr == key.subkeys[0].fpr: + row.model.remove(row.iter) + + def on_delete_activate(self, obj): + "Callback for 'Delete Keys' menu item" + message = { + True: _("This key has a secret key. Deleting this key cannot be"+ + " undone, unless you have a backup copy."), + False: _("This key is a public key. Deleting this key cannot be "+ + "undone easily, although you may be able to get a new " + + "copy from the owner or from a key server.") + } + keytag = self.delete_key_keyinfo + for row in self.get_selected_keys(): + self.delete_key_label.set_text(message[row[-1].secret]) + table = labels2table(row[-1].key_print_labels()) + keytag.add(table) + keytag.show_all() + if self.popup(self.delete_key_dialog) == gtk.RESPONSE_YES: + context = Context() + context.op_delete(row[-1].key, 1) + if row[-1].key.can_encrypt: + self.del_key(row[-1].key, self.encrypt_for_keys_treeview) + row.model.remove(row.iter) + self.update_default_keys() + self.on_keys_changed(self.keys_treeview) + keytag.remove(table) + + def password_cb(self, hint, desc, prev_bad, hook=None): + "Callback to setup verification of a passphrase" + if prev_bad: + header = _("Wrong passphrase, please try again:") + else: + header = _("Please enter the passphrase for the following key:") + self.password_prompt_label.set_text(header) + keyid, userid = hint.split(" ", 1) + table = labels2table([(_("User Name:"), userid), + (_("Key ID:"), keyid[-8:])]) + self.password_prompt_keyinfo.add(table) + self.password_prompt_keyinfo.show_all() + password = None + if self.popup(self.password_prompt_dialog) == gtk.RESPONSE_OK: + password = self.password_prompt_entry.get_text() + self.password_prompt_keyinfo.remove(table) + self.password_prompt_entry.set_text("") + if not password: + GPG_ERR_CANCELED = 99 + raise errors.GPGMEError(GPG_ERR_CANCELED) + return password + + def password_change_cb(self, hint, desc, prev_bad, hook): + "Callback to setup for passphrase change" + if not prev_bad: + hook["count"] += 1 + + if hook["count"] == 1: + return self.password_cb(hint, desc, prev_bad) + else: + password = None + self.password_change_dialog.set_transient_for(self.main_window) + while password == None and \ + self.password_change_dialog.run() == gtk.RESPONSE_OK: + password = self.password_change_passphrase.get_text() + if not self.check_passphrase( + password, + self.password_change_repeat_passphrase.get_text(), + self.password_change_dialog): + password = None + self.password_change_dialog.hide() + self.password_change_passphrase.set_text("") + self.password_change_repeat_passphrase.set_text("") + if not password: + GPG_ERR_CANCELED = 99 + raise errors.GPGMEError(GPG_ERR_CANCELED) + return password + + def on_sign_keys_activate(self, obj): + "Callback for 'Sign keys' menu item" + context = Context() + context.set_passphrase_cb(self.password_cb) + context.set_keylist_mode(keylist.mode.SIGS) + keytag = self.sign_key_keyinfo + for row in self.get_selected_keys(): + if row[-1].key == self.default_key: + continue + if len(row[-1].key.uids) > 1: + self.sign_manyuids_label.show() + else: + self.sign_manyuids_label.hide() + table = labels2table(row[-1].key_print_labels(True)) + keytag.add(table) + keytag.show_all() + if self.popup(self.sign_key_dialog) == gtk.RESPONSE_YES: + try: + sign_key(context, row[-1].key, self.default_key, + self.sign_locally_cb.get_active()) + row[-1].key=context.get_key(row[-1].key.subkeys[0].fpr,0) + self.on_keys_changed(self.keys_treeview) + except errors.GPGMEError as exc: + self.error_message(exc) + keytag.remove(table) + + def on_change_passphrase_clicked(self, obj, key_info): + "Callback for 'Change passphrase' button in editor for a private key" + try: + context = Context() + context.set_passphrase_cb(self.password_change_cb, {"count": 0}) + trigger_change_password(context, key_info.key) + except errors.GPGMEError as exc: + self.error_message(exc) + + def on_change_expiry_expireon_rb_toggled(self, expire_rb): + "Callback for 'never expire' radiobutton in editor for a private key" + self.change_expiry_calendar.set_sensitive(expire_rb.get_active()) + + def on_change_expiration_clicked(self, obj, key_info): + "Callback for 'Change expiration' button in editor for a private key" + if key_info.key.subkeys[0].expires: + year, month, day = time.localtime(key_info.key.subkeys[0].expires)[:3] + self.change_expiry_calendar.select_month(month-1, year) + self.change_expiry_calendar.select_day(day) + self.change_expiry_expireon_rb.set_active(True) + else: + self.change_expiry_never_rb.set_active(True) + if self.popup(self.change_expiry_dialog, + self.edit_key_dialog) == gtk.RESPONSE_OK: + year, month, day = self.change_expiry_calendar.get_date() + expire = "%04d-%02d-%02d" % (year, month+1, day) + try: + context = Context() + context.set_passphrase_cb(self.password_cb) + change_key_expire(context, key_info.key, expire) + context.set_keylist_mode(keylist.mode.SIGS) + key_info.key=context.get_key(key_info.key.subkeys[0].fpr,0) + self.on_keys_changed(self.keys_treeview) + self.edit_key_date_label.set_text(key_info.key_expires_label()) + except errors.GPGMEError as exc: + self.error_message(exc) + + def on_edit_private_key_activate(self, obj): + "Callback for 'Edit Private Key' menu item" + keys = self.get_selected_keys() + if len(keys) != 1 or not keys[0][-1].secret: + return + + key_info = keys[0][-1] + table = labels2table(key_info.key_print_labels()) + self.edit_key_date_label.set_text(key_info.key_expires_label()) + self.edit_key_keyinfo.add(table) + self.edit_key_keyinfo.show_all() + connect1_id = self.edit_key_change_expiration.connect( + "clicked", self.on_change_expiration_clicked, key_info) + connect2_id = self.edit_key_change_passphrase.connect( + "clicked", self.on_change_passphrase_clicked, key_info) + self.popup(self.edit_key_dialog) + self.edit_key_change_expiration.disconnect(connect1_id) + self.edit_key_change_passphrase.disconnect(connect2_id) + self.edit_key_keyinfo.remove(table) + + def on_set_owner_trust_activate(self, obj): + "Callback for 'Set Owner Trust' menu item" + keys = self.get_selected_keys() + if len(keys) != 1: + return + + key_info = keys[0][-1] + table = labels2table(key_info.key_print_labels()) + self.ownertrust_key.add(table) + self.ownertrust_key.show_all() + trust = key_info.key.owner_trust + if trust < 0 or trust not in trusts: + trust = validity.UNDEFINED + getattr(self, "ownertrust_"+trusts[trust]).set_active(True) + if self.popup(self.ownertrust_dialog) == gtk.RESPONSE_OK: + for trust, name in trusts.items(): + if getattr(self, "ownertrust_"+name).get_active(): + try: + context = Context() + change_key_trust(context, key_info.key, trust) + key_info.key.owner_trust = trust + self.on_keys_changed(self.keys_treeview) + except errors.GPGMEError as exc: + self.error_message(exc) + break + self.ownertrust_key.remove(table) + + def import_keys_from_data(self, data): + "Helper function to import keys into application from a Data() object" + context = Context() + status = context.op_import(data) + if status: + self.error_message(status) + else: + result = context.op_import_result() + if result.considered == 0: + self.error_message(_("No keys were found.")) + else: + self.load_keys() + self.info_message(_("%i public keys read\n" + + "%i public keys imported\n" + + "%i public keys unchanged\n" + + "%i secret keys read\n" + + "%i secret keys imported\n" + + "%i secret keys unchanged") % \ + (result.considered, + result.imported, + result.unchanged, + result.secret_read, + result.secret_imported, + result.secret_unchanged)) + + def import_from_clipboard(self, clipboard, text, data): + "Callback to setup extraction of data from a clipboard" + if text: + self.import_keys_from_data(Data(text)) + + def on_paste_activate(self, obj): + "Callback for 'Paste' menu item" + gtk.clipboard_get().request_text(self.import_from_clipboard) + + def on_import_keys_activate(self, obj): + "Callback for 'Import Keys' menu item" + import_file = None + dialog = self.import_file_dialog + dialog.set_transient_for(self.main_window) + while import_file == None and dialog.run() == gtk.RESPONSE_OK: + try: + import_file = open(dialog.get_filename(), "rb") + except IOError as strerror: + self.error_message(strerror, dialog) + import_file = None + dialog.hide() + if import_file != None: + self.import_keys_from_data(Data(file=import_file)) + import_file.close() + + def export_selected_keys(self, armor): + "Helper function to export selected keys into a Data() object" + context = Context() + context.set_armor(armor) + export_keys = Data() + for row in self.get_selected_keys(): + context.op_export(row[-1].key.subkeys[0].fpr, 0, export_keys) + export_keys.seek(0,0) + return export_keys + + def on_copy_activate(self, obj): + "Callback for 'Copy' menu item" + if self.keys_treeview.get_selection().count_selected_rows() > 0: + export_keys = self.export_selected_keys(True) + gtk.clipboard_get().set_text(export_keys.read()) + + def verify_output(self, filename, parent): + "Helper function to verify that user can write into the filename" + if os.path.exists(filename): + if os.path.isdir(filename): + self.error_message(_("%s is a directory")%filename, parent) + return False + else: + return self.yesno_message(_("The file %s already exists.\n" + + "Do you want to overwrite it?") % + filename, parent) + return True + + def on_export_keys_activate(self, obj): + "Callback for 'Export Keys' menu item" + if self.keys_treeview.get_selection().count_selected_rows() < 1: + return + + export_file = None + dialog = self.export_file_dialog + dialog.set_transient_for(self.main_window) + while export_file == None and dialog.run() == gtk.RESPONSE_OK: + filename = dialog.get_filename() + if self.verify_output(filename, dialog): + try: + export_file = open(filename, "wb") + except IOError as strerror: + self.error_message(strerror, dialog) + export_file = None + dialog.hide() + if export_file == None: + return + + export_keys = self.export_selected_keys(export_armor_cb.get_active()) + export_file.write(export_keys.read()) + export_file.close() + + def on_files_changed(self, obj): + "Callback called when selection of files in filemanager is changed" + if self.files_treeview.get_selection().count_selected_rows() < 1: + value = False + else: + value = True + for item in (self.sign, self.verify, self.encrypt, self.decrypt): + item.set_sensitive(value) + + def open(self, filename, complain=False): + "Helper function to add a file into filemanager treeview" + model = self.files_treeview.get_model() + row_list = [] + model.foreach(lambda m,p,i,l: l.append(m[p][0]), row_list) + if filename in row_list: + if complain: + self.file_error_message(_("The file is already open.")) + else: + item = model.append([filename]) + self.files_treeview.get_selection().select_iter(item) + self.on_files_changed(None) + + def on_open_activate(self, obj): + "Callback for 'Open' menu item" + if self.file_popup(self.open_file_dialog) == gtk.RESPONSE_OK: + self.add_file(self.open_file_dialog.get_filename(), True) + self.open_file_dialog.unselect_all() + + def get_selected_files(self): + "Helper function to return selected rows in filemanager treeview" + return self.get_selected_keys(self.files_treeview) + + def on_clear_activate(self, obj): + "Callback for 'Clear' menu item" + for row in self.get_selected_files(): + row.model.remove(row.iter) + + def process_file_start(self, in_name, out_name): + "Helper function to start asynchronous processing of one file" + if self.verify_output(out_name, self.filemanager_window): + try: + self.in_data = Data(file=in_name) + self.out_data = Data() + self.out_name = out_name + self.file_func(self.in_data, self.out_data) + except errors.GPGMEError as exc: + self.file_error_message(exc) + + def process_file_done(self, status): + "The function called when asynchronous processing of the file is done." + try: + errors.errorcheck(status) + self.out_data.seek(0,0) + out_file = file(self.out_name, "wb") + out_file.write(self.out_data.read()) + out_file.close() + self.add_file(self.out_name) + if self.file_list: + self.process_file_start(*(self.file_list.pop(0))) + return True + except (errors.GPGMEError, IOError) as exc: + self.file_error_message(exc) + + # Let python to free the memory. + self.out_data = None + self.in_data = None + self.out_name = None + self.file_list = [] + self.file_func = None + return False + + def process_files_async(self, file_list, func, label): + "Helper function to initialize async processing of the file list" + self.file_list = file_list + self.file_func = func + self.progress_func = self.process_file_done + self.process_file_start(*(self.file_list.pop(0))) + self.popup_progress_dialog(label, self.filemanager_window) + + def on_sign_activate(self, obj): + "Callback for 'Sign' menu item" + files = self.get_selected_files() + if not files: return + + if self.file_popup(self.sign_dialog) == gtk.RESPONSE_OK: + context = Context() + context.set_passphrase_cb(self.password_cb) + context.set_armor(self.sign_armor_cb.get_active()) + for rw in self.get_selected_keys(self.sign_with_keys_treeview): + context.signers_add(rw[-1].key) + for cb,md,ext in [(self.sign_normal, sig.mode.NORMAL, ".gpg"), + (self.sign_clear, sig.mode.CLEAR, ".asc"), + (self.sign_separate,sig.mode.DETACH,".sig")]: + if cb.get_active(): + sigmode = md + sigext = ext + break + self.progress_context = context + def sign(x,y):self.progress_context.op_sign_start(x,y,sigmode) + self.process_files_async([(f[0], f[0]+sigext) for f in files], + sign, _("Signing...")) + + def verify_file_start(self, in_name, out_name): + "Helper function to start file signature verification process" + try: + self.in_name = in_name + self.out_name = out_name + self.signed = Data(file=self.in_name) + if out_name: + self.plain1 = Data(file=self.out_name) + self.plain2 = None + else: + self.plain1 = None + self.plain2 = Data() + self.progress_context.op_verify_start(self.signed, self.plain1, + self.plain2) + except errors.GPGMEError as exc: + self.file_error_message(exc) + + def verify_file_done(self, status): + "The function called when asynchronous file signature verify is done." + try: + errors.errorcheck(status) + result = self.progress_context.op_verify_result() + + model = gtk.ListStore(str, str, str) + treeview = gtk.TreeView(model) + treeview.set_rules_hint(True) + for index, title in enumerate([_("Key ID"), _("Status"), + _("User Name")]): + treeview.append_column(gtk.TreeViewColumn( + title, gtk.CellRendererText(), text=index)) + for sign in result.signatures: + key = self.progress_context.get_key(sign.fpr, 0) + if key and key.uids: + keyid = key.subkeys[0].keyid[-8:] + userid = key.uids[0].uid + else: + keyid = sign.fpr[-8:] + userid = _("[Unknown user ID]") + model.append([keyid, sigsum2str(sign.summary), userid]) + + vbox = gtk.VBox() + if self.out_name: + vbox.add(gtk.Label(_("Verified data in file: %s") % + self.out_name)) + label = gtk.Label(_("Signatures:")) + label.set_alignment(0, 1) + vbox.add(label) + vbox.add(treeview) + self.verified.append((vbox, gtk.Label(self.in_name))) + if self.file_list: + self.verify_file_start(*(self.file_list.pop(0))) + return True + except errors.GPGMEError as exc: + self.file_error_message(exc) + + # Let python to free the memory. + self.signed = None + self.plain1 = None + self.plain2 = None + self.in_name = None + self.out_name = None + self.file_list = [] + self.progress_dialog.hide() + + if self.verified: + notebook = gtk.Notebook() + for page in self.verified: notebook.append_page(*page) + self.verify_result.add(notebook) + self.verify_result.show_all() + self.file_popup(self.verify_dialog) + self.verify_result.remove(notebook) + self.verified = [] + + return False + + def on_verify_activate(self, obj): + "Callback for 'Verify' menu item" + files = self.get_selected_files() + if not files: return + + self.file_list = [] + for onefile in files: + in_name = onefile[0] + if in_name[-4:] == ".sig": + out_name = in_name[:-4] + elif in_name[-5:] == ".sign": + out_name = in_name[:-5] + else: + out_name = None + self.file_list.append((in_name, out_name)) + self.verified = [] + self.progress_context = Context() + self.progress_func = self.verify_file_done + self.verify_file_start(*(self.file_list.pop(0))) + self.popup_progress_dialog(_("Verifying..."), self.filemanager_window) + + def on_encrypt_sign_toggled(self, cb): + "Callback for change of the 'Sign' check box in 'Encrypt files' dialog" + self.encrypt_with_keys_treeview.set_sensitive(cb.get_active()) + + def on_encrypt_activate(self, obj): + "Callback for 'Encrypt' menu item" + files = self.get_selected_files() + if not files: return + + self.on_encrypt_sign_toggled(self.encrypt_sign_cb) + if self.file_popup(self.encrypt_dialog) == gtk.RESPONSE_OK: + context = Context() + context.set_passphrase_cb(self.password_cb) + if self.encrypt_armor_cb.get_active(): + context.set_armor(True) + ext = ".asc" + else: + context.set_armor(False) + ext = ".gpg" + keylist = [row[-1].key for row in self.get_selected_keys( + self.encrypt_for_keys_treeview)] + if self.encrypt_sign_cb.get_active(): + for row in self.get_selected_keys( + self.encrypt_with_keys_treeview): + context.signers_add(row[-1].key) + def encrypt(x,y): + self.progress_context.op_encrypt_sign_start( + keylist, 1, x, y) + else: + def encrypt(x,y): + self.progress_context.op_encrypt_start( + keylist, 1, x, y) + self.progress_context = context + self.process_files_async([(f[0], f[0]+sigext) for f in files], + encrypt, _("Encrypting...")) + + def on_decrypt_activate(self, obj): + "Callback for 'Decrypt' menu item" + files = self.get_selected_files() + if not files: return + + file_list = [] + for onefile in self.get_selected_files(): + in_name = onefile[0] + if in_name[-4:] in [".asc", ".gpg", ".pgp"]: + out_name = in_name[:-4] + else: + out_name = in_name + ".txt" + file_list.append((in_name, out_name)) + self.process_context = Context() + self.process_files_async(file_list, + self.process_context.op_decrypt_start, + _("Decrypting...")) + + def on_select_all_files_activate(self, obj): + "Callback for 'Select All' menu item in filemanager" + self.files_treeview.get_selection().select_all() + + def on_keyring_editor_activate(self, obj): + "Callback for 'Keyring Editor' menu item" + self.main_window.show() + + def on_keyring_editor_close_activate(self, obj, event=None): + "Callback for 'Close' menu item in Keyring Editor" + if self.filemanager_window.get_property("visible"): + self.main_window.hide() + return True + else: + self.on_quit_activate(None) + + def on_filemanager_activate(self, obj): + "Callback for 'Filemanager' menu item" + self.on_files_changed(None) + self.filemanager_window.show() + + def on_filemanager_close_activate(self, obj, event=None): + "Callback for 'Close' menu item in Filemanager" + if self.main_window.get_property("visible"): + self.filemanager_window.hide() + return True + else: + self.on_quit_activate(None) + + def on_about_activate(self, obj): + "Callback for 'About' menu item" + self.popup(self.about_dialog) + + def __repr__(self): + return self.__class__.__name__ + + def __getattr__(self, name): + "Dynamic retrieval of widgets from the glade XML" + if name.startswith("on_"): + self.__dict__[name] = lambda x: sys.stderr.write( + _("Callback %s is not implimented yet\n") % name) + elif name.startswith("_"): + return None + else: + self.__dict__[name] = self.wtree.get_widget(name) + return self.__dict__[name] + + def __init__(self, path): + "PyGpa(path) - path is where pygpa.glade file can be found" + gladefile = os.path.join(path, "pygpa.glade") + self.wtree = gtk.glade.XML(gladefile, None, gtk.glade.textdomain()) + self.wtree.signal_autoconnect(self) + + self.default_key = None + self.load_keys() + self.setup_columns() + self.setup_default_views() + self.in_progress = {} + + gtk.main() + + def on_quit_activate(self, obj): + gtk.main_quit() + +PyGpa(os.path.dirname(sys.argv[0])) |