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