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