Changed IdCard.IsNoIdentity to IdCard.is_no_identity
[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.length > 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             add_id_card_data(id_card);
230             IdCardWidget id_card_widget = add_id_card_widget(id_card);
231             if (id_card_widget.id_card.nai == current_idcard_nai) {
232                 // fill_details(id_card_widget.id_card);
233                 id_card_widget.expand();
234             }
235         }
236     }
237     
238     private IdCard update_id_card_data(IdentityDialog dialog, IdCard id_card)
239     {
240         id_card.display_name = dialog.display_name;
241         id_card.issuer = dialog.issuer;
242         id_card.username = dialog.username;
243         id_card.password = dialog.password;
244         id_card.store_password = dialog.store_password;
245         id_card.services = dialog.get_services();
246
247         return id_card;
248     }
249
250     private void add_id_card_data(IdCard id_card)
251     {
252         TreeIter   iter;
253         Gdk.Pixbuf pixbuf;
254         this.listmodel->append(out iter);
255         pixbuf = get_pixbuf(id_card);
256         listmodel->set(iter,
257                        Columns.IDCARD_COL, id_card,
258                        Columns.LOGO_COL, pixbuf,
259                        Columns.ISSUER_COL, id_card.issuer,
260                        Columns.USERNAME_COL, id_card.username,
261                        Columns.PASSWORD_COL, id_card.password);
262     }
263
264     private void remove_id_card_data(IdCard id_card)
265     {
266         TreeIter iter;
267         string issuer;
268
269         if (listmodel->get_iter_first(out iter))
270         {
271             do
272             {
273                 listmodel->get(iter,
274                                Columns.ISSUER_COL, out issuer);
275
276                 if (id_card.issuer == issuer)
277                 {
278                     listmodel->remove(iter);
279                     break;
280                 }
281             }
282             while (listmodel->iter_next(ref iter));
283         }
284     }
285
286     private IdCardWidget add_id_card_widget(IdCard id_card)
287     {
288         var id_card_widget = new IdCardWidget(id_card);
289         this.custom_vbox.add_id_card_widget(id_card_widget);
290         id_card_widget.expanded.connect(this.widget_selected_cb);
291         id_card_widget.collapsed.connect(this.widget_unselected_cb);
292         return id_card_widget;
293     }
294
295     private void widget_selected_cb(IdCardWidget id_card_widget)
296     {
297         this.remove_button.set_sensitive(true);
298         this.edit_button.set_sensitive(true);
299         this.custom_vbox.receive_expanded_event(id_card_widget);
300
301         if (this.request_queue.length > 0)
302              this.send_button.set_sensitive(true);
303     }
304
305     private void widget_unselected_cb(IdCardWidget id_card_widget)
306     {
307         this.remove_button.set_sensitive(false);
308         this.edit_button.set_sensitive(false);
309         this.custom_vbox.receive_collapsed_event(id_card_widget);
310
311         this.send_button.set_sensitive(false);
312     }
313
314     public bool add_identity(IdCard id_card, bool force_flat_file_store)
315     {
316         #if OS_MACOS
317         /* 
318          * TODO: We should have a confirmation dialog, but currently it will crash on Mac OS
319          * so for now we will install silently
320          */
321         var ret = Gtk.ResponseType.YES;
322         #else
323         Gtk.MessageDialog dialog;
324         IdCard? prev_id = identities_manager.find_id_card(id_card.nai, force_flat_file_store);
325         logger.trace("add_identity: find_id_card returned " + (prev_id != null ? "non-null" : "null"));
326         if (prev_id!=null) {
327             int flags = prev_id.Compare(id_card);
328             logger.trace("add_identity: compare returned " + flags.to_string());
329             if (flags == 0) {
330                 return false; // no changes, no need to update
331             } else if ((flags & (1 << IdCard.DiffFlags.DISPLAY_NAME)) != 0) {
332                 dialog = new Gtk.MessageDialog(this,
333                                                Gtk.DialogFlags.DESTROY_WITH_PARENT,
334                                                Gtk.MessageType.QUESTION,
335                                                Gtk.ButtonsType.YES_NO,
336                                                _("Would you like to replace ID Card '%s' using nai '%s' with the new ID Card '%s'?"),
337                                                prev_id.display_name,
338                                                prev_id.nai,
339                                                id_card.display_name);
340             } else {
341                 dialog = new Gtk.MessageDialog(this,
342                                                Gtk.DialogFlags.DESTROY_WITH_PARENT,
343                                                Gtk.MessageType.QUESTION,
344                                                Gtk.ButtonsType.YES_NO,
345                                                _("Would you like to update ID Card '%s' using nai '%s'?"),
346                                                id_card.display_name,
347                                                id_card.nai);
348             }
349         } else {
350             dialog = new Gtk.MessageDialog(this,
351                                            Gtk.DialogFlags.DESTROY_WITH_PARENT,
352                                            Gtk.MessageType.QUESTION,
353                                            Gtk.ButtonsType.YES_NO,
354                                            _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
355                                            id_card.display_name);
356         }
357         var ret = dialog.run();
358         dialog.destroy();
359         #endif
360
361         if (ret == Gtk.ResponseType.YES) {
362             this.identities_manager.add_card(id_card, force_flat_file_store);
363             return true;
364         }
365         return false;
366     }
367
368     private void add_identity_cb()
369     {
370         var dialog = new IdentityDialog(this);
371         int result = ResponseType.CANCEL;
372         while (!dialog.complete)
373             result = dialog.run();
374
375         switch (result) {
376         case ResponseType.OK:
377             this.identities_manager.add_card(update_id_card_data(dialog, new IdCard()), false);
378             break;
379         default:
380             break;
381         }
382         dialog.destroy();
383     }
384
385     private void edit_identity_cb(IdCard card)
386     {
387         var dialog = new IdentityDialog.with_idcard(card, _("Edit Identity"), this);
388         int result = ResponseType.CANCEL;
389         while (!dialog.complete)
390             result = dialog.run();
391
392         switch (result) {
393         case ResponseType.OK:
394             this.identities_manager.update_card(update_id_card_data(dialog, card));
395             break;
396         default:
397             break;
398         }
399         dialog.destroy();
400     }
401
402     private void remove_identity(IdCardWidget id_card_widget)
403     {
404         var id_card = id_card_widget.id_card;
405         this.custom_vbox.remove_id_card_widget(id_card_widget);
406
407         this.identities_manager.remove_card(id_card);
408
409         // Nothing is selected, so disable buttons
410         this.edit_button.set_sensitive(false);
411         this.remove_button.set_sensitive(false);
412         this.send_button.set_sensitive(false);
413     }
414
415     private void redraw_id_card_widgets()
416     {
417         logger.trace("redraw_id_card_widgets");
418
419         TreeIter iter;
420         IdCard id_card;
421
422         this.custom_vbox.clear();
423
424         if (filter.get_iter_first(out iter))
425         {
426             do
427             {
428                 filter.get(iter,
429                            Columns.IDCARD_COL, out id_card);
430
431                 add_id_card_widget(id_card);
432             }
433             while (filter.iter_next(ref iter));
434         }
435     }
436
437     private void remove_identity_cb(IdCardWidget id_card_widget)
438     {
439         var id_card = id_card_widget.id_card;
440
441         bool remove = WarningDialog.confirm(this, 
442                                             Markup.printf_escaped(
443                                                 "<span font-weight='heavy'>You are about to remove the identity '%s'.</span>",
444                                                 id_card.display_name)
445                                             + "\n\nAre you sure you want to do this?",
446                                             "delete_idcard");
447         if (remove) 
448             remove_identity(id_card_widget);
449     }
450
451     private void set_prompting_service(string service)
452     {
453         clear_selection_prompts();
454
455         var prompting_service = new Label(_("Identity requested for service:\n%s").printf(service));
456         prompting_service.set_line_wrap(true);
457
458         // left-align
459         prompting_service.set_alignment(0, (float )0.5);
460
461         var selection_prompt = new Label(_("Select your identity:"));
462         selection_prompt.set_alignment(0, 1);
463
464         this.service_prompt_vbox.pack_start(prompting_service, false, false, 12);
465         this.service_prompt_vbox.pack_start(selection_prompt, false, false, 2);
466         this.service_prompt_vbox.show_all();
467     }
468
469     private void clear_selection_prompts()
470     {
471         var list = service_prompt_vbox.get_children();
472         foreach (Widget w in list)
473         {
474             service_prompt_vbox.remove(w);
475         }
476     }
477
478
479     public void queue_identity_request(IdentityRequest request)
480     {
481         if (this.request_queue.is_empty())
482         { /* setup widgets */
483             candidates = request.candidates;
484             filter.refilter();
485             redraw_id_card_widgets();
486             set_prompting_service(request.service);
487             remember_identity_binding.show();
488             make_visible();
489         }
490         this.request_queue.push_tail(request);
491     }
492
493
494     /** Makes the window visible, or at least, notifies the user that the window
495      * wants to be visible.
496      *
497      * This differs from show() in that show() does not guarantee that the 
498      * window will be moved to the foreground. Actually, neither does this
499      * method, because the user's settings and window manager may affect the
500      * behavior significantly.
501      */
502     public void make_visible()
503     {
504         set_urgency_hint(true);
505         present();
506     }
507
508     public IdCard check_add_password(IdCard identity, IdentityRequest request, IdentityManagerModel model)
509     {
510         logger.trace(@"check_add_password");
511         IdCard retval = identity;
512         bool idcard_has_pw = (identity.password != null) && (identity.password != "");
513         bool request_has_pw = (request.password != null) && (request.password != "");
514         if ((!idcard_has_pw) && (!identity.is_no_identity())) {
515             if (request_has_pw) {
516                 identity.password = request.password;
517                 retval = model.update_card(identity);
518             } else {
519                 var dialog = new AddPasswordDialog(identity, request);
520                 var result = dialog.run();
521
522                 switch (result) {
523                 case ResponseType.OK:
524                     identity.password = dialog.password;
525                     identity.store_password = dialog.remember;
526                     if (dialog.remember)
527                         identity.temporary = false;
528                     retval = model.update_card(identity);
529                     break;
530                 default:
531                     identity = null;
532                     break;
533                 }
534                 dialog.destroy();
535             }
536         }
537         return retval;
538     }
539
540     private void send_identity_cb(IdCard id)
541     {
542         send_button.set_sensitive(false);
543
544         IdCard identity = id;
545         return_if_fail(request_queue.length > 0);
546
547         candidates = null;
548         var request = this.request_queue.pop_head();
549         identity = check_add_password(identity, request, identities_manager);
550         if (this.request_queue.is_empty())
551         {
552             candidates = null;
553             clear_selection_prompts();
554             if (!parent_app.explicitly_launched) {
555 // The following occasionally causes the app to exit without sending the dbus
556 // reply, so for now we just don't exit
557 //                Gtk.main_quit();
558 // just hide instead
559                 this.hide();
560             }
561         } else {
562             IdentityRequest next = this.request_queue.peek_head();
563             candidates = next.candidates;
564             set_prompting_service(next.service);
565         }
566         filter.refilter();
567         redraw_id_card_widgets();
568
569         if ((identity != null) && (!identity.is_no_identity()))
570             parent_app.default_id_card = identity;
571
572         request.return_identity(identity, remember_identity_binding.active);
573
574         remember_identity_binding.active = false;
575         remember_identity_binding.hide();
576     }
577
578     // private void label_make_bold(Label label)
579     // {
580     //     var font_desc = new Pango.FontDescription();
581
582     //     font_desc.set_weight(Pango.Weight.BOLD);
583
584     //     /* This will only affect the weight of the font, the rest is
585     //      * from the current state of the widget, which comes from the
586     //      * theme or user prefs, since the font desc only has the
587     //      * weight flag turned on.
588     //      */
589     //     label.modify_font(font_desc);
590     // }
591
592     private void on_about_action()
593     {
594         string copyright = "Copyright 2011, 2016 JANET";
595
596         string license =
597         """
598 Copyright (c) 2011, 2016 JANET(UK)
599 All rights reserved.
600
601 Redistribution and use in source and binary forms, with or without
602 modification, are permitted provided that the following conditions
603 are met:
604
605 1. Redistributions of source code must retain the above copyright
606    notice, this list of conditions and the following disclaimer.
607
608 2. Redistributions in binary form must reproduce the above copyright
609    notice, this list of conditions and the following disclaimer in the
610    documentation and/or other materials provided with the distribution.
611
612 3. Neither the name of JANET(UK) nor the names of its contributors
613    may be used to endorse or promote products derived from this software
614    without specific prior written permission.
615
616 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
617 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
618 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
619 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
620 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
621 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
622 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
623 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
624 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
625 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
626 SUCH DAMAGE.
627 """;
628
629         Gtk.show_about_dialog(this,
630                               "comments", _("Moonshot project UI"),
631                               "copyright", copyright,
632                               "website", Config.PACKAGE_URL,
633                               "version", Config.PACKAGE_VERSION,
634                               "license", license,
635                               "website-label", _("Visit the Moonshot project web site"),
636                               "translator-credits", _("translator-credits"),
637                               null
638             );
639     }
640
641     private Gtk.ActionEntry[] create_actions() {
642         Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
643
644         Gtk.ActionEntry helpmenu = { "HelpMenuAction",
645                                      null,
646                                      N_("_Help"),
647                                      null, null, null };
648         actions += helpmenu;
649         Gtk.ActionEntry about = { "AboutAction",
650                                   #if VALA_0_12
651                                   Stock.ABOUT,
652                                   #else
653                                   STOCK_ABOUT,
654                                   #endif
655                                   N_("About"),
656                                   null,
657                                   N_("About this application"),
658                                   on_about_action };
659         actions += about;
660
661         return actions;
662     }
663
664
665     private void create_ui_manager()
666     {
667         Gtk.ActionGroup action_group = new Gtk.ActionGroup("GeneralActionGroup");
668         action_group.add_actions(create_actions(), this);
669         ui_manager.insert_action_group(action_group, 0);
670         try
671         {
672             ui_manager.add_ui_from_string(menu_layout, -1);
673         }
674         catch (Error e)
675         {
676             stderr.printf("%s\n", e.message);
677             logger.error("create_ui_manager: Caught error: " + e.message);
678         }
679         ui_manager.ensure_update();
680     }
681
682     private void build_ui()
683     {
684         // Note: On Debian7/Gtk+2, the menu bar remains gray. This doesn't happen on Debian8/Gtk+3.
685         Gdk.Color white = Gdk.Color();
686         white.red = white.green = white.blue = 65535;
687         this.modify_bg(StateType.NORMAL, white);
688
689         create_ui_manager();
690
691         this.search_entry = new Entry();
692
693         set_atk_name_description(search_entry, _("Search entry"), _("Search for a specific ID Card"));
694         this.search_entry.set_icon_from_pixbuf(EntryIconPosition.PRIMARY,
695                                                find_icon_sized("edit-find", Gtk.IconSize.MENU));
696         this.search_entry.set_icon_tooltip_text(EntryIconPosition.PRIMARY,
697                                                 _("Search for an identity or service"));
698         this.search_entry.set_icon_sensitive(EntryIconPosition.PRIMARY, false);
699
700         this.search_entry.set_icon_from_pixbuf(EntryIconPosition.SECONDARY,
701                                                find_icon_sized("process-stop", Gtk.IconSize.MENU));
702         this.search_entry.set_icon_tooltip_text(EntryIconPosition.SECONDARY,
703                                                 _("Clear the current search"));
704         this.search_entry.set_icon_sensitive(EntryIconPosition.SECONDARY, false);
705
706
707         this.search_entry.icon_press.connect(search_entry_icon_press_cb);
708         this.search_entry.notify["text"].connect(search_entry_text_changed_cb);
709         this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
710         this.search_entry.set_width_chars(30);
711
712
713         this.custom_vbox = new CustomVBox(this, false, 2);
714
715         var viewport = new Viewport(null, null);
716         viewport.set_border_width(2);
717         viewport.set_shadow_type(ShadowType.NONE);
718         viewport.add(custom_vbox);
719         var id_scrollwin = new ScrolledWindow(null, null);
720         id_scrollwin.set_policy(PolicyType.NEVER, PolicyType.AUTOMATIC);
721         id_scrollwin.set_shadow_type(ShadowType.IN);
722         id_scrollwin.add_with_viewport(viewport);
723
724         service_prompt_vbox = new VBox(false, 0);
725
726         var vbox_left = new VBox(false, 0);
727         vbox_left.pack_start(service_prompt_vbox, false, false, 12);
728
729         var search_hbox = new HBox(false, 6);
730         search_hbox.pack_end(search_entry, false, false, 0);
731         //// var search_label = new Label(_("Search:"));
732         //// search_label.set_alignment(1, (float) 0.5);
733         //// set_atk_relation(search_label, search_entry, Atk.RelationType.LABEL_FOR);
734         //// search_hbox.pack_end(search_label, false, false, 6);
735
736         var full_search_label = new Label(_("Search for an identity or service"));
737         full_search_label.set_alignment(1, 0);
738         var search_vbox = new VBox(false, 4);
739         search_vbox.pack_start(full_search_label, false, false, 0);
740         search_vbox.pack_start(search_hbox, false, false, 0);
741
742         var inner_left_vbox = new VBox(false, 6);
743         inner_left_vbox.pack_start(search_vbox, false, false, 6);
744 //        inner_left_vbox.pack_start(selection_prompt, false, false, 6);
745         inner_left_vbox.pack_start(id_scrollwin, true, true, 0);
746
747         var id_and_button_box = new HBox(false, 6);
748         id_and_button_box.pack_start(inner_left_vbox, true, true, 6);
749         vbox_left.pack_start(id_and_button_box, true, true, 0);
750         // vbox_left.pack_start(prompting_service, false, false, 6);
751         vbox_left.set_size_request(WINDOW_WIDTH, 0);
752
753         this.no_identity_title = new Label(_("No Identity: Send this identity to services which should not use Moonshot"));
754         no_identity_title.set_alignment(0, (float ) 0.5);
755         no_identity_title.set_line_wrap(true);
756         no_identity_title.show();
757
758         this.vbox_right = new VBox(false, 6);
759
760         var add_button = new Button.with_label(_("Add"));
761         add_button.clicked.connect((w) => {add_identity_cb();});
762
763         this.edit_button = new Button.with_label(_("Edit"));
764         edit_button.clicked.connect((w) => {edit_identity_cb(custom_vbox.current_idcard.id_card);});
765         edit_button.set_sensitive(false);
766
767         this.remove_button = new Button.with_label(_("Remove"));
768         remove_button.clicked.connect((w) => {remove_identity_cb(custom_vbox.current_idcard);});
769         remove_button.set_sensitive(false);
770
771         this.send_button = new Button.with_label(_("Send"));
772         send_button.clicked.connect((w) => {send_identity_cb(custom_vbox.current_idcard.id_card);});
773         // send_button.set_visible(false);
774         send_button.set_sensitive(false);
775
776         var empty_box = new VBox(false, 0);
777         empty_box.set_size_request(0, 0);
778         vbox_right.pack_start(empty_box, false, false, 14);
779         vbox_right.pack_start(add_button, false, false, 6);
780         vbox_right.pack_start(edit_button, false, false, 6);
781         vbox_right.pack_start(remove_button, false, false, 6);
782         vbox_right.pack_start(send_button, false, false, 24);
783
784         id_and_button_box.pack_start(vbox_right, false, false, 0);
785
786         var main_vbox = new VBox(false, 0);
787
788         // Note: This places a border above the menubar. Is that what we want?
789         main_vbox.set_border_width(12);
790
791 #if OS_MACOS
792         // hide the  File | Quit menu item which is now on the Mac Menu
793 //        Gtk.Widget quit_item =  this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
794 //        quit_item.hide();
795         
796         Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
797         menushell.modify_bg(StateType.NORMAL, white);
798
799         osxApp.set_menu_bar(menushell);
800         osxApp.set_use_quartz_accelerators(true);
801         osxApp.sync_menu_bar();
802         osxApp.ready();
803 #else
804         var menubar = this.ui_manager.get_widget("/MenuBar");
805         main_vbox.pack_start(menubar, false, false, 0);
806         menubar.modify_bg(StateType.NORMAL, white);
807 #endif
808         main_vbox.pack_start(vbox_left, true, true, 0);
809
810         remember_identity_binding = new CheckButton.with_label(_("Remember my identity choice for this service"));
811         remember_identity_binding.active = false;
812         main_vbox.pack_start(remember_identity_binding, false, false, 6);
813
814         add(main_vbox);
815         main_vbox.show_all();
816
817         if (this.request_queue.length == 0)
818             remember_identity_binding.hide();
819     } 
820
821     private void set_atk_name_description(Widget widget, string name, string description)
822     {
823         var atk_widget = widget.get_accessible();
824
825         atk_widget.set_name(name);
826         atk_widget.set_description(description);
827     }
828
829     private void connect_signals()
830     {
831         this.destroy.connect(Gtk.main_quit);
832         this.identities_manager.card_list_changed.connect(this.on_card_list_changed);
833     }
834 }