6090fb8421b7d65745acde085e5e21200d5be1f3
[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.store_id_cards();
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             this.hide ();
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 to use %s?"),
680                                       custom_vbox.current_idcard.id_card.display_name);
681               var ret = dialog.run();
682               dialog.hide();
683               
684               if (ret == Gtk.ResponseType.YES)
685               {
686                 IdCard idcard = custom_vbox.current_idcard.id_card;
687                 var candidate = service_button_map.lookup (remove_button);
688
689                 SList<string> services = new SList<string>();
690                 
691                 foreach (string srv in idcard.services)
692                 {
693                   if (srv == candidate)
694                     continue;
695                   services.append (srv);
696                 }
697                 
698                 idcard.services = new string[services.length()];
699                 for (int j=0; j<idcard.services.length; j++)
700                 {
701                   idcard.services[j] = services.nth_data(j);
702                 }
703                 
704                 var children = services_internal_vbox.get_children ();
705                 foreach (var hbox in children)
706                   hbox.destroy();
707                 
708                 fill_services_vbox (idcard);
709                 custom_vbox.current_idcard.update_id_card_label ();
710               }
711               
712             });
713             services_table.attach_defaults (label, 0, 1, i, i+1);
714             services_table.attach_defaults (remove_button, 1, 2, i, i+1);
715             i++;
716         }
717         this.services_internal_vbox.show_all ();
718     }
719
720     private void on_about_action ()
721     {
722         string[] authors = {
723             "Javier Jardón <jjardon@codethink.co.uk>",
724             "Sam Thursfield <samthursfield@codethink.co.uk>",
725             "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
726             null
727         };
728
729         string copyright = "Copyright 2011 JANET";
730
731         string license =
732 """
733 Copyright (c) 2011, JANET(UK)
734 All rights reserved.
735
736 Redistribution and use in source and binary forms, with or without
737 modification, are permitted provided that the following conditions
738 are met:
739
740 1. Redistributions of source code must retain the above copyright
741    notice, this list of conditions and the following disclaimer.
742
743 2. Redistributions in binary form must reproduce the above copyright
744    notice, this list of conditions and the following disclaimer in the
745    documentation and/or other materials provided with the distribution.
746
747 3. Neither the name of JANET(UK) nor the names of its contributors
748    may be used to endorse or promote products derived from this software
749    without specific prior written permission.
750
751 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
752 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
753 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
754 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
755 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
756 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
757 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
758 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
759 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
760 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
761 SUCH DAMAGE.
762 """;
763
764         Gtk.show_about_dialog (this,
765             "comments", _("Moonshot project UI"),
766             "copyright", copyright,
767             "website", "http://www.project-moonshot.org/",
768             "license", license,
769             "website-label", _("Visit the Moonshot project web site"),
770             "authors", authors,
771             "translator-credits", _("translator-credits"),
772             null
773         );
774     }
775
776     private Gtk.ActionEntry[] create_actions() {
777         Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
778
779         Gtk.ActionEntry filemenu = { "FileMenuAction",
780                                      null,
781                                      N_("_File"),
782                                      null, null, null };
783         actions += filemenu;
784         Gtk.ActionEntry add = { "AddIdCardAction",
785 #if VALA_0_12
786                                 Stock.ADD,
787 #else
788                                 STOCK_ADD,
789 #endif
790                                 N_("Add ID Card"),
791                                 null,
792                                 N_("Add a new ID Card"),
793                                 add_identity_manual_cb };
794         actions += add;
795         Gtk.ActionEntry quit = { "QuitAction",
796 #if VALA_0_12
797                                  Stock.QUIT,
798 #else
799                                  STOCK_QUIT,
800 #endif
801                                  N_("Quit"),
802                                  "<control>Q",
803                                  N_("Quit the application"),
804                                  Gtk.main_quit };
805         actions += quit;
806
807         Gtk.ActionEntry helpmenu = { "HelpMenuAction",
808                                      null,
809                                      N_("_Help"),
810                                      null, null, null };
811         actions += helpmenu;
812         Gtk.ActionEntry about = { "AboutAction",
813 #if VALA_0_12
814                                   Stock.ABOUT,
815 #else
816                                   STOCK_ABOUT,
817 #endif
818                                   N_("About"),
819                                   null,
820                                   N_("About this application"),
821                                   on_about_action };
822         actions += about;
823
824         return actions;
825     }
826
827
828     private void create_ui_manager ()
829     {
830         Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
831         action_group.add_actions (create_actions (), this);
832         ui_manager.insert_action_group (action_group, 0);
833         try
834         {
835             ui_manager.add_ui_from_string (layout, -1);
836         }
837         catch (Error e)
838         {
839             stderr.printf ("%s\n", e.message);
840         }
841         ui_manager.ensure_update ();
842     }
843
844     private void build_ui()
845     {
846         create_ui_manager ();
847
848         this.search_entry = new Entry();
849
850         set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
851         this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
852                                                 find_icon_sized ("edit-find", Gtk.IconSize.MENU));
853 //                                                find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
854         this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
855                                                  _("Search identity or service"));
856         this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
857
858         this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
859                                                 find_icon_sized ("process-stop", Gtk.IconSize.MENU));
860 //                                                find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
861         this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
862                                                  _("Clear the current search"));
863         this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
864
865
866         this.search_entry.icon_press.connect (search_entry_icon_press_cb);
867         this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
868         this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
869
870         this.custom_vbox = new CustomVBox (this, false, 6);
871
872         var viewport = new Viewport (null, null);
873         viewport.set_border_width (6);
874         viewport.set_shadow_type (ShadowType.NONE);
875         viewport.add (custom_vbox);
876         var scroll = new ScrolledWindow (null, null);
877         scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
878         scroll.set_shadow_type (ShadowType.IN);
879         scroll.add_with_viewport (viewport);
880
881         var vbox_left = new VBox (false, 0);
882         vbox_left.pack_start (search_entry, false, false, 6);
883         vbox_left.pack_start (scroll, true, true, 0);
884         vbox_left.set_size_request (WINDOW_WIDTH, 0);
885
886         var login_vbox_title = new Label (_("Login: "));
887         label_make_bold (login_vbox_title);
888         login_vbox_title.set_alignment (0, (float) 0.5);
889         var username_label = new Label (_("Username:"));
890         username_label.set_alignment (1, (float) 0.5);
891         this.username_entry = new Entry ();
892         var password_label = new Label (_("Password:"));
893         password_label.set_alignment (1, (float) 0.5);
894         this.password_entry = new Entry ();
895         password_entry.set_invisible_char ('*');
896         password_entry.set_visibility (false);
897         var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
898         var login_table = new Table (3, 3, false);
899         login_table.set_col_spacings (10);
900         login_table.set_row_spacings (10);
901         login_table.attach_defaults (username_label, 0, 1, 0, 1);
902         login_table.attach_defaults (username_entry, 1, 2, 0, 1);
903         login_table.attach_defaults (password_label, 0, 1, 1, 2);
904         login_table.attach_defaults (password_entry, 1, 2, 1, 2);
905         login_table.attach_defaults (remember_checkbutton,  1, 2, 2, 3);
906         var login_vbox_alignment = new Alignment (0, 0, 0, 0);
907         login_vbox_alignment.set_padding (0, 0, 12, 0);
908         login_vbox_alignment.add (login_table);
909         var login_vbox = new VBox (false, 6);
910         login_vbox.pack_start (login_vbox_title, false, true, 0);
911         login_vbox.pack_start (login_vbox_alignment, false, true, 0);
912
913         var services_vbox_title = new Label (_("Services:"));
914         label_make_bold (services_vbox_title);
915         services_vbox_title.set_alignment (0, (float) 0.5);
916         var services_vbox_alignment = new Alignment (0, 0, 0, 0);
917         services_vbox_alignment.set_padding (0, 0, 12, 0);
918         this.services_internal_vbox = new VBox (true, 6);
919         services_vbox_alignment.add (services_internal_vbox);
920         var services_vbox = new VBox (false, 6);
921         services_vbox.pack_start (services_vbox_title, false, true, 0);
922         services_vbox.pack_start (services_vbox_alignment, false, true, 0);
923
924         this.vbox_right = new VBox (false, 18);
925         vbox_right.pack_start (login_vbox, false, true, 0);
926         vbox_right.pack_start (services_vbox, false, true, 0);
927
928         var hbox = new HBox (false, 12);
929         hbox.pack_start (vbox_left, true, true, 0);
930         hbox.pack_start (vbox_right, false, false, 0);
931
932         var main_vbox = new VBox (false, 0);
933         main_vbox.set_border_width (12);
934         var menubar = this.ui_manager.get_widget ("/MenuBar");
935         main_vbox.pack_start (menubar, false, false, 0);
936         main_vbox.pack_start (hbox, true, true, 0);
937 #if OS_MACOS
938                 var menushell = this.ui_manager.get_widget("/Menu") as Gtk.MenuShell;
939                 osxApp.set_menu_bar(menushell);
940 #endif
941         add (main_vbox);
942
943         main_vbox.show_all();
944         this.vbox_right.hide ();
945     }
946
947     private void set_atk_name_description (Widget widget, string name, string description)
948     {
949        var atk_widget = widget.get_accessible ();
950
951        atk_widget.set_name (name);
952        atk_widget.set_description (description);
953     }
954
955     private void connect_signals()
956     {
957         this.destroy.connect (Gtk.main_quit);
958         this.identities_manager.card_list_changed.connect(this.on_card_list_changed);
959     }
960 }
961
962