First pass at reskinning the main dialog (still need to update menu & detail views)
[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     private ScrolledWindow services_vscroll;
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.id_card);
269                 id_card_widget.expand();
270             }
271         }
272     }
273     
274     private void fill_details(IdCard id_card)
275     {
276         logger.trace("fill_details: id_card=%s".printf(id_card == null ? "null" : "non-null"));
277
278         if (id_card != null) {
279             if (id_card.display_name == IdCard.NO_IDENTITY) {
280                 logger.trace("fill_details: Displaying title for NO_IDENTITY");
281                 login_vbox.hide();
282                 no_identity_title.show_all();
283             } else {
284                 logger.trace("fill_details: Displaying details for selected card");
285                 this.issuer_entry.set_text(id_card.issuer);
286                 this.username_entry.set_text(id_card.username);
287                 this.password_entry.set_text(id_card.password ?? "");
288                 this.remember_checkbutton.active = id_card.store_password;
289                 no_identity_title.hide();
290                 login_vbox.show_all();              
291             }
292
293             fill_services_vbox(id_card);
294         }
295     }
296
297     private void show_details(IdCard id_card)
298     {
299         this.vbox_right.set_visible(!vbox_right.get_visible());
300
301         if (this.vbox_right.get_visible() == false)
302         {
303             this.resize(WINDOW_WIDTH, WINDOW_HEIGHT);
304         }
305     }
306
307     private void details_identity_cb(IdCardWidget id_card_widget)
308     {
309         fill_details(id_card_widget.id_card);
310         show_details(id_card_widget.id_card);
311     }
312
313     private IdCard get_id_card_data(AddIdentityDialog dialog)
314     {
315         var id_card = new IdCard();
316
317         id_card.display_name = dialog.display_name;
318         id_card.issuer = dialog.issuer;
319         id_card.username = dialog.username;
320         id_card.password = dialog.password;
321         id_card.store_password = dialog.store_password;
322         id_card.services = {};
323
324         return id_card;
325     }
326
327     private void add_id_card_data(IdCard id_card)
328     {
329         TreeIter   iter;
330         Gdk.Pixbuf pixbuf;
331         this.listmodel->append(out iter);
332         pixbuf = get_pixbuf(id_card);
333         listmodel->set(iter,
334                        Columns.IDCARD_COL, id_card,
335                        Columns.LOGO_COL, pixbuf,
336                        Columns.ISSUER_COL, id_card.issuer,
337                        Columns.USERNAME_COL, id_card.username,
338                        Columns.PASSWORD_COL, id_card.password);
339     }
340
341     private void remove_id_card_data(IdCard id_card)
342     {
343         TreeIter iter;
344         string issuer;
345
346         if (listmodel->get_iter_first(out iter))
347         {
348             do
349             {
350                 listmodel->get(iter,
351                                Columns.ISSUER_COL, out issuer);
352
353                 if (id_card.issuer == issuer)
354                 {
355                     listmodel->remove(iter);
356                     break;
357                 }
358             }
359             while (listmodel->iter_next(ref iter));
360         }
361     }
362
363     private IdCardWidget add_id_card_widget(IdCard id_card)
364     {
365         var id_card_widget = new IdCardWidget(id_card);
366         this.custom_vbox.add_id_card_widget(id_card_widget);
367         id_card_widget.details_id.connect(details_identity_cb);
368         id_card_widget.remove_id.connect(remove_identity_cb);
369         id_card_widget.send_id.connect((w) => send_identity_cb(w.id_card));
370         id_card_widget.expanded.connect(this.custom_vbox.receive_expanded_event);
371         id_card_widget.expanded.connect((w) => fill_details(w.id_card));
372         return id_card_widget;
373     }
374
375     public bool add_identity(IdCard id_card, bool force_flat_file_store)
376     {
377         #if OS_MACOS
378         /* 
379          * TODO: We should have a confirmation dialog, but currently it will crash on Mac OS
380          * so for now we will install silently
381          */
382         var ret = Gtk.ResponseType.YES;
383         #else
384         Gtk.MessageDialog dialog;
385         IdCard? prev_id = identities_manager.find_id_card(id_card.nai, force_flat_file_store);
386         if (prev_id!=null) {
387             int flags = prev_id.Compare(id_card);
388             if (flags == 0) {
389                 return false; // no changes, no need to update
390             } else if ((flags & (1 << IdCard.DiffFlags.DISPLAY_NAME)) != 0) {
391                 dialog = new Gtk.MessageDialog(this,
392                                                Gtk.DialogFlags.DESTROY_WITH_PARENT,
393                                                Gtk.MessageType.QUESTION,
394                                                Gtk.ButtonsType.YES_NO,
395                                                _("Would you like to replace ID Card '%s' using nai '%s' with the new ID Card '%s'?"),
396                                                prev_id.display_name,
397                                                prev_id.nai,
398                                                id_card.display_name);
399             } else {
400                 dialog = new Gtk.MessageDialog(this,
401                                                Gtk.DialogFlags.DESTROY_WITH_PARENT,
402                                                Gtk.MessageType.QUESTION,
403                                                Gtk.ButtonsType.YES_NO,
404                                                _("Would you like to update ID Card '%s' using nai '%s'?"),
405                                                id_card.display_name,
406                                                id_card.nai);
407             }
408         } else {
409             dialog = new Gtk.MessageDialog(this,
410                                            Gtk.DialogFlags.DESTROY_WITH_PARENT,
411                                            Gtk.MessageType.QUESTION,
412                                            Gtk.ButtonsType.YES_NO,
413                                            _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
414                                            id_card.display_name);
415         }
416         var ret = dialog.run();
417         dialog.destroy();
418         #endif
419
420         if (ret == Gtk.ResponseType.YES) {
421             this.identities_manager.add_card(id_card, force_flat_file_store);
422             return true;
423         }
424         return false;
425     }
426
427     private void add_identity_manual_cb()
428     {
429         var dialog = new AddIdentityDialog();
430         int result = ResponseType.CANCEL;
431         while (!dialog.complete)
432             result = dialog.run();
433
434         switch (result) {
435         case ResponseType.OK:
436             this.identities_manager.add_card(get_id_card_data(dialog), false);
437             break;
438         default:
439             break;
440         }
441         dialog.destroy();
442     }
443
444     private void remove_id_card_widget(IdCardWidget id_card_widget) {
445         this.custom_vbox.remove_id_card_widget(id_card_widget);
446     }
447
448     private void remove_identity(IdCardWidget id_card_widget)
449     {
450         var id_card = id_card_widget.id_card;
451         remove_id_card_widget(id_card_widget);
452
453         this.identities_manager.remove_card(id_card);
454     }
455
456     private void redraw_id_card_widgets()
457     {
458         TreeIter iter;
459         IdCard id_card;
460
461         var children = this.custom_vbox.get_children();
462         foreach (var id_card_widget in children) {
463             remove_id_card_widget((IdCardWidget )id_card_widget); //id_card_widget.destroy();
464         }
465
466         if (filter.get_iter_first(out iter))
467         {
468             do
469             {
470                 filter.get(iter,
471                            Columns.IDCARD_COL, out id_card);
472
473                 add_id_card_widget(id_card);
474             }
475             while (filter.iter_next(ref iter));
476         }
477     }
478
479     private void remove_identity_cb(IdCardWidget id_card_widget)
480     {
481         var id_card = id_card_widget.id_card;
482
483         var dialog = new MessageDialog(this,
484                                        DialogFlags.DESTROY_WITH_PARENT,
485                                        MessageType.QUESTION,
486                                        Gtk.ButtonsType.YES_NO,
487                                        _("Are you sure you want to delete %s ID Card?"), id_card.issuer);
488         var result = dialog.run();
489         switch (result) {
490         case ResponseType.YES:
491             remove_identity(id_card_widget);
492             break;
493         default:
494             break;
495         }
496         dialog.destroy();
497     }
498
499     public void set_prompting_service(string service)
500     {
501         prompting_service.set_label( _("Identity requested for service: %s").printf(service) );
502     }
503
504     public void queue_identity_request(IdentityRequest request)
505     {
506         if (this.request_queue.is_empty())
507         { /* setup widgets */
508             candidates = request.candidates;
509             filter.refilter();
510             redraw_id_card_widgets();
511             set_prompting_service(request.service);
512             make_visible();
513         }
514         this.request_queue.push_tail(request);
515     }
516
517
518     /** Makes the window visible, or at least, notifies the user that the window
519      * wants to be visible.
520      *
521      * This differs from show() in that show() does not guarantee that the 
522      * window will be moved to the foreground. Actually, neither does this
523      * method, because the user's settings and window manager may affect the
524      * behavior significantly.
525      */
526     public void make_visible()
527     {
528         set_urgency_hint(true);
529         present();
530     }
531
532     public IdCard check_add_password(IdCard identity, IdentityRequest request, IdentityManagerModel model)
533     {
534         IdCard retval = identity;
535         bool idcard_has_pw = (identity.password != null) && (identity.password != "");
536         bool request_has_pw = (request.password != null) && (request.password != "");
537         if ((!idcard_has_pw) && (!identity.IsNoIdentity())) {
538             if (request_has_pw) {
539                 identity.password = request.password;
540                 retval = model.update_card(identity);
541             } else {
542                 var dialog = new AddPasswordDialog(identity, request);
543                 var result = dialog.run();
544
545                 switch (result) {
546                 case ResponseType.OK:
547                     identity.password = dialog.password;
548                     identity.store_password = dialog.remember;
549                     if (dialog.remember)
550                         identity.temporary = false;
551                     retval = model.update_card(identity);
552                     break;
553                 default:
554                     identity = null;
555                     break;
556                 }
557                 dialog.destroy();
558             }
559         }
560         return retval;
561     }
562
563     public void send_identity_cb(IdCard id)
564     {
565         IdCard identity = id;
566         return_if_fail(request_queue.length > 0);
567
568         candidates = null;
569         var request = this.request_queue.pop_head();
570         identity = check_add_password(identity, request, identities_manager);
571         if (this.request_queue.is_empty())
572         {
573             candidates = null;
574             prompting_service.set_label(_(""));
575             if (!parent_app.explicitly_launched) {
576 // The following occasionally causes the app to exit without sending the dbus
577 // reply, so for now we just don't exit
578 //                Gtk.main_quit();
579 // just hide instead
580                 this.hide();
581             }
582         } else {
583             IdentityRequest next = this.request_queue.peek_head();
584             candidates = next.candidates;
585             set_prompting_service(next.service);
586         }
587         filter.refilter();
588         redraw_id_card_widgets();
589
590         if ((identity != null) && (!identity.IsNoIdentity()))
591             parent_app.default_id_card = identity;
592
593         request.return_identity(identity);
594     }
595
596     private void label_make_bold(Label label)
597     {
598         var font_desc = new Pango.FontDescription();
599
600         font_desc.set_weight(Pango.Weight.BOLD);
601
602         /* This will only affect the weight of the font, the rest is
603          * from the current state of the widget, which comes from the
604          * theme or user prefs, since the font desc only has the
605          * weight flag turned on.
606          */
607         label.modify_font(font_desc);
608     }
609
610     private void fill_services_vbox(IdCard id_card)
611     {
612         logger.trace("fill_services_vbox");
613
614         var children = this.services_internal_vbox.get_children();
615         foreach (var widget in children) {
616             services_internal_vbox.remove(widget);
617         }
618
619         int i = 0;
620         var n_rows = id_card.services.length;
621
622         var services_table = new Table(n_rows, 2, false);
623         services_table.set_col_spacings(10);
624         services_table.set_row_spacings(10);
625         this.services_internal_vbox.pack_start(services_table, true, false, 0);
626         
627         service_button_map.remove_all();
628
629         foreach (string service in id_card.services)
630         {
631             var label = new Label(service);
632             label.set_alignment(0, (float) 0.5);
633 #if VALA_0_12
634             var remove_button = new Button.from_stock(Stock.REMOVE);
635 #else
636             var remove_button = new Button.from_stock(STOCK_REMOVE);
637 #endif
638
639
640             service_button_map.insert(remove_button, service);
641             
642             remove_button.clicked.connect((remove_button) =>
643                 {
644                     var candidate = service_button_map.lookup(remove_button);
645                     if (candidate == null)
646                         return;
647                     var dialog = new Gtk.MessageDialog(this,
648                                                        Gtk.DialogFlags.DESTROY_WITH_PARENT,
649                                                        Gtk.MessageType.QUESTION,
650                                                        Gtk.ButtonsType.YES_NO,
651                                                        _("Are you sure you want to stop '%s' ID Card from being used with %s?"),
652                                                        custom_vbox.current_idcard.id_card.display_name,
653                                                        candidate);
654                     var ret = dialog.run();
655                     dialog.hide();
656               
657                     if (ret == Gtk.ResponseType.YES)
658                     {
659                         IdCard idcard = custom_vbox.current_idcard.id_card;
660                         if (idcard != null) {
661                             SList<string> services = new SList<string>();
662                 
663                             foreach (string srv in idcard.services)
664                             {
665                                 if (srv == candidate)
666                                     continue;
667                                 services.append(srv);
668                             }
669                 
670                             idcard.services = new string[services.length()];
671                             for (int j = 0; j < idcard.services.length; j++)
672                             {
673                                 idcard.services[j] = services.nth_data(j);
674                             }
675                 
676                             identities_manager.update_card(idcard);
677                         }
678                     }
679               
680                 });
681             services_table.attach_defaults(label, 0, 1, i, i+1);
682             services_table.attach_defaults(remove_button, 1, 2, i, i+1);
683             i++;
684         }
685
686         services_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 id_scrollwin = new ScrolledWindow(null, null);
848         id_scrollwin.set_policy(PolicyType.NEVER, PolicyType.AUTOMATIC);
849         id_scrollwin.set_shadow_type(ShadowType.IN);
850         id_scrollwin.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(id_scrollwin, 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         // Create the login_vbox. This starts off hidden, because the first card we
893         // display, by default, is NO_IDENTITY.
894         var login_table = new Table(5, 2, false);
895         login_table.set_col_spacings(10);
896         login_table.set_row_spacings(10);
897         login_table.attach_defaults(issuer_label, 0, 1, 0, 1);
898         login_table.attach_defaults(issuer_entry, 1, 2, 0, 1);
899         login_table.attach_defaults(username_label, 0, 1, 1, 2);
900         login_table.attach_defaults(username_entry, 1, 2, 1, 2);
901         login_table.attach_defaults(password_label, 0, 1, 2, 3);
902         login_table.attach_defaults(password_entry, 1, 2, 2, 3);
903         login_table.attach_defaults(remember_checkbutton,  1, 2, 3, 4);
904         login_table.attach_defaults(update_password_button, 0, 1, 4, 5);
905         var login_vbox_alignment = new Alignment(0, 0, 0, 0);
906         login_vbox_alignment.set_padding(0, 0, 12, 0);
907         login_vbox_alignment.add(login_table);
908         this.login_vbox = new VBox(false, 6);
909         login_vbox.pack_start(login_vbox_title, false, true, 0);
910         login_vbox.pack_start(login_vbox_alignment, false, true, 0);
911         login_vbox.hide();
912
913         var services_vbox_title = new Label(_("Services:"));
914         label_make_bold(services_vbox_title);
915         services_vbox_title.set_alignment(0, (float) 0.5);
916         
917         this.services_internal_vbox = new VBox(true, 6);
918
919         var services_vbox_alignment = new Alignment(0, 0, 0, 1);
920         services_vbox_alignment.set_padding(6, 6, 6, 6);
921         services_vbox_alignment.add(services_internal_vbox);
922         services_vscroll = new ScrolledWindow(null, null);
923         services_vscroll.set_policy(PolicyType.NEVER, PolicyType.AUTOMATIC);
924         services_vscroll.set_shadow_type(ShadowType.IN);
925         services_vscroll.add_with_viewport(services_vbox_alignment);
926
927         services_vbox = new VBox(false, 6);
928         this.vbox_right = new VBox(false, 18);
929         services_vbox.pack_start(services_vbox_title, false, false, 0);
930         services_vbox.pack_start(services_vscroll, true, true, 0);
931
932         vbox_right.pack_start(no_identity_title, true, false, 0);
933         vbox_right.pack_start(login_vbox, false, false, 0);
934         vbox_right.pack_start(services_vbox, true, true, 0);
935
936         var hbox = new HBox(false, 12);
937         hbox.pack_start(vbox_left, false, false, 0);
938         hbox.pack_start(vbox_right, true, true, 0);
939
940         var main_vbox = new VBox(false, 0);
941         main_vbox.set_border_width(12);
942
943         #if OS_MACOS
944         // hide the  File | Quit menu item which is now on the Mac Menu
945         Gtk.Widget quit_item =  this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
946         quit_item.hide();
947         
948         Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
949         osxApp.set_menu_bar(menushell);
950         osxApp.set_use_quartz_accelerators(true);
951         osxApp.sync_menu_bar();
952         osxApp.ready(); 
953 #else
954         var menubar = this.ui_manager.get_widget("/MenuBar");
955         main_vbox.pack_start(menubar, false, false, 0);
956 #endif
957         main_vbox.pack_start(hbox, true, true, 0);
958         add(main_vbox);
959         main_vbox.show_all();
960         this.vbox_right.hide();
961     } 
962
963     private void set_atk_name_description(Widget widget, string name, string description)
964     {
965         var atk_widget = widget.get_accessible();
966
967         atk_widget.set_name(name);
968         atk_widget.set_description(description);
969     }
970
971     private void connect_signals()
972     {
973         this.destroy.connect(Gtk.main_quit);
974         this.identities_manager.card_list_changed.connect(this.on_card_list_changed);
975     }
976
977     private static void set_atk_relation(Widget widget, Widget target_widget, Atk.RelationType relationship)
978     {
979         var atk_widget = widget.get_accessible();
980         var atk_target_widget = target_widget.get_accessible();
981
982         atk_widget.add_relationship(relationship, atk_target_widget);
983     }
984 }