First cut at supporting trust anchors
[moonshot-ui.git] / src / moonshot-identity-management-view.vala
1 /*
2  * Copyright (c) 2011-2016, JANET(UK)
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * 3. Neither the name of JANET(UK) nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31 */
32 using Gee;
33 using Gtk;
34
35 public class IdentityManagerView : Window {
36     static MoonshotLogger logger = get_logger("IdentityManagerView");
37
38     private const int WINDOW_WIDTH = 700;
39     private const int WINDOW_HEIGHT = 500;
40     protected IdentityManagerApp parent_app;
41     #if OS_MACOS
42         public OSXApplication osxApp;
43     #endif
44     private UIManager ui_manager = new UIManager();
45     private Entry search_entry;
46     private VBox vbox_right;
47     private CustomVBox custom_vbox;
48     private VBox service_prompt_vbox;
49     private Label no_identity_title;
50     private Button edit_button;
51     private Button remove_button;
52
53     private Button send_button;
54     
55     private Gtk.ListStore* listmodel;
56     private TreeModelFilter filter;
57
58     internal IdentityManagerModel identities_manager;
59     private unowned SList<IdCard>    candidates;
60
61     public GLib.Queue<IdentityRequest> request_queue;
62
63     internal CheckButton remember_identity_binding = null;
64
65     private enum Columns
66     {
67         IDCARD_COL,
68         LOGO_COL,
69         ISSUER_COL,
70         USERNAME_COL,
71         PASSWORD_COL,
72         N_COLUMNS
73     }
74
75     private const string menu_layout =
76     "<menubar name='MenuBar'>" +
77     "        <menu name='HelpMenu' action='HelpMenuAction'>" +
78     "             <menuitem name='About' action='AboutAction' />" +
79     "        </menu>" +
80     "</menubar>";
81
82     public IdentityManagerView(IdentityManagerApp app) {
83         parent_app = app;
84         #if OS_MACOS
85             osxApp = OSXApplication.get_instance();
86         #endif
87         identities_manager = parent_app.model;
88         request_queue = new GLib.Queue<IdentityRequest>();
89         this.title = "Moonshot Identity Selector";
90         this.set_position(WindowPosition.CENTER);
91         set_default_size(WINDOW_WIDTH, WINDOW_HEIGHT);
92         build_ui();
93         setup_list_model(); 
94         load_id_cards(); 
95         connect_signals();
96     }
97     
98     public void on_card_list_changed() {
99         load_id_cards();
100     }
101     
102     private bool visible_func(TreeModel model, TreeIter iter)
103     {
104         IdCard id_card;
105
106         model.get(iter,
107                   Columns.IDCARD_COL, out id_card);
108
109         if (id_card == null)
110             return false;
111         
112         if (candidates != null)
113         {
114             bool is_candidate = false;
115             foreach (IdCard candidate in candidates)
116             {
117                 if (candidate == id_card)
118                     is_candidate = true;
119             }
120             if (!is_candidate)
121                 return false;
122         }
123         
124         string entry_text = search_entry.get_text();
125         if (entry_text == null || entry_text == "")
126         {
127             return true;
128         }
129
130         foreach (string search_text in entry_text.split(" "))
131         {
132             if (search_text == "")
133                 continue;
134          
135
136             string search_text_casefold = search_text.casefold();
137
138             if (id_card.issuer != null)
139             {
140                 string issuer_casefold = id_card.issuer;
141
142                 if (issuer_casefold.contains(search_text_casefold))
143                     return true;
144             }
145
146             if (id_card.display_name != null)
147             {
148                 string display_name_casefold = id_card.display_name.casefold();
149               
150                 if (display_name_casefold.contains(search_text_casefold))
151                     return true;
152             }
153             
154             if (id_card.services.size > 0)
155             {
156                 foreach (string service in id_card.services)
157                 {
158                     string service_casefold = service.casefold();
159
160                     if (service_casefold.contains(search_text_casefold))
161                         return true;
162                 }
163             }
164         }
165         return false;
166     }
167
168     private void setup_list_model()
169     {
170         this.listmodel = new Gtk.ListStore(Columns.N_COLUMNS, typeof(IdCard),
171                                            typeof(Gdk.Pixbuf),
172                                            typeof(string),
173                                            typeof(string),
174                                            typeof(string));
175         this.filter = new TreeModelFilter(listmodel, null);
176
177         filter.set_visible_func(visible_func);
178     }
179
180     private void search_entry_icon_press_cb(EntryIconPosition pos, Gdk.Event event)
181     {
182         if (pos == EntryIconPosition.PRIMARY)
183         {
184             print("Search entry icon pressed\n");
185         }
186         else
187         {
188             this.search_entry.set_text("");
189         }
190     }
191
192     private void search_entry_text_changed_cb()
193     {
194         this.filter.refilter();
195         redraw_id_card_widgets();
196
197         var has_text = this.search_entry.get_text_length() > 0;
198         this.search_entry.set_icon_sensitive(EntryIconPosition.PRIMARY, has_text);
199         this.search_entry.set_icon_sensitive(EntryIconPosition.SECONDARY, has_text);
200     }
201
202     private bool search_entry_key_press_event_cb(Gdk.EventKey e)
203     {
204         if(Gdk.keyval_name(e.keyval) == "Escape")
205             this.search_entry.set_text("");
206
207         // Continue processing this event, since the
208         // text entry functionality needs to see it too.
209         return false;
210     }
211
212     private void load_id_cards() {
213         logger.trace("load_id_cards");
214
215         string current_idcard_nai = null;
216         if (this.custom_vbox.current_idcard != null) {
217             current_idcard_nai = custom_vbox.current_idcard.id_card.nai;
218             custom_vbox.current_idcard = null;
219         }
220
221         custom_vbox.clear();
222         this.listmodel->clear();
223         LinkedList<IdCard> card_list = identities_manager.get_card_list() ;
224         if (card_list == null) {
225             return;
226         }
227
228         foreach (IdCard id_card in card_list) {
229             logger.trace(@"load_id_cards: Adding card with display name '$(id_card.display_name)'");
230             add_id_card_data(id_card);
231             IdCardWidget id_card_widget = add_id_card_widget(id_card);
232             if (id_card_widget.id_card.nai == current_idcard_nai) {
233                 // fill_details(id_card_widget.id_card);
234                 id_card_widget.expand();
235             }
236         }
237     }
238     
239     private IdCard update_id_card_data(IdentityDialog dialog, IdCard id_card)
240     {
241         id_card.display_name = dialog.display_name;
242         id_card.issuer = dialog.issuer;
243         id_card.username = dialog.username;
244         id_card.password = dialog.password;
245         id_card.store_password = dialog.store_password;
246
247         id_card.update_services_from_list(dialog.get_services());
248
249         return id_card;
250     }
251
252     private void add_id_card_data(IdCard id_card)
253     {
254         TreeIter   iter;
255         Gdk.Pixbuf pixbuf;
256         this.listmodel->append(out iter);
257         pixbuf = get_pixbuf(id_card);
258         listmodel->set(iter,
259                        Columns.IDCARD_COL, id_card,
260                        Columns.LOGO_COL, pixbuf,
261                        Columns.ISSUER_COL, id_card.issuer,
262                        Columns.USERNAME_COL, id_card.username,
263                        Columns.PASSWORD_COL, id_card.password);
264     }
265
266     private void remove_id_card_data(IdCard id_card)
267     {
268         TreeIter iter;
269         string issuer;
270
271         if (listmodel->get_iter_first(out iter))
272         {
273             do
274             {
275                 listmodel->get(iter,
276                                Columns.ISSUER_COL, out issuer);
277
278                 if (id_card.issuer == issuer)
279                 {
280                     listmodel->remove(iter);
281                     break;
282                 }
283             }
284             while (listmodel->iter_next(ref iter));
285         }
286     }
287
288     private IdCardWidget add_id_card_widget(IdCard id_card)
289     {
290         var id_card_widget = new IdCardWidget(id_card);
291         this.custom_vbox.add_id_card_widget(id_card_widget);
292         id_card_widget.expanded.connect(this.widget_selected_cb);
293         id_card_widget.collapsed.connect(this.widget_unselected_cb);
294         return id_card_widget;
295     }
296
297     private void widget_selected_cb(IdCardWidget id_card_widget)
298     {
299         this.remove_button.set_sensitive(true);
300         this.edit_button.set_sensitive(true);
301         this.custom_vbox.receive_expanded_event(id_card_widget);
302
303         if (this.request_queue.length > 0)
304              this.send_button.set_sensitive(true);
305     }
306
307     private void widget_unselected_cb(IdCardWidget id_card_widget)
308     {
309         this.remove_button.set_sensitive(false);
310         this.edit_button.set_sensitive(false);
311         this.custom_vbox.receive_collapsed_event(id_card_widget);
312
313         this.send_button.set_sensitive(false);
314     }
315
316     public bool add_identity(IdCard id_card, bool force_flat_file_store)
317     {
318         #if OS_MACOS
319         /* 
320          * TODO: We should have a confirmation dialog, but currently it will crash on Mac OS
321          * so for now we will install silently
322          */
323         var ret = Gtk.ResponseType.YES;
324         #else
325         Gtk.MessageDialog dialog;
326         IdCard? prev_id = identities_manager.find_id_card(id_card.nai, force_flat_file_store);
327         logger.trace("add_identity(flat=%s, card='%s'): find_id_card returned %s"
328                      .printf(force_flat_file_store.to_string(), id_card.display_name, (prev_id != null ? "non-null" : "null")));
329         if (prev_id!=null) {
330             int flags = prev_id.Compare(id_card);
331             logger.trace("add_identity: compare returned " + flags.to_string());
332             if (flags == 0) {
333                 return false; // no changes, no need to update
334             } else if ((flags & (1 << IdCard.DiffFlags.DISPLAY_NAME)) != 0) {
335                 dialog = new Gtk.MessageDialog(this,
336                                                Gtk.DialogFlags.DESTROY_WITH_PARENT,
337                                                Gtk.MessageType.QUESTION,
338                                                Gtk.ButtonsType.YES_NO,
339                                                _("Would you like to replace ID Card '%s' using nai '%s' with the new ID Card '%s'?"),
340                                                prev_id.display_name,
341                                                prev_id.nai,
342                                                id_card.display_name);
343             } else {
344                 dialog = new Gtk.MessageDialog(this,
345                                                Gtk.DialogFlags.DESTROY_WITH_PARENT,
346                                                Gtk.MessageType.QUESTION,
347                                                Gtk.ButtonsType.YES_NO,
348                                                _("Would you like to update ID Card '%s' using nai '%s'?"),
349                                                id_card.display_name,
350                                                id_card.nai);
351             }
352         } else {
353             dialog = new Gtk.MessageDialog(this,
354                                            Gtk.DialogFlags.DESTROY_WITH_PARENT,
355                                            Gtk.MessageType.QUESTION,
356                                            Gtk.ButtonsType.YES_NO,
357                                            _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
358                                            id_card.display_name);
359         }
360         var ret = dialog.run();
361         dialog.destroy();
362         #endif
363
364         if (ret == Gtk.ResponseType.YES) {
365             this.identities_manager.add_card(id_card, force_flat_file_store);
366             return true;
367         }
368         return false;
369     }
370
371     private void add_identity_cb()
372     {
373         var dialog = new IdentityDialog(this);
374         int result = ResponseType.CANCEL;
375         while (!dialog.complete)
376             result = dialog.run();
377
378         switch (result) {
379         case ResponseType.OK:
380             this.identities_manager.add_card(update_id_card_data(dialog, new IdCard()), false);
381             break;
382         default:
383             break;
384         }
385         dialog.destroy();
386     }
387
388     private void edit_identity_cb(IdCard card)
389     {
390         var dialog = new IdentityDialog.with_idcard(card, _("Edit Identity"), this);
391         int result = ResponseType.CANCEL;
392         while (!dialog.complete)
393             result = dialog.run();
394
395         switch (result) {
396         case ResponseType.OK:
397             this.identities_manager.update_card(update_id_card_data(dialog, card));
398             break;
399         default:
400             break;
401         }
402         dialog.destroy();
403     }
404
405     private void remove_identity(IdCardWidget id_card_widget)
406     {
407         var id_card = id_card_widget.id_card;
408         this.custom_vbox.remove_id_card_widget(id_card_widget);
409
410         this.identities_manager.remove_card(id_card);
411
412         // Nothing is selected, so disable buttons
413         this.edit_button.set_sensitive(false);
414         this.remove_button.set_sensitive(false);
415         this.send_button.set_sensitive(false);
416     }
417
418     private void redraw_id_card_widgets()
419     {
420         logger.trace("redraw_id_card_widgets");
421
422         TreeIter iter;
423         IdCard id_card;
424
425         this.custom_vbox.clear();
426
427         if (filter.get_iter_first(out iter))
428         {
429             do
430             {
431                 filter.get(iter,
432                            Columns.IDCARD_COL, out id_card);
433
434                 add_id_card_widget(id_card);
435             }
436             while (filter.iter_next(ref iter));
437         }
438     }
439
440     private void remove_identity_cb(IdCardWidget id_card_widget)
441     {
442         var id_card = id_card_widget.id_card;
443
444         bool remove = WarningDialog.confirm(this, 
445                                             Markup.printf_escaped(
446                                                 "<span font-weight='heavy'>You are about to remove the identity '%s'.</span>",
447                                                 id_card.display_name)
448                                             + "\n\nAre you sure you want to do this?",
449                                             "delete_idcard");
450         if (remove) 
451             remove_identity(id_card_widget);
452     }
453
454     private void set_prompting_service(string service)
455     {
456         clear_selection_prompts();
457
458         var prompting_service = new Label(_("Identity requested for service:\n%s").printf(service));
459         prompting_service.set_line_wrap(true);
460
461         // left-align
462         prompting_service.set_alignment(0, (float )0.5);
463
464         var selection_prompt = new Label(_("Select your identity:"));
465         selection_prompt.set_alignment(0, 1);
466
467         this.service_prompt_vbox.pack_start(prompting_service, false, false, 12);
468         this.service_prompt_vbox.pack_start(selection_prompt, false, false, 2);
469         this.service_prompt_vbox.show_all();
470     }
471
472     private void clear_selection_prompts()
473     {
474         var list = service_prompt_vbox.get_children();
475         foreach (Widget w in list)
476         {
477             service_prompt_vbox.remove(w);
478         }
479     }
480
481
482     public void queue_identity_request(IdentityRequest request)
483     {
484         if (this.request_queue.is_empty())
485         { /* setup widgets */
486             candidates = request.candidates;
487             filter.refilter();
488             redraw_id_card_widgets();
489             set_prompting_service(request.service);
490             remember_identity_binding.show();
491             make_visible();
492         }
493         this.request_queue.push_tail(request);
494     }
495
496
497     /** Makes the window visible, or at least, notifies the user that the window
498      * wants to be visible.
499      *
500      * This differs from show() in that show() does not guarantee that the 
501      * window will be moved to the foreground. Actually, neither does this
502      * method, because the user's settings and window manager may affect the
503      * behavior significantly.
504      */
505     public void make_visible()
506     {
507         set_urgency_hint(true);
508         present();
509     }
510
511     public IdCard check_add_password(IdCard identity, IdentityRequest request, IdentityManagerModel model)
512     {
513         logger.trace(@"check_add_password");
514         IdCard retval = identity;
515         bool idcard_has_pw = (identity.password != null) && (identity.password != "");
516         bool request_has_pw = (request.password != null) && (request.password != "");
517         if ((!idcard_has_pw) && (!identity.is_no_identity())) {
518             if (request_has_pw) {
519                 identity.password = request.password;
520                 retval = model.update_card(identity);
521             } else {
522                 var dialog = new AddPasswordDialog(identity, request);
523                 var result = dialog.run();
524
525                 switch (result) {
526                 case ResponseType.OK:
527                     identity.password = dialog.password;
528                     identity.store_password = dialog.remember;
529                     if (dialog.remember)
530                         identity.temporary = false;
531                     retval = model.update_card(identity);
532                     break;
533                 default:
534                     identity = null;
535                     break;
536                 }
537                 dialog.destroy();
538             }
539         }
540         return retval;
541     }
542
543     private void send_identity_cb(IdCard id)
544     {
545         return_if_fail(request_queue.length > 0);
546
547         if (!check_and_confirm_trust_anchor(id)) {
548             // Allow user to pick again
549             return;
550         }
551
552         var request = this.request_queue.pop_head();
553         var identity = check_add_password(id, request, identities_manager);
554         send_button.set_sensitive(false);
555
556         candidates = null;
557       
558         if (this.request_queue.is_empty())
559         {
560             candidates = null;
561             clear_selection_prompts();
562             if (!parent_app.explicitly_launched) {
563 // The following occasionally causes the app to exit without sending the dbus
564 // reply, so for now we just don't exit
565 //                Gtk.main_quit();
566 // just hide instead
567                 this.hide();
568             }
569         } else {
570             IdentityRequest next = this.request_queue.peek_head();
571             candidates = next.candidates;
572             set_prompting_service(next.service);
573         }
574         filter.refilter();
575         redraw_id_card_widgets();
576
577         if ((identity != null) && (!identity.is_no_identity()))
578             parent_app.default_id_card = identity;
579
580         request.return_identity(identity, remember_identity_binding.active);
581
582         remember_identity_binding.active = false;
583         remember_identity_binding.hide();
584     }
585
586     private bool check_and_confirm_trust_anchor(IdCard id)
587     {
588         if (!id.trust_anchor.is_empty() && id.trust_anchor.get_anchor_type() == TrustAnchor.TYPE_ENTERPRISE) {
589             if (get_string_setting("TrustAnchors", id.nai) != id.trust_anchor.server_cert) {
590
591                 bool ret = false;
592                 int result = ResponseType.CANCEL;
593                 var dialog = new TrustAnchorDialog(id, this);
594                 while (!dialog.complete)
595                     result = dialog.run();
596
597                 switch (result) {
598                 case ResponseType.OK:
599                     set_string_setting("TrustAnchors", id.nai, id.trust_anchor.server_cert);
600                     ret = true;
601                     break;
602                 default:
603                     break;
604                 }
605
606                 dialog.destroy();
607                 return ret;
608             }
609         }
610         return true;
611     }
612
613
614     // private void label_make_bold(Label label)
615     // {
616     //     var font_desc = new Pango.FontDescription();
617
618     //     font_desc.set_weight(Pango.Weight.BOLD);
619
620     //     /* This will only affect the weight of the font, the rest is
621     //      * from the current state of the widget, which comes from the
622     //      * theme or user prefs, since the font desc only has the
623     //      * weight flag turned on.
624     //      */
625     //     label.modify_font(font_desc);
626     // }
627
628     private void on_about_action()
629     {
630         string copyright = "Copyright 2011, 2016 JANET";
631
632         string license =
633         """
634 Copyright (c) 2011, 2016 JANET(UK)
635 All rights reserved.
636
637 Redistribution and use in source and binary forms, with or without
638 modification, are permitted provided that the following conditions
639 are met:
640
641 1. Redistributions of source code must retain the above copyright
642    notice, this list of conditions and the following disclaimer.
643
644 2. Redistributions in binary form must reproduce the above copyright
645    notice, this list of conditions and the following disclaimer in the
646    documentation and/or other materials provided with the distribution.
647
648 3. Neither the name of JANET(UK) nor the names of its contributors
649    may be used to endorse or promote products derived from this software
650    without specific prior written permission.
651
652 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
653 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
654 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
655 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
656 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
657 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
658 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
659 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
660 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
661 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
662 SUCH DAMAGE.
663 """;
664
665         Gtk.show_about_dialog(this,
666                               "comments", _("Moonshot project UI"),
667                               "copyright", copyright,
668                               "website", Config.PACKAGE_URL,
669                               "version", Config.PACKAGE_VERSION,
670                               "license", license,
671                               "website-label", _("Visit the Moonshot project web site"),
672                               "translator-credits", _("translator-credits"),
673                               null
674             );
675     }
676
677     private Gtk.ActionEntry[] create_actions() {
678         Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
679
680         Gtk.ActionEntry helpmenu = { "HelpMenuAction",
681                                      null,
682                                      N_("_Help"),
683                                      null, null, null };
684         actions += helpmenu;
685         Gtk.ActionEntry about = { "AboutAction",
686                                   #if VALA_0_12
687                                   Stock.ABOUT,
688                                   #else
689                                   STOCK_ABOUT,
690                                   #endif
691                                   N_("About"),
692                                   null,
693                                   N_("About this application"),
694                                   on_about_action };
695         actions += about;
696
697         return actions;
698     }
699
700
701     private void create_ui_manager()
702     {
703         Gtk.ActionGroup action_group = new Gtk.ActionGroup("GeneralActionGroup");
704         action_group.add_actions(create_actions(), this);
705         ui_manager.insert_action_group(action_group, 0);
706         try
707         {
708             ui_manager.add_ui_from_string(menu_layout, -1);
709         }
710         catch (Error e)
711         {
712             stderr.printf("%s\n", e.message);
713             logger.error("create_ui_manager: Caught error: " + e.message);
714         }
715         ui_manager.ensure_update();
716     }
717
718     private void build_ui()
719     {
720         // Note: On Debian7/Gtk+2, the menu bar remains gray. This doesn't happen on Debian8/Gtk+3.
721         Gdk.Color white = Gdk.Color();
722         white.red = white.green = white.blue = 65535;
723         this.modify_bg(StateType.NORMAL, white);
724
725         create_ui_manager();
726
727         this.search_entry = new Entry();
728
729         set_atk_name_description(search_entry, _("Search entry"), _("Search for a specific ID Card"));
730         this.search_entry.set_icon_from_pixbuf(EntryIconPosition.PRIMARY,
731                                                find_icon_sized("edit-find", Gtk.IconSize.MENU));
732         this.search_entry.set_icon_tooltip_text(EntryIconPosition.PRIMARY,
733                                                 _("Search for an identity or service"));
734         this.search_entry.set_icon_sensitive(EntryIconPosition.PRIMARY, false);
735
736         this.search_entry.set_icon_from_pixbuf(EntryIconPosition.SECONDARY,
737                                                find_icon_sized("process-stop", Gtk.IconSize.MENU));
738         this.search_entry.set_icon_tooltip_text(EntryIconPosition.SECONDARY,
739                                                 _("Clear the current search"));
740         this.search_entry.set_icon_sensitive(EntryIconPosition.SECONDARY, false);
741
742
743         this.search_entry.icon_press.connect(search_entry_icon_press_cb);
744         this.search_entry.notify["text"].connect(search_entry_text_changed_cb);
745         this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
746         this.search_entry.set_width_chars(30);
747
748
749         this.custom_vbox = new CustomVBox(this, false, 2);
750
751         var viewport = new Viewport(null, null);
752         viewport.set_border_width(2);
753         viewport.set_shadow_type(ShadowType.NONE);
754         viewport.add(custom_vbox);
755         var id_scrollwin = new ScrolledWindow(null, null);
756         id_scrollwin.set_policy(PolicyType.NEVER, PolicyType.AUTOMATIC);
757         id_scrollwin.set_shadow_type(ShadowType.IN);
758         id_scrollwin.add_with_viewport(viewport);
759
760         service_prompt_vbox = new VBox(false, 0);
761
762         var vbox_left = new VBox(false, 0);
763         vbox_left.pack_start(service_prompt_vbox, false, false, 12);
764
765         var search_hbox = new HBox(false, 6);
766         search_hbox.pack_end(search_entry, false, false, 0);
767         //// var search_label = new Label(_("Search:"));
768         //// search_label.set_alignment(1, (float) 0.5);
769         //// set_atk_relation(search_label, search_entry, Atk.RelationType.LABEL_FOR);
770         //// search_hbox.pack_end(search_label, false, false, 6);
771
772         var full_search_label = new Label(_("Search for an identity or service"));
773         full_search_label.set_alignment(1, 0);
774         var search_vbox = new VBox(false, 4);
775         search_vbox.pack_start(full_search_label, false, false, 0);
776         search_vbox.pack_start(search_hbox, false, false, 0);
777
778         var inner_left_vbox = new VBox(false, 6);
779         inner_left_vbox.pack_start(search_vbox, false, false, 6);
780 //        inner_left_vbox.pack_start(selection_prompt, false, false, 6);
781         inner_left_vbox.pack_start(id_scrollwin, true, true, 0);
782
783         var id_and_button_box = new HBox(false, 6);
784         id_and_button_box.pack_start(inner_left_vbox, true, true, 6);
785         vbox_left.pack_start(id_and_button_box, true, true, 0);
786         // vbox_left.pack_start(prompting_service, false, false, 6);
787         vbox_left.set_size_request(WINDOW_WIDTH, 0);
788
789         this.no_identity_title = new Label(_("No Identity: Send this identity to services which should not use Moonshot"));
790         no_identity_title.set_alignment(0, (float ) 0.5);
791         no_identity_title.set_line_wrap(true);
792         no_identity_title.show();
793
794         this.vbox_right = new VBox(false, 6);
795
796         var add_button = new Button.with_label(_("Add"));
797         add_button.clicked.connect((w) => {add_identity_cb();});
798
799         this.edit_button = new Button.with_label(_("Edit"));
800         edit_button.clicked.connect((w) => {edit_identity_cb(custom_vbox.current_idcard.id_card);});
801         edit_button.set_sensitive(false);
802
803         this.remove_button = new Button.with_label(_("Remove"));
804         remove_button.clicked.connect((w) => {remove_identity_cb(custom_vbox.current_idcard);});
805         remove_button.set_sensitive(false);
806
807         this.send_button = new Button.with_label(_("Send"));
808         send_button.clicked.connect((w) => {send_identity_cb(custom_vbox.current_idcard.id_card);});
809         // send_button.set_visible(false);
810         send_button.set_sensitive(false);
811
812         var empty_box = new VBox(false, 0);
813         empty_box.set_size_request(0, 0);
814         vbox_right.pack_start(empty_box, false, false, 14);
815         vbox_right.pack_start(add_button, false, false, 6);
816         vbox_right.pack_start(edit_button, false, false, 6);
817         vbox_right.pack_start(remove_button, false, false, 6);
818         vbox_right.pack_start(send_button, false, false, 24);
819
820         id_and_button_box.pack_start(vbox_right, false, false, 0);
821
822         var main_vbox = new VBox(false, 0);
823
824         // Note: This places a border above the menubar. Is that what we want?
825         main_vbox.set_border_width(12);
826
827 #if OS_MACOS
828         // hide the  File | Quit menu item which is now on the Mac Menu
829 //        Gtk.Widget quit_item =  this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
830 //        quit_item.hide();
831         
832         Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
833         menushell.modify_bg(StateType.NORMAL, white);
834
835         osxApp.set_menu_bar(menushell);
836         osxApp.set_use_quartz_accelerators(true);
837         osxApp.sync_menu_bar();
838         osxApp.ready();
839 #else
840         var menubar = this.ui_manager.get_widget("/MenuBar");
841         main_vbox.pack_start(menubar, false, false, 0);
842         menubar.modify_bg(StateType.NORMAL, white);
843 #endif
844         main_vbox.pack_start(vbox_left, true, true, 0);
845
846         remember_identity_binding = new CheckButton.with_label(_("Remember my identity choice for this service"));
847         remember_identity_binding.active = false;
848         main_vbox.pack_start(remember_identity_binding, false, false, 6);
849
850         add(main_vbox);
851         main_vbox.show_all();
852
853         if (this.request_queue.length == 0)
854             remember_identity_binding.hide();
855     } 
856
857     private void set_atk_name_description(Widget widget, string name, string description)
858     {
859         var atk_widget = widget.get_accessible();
860
861         atk_widget.set_name(name);
862         atk_widget.set_description(description);
863     }
864
865     private void connect_signals()
866     {
867         this.destroy.connect(Gtk.main_quit);
868         this.identities_manager.card_list_changed.connect(this.on_card_list_changed);
869     }
870 }