Merge branch 'master' of gitorious.codethink.co.uk:moonshot-ui/moonshot-ui
[moonshot-ui.git] / src / moonshot-window.vala
1 using Gtk;
2
3 class MainWindow : Window
4 {
5     private const int WINDOW_WIDTH = 400;
6     private const int WINDOW_HEIGHT = 500;
7
8     private UIManager ui_manager = new UIManager();
9     private Entry search_entry;
10     private VBox vbox_rigth;
11     private CustomVBox custom_vbox;
12     private VBox services_internal_vbox;
13
14     private Entry username_entry;
15     private Entry password_entry;
16
17     private ListStore listmodel;
18     private TreeModelFilter filter;
19
20     private IdentitiesManager identities_manager;
21
22     private MoonshotServer ipc_server;
23
24     public IdCardWidget selected_id_card_widget;
25
26     private Queue<IdentityRequest> request_queue;
27
28     private enum Columns
29     {
30         IDCARD_COL,
31         LOGO_COL,
32         ISSUER_COL,
33         USERNAME_COL,
34         PASSWORD_COL,
35         N_COLUMNS
36     }
37
38     private const string layout =
39 "
40 <menubar name='MenuBar'>
41         <menu name='FileMenu' action='FileMenuAction'>
42             <menuitem name='AddIdCard' action='AddIdCardAction' />
43             <separator />
44             <menuitem name='Quit' action='QuitAction' />
45         </menu>
46
47         <menu name='HelpMenu' action='HelpMenuAction'>
48              <menuitem name='About' action='AboutAction' />
49         </menu>
50 </menubar>
51 ";
52
53     public MainWindow()
54     {
55         request_queue = new Queue<IdentityRequest>();
56
57         this.title = "Moonshoot";
58         this.set_position (WindowPosition.CENTER);
59         set_default_size (WINDOW_WIDTH, WINDOW_HEIGHT);
60
61         build_ui();
62         setup_identities_list();
63         load_gss_eap_id_file();
64         //load_id_cards();
65         connect_signals();
66         init_ipc_server();
67     }
68
69     private bool visible_func (TreeModel model, TreeIter iter)
70     {
71         string issuer;
72         string search_text;
73         string issuer_casefold;
74         string search_text_casefold;
75
76         model.get (iter,
77                    Columns.ISSUER_COL, out issuer);
78         search_text = this.search_entry.get_text ();
79
80         if (issuer == null || search_text == null)
81             return false;
82
83         issuer_casefold = issuer.casefold ();
84         search_text_casefold = search_text.casefold ();
85
86         if (issuer_casefold.contains (search_text_casefold))
87             return true;
88
89         return false;
90     }
91
92     private void setup_identities_list ()
93     {
94        this.listmodel = new ListStore (Columns.N_COLUMNS, typeof (IdCard),
95                                                           typeof (Gdk.Pixbuf),
96                                                           typeof (string),
97                                                           typeof (string),
98                                                           typeof (string));
99       this.filter = new TreeModelFilter (listmodel, null);
100
101       filter.set_visible_func (visible_func);
102     }
103
104     private void search_entry_icon_press_cb (EntryIconPosition pos, Gdk.Event event)
105     {
106         if (pos == EntryIconPosition.PRIMARY)
107         {
108             print ("Search entry icon pressed\n");
109         }
110         else
111         {
112             this.search_entry.set_text ("");
113         }
114     }
115
116     private void search_entry_text_changed_cb ()
117     {
118         this.filter.refilter ();
119         redraw_id_card_widgets ();
120
121         var has_text = this.search_entry.get_text_length () > 0;
122         this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, has_text);
123         this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, has_text);
124
125         this.vbox_rigth.set_visible (false);
126         this.resize (WINDOW_WIDTH, WINDOW_HEIGHT);
127     }
128
129     private bool search_entry_key_press_event_cb (Gdk.EventKey e)
130     {
131         if(Gdk.keyval_name(e.keyval) == "Escape")
132            this.search_entry.set_text("");
133
134         // Continue processing this event, since the
135         // text entry functionality needs to see it too.
136         return false;
137     }
138
139     private void load_gss_eap_id_file ()
140     {
141         IdCard id_card;
142
143         this.identities_manager = new IdentitiesManager ();
144
145         id_card = this.identities_manager.load_gss_eap_id_file ();
146         if (id_card != null)
147         {
148             add_id_card_data (id_card);
149             add_id_card_widget (id_card);
150         }
151     }
152
153     private void load_id_cards ()
154     {
155         this.identities_manager = new IdentitiesManager ();
156
157         foreach (IdCard id_card in identities_manager.id_card_list)
158         {
159             add_id_card_data (id_card);
160             add_id_card_widget (id_card);
161         }
162     }
163
164     private void fill_details (IdCardWidget id_card_widget)
165     {
166        var id_card = id_card_widget.id_card;
167        this.username_entry.set_text (id_card.username);
168        this.password_entry.set_text (id_card.password ?? "");
169
170        var children = this.services_internal_vbox.get_children ();
171        foreach (var hbox in children)
172            hbox.destroy();
173        fill_services_vbox (id_card_widget.id_card);
174     }
175
176     private void show_details (IdCard id_card)
177     {
178        this.vbox_rigth.set_visible (!vbox_rigth.get_visible ());
179
180        if (this.vbox_rigth.get_visible () == false)
181        {
182            this.resize (WINDOW_WIDTH, WINDOW_HEIGHT);
183        }
184     }
185
186     private void details_identity_cb (IdCardWidget id_card_widget)
187     {
188        fill_details (id_card_widget);
189        show_details (id_card_widget.id_card);
190     }
191
192     private IdCard get_id_card_data (AddIdentityDialog dialog)
193     {
194         var id_card = new IdCard ();
195
196         id_card.issuer = dialog.issuer;
197         if (id_card.issuer == "")
198             id_card.issuer = "Issuer";
199         id_card.username = dialog.username;
200         id_card.password = dialog.password;
201         id_card.nai = id_card.username + "@" + id_card.issuer;
202         id_card.pixbuf = find_icon ("avatar-default", 48);
203         id_card.services = {"email","jabber","irc"};
204
205         return id_card;
206     }
207
208     private void add_id_card_data (IdCard id_card)
209     {
210         TreeIter iter;
211
212         this.listmodel.append (out iter);
213         listmodel.set (iter,
214                        Columns.IDCARD_COL, id_card,
215                        Columns.LOGO_COL, id_card.pixbuf,
216                        Columns.ISSUER_COL, id_card.issuer,
217                        Columns.USERNAME_COL, id_card.username,
218                        Columns.PASSWORD_COL, id_card.password);
219     }
220
221     private void remove_id_card_data (IdCard id_card)
222     {
223         TreeIter iter;
224         string issuer;
225
226         if (listmodel.get_iter_first (out iter))
227         {
228             do
229             {
230                 listmodel.get (iter,
231                                Columns.ISSUER_COL, out issuer);
232
233                 if (id_card.issuer == issuer)
234                 {
235                     listmodel.remove (iter);
236                     break;
237                 }
238             }
239             while (listmodel.iter_next (ref iter));
240         }
241     }
242
243     private void add_id_card_widget (IdCard id_card)
244     {
245         var id_card_widget = new IdCardWidget (id_card);
246
247         this.custom_vbox.add_id_card_widget (id_card_widget);
248
249         id_card_widget.details_id.connect (details_identity_cb);
250         id_card_widget.remove_id.connect (remove_identity_cb);
251         id_card_widget.send_id.connect (send_identity_cb);
252         id_card_widget.expanded.connect (this.custom_vbox.receive_expanded_event);
253         id_card_widget.expanded.connect (fill_details);
254     }
255
256     private void add_identity (AddIdentityDialog dialog)
257     {
258         var id_card = get_id_card_data (dialog);
259
260         this.identities_manager.id_card_list.prepend (id_card);
261         this.identities_manager.store_id_cards ();
262         this.identities_manager.store_gss_eap_id_file (id_card);
263
264         add_id_card_data (id_card);
265         add_id_card_widget (id_card);
266     }
267
268     private void add_identity_cb ()
269     {
270         var dialog = new AddIdentityDialog ();
271         var result = dialog.run ();
272
273         switch (result) {
274         case ResponseType.OK:
275             add_identity (dialog);
276             break;
277         default:
278             break;
279         }
280         dialog.destroy ();
281     }
282
283     private void remove_id_card_widget (IdCardWidget id_card_widget)
284     {
285         remove_id_card_data (id_card_widget.id_card);
286
287         this.custom_vbox.remove_id_card_widget (id_card_widget);
288     }
289
290     private void remove_identity (IdCardWidget id_card_widget)
291     {
292         var id_card = id_card_widget.id_card;
293
294         this.identities_manager.id_card_list.remove (id_card);
295         this.identities_manager.store_id_cards ();
296         this.identities_manager.store_gss_eap_id_file (null);
297
298         remove_id_card_widget (id_card_widget);
299     }
300
301     private void redraw_id_card_widgets ()
302     {
303         TreeIter iter;
304         IdCard id_card;
305
306         var children = this.custom_vbox.get_children ();
307         foreach (var id_card_widget in children)
308             id_card_widget.destroy();
309
310         if (filter.get_iter_first (out iter))
311         {
312             do
313             {
314                 filter.get (iter,
315                             Columns.IDCARD_COL, out id_card);
316
317                 add_id_card_widget (id_card);
318             }
319             while (filter.iter_next (ref iter));
320         }
321     }
322
323     private void remove_identity_cb (IdCardWidget id_card_widget)
324     {
325         var id_card = id_card_widget.id_card;
326
327         var dialog = new MessageDialog (null,
328                                         DialogFlags.DESTROY_WITH_PARENT,
329                                         MessageType.INFO,
330                                         Gtk.ButtonsType.YES_NO,
331                                         _("Are you sure you want to delete %s ID Card?"), id_card.issuer);
332         var result = dialog.run ();
333         switch (result) {
334         case ResponseType.YES:
335             remove_identity (id_card_widget);
336             break;
337         default:
338             break;
339         }
340         dialog.destroy ();
341     }
342
343     public void select_identity (IdentityRequest request)
344     {
345         /* Automatic service matching rules can go here */
346
347         /* Resort to manual selection */
348         this.request_queue.push_tail (request);
349         this.show ();
350     }
351
352     public void send_identity_cb (IdCardWidget id_card_widget)
353     {
354         return_if_fail (request_queue.length > 0);
355
356         this.selected_id_card_widget = id_card_widget;
357
358         var request = this.request_queue.pop_head ();
359         var identity = id_card_widget.id_card;
360         bool reset_password = false;
361
362         if (identity.password == null)
363         {
364             var dialog = new AddPasswordDialog ();
365             var result = dialog.run ();
366
367             switch (result) {
368             case ResponseType.OK:
369                 identity.password = dialog.password;
370                 reset_password = ! dialog.remember;
371                 break;
372             default:
373                 identity = null;
374                 break;
375             }
376
377             dialog.destroy ();
378         }
379
380         if (this.request_queue.is_empty())
381             this.hide ();
382
383         request.return_identity (identity);
384
385         if (reset_password)
386             identity.password = null;
387     }
388
389     private void label_make_bold (Label label)
390     {
391         var font_desc = new Pango.FontDescription ();
392
393         font_desc.set_weight (Pango.Weight.BOLD);
394
395         /* This will only affect the weight of the font, the rest is
396          * from the current state of the widget, which comes from the
397          * theme or user prefs, since the font desc only has the
398          * weight flag turned on.
399          */
400         label.modify_font (font_desc);
401     }
402
403     private void fill_services_vbox (IdCard id_card)
404     {
405         int i = 0;
406         var n_columns = id_card.services.length;
407
408         var services_table = new Table (n_columns, 2, false);
409         services_table.set_col_spacings (10);
410         services_table.set_row_spacings (10);
411         this.services_internal_vbox.add (services_table);
412
413         foreach (string service in id_card.services)
414         {
415             var label = new Label (service);
416             label.set_alignment (0, (float) 0.5);
417 #if VALA_0_12
418             var remove_button = new Button.from_stock (Stock.REMOVE);
419 #else
420             var remove_button = new Button.from_stock (STOCK_REMOVE);
421 #endif
422             services_table.attach_defaults (label, 0, 1, i, i+1);
423             services_table.attach_defaults (remove_button, 1, 2, i, i+1);
424             i++;
425         }
426         this.services_internal_vbox.show_all ();
427     }
428
429     private void on_about_action ()
430     {
431         string[] authors = {
432             "Javier Jardón <jjardon@codethink.co.uk>",
433             "Sam Thursfield <samthursfield@codethink.co.uk>",
434             null
435         };
436
437         string copyright = "Copyright 2011 JANET";
438
439         string license =
440 """
441 Copyright (c) 2011, JANET(UK)
442 All rights reserved.
443
444 Redistribution and use in source and binary forms, with or without
445 modification, are permitted provided that the following conditions
446 are met:
447
448 1. Redistributions of source code must retain the above copyright
449    notice, this list of conditions and the following disclaimer.
450
451 2. Redistributions in binary form must reproduce the above copyright
452    notice, this list of conditions and the following disclaimer in the
453    documentation and/or other materials provided with the distribution.
454
455 3. Neither the name of JANET(UK) nor the names of its contributors
456    may be used to endorse or promote products derived from this software
457    without specific prior written permission.
458
459 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
460 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
461 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
462 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
463 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
464 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
465 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
466 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
467 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
468 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
469 SUCH DAMAGE.
470 """;
471
472         Gtk.show_about_dialog (this,
473             "comments", _("Moonshot project UI"),
474             "copyright", copyright,
475             "website", "http://www.project-moonshot.org/",
476             "license", license,
477             "website-label", _("Visit the Moonshot project web site"),
478             "authors", authors,
479             "translator-credits", _("translator-credits"),
480             null
481         );
482     }
483
484     private Gtk.ActionEntry[] create_actions() {
485         Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
486
487         Gtk.ActionEntry filemenu = { "FileMenuAction",
488                                      null,
489                                      N_("_File"),
490                                      null, null, null };
491         actions += filemenu;
492         Gtk.ActionEntry add = { "AddIdCardAction",
493 #if VALA_0_12
494                                 Stock.ADD,
495 #else
496                                 STOCK_ADD,
497 #endif
498                                 N_("Add ID Card"),
499                                 null,
500                                 N_("Add a new ID Card"),
501                                 add_identity_cb };
502         actions += add;
503         Gtk.ActionEntry quit = { "QuitAction",
504 #if VALA_0_12
505                                  Stock.QUIT,
506 #else
507                                  STOCK_QUIT,
508 #endif
509                                  N_("Quit"),
510                                  "<control>Q",
511                                  N_("Quit the application"),
512                                  Gtk.main_quit };
513         actions += quit;
514
515         Gtk.ActionEntry helpmenu = { "HelpMenuAction",
516                                      null,
517                                      N_("_Help"),
518                                      null, null, null };
519         actions += helpmenu;
520         Gtk.ActionEntry about = { "AboutAction",
521 #if VALA_0_12
522                                   Stock.ABOUT,
523 #else
524                                   STOCK_ABOUT,
525 #endif
526                                   N_("About"),
527                                   null,
528                                   N_("About this application"),
529                                   on_about_action };
530         actions += about;
531
532         return actions;
533     }
534
535
536     private void create_ui_manager ()
537     {
538         Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
539         action_group.add_actions (create_actions (), this);
540         ui_manager.insert_action_group (action_group, 0);
541         try
542         {
543             ui_manager.add_ui_from_string (layout, -1);
544         }
545         catch (Error e)
546         {
547             stderr.printf ("%s\n", e.message);
548         }
549         ui_manager.ensure_update ();
550     }
551
552     private void build_ui()
553     {
554         create_ui_manager ();
555
556         this.search_entry = new Entry();
557
558         set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
559         this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
560                                                 find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
561         this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
562                                                  _("Search identity or service"));
563         this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
564
565         this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
566                                                 find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
567         this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
568                                                  _("Clear the current search"));
569         this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
570
571
572         this.search_entry.icon_press.connect (search_entry_icon_press_cb);
573         this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
574         this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
575
576         this.custom_vbox = new CustomVBox (false, 6);
577
578         var viewport = new Viewport (null, null);
579         viewport.set_border_width (6);
580         viewport.set_shadow_type (ShadowType.NONE);
581         viewport.add (custom_vbox);
582         var scroll = new ScrolledWindow (null, null);
583         scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
584         scroll.set_shadow_type (ShadowType.IN);
585         scroll.add_with_viewport (viewport);
586
587         var vbox_left = new VBox (false, 0);
588         vbox_left.pack_start (search_entry, false, false, 6);
589         vbox_left.pack_start (scroll, true, true, 0);
590         vbox_left.set_size_request (WINDOW_WIDTH, 0);
591
592         var login_vbox_title = new Label (_("Login: "));
593         label_make_bold (login_vbox_title);
594         login_vbox_title.set_alignment (0, (float) 0.5);
595         var username_label = new Label (_("Username:"));
596         username_label.set_alignment (1, (float) 0.5);
597         this.username_entry = new Entry ();
598         var password_label = new Label (_("Password:"));
599         password_label.set_alignment (1, (float) 0.5);
600         this.password_entry = new Entry ();
601         password_entry.set_invisible_char ('*');
602         password_entry.set_visibility (false);
603         var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
604         var login_table = new Table (3, 3, false);
605         login_table.set_col_spacings (10);
606         login_table.set_row_spacings (10);
607         login_table.attach_defaults (username_label, 0, 1, 0, 1);
608         login_table.attach_defaults (username_entry, 1, 2, 0, 1);
609         login_table.attach_defaults (password_label, 0, 1, 1, 2);
610         login_table.attach_defaults (password_entry, 1, 2, 1, 2);
611         login_table.attach_defaults (remember_checkbutton,  1, 2, 2, 3);
612         var login_vbox_alignment = new Alignment (0, 0, 0, 0);
613         login_vbox_alignment.set_padding (0, 0, 12, 0);
614         login_vbox_alignment.add (login_table);
615         var login_vbox = new VBox (false, 6);
616         login_vbox.pack_start (login_vbox_title, false, true, 0);
617         login_vbox.pack_start (login_vbox_alignment, false, true, 0);
618
619         var services_vbox_title = new Label (_("Services:"));
620         label_make_bold (services_vbox_title);
621         services_vbox_title.set_alignment (0, (float) 0.5);
622         var services_vbox_alignment = new Alignment (0, 0, 0, 0);
623         services_vbox_alignment.set_padding (0, 0, 12, 0);
624         this.services_internal_vbox = new VBox (true, 6);
625         services_vbox_alignment.add (services_internal_vbox);
626         var services_vbox = new VBox (false, 6);
627         services_vbox.pack_start (services_vbox_title, false, true, 0);
628         services_vbox.pack_start (services_vbox_alignment, false, true, 0);
629
630         this.vbox_rigth = new VBox (false, 18);
631         vbox_rigth.pack_start (login_vbox, false, true, 0);
632         vbox_rigth.pack_start (services_vbox, false, true, 0);
633
634         var hbox = new HBox (false, 12);
635         hbox.pack_start (vbox_left, false, false, 0);
636         hbox.pack_start (vbox_rigth, false, false, 0);
637
638         var main_vbox = new VBox (false, 0);
639         main_vbox.set_border_width (12);
640         var menubar = this.ui_manager.get_widget ("/MenuBar");
641         main_vbox.pack_start (menubar, false, false, 0);
642         main_vbox.pack_start (hbox, true, true, 0);
643         add (main_vbox);
644
645         main_vbox.show_all();
646         this.vbox_rigth.hide ();
647     }
648
649     private void set_atk_name_description (Widget widget, string name, string description)
650     {
651        var atk_widget = widget.get_accessible ();
652
653        atk_widget.set_name (name);
654        atk_widget.set_description (description);
655     }
656
657     private void connect_signals()
658     {
659         this.destroy.connect (Gtk.main_quit);
660     }
661
662     private void init_ipc_server ()
663     {
664 #if IPC_MSRPC
665         /* Errors will currently be sent via g_log - ie. to an
666          * obtrusive message box, on Windows
667          */
668         this.ipc_server = MoonshotServer.get_instance ();
669         MoonshotServer.start (this);
670 #else
671         try {
672             var conn = DBus.Bus.get (DBus.BusType.SESSION);
673             dynamic DBus.Object bus = conn.get_object ("org.freedesktop.DBus",
674                                                        "/org/freedesktop/DBus",
675                                                        "org.freedesktop.DBus");
676
677             // try to register service in session bus
678             uint reply = bus.request_name ("org.janet.Moonshot", (uint) 0);
679             assert (reply == DBus.RequestNameReply.PRIMARY_OWNER);
680
681             this.ipc_server = new MoonshotServer (this);
682             conn.register_object ("/org/janet/moonshot", ipc_server);
683
684         }
685         catch (DBus.Error e)
686         {
687             stderr.printf ("%s\n", e.message);
688         }
689 #endif
690     }
691
692     public static int main(string[] args)
693     {
694         Gtk.init(ref args);
695
696 #if OS_WIN32
697         // Force specific theme settings on Windows without requiring a gtkrc file
698         Gtk.Settings settings = Gtk.Settings.get_default ();
699         settings.set_string_property ("gtk-theme-name", "ms-windows", "moonshot");
700         settings.set_long_property ("gtk-menu-images", 0, "moonshot");
701 #endif
702
703         Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
704         Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
705         Intl.textdomain (Config.GETTEXT_PACKAGE);
706
707         var window = new MainWindow();
708         window.show ();
709
710         Gtk.main();
711
712         return 0;
713     }
714 }