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