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