Python 3 port of PyME
[gpgme.git] / lang / py3-pyme / examples / PyGtkGpgKeys.py
1 #!/usr/bin/env python3
2 # $Id$
3 # Copyright (C) 2005,2008 Igor Belyi <belyi@users.sourceforge.net>
4 #
5 #    This program is free software; you can redistribute it and/or modify
6 #    it under the terms of the GNU General Public License as published by
7 #    the Free Software Foundation; either version 2 of the License, or
8 #    (at your option) any later version.
9 #
10 #    This program is distributed in the hope that it will be useful,
11 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 #    GNU General Public License for more details.
14 #
15 #    You should have received a copy of the GNU General Public License
16 #    along with this program; if not, write to the Free Software
17 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
19 import gtk, gobject, gtk.glade
20 import time, sys, os
21 from pyme import callbacks, core, errors
22 from pyme.core import Data, Context, pubkey_algo_name
23 from pyme import constants
24 from pyme.constants import validity
25 from pyme.constants.keylist import mode
26
27 # Thanks to Bernhard Reiter for pointing out the following:
28 # gpgme_check_version() necessary for initialisation according to 
29 # gpgme 1.1.6 and this is not done automatically in pyme-0.7.0
30 print("gpgme version:", core.check_version(None))
31
32 # Convert trust constant into a string
33 trusts = {validity.UNKNOWN: "",
34           validity.UNDEFINED: "Undefined",
35           validity.NEVER: "Never",
36           validity.MARGINAL: "Marginal",
37           validity.FULL: "Full",
38           validity.ULTIMATE: "Ultimate"}
39
40 # Convert seconds into a date
41 def sec2str(secs):
42     if secs > 0:    return time.strftime("%Y-%m-%d", time.gmtime(secs))
43     elif secs == 0: return "Unlimited"
44     else:           return ""
45
46 index = 0
47 class KeyColumn:
48     "Helper class for data columns."
49     def __init__(self, name, gtype, vattr=None, tcols=None,
50                  func=lambda x:x, view=None):
51         """new(name, qtype, vattr, column, ocolumn, func):
52         name  - column title
53         qtype - gobject type to use in TreeStore for this column
54         vattr - column data is visible if method vattr present in the object
55         tcols - list of type specific columns to append its name to.
56         func  - function converting object data into viewable presentation
57         view  - to put or not the column in the view menu"""
58         global index
59         self.name = name
60         self.type = gtype
61         self.vattr = vattr
62         self.func = func
63         self.view = view
64         self.index = index
65         self.attrs = {}
66         if tcols != None: tcols.append(name)
67         index += 1
68
69 # List column names specific to an object type
70 key_columns = []                        # names only in key
71 uid_columns = []                        # names only in uids
72 sub_columns = []                        # names only in subkeys
73 sign_columns = []                       # names only in signatures
74 sub_sign_columns = []                   # names in subkeys and signatures
75
76 # Explicite columns
77 visible_columns = [
78     KeyColumn("Secret", gobject.TYPE_BOOLEAN, "subkeys"),
79     KeyColumn("Name", gobject.TYPE_STRING, "name", uid_columns,
80               lambda x: x.name+(x.comment and " (%s)"%x.comment)),
81     KeyColumn("Email", gobject.TYPE_STRING, "email", uid_columns,
82               lambda x: x.email),
83     KeyColumn("Owner Trust", gobject.TYPE_STRING, "owner_trust", key_columns,
84               lambda x: trusts[x.owner_trust], True),
85     KeyColumn("Type", gobject.TYPE_STRING, "pubkey_algo", sub_sign_columns,
86               lambda x: pubkey_algo_name(x.pubkey_algo)),
87     KeyColumn("Length", gobject.TYPE_INT, "length", sub_columns,
88               lambda x: x.length),
89     KeyColumn("Can Auth", gobject.TYPE_BOOLEAN,"can_authenticate", sub_columns,
90               lambda x: x.can_authenticate, False),
91     KeyColumn("Can Cert", gobject.TYPE_BOOLEAN, "can_certify", sub_columns,
92               lambda x: x.can_certify, False),
93     KeyColumn("Can Encr", gobject.TYPE_BOOLEAN, "can_encrypt", sub_columns,
94               lambda x: x.can_encrypt, False),
95     KeyColumn("Can Sign", gobject.TYPE_BOOLEAN, "can_sign", sub_columns,
96               lambda x: x.can_sign, False),
97     KeyColumn("Created", gobject.TYPE_STRING, "timestamp", sub_sign_columns,
98               lambda x: sec2str(x.timestamp), True),
99     KeyColumn("Expires", gobject.TYPE_STRING, "expires", sub_sign_columns,
100               lambda x: sec2str(x.expires), True),
101     KeyColumn("Id", gobject.TYPE_STRING, "keyid", sub_sign_columns,
102               lambda x: x.keyid)
103     ]
104
105 helper_columns = [
106     KeyColumn("Name Invalid", gobject.TYPE_BOOLEAN, None, uid_columns,
107               lambda x: x.revoked or x.invalid),
108     KeyColumn("Subkey Invalid", gobject.TYPE_BOOLEAN, None, sub_sign_columns,
109               lambda x: x.revoked or x.invalid or x.expired),
110     KeyColumn("FPR", gobject.TYPE_STRING, None, sub_columns,
111               lambda x: x.fpr)
112     ]
113
114 # Calculate implicite columns - defining visibility of the data in a column.
115 # In the same loop calculate tuple for rows having only name in them.
116 name_only = ()
117 for item in visible_columns:
118     vis_item = KeyColumn("Show"+item.name, gobject.TYPE_BOOLEAN)
119     helper_columns.append(vis_item)
120     item.attrs["visible"] = vis_item.index
121     name_only += (vis_item.index, item.name == "Name")
122
123 columns = {}
124 for item in visible_columns + helper_columns:
125     columns[item.name] = item
126
127 # Use strikethrough to indicate revoked or invalid keys and uids
128 columns["Name"].attrs["strikethrough"] = columns["Name Invalid"].index
129 columns["Id"].attrs["strikethrough"] = columns["Subkey Invalid"].index
130
131 def pair(name, value):
132     "pair(name, value) creates (index, func(value)) tuple based on column name"
133     item = columns[name]
134     if item.index < len(visible_columns):
135         return (item.index, item.func(value), columns["Show"+name].index, True)
136     else:
137         return (item.index, item.func(value))
138
139 class PyGtkGpgKeys:
140     "Main class representing PyGtkGpgKeys application"
141     def error_message(self, text, parent=None):
142         dialog = gtk.MessageDialog(parent or self.mainwin,
143                                    gtk.DIALOG_MODAL |
144                                    gtk.DIALOG_DESTROY_WITH_PARENT,
145                                    gtk.MESSAGE_ERROR,
146                                    gtk.BUTTONS_OK,
147                                    text)
148         dialog.run()
149         dialog.destroy()        
150
151     def yesno_message(self, text, parent=None):
152         dialog = gtk.MessageDialog(parent or self.mainwin,
153                                    gtk.DIALOG_MODAL |
154                                    gtk.DIALOG_DESTROY_WITH_PARENT,
155                                    gtk.MESSAGE_QUESTION,
156                                    gtk.BUTTONS_YES_NO,
157                                    text)
158         result = dialog.run() == gtk.RESPONSE_YES
159         dialog.destroy()
160         return result
161     
162     def load_keys(self, first_time=False):
163         if not first_time: self.model.clear()
164         secret_keys = {}
165         for key in self.context.op_keylist_all(None, 1):
166             secret_keys[key.subkeys[0].fpr] = 1
167         for key in self.context.op_keylist_all(None, 0):
168             self.add_key(key, key.subkeys[0].fpr in secret_keys)
169     
170     def add_key(self, key, secret):
171         "self.add_key(key) - add key to the TreeStore model"
172         iter = self.model.append(None)
173         # Can delete only the whole key
174         param = (iter,) + pair("Secret", secret)
175         # Key information is a combination of the key and first uid and subkey
176         for col in key_columns: param += pair(col, key)
177         for col in uid_columns: param += pair(col, key.uids[0])
178         for col in sub_columns: param += pair(col, key.subkeys[0])
179         for col in sub_sign_columns: param += pair(col, key.subkeys[0])
180         self.model.set(*param)
181         if key.uids:
182             self.add_signatures(key.uids[0].signatures, iter)
183             self.add_uids(key.uids[1:], iter)
184         self.add_subkeys(key.subkeys[1:], iter)
185
186     def add_subkeys(self, subkeys, iter):
187         "self.add_subkeys(subkey, iter) - add subkey as child to key's iter"
188         if not subkeys:
189             return
190         key_iter = self.model.append(iter)
191         self.model.set(key_iter, columns["Name"].index, "Subkeys", *name_only)
192         for subkey in subkeys:
193             child_iter = self.model.append(key_iter)
194             param = (child_iter,)
195             for col in sub_columns: param += pair(col, subkey)
196             for col in sub_sign_columns: param += pair(col, subkey)
197             self.model.set(*param)
198
199     def add_uids(self, uids, iter):
200         "self.add_uids(uid, iter) - add uid as a child to key's iter"
201         if not uids:
202             return
203         uid_iter = self.model.append(iter)
204         self.model.set(uid_iter,columns["Name"].index,"Other UIDs",*name_only)
205         for uid in uids:
206             child_iter = self.model.append(uid_iter)
207             param = (child_iter,)
208             for col in uid_columns: param += pair(col, uid)
209             self.model.set(*param)
210             self.add_signatures(uid.signatures, child_iter)
211
212     def add_signatures(self, signs, iter):
213         "self.add_signatures(sign, iter) - add signature as a child to iter"
214         if not signs:
215             return
216         sign_iter = self.model.append(iter)
217         self.model.set(sign_iter,columns["Name"].index,"Signatures",*name_only)
218         for sign in signs:
219             child_iter = self.model.append(sign_iter)
220             param = (child_iter,)
221             for col in uid_columns: param += pair(col, sign)
222             for col in sign_columns: param += pair(col, sign)
223             for col in sub_sign_columns: param += pair(col, sign)
224             self.model.set(*param)
225
226     def add_columns(self):
227         "Add viewable columns for the data in TreeStore model"
228         view_menu = gtk.Menu()
229         for item in visible_columns:
230             if item.type == gobject.TYPE_BOOLEAN:
231                 renderer = gtk.CellRendererToggle()
232                 item.attrs["active"] = item.index
233             else:
234                 renderer = gtk.CellRendererText()
235                 item.attrs["text"] = item.index
236             column = self.treeview.insert_column_with_attributes(
237                 item.index, item.name, renderer, **item.attrs)
238             column.set_sort_column_id(item.index)
239             # Create callback for a View menu item
240             if item.view != None:
241                 check = gtk.CheckMenuItem(item.name)
242                 check.set_active(item.view)
243                 check.connect("activate",
244                               lambda x, y: y.set_visible(x.get_active()),
245                               column)
246                 view_menu.append(check)
247                 column.set_visible(check.get_active())
248                 
249         view_menu.show_all()
250         self.wtree.get_widget("view_menu").set_submenu(view_menu)
251
252     def on_GPGKeysView_button_press_event(self, obj, event):
253         if event.button != 3:
254             return False
255
256         menu = gtk.Menu()
257         for title, callback in [
258             ("Reload", self.on_reload_activate),
259             (None, None),
260             ("Delete", self.on_delete_activate),
261             ("Export (txt)", self.on_export_keys_text_activate),
262             ("Export (bin)", self.on_export_keys_activate)
263             ]:
264             if title:
265                 item = gtk.MenuItem(title)
266                 item.connect("activate", callback)
267             else:
268                 item = gtk.SeparatorMenuItem()
269             menu.append(item)
270         menu.show_all()
271         
272         menu.popup(None, None, None, event.button, event.time)
273         return True
274
275     def editor_func(self, status, args, val_dict):
276         state = val_dict["state"]
277         prompt = "%s %s" % (state, args)
278         if prompt in val_dict:
279             val_dict["state"] = val_dict[prompt][0]
280             return val_dict[prompt][1]
281         elif args:
282             sys.stderr.write("Unexpected prompt in editor_func: %s\n" % prompt)
283             raise EOFError()
284         return ""
285
286     def change_key_trust(self, key, new_trust):
287         val_dict = {
288             "state": "start",
289             "start keyedit.prompt": ("trust", "trust"),
290             "trust edit_ownertrust.value": ("prompt", "%d" % new_trust),
291             "prompt edit_ownertrust.set_ultimate.okay": ("prompt", "Y"),
292             "prompt keyedit.prompt": ("finish", "quit")
293             }
294         out = Data()
295         self.context.op_edit(key, self.editor_func, val_dict, out)
296
297     def on_change_trust(self, new_trust):
298         selection = self.treeview.get_selection()
299         if selection.count_selected_rows() <= 0:
300             return
301         
302         key_list = []
303         selection.selected_foreach(self.collect_keys, key_list)
304
305         message = "Change trust to %s on the following keys?\n" % \
306                   trusts[new_trust]
307         for key, row in key_list:
308             message += "\n%s\t" % key.subkeys[0].keyid
309             if key.uids: message += key.uids[0].uid
310             else:        message += "<undefined>"                
311         if self.yesno_message(message):
312             for key, row in key_list:
313                 if key.owner_trust != new_trust:
314                     self.change_key_trust(key, new_trust)
315                     row[columns["Owner Trust"].index] = trusts[new_trust]
316
317     def on_undefined_trust_activate(self, obj):
318         self.on_change_trust(1)
319
320     def on_never_trust_activate(self, obj):
321         self.on_change_trust(2)
322
323     def on_marginal_trust_activate(self, obj):
324         self.on_change_trust(3)
325
326     def on_full_trust_activate(self, obj):
327         self.on_change_trust(4)
328
329     def on_ultimate_trust_activate(self, obj):
330         self.on_change_trust(5)
331
332     def collect_keys(self, model, path, iter, key_list):
333         row = model[path[:1]]
334         keyid = row[columns["FPR"].index]
335         key = self.context.get_key(keyid, 0)
336         key_list.append((key, row))
337
338     def export_keys(self):
339         selection = self.treeview.get_selection()
340         if selection.count_selected_rows() <= 0:
341             return
342         
343         export_file = None
344         dialog = gtk.FileChooserDialog("Export Keys (Public only) into a File",
345                                        self.mainwin,
346                                        gtk.FILE_CHOOSER_ACTION_SAVE,
347                                        (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
348                                         gtk.STOCK_OK, gtk.RESPONSE_OK))
349         while dialog.run() == gtk.RESPONSE_OK:
350             filename = dialog.get_filename()
351             if os.path.exists(filename):
352                 if os.path.isdir(filename):
353                     self.error_message("%s is a directory!" % filename,
354                                        dialog)
355                     continue
356                 elif not self.yesno_message("%s exists. Override?" % filename,
357                                             dialog):
358                     continue
359
360             # FIXME. Verify that file can be written to
361             export_file = open(filename, "wb")
362             break
363         dialog.destroy()
364         if export_file == None:
365             return
366
367         key_list = []
368         selection.selected_foreach(self.collect_keys, key_list)
369         expkeys = Data()
370         for key, row in key_list:
371             self.context.op_export(key.subkeys[0].fpr, 0, expkeys)
372         expkeys.seek(0,0)
373         export_file.write(expkeys.read())
374         export_file.close()
375             
376     def on_export_keys_activate(self, obj):
377         self.context.set_armor(0)
378         self.export_keys()
379
380     def on_export_keys_text_activate(self, obj):
381         self.context.set_armor(1)
382         self.export_keys()
383
384     def on_import_keys_activate(self, obj):
385         import_file = None
386         dialog = gtk.FileChooserDialog("Import Keys from a File",
387                                        self.mainwin,
388                                        gtk.FILE_CHOOSER_ACTION_OPEN,
389                                        (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
390                                         gtk.STOCK_OK, gtk.RESPONSE_OK))
391         while dialog.run() == gtk.RESPONSE_OK:
392             filename = dialog.get_filename()
393             if os.path.exists(filename):
394                 if os.path.isdir(filename):
395                     self.error_message("%s is a directory!" % filename,
396                                        dialog)
397                 else:
398                     # FIXME. Verify that file can be open.
399                     import_file = filename
400                     break
401             else:
402                 self.error_message("%s does not exist." % filename,
403                                    dialog)
404         dialog.destroy()
405         if import_file == None:
406             return
407
408         impkeys = Data(file=import_file)
409         status = self.context.op_import(impkeys)
410         if status:
411             self.error_message("Import return an error message %d" % status)
412         result = self.context.op_import_result()
413         if result.considered == 0:
414             self.error_message("There's no keys in the file.")
415         # FIXME. Instead of rereading everything we could find out what's new
416         # from the result based on the ORed value of impkey:
417         # constants.import.NEW    - The key was new.
418         # constants.import.UID    - The key contained new user IDs.
419         # constants.import.SIG    - The key contained new signatures.
420         # constants.import.SUBKEY - The key contained new sub keys.
421         # constants.import.SECRET - The key contained a secret key.
422         # It would be nice to highlight new things as well.
423         self.load_keys()
424         #if result:
425         #    impkey = result.imports
426         #    while impkey:
427         #        if impkey.status & constants.import.NEW:
428         #            self.add_key(self.context.get_key(impkey.fpr, 0))
429         #        impkey = impkey.next
430
431     def on_delete_activate(self, obj):
432         "self.on_delete_activate(obj) - callback for key deletion request"
433         selection = self.treeview.get_selection()
434         if selection.count_selected_rows() > 0:
435             key_list = []
436             selection.selected_foreach(self.collect_keys, key_list)
437             
438             message = "Delete selected keys?\n"
439             for key, row in key_list:
440                 message += "\n%s\t" % key.subkeys[0].keyid
441                 if key.uids: message += key.uids[0].uid
442                 else:        message += "<undefined>"                
443             if self.yesno_message(message):
444                 for key, row in key_list:
445                     self.context.op_delete(key, 1)
446                     row.model.remove(row.iter)
447
448     def get_widget_values(self, widgets):
449         "Create an array of values from widgets' getter methods"
450         return [getattr(self.wtree.get_widget(w),"get_"+f)() for w,f in widgets]
451
452     def set_widget_values(self, widgets, values):
453         "Set values using widgets' setter methods"
454         for (w,f), v in zip(widgets, values):
455             # ComboBox.set_active_iter(None) does not reset active. Fixing.
456             if f == "active_iter" and v == None:
457                 f, v = "active", -1
458             getattr(self.wtree.get_widget(w), "set_"+f)(v)
459
460     def key_type_changed(self, which):
461         """self.key_type_changed([\"key\"|\"subkey\"]) - helper function to
462         adjust allowed key length based on the Algorithm selected"""
463         (key_type,) = self.get_widget_values([(which+"_type", "active_iter")])
464         if key_type:
465             key_type = self.wtree.get_widget(which+"_type").get_model(
466                 ).get_value(key_type,0)
467             length_widget = self.wtree.get_widget(which+"_length")
468             if key_type == "DSA":
469                 length_widget.set_range(1024, 1024)
470                 length_widget.set_value(1024)
471             elif key_type == "RSA" or key_type == "ELG-E":
472                 length_widget.set_range(1024, 4096)
473
474     def on_key_type_changed(self, obj):
475         self.key_type_changed("key")
476
477     def on_subkey_type_changed(self, obj):
478         self.key_type_changed("subkey")
479
480     def on_expire_calendar_day_selected(self, obj):
481         "Callback for selecting a day on the calendar"
482         (year, month, day)=self.wtree.get_widget("expire_calendar").get_date()
483         expander = self.wtree.get_widget("expire_date")
484         # Past dates means no expiration date
485         if time.localtime() < (year, month+1, day):
486             expander.set_label("%04d-%02d-%02d" % (year, month+1, day))
487         else:
488             expander.set_label("Unlimited")
489         expander.set_expanded(False)
490
491     def on_generate_activate(self, obj):
492         "Callback to generate new key"
493         
494         # Set of (widget, common suffix of getter/setter function) tuples
495         # from the GenerateDialog prompt for new key properties.
496         widgets = [
497             ("key_type", "active_iter"),
498             ("key_length", "value"),
499             ("key_encrypt", "active"),
500             ("key_sign", "active"),
501             ("subkey_type", "active_iter"),
502             ("subkey_length", "value"),
503             ("subkey_encrypt", "active"),
504             ("subkey_sign", "active"),
505             ("name_real", "text"),
506             ("name_comment", "text"),
507             ("name_email", "text"),
508             ("expire_date", "label"),
509             ("passphrase", "text"),
510             ("passphrase_repeat", "text")
511             ]
512
513         saved_values = self.get_widget_values(widgets)
514         result = None
515         dialog = self.wtree.get_widget("GenerateDialog")
516         if dialog.run() == gtk.RESPONSE_OK:
517             (key_type, key_length, key_encrypt, key_sign,
518              subkey_type, subkey_length, subkey_encrypt, subkey_sign,
519              name_real, name_comment, name_email, expire_date,
520              passphrase, passphrase2) = self.get_widget_values(widgets)
521             if key_type and passphrase == passphrase2:
522                 key_type = self.wtree.get_widget("key_type").get_model(
523                     ).get_value(key_type,0)
524                 result = "<GnupgKeyParms format=\"internal\">\n"
525                 result += "Key-Type: %s\n" % key_type
526                 result += "Key-Length: %d\n" % int(key_length)
527                 if key_encrypt or key_sign:
528                     result += "Key-Usage:" + \
529                               ((key_encrypt and " encrypt") or "") + \
530                               ((key_sign and " sign") or "") + "\n"
531                 if subkey_type:
532                     subkey_type=self.wtree.get_widget("subkey_type").get_model(
533                         ).get_value(subkey_type,0)
534                     result += "Subkey-Type: %s\n" % subkey_type
535                     result += "Subkey-Length: %d\n" % int(subkey_length)
536                     if subkey_encrypt or subkey_sign:
537                         result += "Subkey-Usage:" + \
538                                   ((subkey_encrypt and " encrypt") or "") + \
539                                   ((subkey_sign and " sign") or "") + "\n"
540                 if name_real:
541                     result += "Name-Real: %s\n" % name_real
542                 if name_comment:
543                     result += "Name-Comment: %s\n" % name_comment
544                 if name_email:
545                     result += "Name-Email: %s\n" % name_email
546                 if passphrase:
547                     result += "Passphrase: %s\n" % passphrase
548                 if expire_date != "Unlimited":
549                     result += "Expire-Date: %s\n" % expire_date
550                 else:
551                     result += "Expire-Date: 0\n"
552                 result += "</GnupgKeyParms>\n"
553             else:
554                 if not key_type:
555                     message = "Type of the primary key is not specified."
556                 elif passphrase != passphrase2:
557                     message = "Passphrases do not match."
558                 else:
559                     message = "Unknown error."
560                 self.error_message(message, dialog)
561         else:
562             self.set_widget_values(widgets, saved_values)
563
564         dialog.hide()
565         if result:
566             # Setup and show progress Dialog
567             self.progress = ""
568             self.progress_entry = self.wtree.get_widget(
569                 "progress_entry").get_buffer()
570             self.progress_entry.set_text("")
571             gobject.timeout_add(500, self.update_progress)
572             self.wtree.get_widget("GenerateProgress").show_all()
573             # Start asynchronous key generation
574             self.context.op_genkey_start(result, None, None)
575
576     def gen_progress(self, what=None, type=None, current=None,
577                      total=None, hook=None):
578         "Gpg's progress_cb"
579         if self.progress != None:
580             self.progress += "%c" % type
581         else:
582             sys.stderr.write("%c" % type)
583
584     def update_progress(self):
585         "Timeout callback to yeild to gpg and update progress Dialog view"
586         status = self.context.wait(False)
587         if status == None:
588             self.progress_entry.set_text(self.progress)
589             return True
590         elif status == 0:
591             fpr = self.context.op_genkey_result().fpr
592             self.add_key(self.context.get_key(fpr, 0), True)
593         self.wtree.get_widget("GenerateProgress").hide()
594         self.progress = None
595
596         if status:
597             self.error_message("Got an error during key generation:\n%s" %
598                                errors.GPGMEError(status).getstring())
599
600         # Let callback to be removed.
601         return False
602
603     def on_generating_close_clicked(self, obj):
604         # Request cancelation of the outstanding asynchronous call
605         self.context.cancel()
606
607     def get_password(self, hint, desc, hook):
608         "Gpg's password_cb"
609         dialog = self.wtree.get_widget("PasswordDialog")
610         label = self.wtree.get_widget("pwd_prompt")
611         entry = self.wtree.get_widget("password")
612         label.set_text("Please supply %s's password%s:" %
613                        (hint, (hook and (' '+hook)) or ''))
614         if dialog.run() == gtk.RESPONSE_OK:
615             result = entry.get_text()
616         else:
617             result = ""
618         entry.set_text("")
619         dialog.hide()
620         return result
621
622     def on_reload_activate(self, obj):
623         self.load_keys()
624
625     def on_about_activate(self, obj):
626         about = self.wtree.get_widget("AboutDialog")
627         about.run()
628         about.hide()
629
630     def __init__(self, path):
631         "new(path) path - location of the glade file"
632         gladefile = os.path.join(path, "PyGtkGpgKeys.glade")
633         self.wtree = gtk.glade.XML(gladefile)
634         self.wtree.signal_autoconnect(self)
635
636         self.mainwin = self.wtree.get_widget("GPGAdminWindow")
637         self.treeview = self.wtree.get_widget("GPGKeysView")
638
639         self.model = gtk.TreeStore(*[x.type for x in visible_columns +
640                                      helper_columns])        
641
642         self.context = Context()
643         self.context.set_passphrase_cb(self.get_password, "")
644         self.progress = None
645         self.context.set_progress_cb(self.gen_progress, None)
646         # Use mode.SIGS to include signatures in the list.
647         self.context.set_keylist_mode(mode.SIGS)
648         self.load_keys(True)
649
650         self.treeview.set_model(self.model)
651         self.treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
652         self.add_columns()
653
654         gtk.main()
655
656     def on_Exit(self, obj):
657         gtk.main_quit()
658
659 try:
660     # Glade file is expected to be in the same location as this script
661     PyGtkGpgKeys(os.path.dirname(sys.argv[0]))
662 except IOError as message:
663     print("%s:%s" %(sys.argv[0], message))