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