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