Attempted merge of formatting updates
[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 Log4Vala.Logger 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
431         return false;
432     }
433
434     private void add_identity_manual_cb()
435     {
436         var dialog = new AddIdentityDialog();
437         int result = ResponseType.CANCEL;
438         while (!dialog.complete)
439             result = dialog.run();
440
441         switch (result) {
442         case ResponseType.OK:
443             this.identities_manager.add_card(get_id_card_data(dialog), false);
444             break;
445         default:
446             break;
447         }
448         dialog.destroy();
449     }
450
451     private void remove_id_card_widget(IdCardWidget id_card_widget) {
452         this.custom_vbox.remove_id_card_widget(id_card_widget);
453     }
454
455     private void remove_identity(IdCardWidget id_card_widget)
456     {
457         var id_card = id_card_widget.id_card;
458         remove_id_card_widget(id_card_widget);
459
460         this.identities_manager.remove_card(id_card);
461     }
462
463     private void redraw_id_card_widgets()
464     {
465         TreeIter iter;
466         IdCard id_card;
467
468         var children = this.custom_vbox.get_children();
469         foreach (var id_card_widget in children) {
470             remove_id_card_widget((IdCardWidget )id_card_widget); //id_card_widget.destroy();
471         }
472
473         if (filter.get_iter_first(out iter))
474         {
475             do
476             {
477                 filter.get(iter,
478                            Columns.IDCARD_COL, out id_card);
479
480                 add_id_card_widget(id_card);
481             }
482             while (filter.iter_next(ref iter));
483         }
484     }
485
486     private void remove_identity_cb(IdCardWidget id_card_widget)
487     {
488         var id_card = id_card_widget.id_card;
489
490         var dialog = new MessageDialog(this,
491                                        DialogFlags.DESTROY_WITH_PARENT,
492                                        MessageType.QUESTION,
493                                        Gtk.ButtonsType.YES_NO,
494                                        _("Are you sure you want to delete %s ID Card?"), id_card.issuer);
495         var result = dialog.run();
496         switch (result) {
497         case ResponseType.YES:
498             remove_identity(id_card_widget);
499             break;
500         default:
501             break;
502         }
503         dialog.destroy();
504     }
505
506     public void set_prompting_service(string service)
507     {
508         prompting_service.set_label( _("Identity requested for service: %s").printf(service) );
509     }
510
511     public void queue_identity_request(IdentityRequest request)
512     {
513         if (this.request_queue.is_empty())
514         { /* setup widgets */
515             candidates = request.candidates;
516             filter.refilter();
517             redraw_id_card_widgets();
518             set_prompting_service(request.service);
519             make_visible();
520         }
521         this.request_queue.push_tail(request);
522     }
523
524
525     /** Makes the window visible, or at least, notifies the user that the window
526      * wants to be visible.
527      *
528      * This differs from show() in that show() does not guarantee that the 
529      * window will be moved to the foreground. Actually, neither does this
530      * method, because the user's settings and window manager may affect the
531      * behavior significantly.
532      */
533     public void make_visible()
534     {
535                 logger.trace("make_visible()");
536         set_urgency_hint(true);
537         present();
538     }
539
540     public IdCard check_add_password(IdCard identity, IdentityRequest request, IdentityManagerModel model)
541     {
542         IdCard retval = identity;
543         bool idcard_has_pw = (identity.password != null) && (identity.password != "");
544         bool request_has_pw = (request.password != null) && (request.password != "");
545         if ((!idcard_has_pw) && (!identity.IsNoIdentity())) {
546             if (request_has_pw) {
547                 identity.password = request.password;
548                 retval = model.update_card(identity);
549             } else {
550                 var dialog = new AddPasswordDialog(identity, request);
551                 var result = dialog.run();
552
553                 switch (result) {
554                 case ResponseType.OK:
555                     identity.password = dialog.password;
556                     identity.store_password = dialog.remember;
557                     if (dialog.remember)
558                         identity.temporary = false;
559                     retval = model.update_card(identity);
560                     break;
561                 default:
562                     identity = null;
563                     break;
564                 }
565                 dialog.destroy();
566             }
567         }
568         return retval;
569     }
570
571     public void send_identity_cb(IdCard id)
572     {
573         IdCard identity = id;
574         return_if_fail(request_queue.length > 0);
575
576         candidates = null;
577         var request = this.request_queue.pop_head();
578         identity = check_add_password(identity, request, identities_manager);
579         if (this.request_queue.is_empty())
580         {
581             candidates = null;
582             prompting_service.set_label(_(""));
583             if (!parent_app.explicitly_launched) {
584 // The following occasionally causes the app to exit without sending the dbus
585 // reply, so for now we just don't exit
586 //                Gtk.main_quit();
587 // just hide instead
588                 this.hide();
589             }
590         } else {
591             IdentityRequest next = this.request_queue.peek_head();
592             candidates = next.candidates;
593             set_prompting_service(next.service);
594         }
595         filter.refilter();
596         redraw_id_card_widgets();
597
598         if ((identity != null) && (!identity.IsNoIdentity()))
599             parent_app.default_id_card = identity;
600
601         request.return_identity(identity);
602     }
603
604     private void label_make_bold(Label label)
605     {
606         var font_desc = new Pango.FontDescription();
607
608         font_desc.set_weight(Pango.Weight.BOLD);
609
610         /* This will only affect the weight of the font, the rest is
611          * from the current state of the widget, which comes from the
612          * theme or user prefs, since the font desc only has the
613          * weight flag turned on.
614          */
615         label.modify_font(font_desc);
616     }
617
618     private void fill_services_vbox(IdCard id_card)
619     {
620         int i = 0;
621         var n_columns = id_card.services.length;
622
623         var services_table = new Table(n_columns, 2, false);
624         services_table.set_col_spacings(10);
625         services_table.set_row_spacings(10);
626         this.services_internal_vbox.add(services_table);
627         
628         service_button_map.remove_all();
629
630         foreach (string service in id_card.services)
631         {
632             var label = new Label(service);
633             label.set_alignment(0, (float) 0.5);
634 #if VALA_0_12
635             var remove_button = new Button.from_stock(Stock.REMOVE);
636 #else
637             var remove_button = new Button.from_stock(STOCK_REMOVE);
638 #endif
639
640
641             service_button_map.insert(remove_button, service);
642             
643             remove_button.clicked.connect((remove_button) =>
644                 {
645                     var candidate = service_button_map.lookup(remove_button);
646                     if (candidate == null)
647                         return;
648                     var dialog = new Gtk.MessageDialog(this,
649                                                        Gtk.DialogFlags.DESTROY_WITH_PARENT,
650                                                        Gtk.MessageType.QUESTION,
651                                                        Gtk.ButtonsType.YES_NO,
652                                                        _("Are you sure you want to stop '%s' ID Card from being used with %s?"),
653                                                        custom_vbox.current_idcard.id_card.display_name,
654                                                        candidate);
655                     var ret = dialog.run();
656                     dialog.hide();
657               
658                     if (ret == Gtk.ResponseType.YES)
659                     {
660                         IdCard idcard = custom_vbox.current_idcard.id_card;
661                         if (idcard != null) {
662                             SList<string> services = new SList<string>();
663                 
664                             foreach (string srv in idcard.services)
665                             {
666                                 if (srv == candidate)
667                                     continue;
668                                 services.append(srv);
669                             }
670                 
671                             idcard.services = new string[services.length()];
672                             for (int j = 0; j < idcard.services.length; j++)
673                             {
674                                 idcard.services[j] = services.nth_data(j);
675                             }
676                 
677                             identities_manager.update_card(idcard);
678                         }
679                     }
680               
681                 });
682             services_table.attach_defaults(label, 0, 1, i, i+1);
683             services_table.attach_defaults(remove_button, 1, 2, i, i+1);
684             i++;
685         }
686         this.services_internal_vbox.show_all();
687     }
688
689     private void on_about_action()
690     {
691         string[] authors = {
692             "Javier Jardón <jjardon@codethink.co.uk>",
693             "Sam Thursfield <samthursfield@codethink.co.uk>",
694             "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
695             null
696         };
697
698         string copyright = "Copyright 2011 JANET";
699
700         string license =
701         """
702 Copyright (c) 2011, JANET(UK)
703 All rights reserved.
704
705 Redistribution and use in source and binary forms, with or without
706 modification, are permitted provided that the following conditions
707 are met:
708
709 1. Redistributions of source code must retain the above copyright
710    notice, this list of conditions and the following disclaimer.
711
712 2. Redistributions in binary form must reproduce the above copyright
713    notice, this list of conditions and the following disclaimer in the
714    documentation and/or other materials provided with the distribution.
715
716 3. Neither the name of JANET(UK) nor the names of its contributors
717    may be used to endorse or promote products derived from this software
718    without specific prior written permission.
719
720 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
721 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
722 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
723 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
724 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
725 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
726 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
727 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
728 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
729 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
730 SUCH DAMAGE.
731 """;
732
733         Gtk.show_about_dialog(this,
734                               "comments", _("Moonshot project UI"),
735                               "copyright", copyright,
736                               "website", Config.PACKAGE_URL,
737                               "version", Config.PACKAGE_VERSION,
738                               "license", license,
739                               "website-label", _("Visit the Moonshot project web site"),
740                               "authors", authors,
741                               "translator-credits", _("translator-credits"),
742                               null
743             );
744     }
745
746     private Gtk.ActionEntry[] create_actions() {
747         Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
748
749         Gtk.ActionEntry filemenu = { "FileMenuAction",
750                                      null,
751                                      N_("_File"),
752                                      null, null, null };
753         actions += filemenu;
754         Gtk.ActionEntry add = { "AddIdCardAction",
755 #if VALA_0_12
756                                 Stock.ADD,
757 #else
758                                 STOCK_ADD,
759 #endif
760                                 N_("Add ID Card"),
761                                 null,
762                                 N_("Add a new ID Card"),
763                                 add_identity_manual_cb };
764         actions += add;
765         Gtk.ActionEntry quit = { "QuitAction",
766 #if VALA_0_12
767                                  Stock.QUIT,
768 #else
769                                  STOCK_QUIT,
770 #endif
771                                  N_("Quit"),
772                                  "<control>Q",
773                                  N_("Quit the application"),
774                                  Gtk.main_quit };
775         actions += quit;
776
777         Gtk.ActionEntry helpmenu = { "HelpMenuAction",
778                                      null,
779                                      N_("_Help"),
780                                      null, null, null };
781         actions += helpmenu;
782         Gtk.ActionEntry about = { "AboutAction",
783 #if VALA_0_12
784                                   Stock.ABOUT,
785 #else
786                                   STOCK_ABOUT,
787 #endif
788                                   N_("About"),
789                                   null,
790                                   N_("About this application"),
791                                   on_about_action };
792         actions += about;
793
794         return actions;
795     }
796
797
798     private void create_ui_manager()
799     {
800         Gtk.ActionGroup action_group = new Gtk.ActionGroup("GeneralActionGroup");
801         action_group.add_actions(create_actions(), this);
802         ui_manager.insert_action_group(action_group, 0);
803         try
804         {
805             ui_manager.add_ui_from_string(layout, -1);
806         }
807         catch (Error e)
808         {
809             stderr.printf("%s\n", e.message);
810             logger.error("create_ui_manager: Caught error: " + e.message);
811         }
812         ui_manager.ensure_update();
813     }
814
815     private void build_ui()
816     {
817         create_ui_manager();
818
819         this.search_entry = new Entry();
820
821         set_atk_name_description(search_entry, _("Search entry"), _("Search for a specific ID Card"));
822         this.search_entry.set_icon_from_pixbuf(EntryIconPosition.PRIMARY,
823                                                find_icon_sized("edit-find", Gtk.IconSize.MENU));
824 //                                                find_icon_sized("edit-find-symbolic", Gtk.IconSize.MENU));
825         this.search_entry.set_icon_tooltip_text(EntryIconPosition.PRIMARY,
826                                                 _("Search identity or service"));
827         this.search_entry.set_icon_sensitive(EntryIconPosition.PRIMARY, false);
828
829         this.search_entry.set_icon_from_pixbuf(EntryIconPosition.SECONDARY,
830                                                find_icon_sized("process-stop", Gtk.IconSize.MENU));
831 //                                                find_icon_sized("edit-clear-symbolic", Gtk.IconSize.MENU));
832         this.search_entry.set_icon_tooltip_text(EntryIconPosition.SECONDARY,
833                                                 _("Clear the current search"));
834         this.search_entry.set_icon_sensitive(EntryIconPosition.SECONDARY, false);
835
836
837         this.search_entry.icon_press.connect(search_entry_icon_press_cb);
838         this.search_entry.notify["text"].connect(search_entry_text_changed_cb);
839         this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
840
841         this.custom_vbox = new CustomVBox(this, false, 6);
842
843         var viewport = new Viewport(null, null);
844         viewport.set_border_width(6);
845         viewport.set_shadow_type(ShadowType.NONE);
846         viewport.add(custom_vbox);
847         var scroll = new ScrolledWindow(null, null);
848         scroll.set_policy(PolicyType.NEVER, PolicyType.AUTOMATIC);
849         scroll.set_shadow_type(ShadowType.IN);
850         scroll.add_with_viewport(viewport);
851         this.prompting_service = new Label(_(""));
852         // left-align
853         prompting_service.set_alignment(0, (float )0.5);
854
855         var vbox_left = new VBox(false, 0);
856         vbox_left.pack_start(search_entry, false, false, 6);
857         vbox_left.pack_start(scroll, true, true, 0);
858         vbox_left.pack_start(prompting_service, false, false, 6);
859         vbox_left.set_size_request(WINDOW_WIDTH, 0);
860
861         this.no_identity_title = new Label(_("No Identity: Send this identity to services which should not use Moonshot"));
862         no_identity_title.set_alignment(0, (float ) 0.5);
863         no_identity_title.set_line_wrap(true);
864         no_identity_title.show();
865
866         var login_vbox_title = new Label(_("Login: "));
867         label_make_bold(login_vbox_title);
868         login_vbox_title.set_alignment(0, (float) 0.5);
869         var issuer_label = new Label(_("Issuer:"));
870         issuer_label.set_alignment(1, (float) 0.5);
871         this.issuer_entry = new Entry();
872         issuer_entry.set_can_focus(false);
873         var username_label = new Label(_("Username:"));
874         username_label.set_alignment(1, (float) 0.5);
875         this.username_entry = new Entry();
876         username_entry.set_can_focus(false);
877         var password_label = new Label(_("Password:"));
878         password_label.set_alignment(1, (float) 0.5);
879         this.password_entry = new Entry();
880         password_entry.set_invisible_char('*');
881         password_entry.set_visibility(false);
882         password_entry.set_sensitive(false);
883         this.remember_checkbutton = new CheckButton.with_label(_("Remember password"));
884         remember_checkbutton.set_sensitive(false);
885         this.update_password_button = new Button.with_label(_("Update Password"));
886         this.update_password_button.clicked.connect(update_password_cb);
887
888         set_atk_relation(issuer_label, issuer_entry, Atk.RelationType.LABEL_FOR);
889         set_atk_relation(username_label, username_entry, Atk.RelationType.LABEL_FOR);
890         set_atk_relation(password_entry, password_entry, Atk.RelationType.LABEL_FOR);
891
892         var login_table = new Table(5, 2, false);
893         login_table.set_col_spacings(10);
894         login_table.set_row_spacings(10);
895         login_table.attach_defaults(issuer_label, 0, 1, 0, 1);
896         login_table.attach_defaults(issuer_entry, 1, 2, 0, 1);
897         login_table.attach_defaults(username_label, 0, 1, 1, 2);
898         login_table.attach_defaults(username_entry, 1, 2, 1, 2);
899         login_table.attach_defaults(password_label, 0, 1, 2, 3);
900         login_table.attach_defaults(password_entry, 1, 2, 2, 3);
901         login_table.attach_defaults(remember_checkbutton,  1, 2, 3, 4);
902         login_table.attach_defaults(update_password_button, 0, 1, 4, 5);
903         var login_vbox_alignment = new Alignment(0, 0, 0, 0);
904         login_vbox_alignment.set_padding(0, 0, 12, 0);
905         login_vbox_alignment.add(login_table);
906         this.login_vbox = new VBox(false, 6);
907         login_vbox.pack_start(login_vbox_title, false, true, 0);
908         login_vbox.pack_start(login_vbox_alignment, false, true, 0);
909
910         var services_vbox_title = new Label(_("Services:"));
911         label_make_bold(services_vbox_title);
912         services_vbox_title.set_alignment(0, (float) 0.5);
913         var services_vbox_alignment = new Alignment(0, 0, 0, 0);
914         services_vbox_alignment.set_padding(0, 0, 12, 0);
915         this.services_internal_vbox = new VBox(true, 6);
916         services_vbox_alignment.add(services_internal_vbox);
917         this.services_vbox = new VBox(false, 6);
918         services_vbox.pack_start(services_vbox_title, false, true, 0);
919         services_vbox.pack_start(services_vbox_alignment, false, true, 0);
920
921         this.vbox_right = new VBox(false, 18);
922         vbox_right.pack_start(login_vbox, false, true, 0);
923         vbox_right.pack_start(services_vbox, false, true, 0);
924
925         var hbox = new HBox(false, 12);
926         hbox.pack_start(vbox_left, false, false, 0);
927         hbox.pack_start(vbox_right, true, true, 0);
928
929         var main_vbox = new VBox(false, 0);
930         main_vbox.set_border_width(12);
931  
932 #if OS_MACOS
933         // hide the  File | Quit menu item which is now on the Mac Menu
934         Gtk.Widget quit_item =  this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
935         quit_item.hide();
936         
937         Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
938         osxApp.set_menu_bar(menushell);
939         osxApp.set_use_quartz_accelerators(true);
940         osxApp.sync_menu_bar();
941         osxApp.ready(); 
942 #else
943         var menubar = this.ui_manager.get_widget("/MenuBar");
944         main_vbox.pack_start(menubar, false, false, 0);
945 #endif
946         main_vbox.pack_start(hbox, true, true, 0);
947         add(main_vbox);
948         main_vbox.show_all();
949         this.vbox_right.hide();
950     } 
951
952     private void set_atk_name_description(Widget widget, string name, string description)
953     {
954         var atk_widget = widget.get_accessible();
955
956         atk_widget.set_name(name);
957         atk_widget.set_description(description);
958     }
959
960     private void connect_signals()
961     {
962         this.destroy.connect(Gtk.main_quit);
963         this.identities_manager.card_list_changed.connect(this.on_card_list_changed);
964     }
965
966     private static void set_atk_relation(Widget widget, Widget target_widget, Atk.RelationType relationship)
967     {
968         var atk_widget = widget.get_accessible();
969         var atk_target_widget = target_widget.get_accessible();
970
971         atk_widget.add_relationship(relationship, atk_target_widget);
972     }
973 }
974
975