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