Use AC_INIT to set the project web page
[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             string[] services = new string[identity.services.length + 1];
591
592             for (int i = 0; i < identity.services.length; i++)
593                 services[i] = identity.services[i];
594
595             services[identity.services.length] = request.service;
596
597             identity.services = services;
598
599             identities_manager.update_card (identity);
600         }
601
602         if (identity.password == null)
603         {
604             var dialog = new AddPasswordDialog ();
605             var result = dialog.run ();
606
607             switch (result) {
608             case ResponseType.OK:
609                 identity.password = dialog.password;
610                 reset_password = ! dialog.remember;
611                 break;
612             default:
613                 identity = null;
614                 break;
615             }
616
617             dialog.destroy ();
618         }
619
620         if (this.request_queue.is_empty())
621             Gtk.main_quit ();
622
623         if (identity != null)
624             this.default_id_card = identity;
625
626         request.return_identity (identity);
627
628         if (reset_password)
629             identity.password = null;
630
631         candidates = null;
632     }
633
634     private void label_make_bold (Label label)
635     {
636         var font_desc = new Pango.FontDescription ();
637
638         font_desc.set_weight (Pango.Weight.BOLD);
639
640         /* This will only affect the weight of the font, the rest is
641          * from the current state of the widget, which comes from the
642          * theme or user prefs, since the font desc only has the
643          * weight flag turned on.
644          */
645         label.modify_font (font_desc);
646     }
647
648     private void fill_services_vbox (IdCard id_card)
649     {
650         int i = 0;
651         var n_columns = id_card.services.length;
652
653         var services_table = new Table (n_columns, 2, false);
654         services_table.set_col_spacings (10);
655         services_table.set_row_spacings (10);
656         this.services_internal_vbox.add (services_table);
657         
658         service_button_map.remove_all ();
659
660         foreach (string service in id_card.services)
661         {
662             var label = new Label (service);
663             label.set_alignment (0, (float) 0.5);
664 #if VALA_0_12
665             var remove_button = new Button.from_stock (Stock.REMOVE);
666 #else
667             var remove_button = new Button.from_stock (STOCK_REMOVE);
668 #endif
669
670
671             service_button_map.insert (remove_button, service);
672             
673             remove_button.clicked.connect ((remove_button) =>
674             {
675               var dialog = new Gtk.MessageDialog (this,
676                                       Gtk.DialogFlags.DESTROY_WITH_PARENT,
677                                       Gtk.MessageType.QUESTION,
678                                       Gtk.ButtonsType.YES_NO,
679                                       _("Are you sure you want to stop '%s' ID Card from being used with %s?"),
680                                       custom_vbox.current_idcard.id_card.display_name,
681                                       _("this service"));
682               var ret = dialog.run();
683               dialog.hide();
684               
685               if (ret == Gtk.ResponseType.YES)
686               {
687                 IdCard idcard = custom_vbox.current_idcard.id_card;
688                 var candidate = service_button_map.lookup (remove_button);
689
690                 SList<string> services = new SList<string>();
691                 
692                 foreach (string srv in idcard.services)
693                 {
694                   if (srv == candidate)
695                     continue;
696                   services.append (srv);
697                 }
698                 
699                 idcard.services = new string[services.length()];
700                 for (int j=0; j<idcard.services.length; j++)
701                 {
702                   idcard.services[j] = services.nth_data(j);
703                 }
704                 
705                 var children = services_internal_vbox.get_children ();
706                 foreach (var hbox in children)
707                   hbox.destroy();
708                 
709                 fill_services_vbox (idcard);
710                 custom_vbox.current_idcard.update_id_card_label ();
711               }
712               
713             });
714             services_table.attach_defaults (label, 0, 1, i, i+1);
715             services_table.attach_defaults (remove_button, 1, 2, i, i+1);
716             i++;
717         }
718         this.services_internal_vbox.show_all ();
719     }
720
721     private void on_about_action ()
722     {
723         string[] authors = {
724             "Javier Jardón <jjardon@codethink.co.uk>",
725             "Sam Thursfield <samthursfield@codethink.co.uk>",
726             "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
727             null
728         };
729
730         string copyright = "Copyright 2011 JANET";
731
732         const string license =
733 """
734 Copyright (c) 2011, JANET(UK)
735 All rights reserved.
736
737 Redistribution and use in source and binary forms, with or without
738 modification, are permitted provided that the following conditions
739 are met:
740
741 1. Redistributions of source code must retain the above copyright
742    notice, this list of conditions and the following disclaimer.
743
744 2. Redistributions in binary form must reproduce the above copyright
745    notice, this list of conditions and the following disclaimer in the
746    documentation and/or other materials provided with the distribution.
747
748 3. Neither the name of JANET(UK) nor the names of its contributors
749    may be used to endorse or promote products derived from this software
750    without specific prior written permission.
751
752 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
753 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
754 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
755 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
756 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
757 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
758 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
759 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
760 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
761 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
762 SUCH DAMAGE.
763 """;
764
765         Gtk.show_about_dialog (this,
766             "comments", _("Moonshot project UI"),
767             "copyright", copyright,
768             "website", Config.PACKAGE_URL,
769             "version", Config.PACKAGE_VERSION,
770             "license", license,
771             "website-label", _("Visit the Moonshot project web site"),
772             "authors", authors,
773             "translator-credits", _("translator-credits"),
774             null
775         );
776     }
777
778     private Gtk.ActionEntry[] create_actions() {
779         Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
780
781         Gtk.ActionEntry filemenu = { "FileMenuAction",
782                                      null,
783                                      N_("_File"),
784                                      null, null, null };
785         actions += filemenu;
786         Gtk.ActionEntry add = { "AddIdCardAction",
787 #if VALA_0_12
788                                 Stock.ADD,
789 #else
790                                 STOCK_ADD,
791 #endif
792                                 N_("Add ID Card"),
793                                 null,
794                                 N_("Add a new ID Card"),
795                                 add_identity_manual_cb };
796         actions += add;
797         Gtk.ActionEntry quit = { "QuitAction",
798 #if VALA_0_12
799                                  Stock.QUIT,
800 #else
801                                  STOCK_QUIT,
802 #endif
803                                  N_("Quit"),
804                                  "<control>Q",
805                                  N_("Quit the application"),
806                                  Gtk.main_quit };
807         actions += quit;
808
809         Gtk.ActionEntry helpmenu = { "HelpMenuAction",
810                                      null,
811                                      N_("_Help"),
812                                      null, null, null };
813         actions += helpmenu;
814         Gtk.ActionEntry about = { "AboutAction",
815 #if VALA_0_12
816                                   Stock.ABOUT,
817 #else
818                                   STOCK_ABOUT,
819 #endif
820                                   N_("About"),
821                                   null,
822                                   N_("About this application"),
823                                   on_about_action };
824         actions += about;
825
826         return actions;
827     }
828
829
830     private void create_ui_manager ()
831     {
832         Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
833         action_group.add_actions (create_actions (), this);
834         ui_manager.insert_action_group (action_group, 0);
835         try
836         {
837             ui_manager.add_ui_from_string (layout, -1);
838         }
839         catch (Error e)
840         {
841             stderr.printf ("%s\n", e.message);
842         }
843         ui_manager.ensure_update ();
844     }
845
846     private void build_ui()
847     {
848         create_ui_manager ();
849
850         this.search_entry = new Entry();
851
852         set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
853         this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
854                                                 find_icon_sized ("edit-find", Gtk.IconSize.MENU));
855 //                                                find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
856         this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
857                                                  _("Search identity or service"));
858         this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
859
860         this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
861                                                 find_icon_sized ("process-stop", Gtk.IconSize.MENU));
862 //                                                find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
863         this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
864                                                  _("Clear the current search"));
865         this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
866
867
868         this.search_entry.icon_press.connect (search_entry_icon_press_cb);
869         this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
870         this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
871
872         this.custom_vbox = new CustomVBox (this, false, 6);
873
874         var viewport = new Viewport (null, null);
875         viewport.set_border_width (6);
876         viewport.set_shadow_type (ShadowType.NONE);
877         viewport.add (custom_vbox);
878         var scroll = new ScrolledWindow (null, null);
879         scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
880         scroll.set_shadow_type (ShadowType.IN);
881         scroll.add_with_viewport (viewport);
882
883         var vbox_left = new VBox (false, 0);
884         vbox_left.pack_start (search_entry, false, false, 6);
885         vbox_left.pack_start (scroll, true, true, 0);
886         vbox_left.set_size_request (WINDOW_WIDTH, 0);
887
888         var login_vbox_title = new Label (_("Login: "));
889         label_make_bold (login_vbox_title);
890         login_vbox_title.set_alignment (0, (float) 0.5);
891         var username_label = new Label (_("Username:"));
892         username_label.set_alignment (1, (float) 0.5);
893         this.username_entry = new Entry ();
894         var password_label = new Label (_("Password:"));
895         password_label.set_alignment (1, (float) 0.5);
896         this.password_entry = new Entry ();
897         password_entry.set_invisible_char ('*');
898         password_entry.set_visibility (false);
899         var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
900         var login_table = new Table (3, 3, false);
901         login_table.set_col_spacings (10);
902         login_table.set_row_spacings (10);
903         login_table.attach_defaults (username_label, 0, 1, 0, 1);
904         login_table.attach_defaults (username_entry, 1, 2, 0, 1);
905         login_table.attach_defaults (password_label, 0, 1, 1, 2);
906         login_table.attach_defaults (password_entry, 1, 2, 1, 2);
907         login_table.attach_defaults (remember_checkbutton,  1, 2, 2, 3);
908         var login_vbox_alignment = new Alignment (0, 0, 0, 0);
909         login_vbox_alignment.set_padding (0, 0, 12, 0);
910         login_vbox_alignment.add (login_table);
911         var login_vbox = new VBox (false, 6);
912         login_vbox.pack_start (login_vbox_title, false, true, 0);
913         login_vbox.pack_start (login_vbox_alignment, false, true, 0);
914
915         var services_vbox_title = new Label (_("Services:"));
916         label_make_bold (services_vbox_title);
917         services_vbox_title.set_alignment (0, (float) 0.5);
918         var services_vbox_alignment = new Alignment (0, 0, 0, 0);
919         services_vbox_alignment.set_padding (0, 0, 12, 0);
920         this.services_internal_vbox = new VBox (true, 6);
921         services_vbox_alignment.add (services_internal_vbox);
922         var services_vbox = new VBox (false, 6);
923         services_vbox.pack_start (services_vbox_title, false, true, 0);
924         services_vbox.pack_start (services_vbox_alignment, false, true, 0);
925
926         this.vbox_right = new VBox (false, 18);
927         vbox_right.pack_start (login_vbox, false, true, 0);
928         vbox_right.pack_start (services_vbox, false, true, 0);
929
930         var hbox = new HBox (false, 12);
931         hbox.pack_start (vbox_left, true, true, 0);
932         hbox.pack_start (vbox_right, false, false, 0);
933
934         var main_vbox = new VBox (false, 0);
935         main_vbox.set_border_width (12);
936  
937 #if OS_MACOS
938         // hide the  File | Quit menu item which is now on the Mac Menu
939         Gtk.Widget quit_item =  this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
940         quit_item.hide();
941         
942                 Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
943                 osxApp.set_menu_bar(menushell);
944                 osxApp.set_use_quartz_accelerators(true);
945                 osxApp.sync_menu_bar();
946                 osxApp.ready(); 
947 #else
948         var menubar = this.ui_manager.get_widget ("/MenuBar");
949         main_vbox.pack_start (menubar, false, false, 0);
950 #endif
951         main_vbox.pack_start (hbox, true, true, 0);
952         add (main_vbox);
953         main_vbox.show_all();
954         this.vbox_right.hide ();
955   } 
956
957     private void set_atk_name_description (Widget widget, string name, string description)
958     {
959        var atk_widget = widget.get_accessible ();
960
961        atk_widget.set_name (name);
962        atk_widget.set_description (description);
963     }
964
965     private void connect_signals()
966     {
967         this.destroy.connect (Gtk.main_quit);
968         this.identities_manager.card_list_changed.connect(this.on_card_list_changed);
969     }
970 }
971
972