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