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