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