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