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;
21 private SList<IdCard> candidates;
23 private MoonshotServer ipc_server;
25 private IdCard default_id_card;
26 public Queue<IdentityRequest> request_queue;
28 private HashTable<Gtk.Button, string> service_button_map;
40 private const string layout =
42 <menubar name='MenuBar'>
43 <menu name='FileMenu' action='FileMenuAction'>
44 <menuitem name='AddIdCard' action='AddIdCardAction' />
46 <menuitem name='Quit' action='QuitAction' />
49 <menu name='HelpMenu' action='HelpMenuAction'>
50 <menuitem name='About' action='AboutAction' />
57 request_queue = new Queue<IdentityRequest>();
59 service_button_map = new HashTable<Gtk.Button, string> (direct_hash, direct_equal);
61 this.title = "Moonshoot";
62 this.set_position (WindowPosition.CENTER);
63 set_default_size (WINDOW_WIDTH, WINDOW_HEIGHT);
66 setup_identities_list();
72 public void add_candidate (IdCard idcard)
74 candidates.append (idcard);
77 private bool visible_func (TreeModel model, TreeIter iter)
82 Columns.IDCARD_COL, out id_card);
87 if (candidates != null)
89 bool is_candidate = false;
90 foreach (IdCard candidate in candidates)
92 if (candidate == id_card)
99 string entry_text = search_entry.get_text ();
100 if (entry_text == null || entry_text == "")
105 foreach (string search_text in entry_text.split(" "))
107 if (search_text == "")
111 string search_text_casefold = search_text.casefold ();
113 if (id_card.issuer != null)
115 string issuer_casefold = id_card.issuer;
117 if (issuer_casefold.contains (search_text_casefold))
121 if (id_card.display_name != null)
123 string display_name_casefold = id_card.display_name.casefold ();
125 if (display_name_casefold.contains (search_text_casefold))
129 if (id_card.services.length > 0)
131 foreach (string service in id_card.services)
133 string service_casefold = service.casefold ();
135 if (service_casefold.contains (search_text_casefold))
143 private void setup_identities_list ()
145 this.listmodel = new ListStore (Columns.N_COLUMNS, typeof (IdCard),
150 this.filter = new TreeModelFilter (listmodel, null);
152 filter.set_visible_func (visible_func);
155 private void search_entry_icon_press_cb (EntryIconPosition pos, Gdk.Event event)
157 if (pos == EntryIconPosition.PRIMARY)
159 print ("Search entry icon pressed\n");
163 this.search_entry.set_text ("");
167 private void search_entry_text_changed_cb ()
169 this.filter.refilter ();
170 redraw_id_card_widgets ();
172 var has_text = this.search_entry.get_text_length () > 0;
173 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, has_text);
174 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, has_text);
176 this.vbox_right.set_visible (false);
179 private bool search_entry_key_press_event_cb (Gdk.EventKey e)
181 if(Gdk.keyval_name(e.keyval) == "Escape")
182 this.search_entry.set_text("");
184 // Continue processing this event, since the
185 // text entry functionality needs to see it too.
189 private void load_id_cards ()
191 identities_manager = new IdentitiesManager ();
193 if (identities_manager.id_card_list == null)
196 foreach (IdCard id_card in identities_manager.id_card_list)
198 add_id_card_data (id_card);
199 add_id_card_widget (id_card);
202 this.default_id_card = identities_manager.id_card_list.data;
205 private void fill_details (IdCardWidget id_card_widget)
207 var id_card = id_card_widget.id_card;
208 this.username_entry.set_text (id_card.username);
209 this.password_entry.set_text (id_card.password ?? "");
211 var children = this.services_internal_vbox.get_children ();
212 foreach (var hbox in children)
214 fill_services_vbox (id_card_widget.id_card);
215 identities_manager.store_id_cards();
218 private void show_details (IdCard id_card)
220 this.vbox_right.set_visible (!vbox_right.get_visible ());
222 if (this.vbox_right.get_visible () == false)
224 this.resize (WINDOW_WIDTH, WINDOW_HEIGHT);
228 private void details_identity_cb (IdCardWidget id_card_widget)
230 fill_details (id_card_widget);
231 show_details (id_card_widget.id_card);
234 private IdCard get_id_card_data (AddIdentityDialog dialog)
236 var id_card = new IdCard ();
238 id_card.display_name = dialog.display_name;
239 id_card.issuer = dialog.issuer;
240 if (id_card.issuer == "")
241 id_card.issuer = "Issuer";
242 id_card.username = dialog.username;
243 id_card.password = dialog.password;
244 id_card.services = {};
245 id_card.set_data("pixbuf", find_icon ("avatar-default", 48));
250 private void add_id_card_data (IdCard id_card)
255 this.listmodel.append (out iter);
256 pixbuf = id_card.get_data("pixbuf");
258 Columns.IDCARD_COL, id_card,
259 Columns.LOGO_COL, pixbuf,
260 Columns.ISSUER_COL, id_card.issuer,
261 Columns.USERNAME_COL, id_card.username,
262 Columns.PASSWORD_COL, id_card.password);
265 private void remove_id_card_data (IdCard id_card)
270 if (listmodel.get_iter_first (out iter))
275 Columns.ISSUER_COL, out issuer);
277 if (id_card.issuer == issuer)
279 listmodel.remove (iter);
283 while (listmodel.iter_next (ref iter));
287 private void add_id_card_widget (IdCard id_card)
289 var id_card_widget = new IdCardWidget (id_card);
291 this.custom_vbox.add_id_card_widget (id_card_widget);
293 id_card_widget.details_id.connect (details_identity_cb);
294 id_card_widget.remove_id.connect (remove_identity_cb);
295 id_card_widget.send_id.connect ((w) => send_identity_cb (w.id_card));
296 id_card_widget.expanded.connect (this.custom_vbox.receive_expanded_event);
297 id_card_widget.expanded.connect (fill_details);
300 /* This method finds a valid display name */
301 public bool display_name_is_valid (string name,
302 out string? candidate)
304 foreach (IdCard id_card in identities_manager.id_card_list)
306 if (id_card.display_name == name)
308 if (&candidate != null)
310 for (int i=0; i<1000; i++)
312 string tmp = "%s %d".printf (name, i);
313 if (display_name_is_valid (tmp, null))
327 public void insert_id_card (IdCard id_card)
331 if (!display_name_is_valid (id_card.display_name, out candidate))
333 id_card.display_name = candidate;
336 this.identities_manager.id_card_list.prepend (id_card);
337 this.identities_manager.store_id_cards ();
339 add_id_card_data (id_card);
340 add_id_card_widget (id_card);
343 public bool add_identity (IdCard id_card)
345 /* TODO: Check if display name already exists */
347 var dialog = new Gtk.MessageDialog (this,
348 Gtk.DialogFlags.DESTROY_WITH_PARENT,
349 Gtk.MessageType.QUESTION,
350 Gtk.ButtonsType.YES_NO,
351 _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
352 id_card.display_name);
355 var ret = dialog.run ();
358 if (ret == Gtk.ResponseType.YES) {
359 id_card.set_data ("pixbuf", find_icon ("avatar-default", 48));
360 this.insert_id_card (id_card);
367 private void add_identity_manual_cb ()
369 var dialog = new AddIdentityDialog ();
370 var result = dialog.run ();
373 case ResponseType.OK:
374 insert_id_card (get_id_card_data (dialog));
382 private void remove_id_card_widget (IdCardWidget id_card_widget)
384 remove_id_card_data (id_card_widget.id_card);
386 this.custom_vbox.remove_id_card_widget (id_card_widget);
389 private void remove_identity (IdCardWidget id_card_widget)
391 var id_card = id_card_widget.id_card;
393 this.identities_manager.id_card_list.remove (id_card);
394 this.identities_manager.store_id_cards ();
396 remove_id_card_widget (id_card_widget);
399 private void redraw_id_card_widgets ()
404 var children = this.custom_vbox.get_children ();
405 foreach (var id_card_widget in children)
406 id_card_widget.destroy();
408 if (filter.get_iter_first (out iter))
413 Columns.IDCARD_COL, out id_card);
415 add_id_card_widget (id_card);
417 while (filter.iter_next (ref iter));
421 private void remove_identity_cb (IdCardWidget id_card_widget)
423 var id_card = id_card_widget.id_card;
425 var dialog = new MessageDialog (null,
426 DialogFlags.DESTROY_WITH_PARENT,
428 Gtk.ButtonsType.YES_NO,
429 _("Are you sure you want to delete %s ID Card?"), id_card.issuer);
430 var result = dialog.run ();
432 case ResponseType.YES:
433 remove_identity (id_card_widget);
441 public void select_identity (IdentityRequest request)
443 IdCard identity = null;
445 this.request_queue.push_tail (request);
447 if (custom_vbox.current_idcard != null &&
448 custom_vbox.current_idcard.send_button != null)
449 custom_vbox.current_idcard.send_button.set_sensitive (true);
451 if (request.select_default)
453 identity = default_id_card;
456 if (identity == null)
458 bool has_nai = request.nai != null && request.nai != "";
459 bool has_srv = request.service != null && request.service != "";
460 bool confirm = false;
461 IdCard nai_provided = null;
463 foreach (IdCard id in identities_manager.id_card_list)
465 /* If NAI matches we add id card to the candidate list */
466 if (has_nai && request.nai == id.nai)
473 /* If any service matches we add id card to the candidate list */
476 foreach (string srv in id.services)
478 if (request.service == srv)
487 /* If more than one candidate we dissasociate service from all ids */
488 if (has_srv && candidates.length() > 1)
490 foreach (IdCard id in candidates)
493 SList<string> services_list = null;
494 foreach (string srv in id.services)
496 if (srv == request.service)
498 services_list.append (srv);
501 if (services_list.length () == 0)
507 string[] services = new string[services_list.length ()];
508 foreach (string srv in services_list)
514 id.services = services;
518 /* If there are no candidates we use the service matching rules */
519 if (candidates.length () == 0)
521 foreach (IdCard id in identities_manager.id_card_list)
523 foreach (Rule rule in id.rules)
525 if (!match_service_pattern (request.service, rule.pattern))
528 candidates.append (id);
530 if (rule.always_confirm == "true")
536 if (candidates.length () > 1)
538 if (has_nai && nai_provided != null)
540 identity = nai_provided;
548 identity = candidates.nth_data (0);
552 /* TODO: If candidate list empty return fail */
557 redraw_id_card_widgets ();
562 // Send back the identity (we can't directly run the
563 // callback because we may be being called from a 'yield')
564 Idle.add (() => { send_identity_cb (identity); return false; });
568 private bool match_service_pattern (string service, string pattern)
570 var pspec = new PatternSpec (pattern);
571 return pspec.match_string (service);
574 public void send_identity_cb (IdCard identity)
576 return_if_fail (request_queue.length > 0);
578 var request = this.request_queue.pop_head ();
579 bool reset_password = false;
581 if (identity.password == null)
583 var dialog = new AddPasswordDialog ();
584 var result = dialog.run ();
587 case ResponseType.OK:
588 identity.password = dialog.password;
589 reset_password = ! dialog.remember;
599 if (this.request_queue.is_empty())
602 if (identity != null)
603 this.default_id_card = identity;
605 request.return_identity (identity);
608 identity.password = null;
613 private void label_make_bold (Label label)
615 var font_desc = new Pango.FontDescription ();
617 font_desc.set_weight (Pango.Weight.BOLD);
619 /* This will only affect the weight of the font, the rest is
620 * from the current state of the widget, which comes from the
621 * theme or user prefs, since the font desc only has the
622 * weight flag turned on.
624 label.modify_font (font_desc);
627 private void fill_services_vbox (IdCard id_card)
630 var n_columns = id_card.services.length;
632 var services_table = new Table (n_columns, 2, false);
633 services_table.set_col_spacings (10);
634 services_table.set_row_spacings (10);
635 this.services_internal_vbox.add (services_table);
637 service_button_map.remove_all ();
639 foreach (string service in id_card.services)
641 var label = new Label (service);
642 label.set_alignment (0, (float) 0.5);
644 var remove_button = new Button.from_stock (Stock.REMOVE);
646 var remove_button = new Button.from_stock (STOCK_REMOVE);
650 service_button_map.insert (remove_button, service);
652 remove_button.clicked.connect ((remove_button) =>
654 var dialog = new Gtk.MessageDialog (this,
655 Gtk.DialogFlags.DESTROY_WITH_PARENT,
656 Gtk.MessageType.QUESTION,
657 Gtk.ButtonsType.YES_NO,
658 _("Are you sure you want to stop '%s' ID Card to use %s?"),
659 custom_vbox.current_idcard.id_card.display_name);
660 var ret = dialog.run();
663 if (ret == Gtk.ResponseType.YES)
665 IdCard idcard = custom_vbox.current_idcard.id_card;
666 var candidate = service_button_map.lookup (remove_button);
668 SList<string> services = new SList<string>();
670 foreach (string srv in idcard.services)
672 if (srv == candidate)
674 services.append (srv);
677 idcard.services = new string[services.length()];
678 for (int j=0; j<idcard.services.length; j++)
680 idcard.services[j] = services.nth_data(j);
683 var children = services_internal_vbox.get_children ();
684 foreach (var hbox in children)
687 fill_services_vbox (idcard);
688 custom_vbox.current_idcard.update_id_card_label ();
692 services_table.attach_defaults (label, 0, 1, i, i+1);
693 services_table.attach_defaults (remove_button, 1, 2, i, i+1);
696 this.services_internal_vbox.show_all ();
699 private void on_about_action ()
702 "Javier Jardón <jjardon@codethink.co.uk>",
703 "Sam Thursfield <samthursfield@codethink.co.uk>",
704 "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
708 string copyright = "Copyright 2011 JANET";
712 Copyright (c) 2011, JANET(UK)
715 Redistribution and use in source and binary forms, with or without
716 modification, are permitted provided that the following conditions
719 1. Redistributions of source code must retain the above copyright
720 notice, this list of conditions and the following disclaimer.
722 2. Redistributions in binary form must reproduce the above copyright
723 notice, this list of conditions and the following disclaimer in the
724 documentation and/or other materials provided with the distribution.
726 3. Neither the name of JANET(UK) nor the names of its contributors
727 may be used to endorse or promote products derived from this software
728 without specific prior written permission.
730 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
731 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
732 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
733 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
734 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
735 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
736 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
737 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
738 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
739 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
743 Gtk.show_about_dialog (this,
744 "comments", _("Moonshot project UI"),
745 "copyright", copyright,
746 "website", "http://www.project-moonshot.org/",
748 "website-label", _("Visit the Moonshot project web site"),
750 "translator-credits", _("translator-credits"),
755 private Gtk.ActionEntry[] create_actions() {
756 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
758 Gtk.ActionEntry filemenu = { "FileMenuAction",
763 Gtk.ActionEntry add = { "AddIdCardAction",
771 N_("Add a new ID Card"),
772 add_identity_manual_cb };
774 Gtk.ActionEntry quit = { "QuitAction",
782 N_("Quit the application"),
786 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
791 Gtk.ActionEntry about = { "AboutAction",
799 N_("About this application"),
807 private void create_ui_manager ()
809 Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
810 action_group.add_actions (create_actions (), this);
811 ui_manager.insert_action_group (action_group, 0);
814 ui_manager.add_ui_from_string (layout, -1);
818 stderr.printf ("%s\n", e.message);
820 ui_manager.ensure_update ();
823 private void build_ui()
825 create_ui_manager ();
827 this.search_entry = new Entry();
829 set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
830 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
831 find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
832 this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
833 _("Search identity or service"));
834 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
836 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
837 find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
838 this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
839 _("Clear the current search"));
840 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
843 this.search_entry.icon_press.connect (search_entry_icon_press_cb);
844 this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
845 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
847 this.custom_vbox = new CustomVBox (this, false, 6);
849 var viewport = new Viewport (null, null);
850 viewport.set_border_width (6);
851 viewport.set_shadow_type (ShadowType.NONE);
852 viewport.add (custom_vbox);
853 var scroll = new ScrolledWindow (null, null);
854 scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
855 scroll.set_shadow_type (ShadowType.IN);
856 scroll.add_with_viewport (viewport);
858 var vbox_left = new VBox (false, 0);
859 vbox_left.pack_start (search_entry, false, false, 6);
860 vbox_left.pack_start (scroll, true, true, 0);
861 vbox_left.set_size_request (WINDOW_WIDTH, 0);
863 var login_vbox_title = new Label (_("Login: "));
864 label_make_bold (login_vbox_title);
865 login_vbox_title.set_alignment (0, (float) 0.5);
866 var username_label = new Label (_("Username:"));
867 username_label.set_alignment (1, (float) 0.5);
868 this.username_entry = new Entry ();
869 var password_label = new Label (_("Password:"));
870 password_label.set_alignment (1, (float) 0.5);
871 this.password_entry = new Entry ();
872 password_entry.set_invisible_char ('*');
873 password_entry.set_visibility (false);
874 var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
875 var login_table = new Table (3, 3, false);
876 login_table.set_col_spacings (10);
877 login_table.set_row_spacings (10);
878 login_table.attach_defaults (username_label, 0, 1, 0, 1);
879 login_table.attach_defaults (username_entry, 1, 2, 0, 1);
880 login_table.attach_defaults (password_label, 0, 1, 1, 2);
881 login_table.attach_defaults (password_entry, 1, 2, 1, 2);
882 login_table.attach_defaults (remember_checkbutton, 1, 2, 2, 3);
883 var login_vbox_alignment = new Alignment (0, 0, 0, 0);
884 login_vbox_alignment.set_padding (0, 0, 12, 0);
885 login_vbox_alignment.add (login_table);
886 var login_vbox = new VBox (false, 6);
887 login_vbox.pack_start (login_vbox_title, false, true, 0);
888 login_vbox.pack_start (login_vbox_alignment, false, true, 0);
890 var services_vbox_title = new Label (_("Services:"));
891 label_make_bold (services_vbox_title);
892 services_vbox_title.set_alignment (0, (float) 0.5);
893 var services_vbox_alignment = new Alignment (0, 0, 0, 0);
894 services_vbox_alignment.set_padding (0, 0, 12, 0);
895 this.services_internal_vbox = new VBox (true, 6);
896 services_vbox_alignment.add (services_internal_vbox);
897 var services_vbox = new VBox (false, 6);
898 services_vbox.pack_start (services_vbox_title, false, true, 0);
899 services_vbox.pack_start (services_vbox_alignment, false, true, 0);
901 this.vbox_right = new VBox (false, 18);
902 vbox_right.pack_start (login_vbox, false, true, 0);
903 vbox_right.pack_start (services_vbox, false, true, 0);
905 var hbox = new HBox (false, 12);
906 hbox.pack_start (vbox_left, true, true, 0);
907 hbox.pack_start (vbox_right, false, false, 0);
909 var main_vbox = new VBox (false, 0);
910 main_vbox.set_border_width (12);
911 var menubar = this.ui_manager.get_widget ("/MenuBar");
912 main_vbox.pack_start (menubar, false, false, 0);
913 main_vbox.pack_start (hbox, true, true, 0);
916 main_vbox.show_all();
917 this.vbox_right.hide ();
920 private void set_atk_name_description (Widget widget, string name, string description)
922 var atk_widget = widget.get_accessible ();
924 atk_widget.set_name (name);
925 atk_widget.set_description (description);
928 private void connect_signals()
930 this.destroy.connect (Gtk.main_quit);
933 private void init_ipc_server ()
936 /* Errors will currently be sent via g_log - ie. to an
937 * obtrusive message box, on Windows
939 this.ipc_server = MoonshotServer.get_instance ();
940 MoonshotServer.start (this);
943 var conn = DBus.Bus.get (DBus.BusType.SESSION);
944 dynamic DBus.Object bus = conn.get_object ("org.freedesktop.DBus",
945 "/org/freedesktop/DBus",
946 "org.freedesktop.DBus");
948 // try to register service in session bus
949 uint reply = bus.request_name ("org.janet.Moonshot", (uint) 0);
950 assert (reply == DBus.RequestNameReply.PRIMARY_OWNER);
952 this.ipc_server = new MoonshotServer (this);
953 conn.register_object ("/org/janet/moonshot", ipc_server);
958 stderr.printf ("%s\n", e.message);
963 public static int main(string[] args)
968 // Force specific theme settings on Windows without requiring a gtkrc file
969 Gtk.Settings settings = Gtk.Settings.get_default ();
970 settings.set_string_property ("gtk-theme-name", "ms-windows", "moonshot");
971 settings.set_long_property ("gtk-menu-images", 0, "moonshot");
974 Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
975 Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
976 Intl.textdomain (Config.GETTEXT_PACKAGE);
978 var window = new MainWindow();