Do not exit app when responding to identity request, just hide.
[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 = "Moonshot Identity Selector";
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         this.listmodel->clear();
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            services_internal_vbox.remove(hbox);
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             remove_id_card_widget((IdCardWidget )id_card_widget); //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             if (!parent_app.explicitly_launched) {
475 // The following occasionally causes the app to exit without sending the dbus
476 // reply, so for now we just don't exit
477 //                Gtk.main_quit ();
478 // just hide instead
479                 this.hide();
480             }
481         } else {
482             candidates = this.request_queue.peek_head().candidates;
483         }
484         filter.refilter();
485         redraw_id_card_widgets ();
486
487         if (identity != null)
488             parent_app.default_id_card = identity;
489
490         request.return_identity (identity);
491
492         if (reset_password)
493             identity.password = null;
494
495     }
496
497     private void label_make_bold (Label label)
498     {
499         var font_desc = new Pango.FontDescription ();
500
501         font_desc.set_weight (Pango.Weight.BOLD);
502
503         /* This will only affect the weight of the font, the rest is
504          * from the current state of the widget, which comes from the
505          * theme or user prefs, since the font desc only has the
506          * weight flag turned on.
507          */
508         label.modify_font (font_desc);
509     }
510
511     private void fill_services_vbox (IdCard id_card)
512     {
513         int i = 0;
514         var n_columns = id_card.services.length;
515
516         var services_table = new Table (n_columns, 2, false);
517         services_table.set_col_spacings (10);
518         services_table.set_row_spacings (10);
519         this.services_internal_vbox.add (services_table);
520         
521         service_button_map.remove_all ();
522
523         foreach (string service in id_card.services)
524         {
525             var label = new Label (service);
526             label.set_alignment (0, (float) 0.5);
527 #if VALA_0_12
528             var remove_button = new Button.from_stock (Stock.REMOVE);
529 #else
530             var remove_button = new Button.from_stock (STOCK_REMOVE);
531 #endif
532
533
534             service_button_map.insert (remove_button, service);
535             
536             remove_button.clicked.connect ((remove_button) =>
537             {
538               var dialog = new Gtk.MessageDialog (this,
539                                       Gtk.DialogFlags.DESTROY_WITH_PARENT,
540                                       Gtk.MessageType.QUESTION,
541                                       Gtk.ButtonsType.YES_NO,
542                                       _("Are you sure you want to stop '%s' ID Card from being used with %s?"),
543                                       custom_vbox.current_idcard.id_card.display_name,
544                                       _("this service"));
545               var ret = dialog.run();
546               dialog.hide();
547               
548               if (ret == Gtk.ResponseType.YES)
549               {
550                 IdCard idcard = custom_vbox.current_idcard.id_card;
551                 var candidate = service_button_map.lookup (remove_button);
552
553                 SList<string> services = new SList<string>();
554                 
555                 foreach (string srv in idcard.services)
556                 {
557                   if (srv == candidate)
558                     continue;
559                   services.append (srv);
560                 }
561                 
562                 idcard.services = new string[services.length()];
563                 for (int j=0; j<idcard.services.length; j++)
564                 {
565                   idcard.services[j] = services.nth_data(j);
566                 }
567                 
568                 var children = services_internal_vbox.get_children ();
569                 foreach (var hbox in children)
570                   services_internal_vbox.remove(hbox);
571                 
572                 fill_services_vbox (idcard);
573                 custom_vbox.current_idcard.update_id_card_label ();
574                 identities_manager.update_card(idcard);
575               }
576               
577             });
578             services_table.attach_defaults (label, 0, 1, i, i+1);
579             services_table.attach_defaults (remove_button, 1, 2, i, i+1);
580             i++;
581         }
582         this.services_internal_vbox.show_all ();
583     }
584
585     private void on_about_action ()
586     {
587         string[] authors = {
588             "Javier Jardón <jjardon@codethink.co.uk>",
589             "Sam Thursfield <samthursfield@codethink.co.uk>",
590             "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
591             null
592         };
593
594         string copyright = "Copyright 2011 JANET";
595
596         string license =
597 """
598 Copyright (c) 2011, JANET(UK)
599 All rights reserved.
600
601 Redistribution and use in source and binary forms, with or without
602 modification, are permitted provided that the following conditions
603 are met:
604
605 1. Redistributions of source code must retain the above copyright
606    notice, this list of conditions and the following disclaimer.
607
608 2. Redistributions in binary form must reproduce the above copyright
609    notice, this list of conditions and the following disclaimer in the
610    documentation and/or other materials provided with the distribution.
611
612 3. Neither the name of JANET(UK) nor the names of its contributors
613    may be used to endorse or promote products derived from this software
614    without specific prior written permission.
615
616 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
617 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
618 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
619 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
620 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
621 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
622 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
623 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
624 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
625 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
626 SUCH DAMAGE.
627 """;
628
629         Gtk.show_about_dialog (this,
630             "comments", _("Moonshot project UI"),
631             "copyright", copyright,
632             "website", Config.PACKAGE_URL,
633             "version", Config.PACKAGE_VERSION,
634             "license", license,
635             "website-label", _("Visit the Moonshot project web site"),
636             "authors", authors,
637             "translator-credits", _("translator-credits"),
638             null
639         );
640     }
641
642     private Gtk.ActionEntry[] create_actions() {
643         Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
644
645         Gtk.ActionEntry filemenu = { "FileMenuAction",
646                                      null,
647                                      N_("_File"),
648                                      null, null, null };
649         actions += filemenu;
650         Gtk.ActionEntry add = { "AddIdCardAction",
651 #if VALA_0_12
652                                 Stock.ADD,
653 #else
654                                 STOCK_ADD,
655 #endif
656                                 N_("Add ID Card"),
657                                 null,
658                                 N_("Add a new ID Card"),
659                                 add_identity_manual_cb };
660         actions += add;
661         Gtk.ActionEntry quit = { "QuitAction",
662 #if VALA_0_12
663                                  Stock.QUIT,
664 #else
665                                  STOCK_QUIT,
666 #endif
667                                  N_("Quit"),
668                                  "<control>Q",
669                                  N_("Quit the application"),
670                                  Gtk.main_quit };
671         actions += quit;
672
673         Gtk.ActionEntry helpmenu = { "HelpMenuAction",
674                                      null,
675                                      N_("_Help"),
676                                      null, null, null };
677         actions += helpmenu;
678         Gtk.ActionEntry about = { "AboutAction",
679 #if VALA_0_12
680                                   Stock.ABOUT,
681 #else
682                                   STOCK_ABOUT,
683 #endif
684                                   N_("About"),
685                                   null,
686                                   N_("About this application"),
687                                   on_about_action };
688         actions += about;
689
690         return actions;
691     }
692
693
694     private void create_ui_manager ()
695     {
696         Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
697         action_group.add_actions (create_actions (), this);
698         ui_manager.insert_action_group (action_group, 0);
699         try
700         {
701             ui_manager.add_ui_from_string (layout, -1);
702         }
703         catch (Error e)
704         {
705             stderr.printf ("%s\n", e.message);
706         }
707         ui_manager.ensure_update ();
708     }
709
710     private void build_ui()
711     {
712         create_ui_manager ();
713
714         this.search_entry = new Entry();
715
716         set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
717         this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
718                                                 find_icon_sized ("edit-find", Gtk.IconSize.MENU));
719 //                                                find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
720         this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
721                                                  _("Search identity or service"));
722         this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
723
724         this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
725                                                 find_icon_sized ("process-stop", Gtk.IconSize.MENU));
726 //                                                find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
727         this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
728                                                  _("Clear the current search"));
729         this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
730
731
732         this.search_entry.icon_press.connect (search_entry_icon_press_cb);
733         this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
734         this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
735
736         this.custom_vbox = new CustomVBox (this, false, 6);
737
738         var viewport = new Viewport (null, null);
739         viewport.set_border_width (6);
740         viewport.set_shadow_type (ShadowType.NONE);
741         viewport.add (custom_vbox);
742         var scroll = new ScrolledWindow (null, null);
743         scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
744         scroll.set_shadow_type (ShadowType.IN);
745         scroll.add_with_viewport (viewport);
746
747         var vbox_left = new VBox (false, 0);
748         vbox_left.pack_start (search_entry, false, false, 6);
749         vbox_left.pack_start (scroll, true, true, 0);
750         vbox_left.set_size_request (WINDOW_WIDTH, 0);
751
752         var login_vbox_title = new Label (_("Login: "));
753         label_make_bold (login_vbox_title);
754         login_vbox_title.set_alignment (0, (float) 0.5);
755         var username_label = new Label (_("Username:"));
756         username_label.set_alignment (1, (float) 0.5);
757         this.username_entry = new Entry ();
758         var password_label = new Label (_("Password:"));
759         password_label.set_alignment (1, (float) 0.5);
760         this.password_entry = new Entry ();
761         password_entry.set_invisible_char ('*');
762         password_entry.set_visibility (false);
763         var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
764         var login_table = new Table (3, 3, false);
765         login_table.set_col_spacings (10);
766         login_table.set_row_spacings (10);
767         login_table.attach_defaults (username_label, 0, 1, 0, 1);
768         login_table.attach_defaults (username_entry, 1, 2, 0, 1);
769         login_table.attach_defaults (password_label, 0, 1, 1, 2);
770         login_table.attach_defaults (password_entry, 1, 2, 1, 2);
771         login_table.attach_defaults (remember_checkbutton,  1, 2, 2, 3);
772         var login_vbox_alignment = new Alignment (0, 0, 0, 0);
773         login_vbox_alignment.set_padding (0, 0, 12, 0);
774         login_vbox_alignment.add (login_table);
775         var login_vbox = new VBox (false, 6);
776         login_vbox.pack_start (login_vbox_title, false, true, 0);
777         login_vbox.pack_start (login_vbox_alignment, false, true, 0);
778
779         var services_vbox_title = new Label (_("Services:"));
780         label_make_bold (services_vbox_title);
781         services_vbox_title.set_alignment (0, (float) 0.5);
782         var services_vbox_alignment = new Alignment (0, 0, 0, 0);
783         services_vbox_alignment.set_padding (0, 0, 12, 0);
784         this.services_internal_vbox = new VBox (true, 6);
785         services_vbox_alignment.add (services_internal_vbox);
786         var services_vbox = new VBox (false, 6);
787         services_vbox.pack_start (services_vbox_title, false, true, 0);
788         services_vbox.pack_start (services_vbox_alignment, false, true, 0);
789
790         this.vbox_right = new VBox (false, 18);
791         vbox_right.pack_start (login_vbox, false, true, 0);
792         vbox_right.pack_start (services_vbox, false, true, 0);
793
794         var hbox = new HBox (false, 12);
795         hbox.pack_start (vbox_left, true, true, 0);
796         hbox.pack_start (vbox_right, false, false, 0);
797
798         var main_vbox = new VBox (false, 0);
799         main_vbox.set_border_width (12);
800  
801 #if OS_MACOS
802         // hide the  File | Quit menu item which is now on the Mac Menu
803         Gtk.Widget quit_item =  this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
804         quit_item.hide();
805         
806                 Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
807                 osxApp.set_menu_bar(menushell);
808                 osxApp.set_use_quartz_accelerators(true);
809                 osxApp.sync_menu_bar();
810                 osxApp.ready(); 
811 #else
812         var menubar = this.ui_manager.get_widget ("/MenuBar");
813         main_vbox.pack_start (menubar, false, false, 0);
814 #endif
815         main_vbox.pack_start (hbox, true, true, 0);
816         add (main_vbox);
817         main_vbox.show_all();
818         this.vbox_right.hide ();
819   } 
820
821     private void set_atk_name_description (Widget widget, string name, string description)
822     {
823        var atk_widget = widget.get_accessible ();
824
825        atk_widget.set_name (name);
826        atk_widget.set_description (description);
827     }
828
829     private void connect_signals()
830     {
831         this.destroy.connect (Gtk.main_quit);
832         this.identities_manager.card_list_changed.connect(this.on_card_list_changed);
833     }
834 }
835
836