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