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