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