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