Identity Selector: Add special 'No Identity' idcard (LP# 1164611)
[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     /* This method finds a valid display name */
310     public bool display_name_is_valid (string name,
311                                        out string? candidate)
312     {
313         foreach (IdCard id_card in identities_manager.get_card_list())
314         {
315           if (id_card.display_name == name)
316           {
317             if (&candidate != null)
318             {
319               for (int i=0; i<1000; i++)
320               {
321                 string tmp = "%s %d".printf (name, i);
322                 if (display_name_is_valid (tmp, null))
323                 {
324                   candidate = tmp;
325                   break;
326                 }
327               }
328             }
329             return false;
330           }
331         }
332         
333         return true;
334     }
335     
336     public void insert_id_card (IdCard id_card)
337     {
338         string candidate;
339         
340         if (!display_name_is_valid (id_card.display_name, out candidate))
341         {
342           id_card.display_name = candidate;
343         }
344     
345     this.identities_manager.add_card(id_card);
346     }
347
348     public bool add_identity (IdCard id_card)
349     {
350 #if OS_MACOS
351         /* 
352          * TODO: We should have a confirmation dialog, but currently it will crash on Mac OS
353          * so for now we will install silently
354          */
355         var ret = Gtk.ResponseType.YES;
356 #else
357
358         var dialog = new Gtk.MessageDialog (this,
359                                             Gtk.DialogFlags.DESTROY_WITH_PARENT,
360                                             Gtk.MessageType.QUESTION,
361                                             Gtk.ButtonsType.YES_NO,
362                                             _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
363                                             id_card.display_name);
364
365         var ret = dialog.run ();
366         dialog.destroy ();
367 #endif
368
369         if (ret == Gtk.ResponseType.YES) {
370             id_card.set_data ("pixbuf", find_icon ("avatar-default", 48));
371             this.insert_id_card (id_card);
372             return true;
373         }
374
375         return false;
376     }
377
378     private void add_identity_manual_cb ()
379     {
380         var dialog = new AddIdentityDialog ();
381         var result = dialog.run ();
382
383         switch (result) {
384         case ResponseType.OK:
385             insert_id_card (get_id_card_data (dialog));
386             break;
387         default:
388             break;
389         }
390         dialog.destroy ();
391     }
392
393     private void remove_id_card_widget (IdCardWidget id_card_widget) {
394        this.custom_vbox.remove_id_card_widget (id_card_widget);
395     }
396
397     private void remove_identity (IdCardWidget id_card_widget)
398     {
399         var id_card = id_card_widget.id_card;
400         remove_id_card_widget (id_card_widget);
401
402         this.identities_manager.remove_card(id_card);
403     }
404
405     private void redraw_id_card_widgets ()
406     {
407         TreeIter iter;
408         IdCard id_card;
409
410         var children = this.custom_vbox.get_children ();
411         foreach (var id_card_widget in children)
412             remove_id_card_widget((IdCardWidget )id_card_widget); //id_card_widget.destroy();
413
414         if (filter.get_iter_first (out iter))
415         {
416             do
417             {
418                 filter.get (iter,
419                             Columns.IDCARD_COL, out id_card);
420
421                 add_id_card_widget (id_card);
422             }
423             while (filter.iter_next (ref iter));
424         }
425     }
426
427     private void remove_identity_cb (IdCardWidget id_card_widget)
428     {
429         var id_card = id_card_widget.id_card;
430
431         var dialog = new MessageDialog (this,
432                                         DialogFlags.DESTROY_WITH_PARENT,
433                                         MessageType.QUESTION,
434                                         Gtk.ButtonsType.YES_NO,
435                                         _("Are you sure you want to delete %s ID Card?"), id_card.issuer);
436         var result = dialog.run ();
437         switch (result) {
438         case ResponseType.YES:
439             remove_identity (id_card_widget);
440             break;
441         default:
442             break;
443         }
444         dialog.destroy ();
445     }
446
447     public void set_prompting_service(string service)
448     {
449         prompting_service.set_label( _("Identity requested for service: %s").printf(service) );
450     }
451
452     public void queue_identity_request(IdentityRequest request)
453     {
454         if (this.request_queue.is_empty())
455         { /* setup widgets */
456             candidates = request.candidates;
457             filter.refilter();
458             redraw_id_card_widgets ();
459             set_prompting_service(request.service);
460             show ();
461         }
462         this.request_queue.push_tail (request);
463     }
464
465     public void send_identity_cb (IdCard identity)
466     {
467         return_if_fail (request_queue.length > 0);
468
469         candidates = null;
470         var request = this.request_queue.pop_head ();
471         bool reset_password = false;
472
473         if (identity.password == null)
474         {
475             var dialog = new AddPasswordDialog ();
476             var result = dialog.run ();
477
478             switch (result) {
479             case ResponseType.OK:
480                 identity.password = dialog.password;
481                 reset_password = ! dialog.remember;
482                 break;
483             default:
484                 identity = null;
485                 break;
486             }
487
488             dialog.destroy ();
489         }
490
491         if (this.request_queue.is_empty())
492         {
493             candidates = null;
494             prompting_service.set_label(_(""));
495             if (!parent_app.explicitly_launched) {
496 // The following occasionally causes the app to exit without sending the dbus
497 // reply, so for now we just don't exit
498 //                Gtk.main_quit ();
499 // just hide instead
500                 this.hide();
501             }
502         } else {
503             IdentityRequest next = this.request_queue.peek_head();
504             candidates = next.candidates;
505             set_prompting_service(next.service);
506         }
507         filter.refilter();
508         redraw_id_card_widgets ();
509
510         if (identity != null)
511             parent_app.default_id_card = identity;
512
513         request.return_identity (identity);
514
515         if (reset_password)
516             identity.password = null;
517
518     }
519
520     private void label_make_bold (Label label)
521     {
522         var font_desc = new Pango.FontDescription ();
523
524         font_desc.set_weight (Pango.Weight.BOLD);
525
526         /* This will only affect the weight of the font, the rest is
527          * from the current state of the widget, which comes from the
528          * theme or user prefs, since the font desc only has the
529          * weight flag turned on.
530          */
531         label.modify_font (font_desc);
532     }
533
534     private void fill_services_vbox (IdCard id_card)
535     {
536         int i = 0;
537         var n_columns = id_card.services.length;
538
539         var services_table = new Table (n_columns, 2, false);
540         services_table.set_col_spacings (10);
541         services_table.set_row_spacings (10);
542         this.services_internal_vbox.add (services_table);
543         
544         service_button_map.remove_all ();
545
546         foreach (string service in id_card.services)
547         {
548             var label = new Label (service);
549             label.set_alignment (0, (float) 0.5);
550 #if VALA_0_12
551             var remove_button = new Button.from_stock (Stock.REMOVE);
552 #else
553             var remove_button = new Button.from_stock (STOCK_REMOVE);
554 #endif
555
556
557             service_button_map.insert (remove_button, service);
558             
559             remove_button.clicked.connect ((remove_button) =>
560             {
561               var dialog = new Gtk.MessageDialog (this,
562                                       Gtk.DialogFlags.DESTROY_WITH_PARENT,
563                                       Gtk.MessageType.QUESTION,
564                                       Gtk.ButtonsType.YES_NO,
565                                       _("Are you sure you want to stop '%s' ID Card from being used with %s?"),
566                                       custom_vbox.current_idcard.id_card.display_name,
567                                       _("this service"));
568               var ret = dialog.run();
569               dialog.hide();
570               
571               if (ret == Gtk.ResponseType.YES)
572               {
573                 IdCard idcard = custom_vbox.current_idcard.id_card;
574                 var candidate = service_button_map.lookup (remove_button);
575
576                 SList<string> services = new SList<string>();
577                 
578                 foreach (string srv in idcard.services)
579                 {
580                   if (srv == candidate)
581                     continue;
582                   services.append (srv);
583                 }
584                 
585                 idcard.services = new string[services.length()];
586                 for (int j=0; j<idcard.services.length; j++)
587                 {
588                   idcard.services[j] = services.nth_data(j);
589                 }
590                 
591                 var children = services_internal_vbox.get_children ();
592                 foreach (var hbox in children)
593                   services_internal_vbox.remove(hbox);
594                 
595                 fill_services_vbox (idcard);
596                 custom_vbox.current_idcard.update_id_card_label ();
597                 identities_manager.update_card(idcard);
598               }
599               
600             });
601             services_table.attach_defaults (label, 0, 1, i, i+1);
602             services_table.attach_defaults (remove_button, 1, 2, i, i+1);
603             i++;
604         }
605         this.services_internal_vbox.show_all ();
606     }
607
608     private void on_about_action ()
609     {
610         string[] authors = {
611             "Javier Jardón <jjardon@codethink.co.uk>",
612             "Sam Thursfield <samthursfield@codethink.co.uk>",
613             "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
614             null
615         };
616
617         string copyright = "Copyright 2011 JANET";
618
619         string license =
620 """
621 Copyright (c) 2011, JANET(UK)
622 All rights reserved.
623
624 Redistribution and use in source and binary forms, with or without
625 modification, are permitted provided that the following conditions
626 are met:
627
628 1. Redistributions of source code must retain the above copyright
629    notice, this list of conditions and the following disclaimer.
630
631 2. Redistributions in binary form must reproduce the above copyright
632    notice, this list of conditions and the following disclaimer in the
633    documentation and/or other materials provided with the distribution.
634
635 3. Neither the name of JANET(UK) nor the names of its contributors
636    may be used to endorse or promote products derived from this software
637    without specific prior written permission.
638
639 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
640 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
641 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
642 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
643 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
644 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
645 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
646 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
647 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
648 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
649 SUCH DAMAGE.
650 """;
651
652         Gtk.show_about_dialog (this,
653             "comments", _("Moonshot project UI"),
654             "copyright", copyright,
655             "website", Config.PACKAGE_URL,
656             "version", Config.PACKAGE_VERSION,
657             "license", license,
658             "website-label", _("Visit the Moonshot project web site"),
659             "authors", authors,
660             "translator-credits", _("translator-credits"),
661             null
662         );
663     }
664
665     private Gtk.ActionEntry[] create_actions() {
666         Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
667
668         Gtk.ActionEntry filemenu = { "FileMenuAction",
669                                      null,
670                                      N_("_File"),
671                                      null, null, null };
672         actions += filemenu;
673         Gtk.ActionEntry add = { "AddIdCardAction",
674 #if VALA_0_12
675                                 Stock.ADD,
676 #else
677                                 STOCK_ADD,
678 #endif
679                                 N_("Add ID Card"),
680                                 null,
681                                 N_("Add a new ID Card"),
682                                 add_identity_manual_cb };
683         actions += add;
684         Gtk.ActionEntry quit = { "QuitAction",
685 #if VALA_0_12
686                                  Stock.QUIT,
687 #else
688                                  STOCK_QUIT,
689 #endif
690                                  N_("Quit"),
691                                  "<control>Q",
692                                  N_("Quit the application"),
693                                  Gtk.main_quit };
694         actions += quit;
695
696         Gtk.ActionEntry helpmenu = { "HelpMenuAction",
697                                      null,
698                                      N_("_Help"),
699                                      null, null, null };
700         actions += helpmenu;
701         Gtk.ActionEntry about = { "AboutAction",
702 #if VALA_0_12
703                                   Stock.ABOUT,
704 #else
705                                   STOCK_ABOUT,
706 #endif
707                                   N_("About"),
708                                   null,
709                                   N_("About this application"),
710                                   on_about_action };
711         actions += about;
712
713         return actions;
714     }
715
716
717     private void create_ui_manager ()
718     {
719         Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
720         action_group.add_actions (create_actions (), this);
721         ui_manager.insert_action_group (action_group, 0);
722         try
723         {
724             ui_manager.add_ui_from_string (layout, -1);
725         }
726         catch (Error e)
727         {
728             stderr.printf ("%s\n", e.message);
729         }
730         ui_manager.ensure_update ();
731     }
732
733     private void build_ui()
734     {
735         create_ui_manager ();
736
737         this.search_entry = new Entry();
738
739         set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
740         this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
741                                                 find_icon_sized ("edit-find", Gtk.IconSize.MENU));
742 //                                                find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
743         this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
744                                                  _("Search identity or service"));
745         this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
746
747         this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
748                                                 find_icon_sized ("process-stop", Gtk.IconSize.MENU));
749 //                                                find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
750         this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
751                                                  _("Clear the current search"));
752         this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
753
754
755         this.search_entry.icon_press.connect (search_entry_icon_press_cb);
756         this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
757         this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
758
759         this.custom_vbox = new CustomVBox (this, false, 6);
760
761         var viewport = new Viewport (null, null);
762         viewport.set_border_width (6);
763         viewport.set_shadow_type (ShadowType.NONE);
764         viewport.add (custom_vbox);
765         var scroll = new ScrolledWindow (null, null);
766         scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
767         scroll.set_shadow_type (ShadowType.IN);
768         scroll.add_with_viewport (viewport);
769         this.prompting_service = new Label (_(""));
770         // left-align
771         prompting_service.set_alignment(0, (float )0.5);
772
773         var vbox_left = new VBox (false, 0);
774         vbox_left.pack_start (search_entry, false, false, 6);
775         vbox_left.pack_start (scroll, true, true, 0);
776         vbox_left.pack_start (prompting_service, false, false, 6);
777         vbox_left.set_size_request (WINDOW_WIDTH, 0);
778
779         this.no_identity_title = new Label (_("No Identity: Send this identity to services which should not use Moonshot"));
780         no_identity_title.set_alignment(0, (float ) 0.5);
781         no_identity_title.set_size_request(RIGHT_PANE_WIDTH, -1);
782         no_identity_title.set_line_wrap(true);
783         no_identity_title.show();
784
785         var login_vbox_title = new Label (_("Login: "));
786         label_make_bold (login_vbox_title);
787         login_vbox_title.set_alignment (0, (float) 0.5);
788         var username_label = new Label (_("Username:"));
789         username_label.set_alignment (1, (float) 0.5);
790         this.username_entry = new Entry ();
791         var password_label = new Label (_("Password:"));
792         password_label.set_alignment (1, (float) 0.5);
793         this.password_entry = new Entry ();
794         password_entry.set_invisible_char ('*');
795         password_entry.set_visibility (false);
796         var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
797         var login_table = new Table (3, 3, false);
798         login_table.set_col_spacings (10);
799         login_table.set_row_spacings (10);
800         login_table.attach_defaults (username_label, 0, 1, 0, 1);
801         login_table.attach_defaults (username_entry, 1, 2, 0, 1);
802         login_table.attach_defaults (password_label, 0, 1, 1, 2);
803         login_table.attach_defaults (password_entry, 1, 2, 1, 2);
804         login_table.attach_defaults (remember_checkbutton,  1, 2, 2, 3);
805         var login_vbox_alignment = new Alignment (0, 0, 0, 0);
806         login_vbox_alignment.set_padding (0, 0, 12, 0);
807         login_vbox_alignment.add (login_table);
808         this.login_vbox = new VBox (false, 6);
809         login_vbox.pack_start (login_vbox_title, false, true, 0);
810         login_vbox.pack_start (login_vbox_alignment, false, true, 0);
811
812         var services_vbox_title = new Label (_("Services:"));
813         label_make_bold (services_vbox_title);
814         services_vbox_title.set_alignment (0, (float) 0.5);
815         var services_vbox_alignment = new Alignment (0, 0, 0, 0);
816         services_vbox_alignment.set_padding (0, 0, 12, 0);
817         this.services_internal_vbox = new VBox (true, 6);
818         services_vbox_alignment.add (services_internal_vbox);
819         this.services_vbox = new VBox (false, 6);
820         services_vbox.pack_start (services_vbox_title, false, true, 0);
821         services_vbox.pack_start (services_vbox_alignment, false, true, 0);
822
823         this.vbox_right = new VBox (false, 18);
824         vbox_right.pack_start (login_vbox, false, true, 0);
825         vbox_right.pack_start (services_vbox, false, true, 0);
826         vbox_right.set_size_request( RIGHT_PANE_WIDTH, -1 );
827
828         var hbox = new HBox (false, 12);
829         hbox.pack_start (vbox_left, true, true, 0);
830         hbox.pack_start (vbox_right, false, false, 0);
831
832         var main_vbox = new VBox (false, 0);
833         main_vbox.set_border_width (12);
834  
835 #if OS_MACOS
836         // hide the  File | Quit menu item which is now on the Mac Menu
837         Gtk.Widget quit_item =  this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
838         quit_item.hide();
839         
840                 Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
841                 osxApp.set_menu_bar(menushell);
842                 osxApp.set_use_quartz_accelerators(true);
843                 osxApp.sync_menu_bar();
844                 osxApp.ready(); 
845 #else
846         var menubar = this.ui_manager.get_widget ("/MenuBar");
847         main_vbox.pack_start (menubar, false, false, 0);
848 #endif
849         main_vbox.pack_start (hbox, true, true, 0);
850         add (main_vbox);
851         main_vbox.show_all();
852         this.vbox_right.hide ();
853   } 
854
855     private void set_atk_name_description (Widget widget, string name, string description)
856     {
857        var atk_widget = widget.get_accessible ();
858
859        atk_widget.set_name (name);
860        atk_widget.set_description (description);
861     }
862
863     private void connect_signals()
864     {
865         this.destroy.connect (Gtk.main_quit);
866         this.identities_manager.card_list_changed.connect(this.on_card_list_changed);
867     }
868 }
869
870