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