3 class MainWindow : Window
5 private const int WINDOW_WIDTH = 400;
6 private const int WINDOW_HEIGHT = 500;
8 private UIManager ui_manager = new UIManager();
9 private Entry search_entry;
10 private VBox vbox_right;
11 private CustomVBox custom_vbox;
12 private VBox services_internal_vbox;
14 private Entry username_entry;
15 private Entry password_entry;
17 private ListStore listmodel;
18 private TreeModelFilter filter;
20 public IdentitiesManager identities_manager;
22 private MoonshotServer ipc_server;
24 private IdCard default_id_card;
25 private Queue<IdentityRequest> request_queue;
27 private HashTable<Gtk.Button, string> service_button_map;
39 private const string layout =
41 <menubar name='MenuBar'>
42 <menu name='FileMenu' action='FileMenuAction'>
43 <menuitem name='AddIdCard' action='AddIdCardAction' />
45 <menuitem name='Quit' action='QuitAction' />
48 <menu name='HelpMenu' action='HelpMenuAction'>
49 <menuitem name='About' action='AboutAction' />
56 request_queue = new Queue<IdentityRequest>();
58 service_button_map = new HashTable<Gtk.Button, string> (direct_hash, direct_equal);
60 this.title = "Moonshoot";
61 this.set_position (WindowPosition.CENTER);
62 set_default_size (WINDOW_WIDTH, WINDOW_HEIGHT);
65 setup_identities_list();
71 private bool visible_func (TreeModel model, TreeIter iter)
75 string issuer_casefold;
76 string search_text_casefold;
79 Columns.ISSUER_COL, out issuer);
80 search_text = this.search_entry.get_text ();
82 if (issuer == null || search_text == null)
85 issuer_casefold = issuer.casefold ();
86 search_text_casefold = search_text.casefold ();
88 if (issuer_casefold.contains (search_text_casefold))
94 private void setup_identities_list ()
96 this.listmodel = new ListStore (Columns.N_COLUMNS, typeof (IdCard),
101 this.filter = new TreeModelFilter (listmodel, null);
103 filter.set_visible_func (visible_func);
106 private void search_entry_icon_press_cb (EntryIconPosition pos, Gdk.Event event)
108 if (pos == EntryIconPosition.PRIMARY)
110 print ("Search entry icon pressed\n");
114 this.search_entry.set_text ("");
118 private void search_entry_text_changed_cb ()
120 this.filter.refilter ();
121 redraw_id_card_widgets ();
123 var has_text = this.search_entry.get_text_length () > 0;
124 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, has_text);
125 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, has_text);
127 this.vbox_right.set_visible (false);
128 this.resize (WINDOW_WIDTH, WINDOW_HEIGHT);
131 private bool search_entry_key_press_event_cb (Gdk.EventKey e)
133 if(Gdk.keyval_name(e.keyval) == "Escape")
134 this.search_entry.set_text("");
136 // Continue processing this event, since the
137 // text entry functionality needs to see it too.
141 private void load_id_cards ()
143 identities_manager = new IdentitiesManager ();
145 if (identities_manager.id_card_list == null)
148 foreach (IdCard id_card in identities_manager.id_card_list)
150 add_id_card_data (id_card);
151 add_id_card_widget (id_card);
154 this.default_id_card = identities_manager.id_card_list.data;
157 private void fill_details (IdCardWidget id_card_widget)
159 var id_card = id_card_widget.id_card;
160 this.username_entry.set_text (id_card.username);
161 this.password_entry.set_text (id_card.password ?? "");
163 var children = this.services_internal_vbox.get_children ();
164 foreach (var hbox in children)
166 fill_services_vbox (id_card_widget.id_card);
167 identities_manager.store_id_cards();
170 private void show_details (IdCard id_card)
172 this.vbox_right.set_visible (!vbox_right.get_visible ());
174 if (this.vbox_right.get_visible () == false)
176 this.resize (WINDOW_WIDTH, WINDOW_HEIGHT);
180 private void details_identity_cb (IdCardWidget id_card_widget)
182 fill_details (id_card_widget);
183 show_details (id_card_widget.id_card);
186 private IdCard get_id_card_data (AddIdentityDialog dialog)
188 var id_card = new IdCard ();
190 id_card.display_name = dialog.display_name;
191 id_card.issuer = dialog.issuer;
192 if (id_card.issuer == "")
193 id_card.issuer = "Issuer";
194 id_card.username = dialog.username;
195 id_card.password = dialog.password;
196 id_card.pixbuf = find_icon ("avatar-default", 48);
197 id_card.services = {};
202 private void add_id_card_data (IdCard id_card)
206 this.listmodel.append (out iter);
208 Columns.IDCARD_COL, id_card,
209 Columns.LOGO_COL, id_card.pixbuf,
210 Columns.ISSUER_COL, id_card.issuer,
211 Columns.USERNAME_COL, id_card.username,
212 Columns.PASSWORD_COL, id_card.password);
215 private void remove_id_card_data (IdCard id_card)
220 if (listmodel.get_iter_first (out iter))
225 Columns.ISSUER_COL, out issuer);
227 if (id_card.issuer == issuer)
229 listmodel.remove (iter);
233 while (listmodel.iter_next (ref iter));
237 private void add_id_card_widget (IdCard id_card)
239 var id_card_widget = new IdCardWidget (id_card);
241 this.custom_vbox.add_id_card_widget (id_card_widget);
243 id_card_widget.details_id.connect (details_identity_cb);
244 id_card_widget.remove_id.connect (remove_identity_cb);
245 id_card_widget.send_id.connect ((w) => send_identity_cb (w.id_card));
246 id_card_widget.expanded.connect (this.custom_vbox.receive_expanded_event);
247 id_card_widget.expanded.connect (fill_details);
250 private void add_identity (AddIdentityDialog dialog)
252 insert_id_card (get_id_card_data (dialog));
255 public void insert_id_card (IdCard id_card)
257 this.identities_manager.id_card_list.prepend (id_card);
258 this.identities_manager.store_id_cards ();
260 add_id_card_data (id_card);
261 add_id_card_widget (id_card);
264 private void add_identity_cb ()
266 var dialog = new AddIdentityDialog ();
267 var result = dialog.run ();
270 case ResponseType.OK:
271 add_identity (dialog);
279 private void remove_id_card_widget (IdCardWidget id_card_widget)
281 remove_id_card_data (id_card_widget.id_card);
283 this.custom_vbox.remove_id_card_widget (id_card_widget);
286 private void remove_identity (IdCardWidget id_card_widget)
288 var id_card = id_card_widget.id_card;
290 this.identities_manager.id_card_list.remove (id_card);
291 this.identities_manager.store_id_cards ();
293 remove_id_card_widget (id_card_widget);
296 private void redraw_id_card_widgets ()
301 var children = this.custom_vbox.get_children ();
302 foreach (var id_card_widget in children)
303 id_card_widget.destroy();
305 if (filter.get_iter_first (out iter))
310 Columns.IDCARD_COL, out id_card);
312 add_id_card_widget (id_card);
314 while (filter.iter_next (ref iter));
318 private void remove_identity_cb (IdCardWidget id_card_widget)
320 var id_card = id_card_widget.id_card;
322 var dialog = new MessageDialog (null,
323 DialogFlags.DESTROY_WITH_PARENT,
325 Gtk.ButtonsType.YES_NO,
326 _("Are you sure you want to delete %s ID Card?"), id_card.issuer);
327 var result = dialog.run ();
329 case ResponseType.YES:
330 remove_identity (id_card_widget);
338 public void select_identity (IdentityRequest request)
340 IdCard identity = null;
342 this.request_queue.push_tail (request);
344 if (request.select_default)
346 identity = this.default_id_card;
349 /* Automatic service matching rules can go here */
351 if (identity == null)
353 // Resort to manual selection
358 // Send back the identity (we can't directly run the
359 // callback because we may be being called from a 'yield')
360 Idle.add (() => { send_identity_cb (identity); return false; });
365 public void send_identity_cb (IdCard identity)
367 return_if_fail (request_queue.length > 0);
369 var request = this.request_queue.pop_head ();
370 bool reset_password = false;
372 if (identity.password == null)
374 var dialog = new AddPasswordDialog ();
375 var result = dialog.run ();
378 case ResponseType.OK:
379 identity.password = dialog.password;
380 reset_password = ! dialog.remember;
390 if (this.request_queue.is_empty())
393 if (identity != null)
394 this.default_id_card = identity;
396 request.return_identity (identity);
399 identity.password = null;
402 private void label_make_bold (Label label)
404 var font_desc = new Pango.FontDescription ();
406 font_desc.set_weight (Pango.Weight.BOLD);
408 /* This will only affect the weight of the font, the rest is
409 * from the current state of the widget, which comes from the
410 * theme or user prefs, since the font desc only has the
411 * weight flag turned on.
413 label.modify_font (font_desc);
416 private void fill_services_vbox (IdCard id_card)
419 var n_columns = id_card.services.length;
421 var services_table = new Table (n_columns, 2, false);
422 services_table.set_col_spacings (10);
423 services_table.set_row_spacings (10);
424 this.services_internal_vbox.add (services_table);
426 service_button_map.remove_all ();
428 foreach (string service in id_card.services)
430 var label = new Label (service);
431 label.set_alignment (0, (float) 0.5);
433 var remove_button = new Button.from_stock (Stock.REMOVE);
435 var remove_button = new Button.from_stock (STOCK_REMOVE);
439 service_button_map.insert (remove_button, service);
441 remove_button.clicked.connect ((remove_button) =>
443 var dialog = new Gtk.MessageDialog (this,
444 Gtk.DialogFlags.DESTROY_WITH_PARENT,
445 Gtk.MessageType.QUESTION,
446 Gtk.ButtonsType.YES_NO,
447 _("Are you sure you want to stop '%s' ID Card to use %s?"),
448 custom_vbox.current_idcard.id_card.display_name);
449 var ret = dialog.run();
452 if (ret == Gtk.ResponseType.YES)
454 IdCard idcard = custom_vbox.current_idcard.id_card;
455 var candidate = service_button_map.lookup (remove_button);
457 SList<string> services = new SList<string>();
459 foreach (string srv in id_card.services)
461 if (srv == candidate)
463 services.append (srv);
466 id_card.services = new string[services.length()];
467 for (int j=0; j<id_card.services.length; j++)
469 id_card.services[j] = services.nth_data(j);
472 var children = services_internal_vbox.get_children ();
473 foreach (var hbox in children)
476 fill_services_vbox (id_card);
481 services_table.attach_defaults (label, 0, 1, i, i+1);
482 services_table.attach_defaults (remove_button, 1, 2, i, i+1);
485 this.services_internal_vbox.show_all ();
488 private void on_about_action ()
491 "Javier Jardón <jjardon@codethink.co.uk>",
492 "Sam Thursfield <samthursfield@codethink.co.uk>",
493 "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
497 string copyright = "Copyright 2011 JANET";
501 Copyright (c) 2011, JANET(UK)
504 Redistribution and use in source and binary forms, with or without
505 modification, are permitted provided that the following conditions
508 1. Redistributions of source code must retain the above copyright
509 notice, this list of conditions and the following disclaimer.
511 2. Redistributions in binary form must reproduce the above copyright
512 notice, this list of conditions and the following disclaimer in the
513 documentation and/or other materials provided with the distribution.
515 3. Neither the name of JANET(UK) nor the names of its contributors
516 may be used to endorse or promote products derived from this software
517 without specific prior written permission.
519 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
520 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
521 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
522 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
523 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
524 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
525 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
526 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
527 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
528 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
532 Gtk.show_about_dialog (this,
533 "comments", _("Moonshot project UI"),
534 "copyright", copyright,
535 "website", "http://www.project-moonshot.org/",
537 "website-label", _("Visit the Moonshot project web site"),
539 "translator-credits", _("translator-credits"),
544 private Gtk.ActionEntry[] create_actions() {
545 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
547 Gtk.ActionEntry filemenu = { "FileMenuAction",
552 Gtk.ActionEntry add = { "AddIdCardAction",
560 N_("Add a new ID Card"),
563 Gtk.ActionEntry quit = { "QuitAction",
571 N_("Quit the application"),
575 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
580 Gtk.ActionEntry about = { "AboutAction",
588 N_("About this application"),
596 private void create_ui_manager ()
598 Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
599 action_group.add_actions (create_actions (), this);
600 ui_manager.insert_action_group (action_group, 0);
603 ui_manager.add_ui_from_string (layout, -1);
607 stderr.printf ("%s\n", e.message);
609 ui_manager.ensure_update ();
612 private void build_ui()
614 create_ui_manager ();
616 this.search_entry = new Entry();
618 set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
619 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
620 find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
621 this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
622 _("Search identity or service"));
623 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
625 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
626 find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
627 this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
628 _("Clear the current search"));
629 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
632 this.search_entry.icon_press.connect (search_entry_icon_press_cb);
633 this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
634 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
636 this.custom_vbox = new CustomVBox (false, 6);
638 var viewport = new Viewport (null, null);
639 viewport.set_border_width (6);
640 viewport.set_shadow_type (ShadowType.NONE);
641 viewport.add (custom_vbox);
642 var scroll = new ScrolledWindow (null, null);
643 scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
644 scroll.set_shadow_type (ShadowType.IN);
645 scroll.add_with_viewport (viewport);
647 var vbox_left = new VBox (false, 0);
648 vbox_left.pack_start (search_entry, false, false, 6);
649 vbox_left.pack_start (scroll, true, true, 0);
650 vbox_left.set_size_request (WINDOW_WIDTH, 0);
652 var login_vbox_title = new Label (_("Login: "));
653 label_make_bold (login_vbox_title);
654 login_vbox_title.set_alignment (0, (float) 0.5);
655 var username_label = new Label (_("Username:"));
656 username_label.set_alignment (1, (float) 0.5);
657 this.username_entry = new Entry ();
658 var password_label = new Label (_("Password:"));
659 password_label.set_alignment (1, (float) 0.5);
660 this.password_entry = new Entry ();
661 password_entry.set_invisible_char ('*');
662 password_entry.set_visibility (false);
663 var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
664 var login_table = new Table (3, 3, false);
665 login_table.set_col_spacings (10);
666 login_table.set_row_spacings (10);
667 login_table.attach_defaults (username_label, 0, 1, 0, 1);
668 login_table.attach_defaults (username_entry, 1, 2, 0, 1);
669 login_table.attach_defaults (password_label, 0, 1, 1, 2);
670 login_table.attach_defaults (password_entry, 1, 2, 1, 2);
671 login_table.attach_defaults (remember_checkbutton, 1, 2, 2, 3);
672 var login_vbox_alignment = new Alignment (0, 0, 0, 0);
673 login_vbox_alignment.set_padding (0, 0, 12, 0);
674 login_vbox_alignment.add (login_table);
675 var login_vbox = new VBox (false, 6);
676 login_vbox.pack_start (login_vbox_title, false, true, 0);
677 login_vbox.pack_start (login_vbox_alignment, false, true, 0);
679 var services_vbox_title = new Label (_("Services:"));
680 label_make_bold (services_vbox_title);
681 services_vbox_title.set_alignment (0, (float) 0.5);
682 var services_vbox_alignment = new Alignment (0, 0, 0, 0);
683 services_vbox_alignment.set_padding (0, 0, 12, 0);
684 this.services_internal_vbox = new VBox (true, 6);
685 services_vbox_alignment.add (services_internal_vbox);
686 var services_vbox = new VBox (false, 6);
687 services_vbox.pack_start (services_vbox_title, false, true, 0);
688 services_vbox.pack_start (services_vbox_alignment, false, true, 0);
690 this.vbox_right = new VBox (false, 18);
691 vbox_right.pack_start (login_vbox, false, true, 0);
692 vbox_right.pack_start (services_vbox, false, true, 0);
694 var hbox = new HBox (false, 12);
695 hbox.pack_start (vbox_left, false, false, 0);
696 hbox.pack_start (vbox_right, false, false, 0);
698 var main_vbox = new VBox (false, 0);
699 main_vbox.set_border_width (12);
700 var menubar = this.ui_manager.get_widget ("/MenuBar");
701 main_vbox.pack_start (menubar, false, false, 0);
702 main_vbox.pack_start (hbox, true, true, 0);
705 main_vbox.show_all();
706 this.vbox_right.hide ();
709 private void set_atk_name_description (Widget widget, string name, string description)
711 var atk_widget = widget.get_accessible ();
713 atk_widget.set_name (name);
714 atk_widget.set_description (description);
717 private void connect_signals()
719 this.destroy.connect (Gtk.main_quit);
722 private void init_ipc_server ()
725 /* Errors will currently be sent via g_log - ie. to an
726 * obtrusive message box, on Windows
728 this.ipc_server = MoonshotServer.get_instance ();
729 MoonshotServer.start (this);
732 var conn = DBus.Bus.get (DBus.BusType.SESSION);
733 dynamic DBus.Object bus = conn.get_object ("org.freedesktop.DBus",
734 "/org/freedesktop/DBus",
735 "org.freedesktop.DBus");
737 // try to register service in session bus
738 uint reply = bus.request_name ("org.janet.Moonshot", (uint) 0);
739 assert (reply == DBus.RequestNameReply.PRIMARY_OWNER);
741 this.ipc_server = new MoonshotServer (this);
742 conn.register_object ("/org/janet/moonshot", ipc_server);
747 stderr.printf ("%s\n", e.message);
752 public static int main(string[] args)
757 // Force specific theme settings on Windows without requiring a gtkrc file
758 Gtk.Settings settings = Gtk.Settings.get_default ();
759 settings.set_string_property ("gtk-theme-name", "ms-windows", "moonshot");
760 settings.set_long_property ("gtk-menu-images", 0, "moonshot");
763 Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
764 Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
765 Intl.textdomain (Config.GETTEXT_PACKAGE);
767 var window = new MainWindow();