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 public void clear_candidates ()
79 candidates = new SList<IdCard>();
82 private bool visible_func (TreeModel model, TreeIter iter)
87 Columns.IDCARD_COL, out id_card);
91 foreach (IdCard candidate in candidates)
93 if (&candidate == &id_card)
97 string entry_text = search_entry.get_text ();
98 if (entry_text == null || entry_text == "")
103 foreach (string search_text in entry_text.split(" "))
105 if (search_text == "")
109 string search_text_casefold = search_text.casefold ();
111 if (id_card.issuer != null)
113 string issuer_casefold = id_card.issuer;
115 if (issuer_casefold.contains (search_text_casefold))
119 if (id_card.display_name != null)
121 string display_name_casefold = id_card.display_name.casefold ();
123 if (display_name_casefold.contains (search_text_casefold))
127 if (id_card.services.length > 0)
129 foreach (string service in id_card.services)
131 string service_casefold = service.casefold ();
133 if (service_casefold.contains (search_text_casefold))
141 private void setup_identities_list ()
143 this.listmodel = new ListStore (Columns.N_COLUMNS, typeof (IdCard),
148 this.filter = new TreeModelFilter (listmodel, null);
150 filter.set_visible_func (visible_func);
153 private void search_entry_icon_press_cb (EntryIconPosition pos, Gdk.Event event)
155 if (pos == EntryIconPosition.PRIMARY)
157 print ("Search entry icon pressed\n");
161 this.search_entry.set_text ("");
165 private void search_entry_text_changed_cb ()
167 this.filter.refilter ();
168 redraw_id_card_widgets ();
170 var has_text = this.search_entry.get_text_length () > 0;
171 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, has_text);
172 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, has_text);
174 this.vbox_right.set_visible (false);
177 private bool search_entry_key_press_event_cb (Gdk.EventKey e)
179 if(Gdk.keyval_name(e.keyval) == "Escape")
180 this.search_entry.set_text("");
182 // Continue processing this event, since the
183 // text entry functionality needs to see it too.
187 private void load_id_cards ()
189 identities_manager = new IdentitiesManager ();
191 if (identities_manager.id_card_list == null)
194 foreach (IdCard id_card in identities_manager.id_card_list)
196 add_id_card_data (id_card);
197 add_id_card_widget (id_card);
200 this.default_id_card = identities_manager.id_card_list.data;
203 private void fill_details (IdCardWidget id_card_widget)
205 var id_card = id_card_widget.id_card;
206 this.username_entry.set_text (id_card.username);
207 this.password_entry.set_text (id_card.password ?? "");
209 var children = this.services_internal_vbox.get_children ();
210 foreach (var hbox in children)
212 fill_services_vbox (id_card_widget.id_card);
213 identities_manager.store_id_cards();
216 private void show_details (IdCard id_card)
218 this.vbox_right.set_visible (!vbox_right.get_visible ());
220 if (this.vbox_right.get_visible () == false)
222 this.resize (WINDOW_WIDTH, WINDOW_HEIGHT);
226 private void details_identity_cb (IdCardWidget id_card_widget)
228 fill_details (id_card_widget);
229 show_details (id_card_widget.id_card);
232 private IdCard get_id_card_data (AddIdentityDialog dialog)
234 var id_card = new IdCard ();
236 id_card.display_name = dialog.display_name;
237 id_card.issuer = dialog.issuer;
238 if (id_card.issuer == "")
239 id_card.issuer = "Issuer";
240 id_card.username = dialog.username;
241 id_card.password = dialog.password;
242 id_card.services = {};
243 id_card.set_data("pixbuf", find_icon ("avatar-default", 48));
248 private void add_id_card_data (IdCard id_card)
253 this.listmodel.append (out iter);
254 pixbuf = id_card.get_data("pixbuf");
256 Columns.IDCARD_COL, id_card,
257 Columns.LOGO_COL, pixbuf,
258 Columns.ISSUER_COL, id_card.issuer,
259 Columns.USERNAME_COL, id_card.username,
260 Columns.PASSWORD_COL, id_card.password);
263 private void remove_id_card_data (IdCard id_card)
268 if (listmodel.get_iter_first (out iter))
273 Columns.ISSUER_COL, out issuer);
275 if (id_card.issuer == issuer)
277 listmodel.remove (iter);
281 while (listmodel.iter_next (ref iter));
285 private void add_id_card_widget (IdCard id_card)
287 var id_card_widget = new IdCardWidget (id_card);
289 this.custom_vbox.add_id_card_widget (id_card_widget);
291 id_card_widget.details_id.connect (details_identity_cb);
292 id_card_widget.remove_id.connect (remove_identity_cb);
293 id_card_widget.send_id.connect ((w) => send_identity_cb (w.id_card));
294 id_card_widget.expanded.connect (this.custom_vbox.receive_expanded_event);
295 id_card_widget.expanded.connect (fill_details);
298 /* This method finds a valid display name */
299 public bool display_name_is_valid (string name,
300 out string? candidate)
302 foreach (IdCard id_card in identities_manager.id_card_list)
304 if (id_card.display_name == name)
306 if (&candidate != null)
308 for (int i=0; i<1000; i++)
310 string tmp = "%s %d".printf (name, i);
311 if (display_name_is_valid (tmp, null))
325 public void insert_id_card (IdCard id_card)
329 if (!display_name_is_valid (id_card.display_name, out candidate))
331 id_card.display_name = candidate;
334 this.identities_manager.id_card_list.prepend (id_card);
335 this.identities_manager.store_id_cards ();
337 add_id_card_data (id_card);
338 add_id_card_widget (id_card);
341 public bool add_identity (IdCard id_card)
343 /* TODO: Check if display name already exists */
345 var dialog = new Gtk.MessageDialog (this,
346 Gtk.DialogFlags.DESTROY_WITH_PARENT,
347 Gtk.MessageType.QUESTION,
348 Gtk.ButtonsType.YES_NO,
349 _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
350 id_card.display_name);
353 var ret = dialog.run ();
356 if (ret == Gtk.ResponseType.YES) {
357 id_card.set_data ("pixbuf", find_icon ("avatar-default", 48));
358 this.insert_id_card (id_card);
365 private void add_identity_manual_cb ()
367 var dialog = new AddIdentityDialog ();
368 var result = dialog.run ();
371 case ResponseType.OK:
372 insert_id_card (get_id_card_data (dialog));
380 private void remove_id_card_widget (IdCardWidget id_card_widget)
382 remove_id_card_data (id_card_widget.id_card);
384 this.custom_vbox.remove_id_card_widget (id_card_widget);
387 private void remove_identity (IdCardWidget id_card_widget)
389 var id_card = id_card_widget.id_card;
391 this.identities_manager.id_card_list.remove (id_card);
392 this.identities_manager.store_id_cards ();
394 remove_id_card_widget (id_card_widget);
397 private void redraw_id_card_widgets ()
402 var children = this.custom_vbox.get_children ();
403 foreach (var id_card_widget in children)
404 id_card_widget.destroy();
406 if (filter.get_iter_first (out iter))
411 Columns.IDCARD_COL, out id_card);
413 add_id_card_widget (id_card);
415 while (filter.iter_next (ref iter));
419 private void remove_identity_cb (IdCardWidget id_card_widget)
421 var id_card = id_card_widget.id_card;
423 var dialog = new MessageDialog (null,
424 DialogFlags.DESTROY_WITH_PARENT,
426 Gtk.ButtonsType.YES_NO,
427 _("Are you sure you want to delete %s ID Card?"), id_card.issuer);
428 var result = dialog.run ();
430 case ResponseType.YES:
431 remove_identity (id_card_widget);
439 public void select_identity (IdentityRequest request)
441 IdCard identity = null;
443 this.request_queue.push_tail (request);
445 if (custom_vbox.current_idcard != null &&
446 custom_vbox.current_idcard.send_button != null)
447 custom_vbox.current_idcard.send_button.set_sensitive (true);
449 if (request.select_default)
451 identity = default_id_card;
454 if (identity == null)
456 bool has_nai = request.nai != null && request.nai != "";
457 bool has_srv = request.service != null && request.service != "";
458 foreach (IdCard tmp in identities_manager.id_card_list)
460 /* If NAI matches we add id card to the candidate list */
461 if (has_nai && request.nai == tmp.nai)
467 /* If any service matches we add id card to the candidate list */
470 foreach (string srv in tmp.services)
472 if (request.service == srv)
481 /* If more than one candidate we dissasociate service from all ids */
482 if (has_srv && candidates.length() > 1)
484 foreach (IdCard id in candidates)
487 SList<string> services_list = null;
488 foreach (string srv in id.services)
490 if (srv == request.service)
492 services_list.append (srv);
495 if (services_list.length () == 0)
501 string[] services = new string[services_list.length ()];
502 foreach (string srv in services_list)
508 id.services = services;
512 if (candidates.length () == 0)
514 /*TODO: Check regex rules against service */
522 // Send back the identity (we can't directly run the
523 // callback because we may be being called from a 'yield')
524 Idle.add (() => { send_identity_cb (identity); return false; });
529 public void send_identity_cb (IdCard identity)
531 return_if_fail (request_queue.length > 0);
533 var request = this.request_queue.pop_head ();
534 bool reset_password = false;
536 if (identity.password == null)
538 var dialog = new AddPasswordDialog ();
539 var result = dialog.run ();
542 case ResponseType.OK:
543 identity.password = dialog.password;
544 reset_password = ! dialog.remember;
554 if (this.request_queue.is_empty())
557 if (identity != null)
558 this.default_id_card = identity;
560 request.return_identity (identity);
563 identity.password = null;
568 private void label_make_bold (Label label)
570 var font_desc = new Pango.FontDescription ();
572 font_desc.set_weight (Pango.Weight.BOLD);
574 /* This will only affect the weight of the font, the rest is
575 * from the current state of the widget, which comes from the
576 * theme or user prefs, since the font desc only has the
577 * weight flag turned on.
579 label.modify_font (font_desc);
582 private void fill_services_vbox (IdCard id_card)
585 var n_columns = id_card.services.length;
587 var services_table = new Table (n_columns, 2, false);
588 services_table.set_col_spacings (10);
589 services_table.set_row_spacings (10);
590 this.services_internal_vbox.add (services_table);
592 service_button_map.remove_all ();
594 foreach (string service in id_card.services)
596 var label = new Label (service);
597 label.set_alignment (0, (float) 0.5);
599 var remove_button = new Button.from_stock (Stock.REMOVE);
601 var remove_button = new Button.from_stock (STOCK_REMOVE);
605 service_button_map.insert (remove_button, service);
607 remove_button.clicked.connect ((remove_button) =>
609 var dialog = new Gtk.MessageDialog (this,
610 Gtk.DialogFlags.DESTROY_WITH_PARENT,
611 Gtk.MessageType.QUESTION,
612 Gtk.ButtonsType.YES_NO,
613 _("Are you sure you want to stop '%s' ID Card to use %s?"),
614 custom_vbox.current_idcard.id_card.display_name);
615 var ret = dialog.run();
618 if (ret == Gtk.ResponseType.YES)
620 IdCard idcard = custom_vbox.current_idcard.id_card;
621 var candidate = service_button_map.lookup (remove_button);
623 SList<string> services = new SList<string>();
625 foreach (string srv in id_card.services)
627 if (srv == candidate)
629 services.append (srv);
632 id_card.services = new string[services.length()];
633 for (int j=0; j<id_card.services.length; j++)
635 id_card.services[j] = services.nth_data(j);
638 var children = services_internal_vbox.get_children ();
639 foreach (var hbox in children)
642 fill_services_vbox (id_card);
643 custom_vbox.current_idcard.update_id_card_label ();
647 services_table.attach_defaults (label, 0, 1, i, i+1);
648 services_table.attach_defaults (remove_button, 1, 2, i, i+1);
651 this.services_internal_vbox.show_all ();
654 private void on_about_action ()
657 "Javier Jardón <jjardon@codethink.co.uk>",
658 "Sam Thursfield <samthursfield@codethink.co.uk>",
659 "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
663 string copyright = "Copyright 2011 JANET";
667 Copyright (c) 2011, JANET(UK)
670 Redistribution and use in source and binary forms, with or without
671 modification, are permitted provided that the following conditions
674 1. Redistributions of source code must retain the above copyright
675 notice, this list of conditions and the following disclaimer.
677 2. Redistributions in binary form must reproduce the above copyright
678 notice, this list of conditions and the following disclaimer in the
679 documentation and/or other materials provided with the distribution.
681 3. Neither the name of JANET(UK) nor the names of its contributors
682 may be used to endorse or promote products derived from this software
683 without specific prior written permission.
685 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
686 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
687 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
688 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
689 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
690 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
691 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
692 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
693 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
694 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
698 Gtk.show_about_dialog (this,
699 "comments", _("Moonshot project UI"),
700 "copyright", copyright,
701 "website", "http://www.project-moonshot.org/",
703 "website-label", _("Visit the Moonshot project web site"),
705 "translator-credits", _("translator-credits"),
710 private Gtk.ActionEntry[] create_actions() {
711 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
713 Gtk.ActionEntry filemenu = { "FileMenuAction",
718 Gtk.ActionEntry add = { "AddIdCardAction",
726 N_("Add a new ID Card"),
727 add_identity_manual_cb };
729 Gtk.ActionEntry quit = { "QuitAction",
737 N_("Quit the application"),
741 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
746 Gtk.ActionEntry about = { "AboutAction",
754 N_("About this application"),
762 private void create_ui_manager ()
764 Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
765 action_group.add_actions (create_actions (), this);
766 ui_manager.insert_action_group (action_group, 0);
769 ui_manager.add_ui_from_string (layout, -1);
773 stderr.printf ("%s\n", e.message);
775 ui_manager.ensure_update ();
778 private void build_ui()
780 create_ui_manager ();
782 this.search_entry = new Entry();
784 set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
785 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
786 find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
787 this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
788 _("Search identity or service"));
789 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
791 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
792 find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
793 this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
794 _("Clear the current search"));
795 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
798 this.search_entry.icon_press.connect (search_entry_icon_press_cb);
799 this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
800 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
802 this.custom_vbox = new CustomVBox (this, false, 6);
804 var viewport = new Viewport (null, null);
805 viewport.set_border_width (6);
806 viewport.set_shadow_type (ShadowType.NONE);
807 viewport.add (custom_vbox);
808 var scroll = new ScrolledWindow (null, null);
809 scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
810 scroll.set_shadow_type (ShadowType.IN);
811 scroll.add_with_viewport (viewport);
813 var vbox_left = new VBox (false, 0);
814 vbox_left.pack_start (search_entry, false, false, 6);
815 vbox_left.pack_start (scroll, true, true, 0);
816 vbox_left.set_size_request (WINDOW_WIDTH, 0);
818 var login_vbox_title = new Label (_("Login: "));
819 label_make_bold (login_vbox_title);
820 login_vbox_title.set_alignment (0, (float) 0.5);
821 var username_label = new Label (_("Username:"));
822 username_label.set_alignment (1, (float) 0.5);
823 this.username_entry = new Entry ();
824 var password_label = new Label (_("Password:"));
825 password_label.set_alignment (1, (float) 0.5);
826 this.password_entry = new Entry ();
827 password_entry.set_invisible_char ('*');
828 password_entry.set_visibility (false);
829 var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
830 var login_table = new Table (3, 3, false);
831 login_table.set_col_spacings (10);
832 login_table.set_row_spacings (10);
833 login_table.attach_defaults (username_label, 0, 1, 0, 1);
834 login_table.attach_defaults (username_entry, 1, 2, 0, 1);
835 login_table.attach_defaults (password_label, 0, 1, 1, 2);
836 login_table.attach_defaults (password_entry, 1, 2, 1, 2);
837 login_table.attach_defaults (remember_checkbutton, 1, 2, 2, 3);
838 var login_vbox_alignment = new Alignment (0, 0, 0, 0);
839 login_vbox_alignment.set_padding (0, 0, 12, 0);
840 login_vbox_alignment.add (login_table);
841 var login_vbox = new VBox (false, 6);
842 login_vbox.pack_start (login_vbox_title, false, true, 0);
843 login_vbox.pack_start (login_vbox_alignment, false, true, 0);
845 var services_vbox_title = new Label (_("Services:"));
846 label_make_bold (services_vbox_title);
847 services_vbox_title.set_alignment (0, (float) 0.5);
848 var services_vbox_alignment = new Alignment (0, 0, 0, 0);
849 services_vbox_alignment.set_padding (0, 0, 12, 0);
850 this.services_internal_vbox = new VBox (true, 6);
851 services_vbox_alignment.add (services_internal_vbox);
852 var services_vbox = new VBox (false, 6);
853 services_vbox.pack_start (services_vbox_title, false, true, 0);
854 services_vbox.pack_start (services_vbox_alignment, false, true, 0);
856 this.vbox_right = new VBox (false, 18);
857 vbox_right.pack_start (login_vbox, false, true, 0);
858 vbox_right.pack_start (services_vbox, false, true, 0);
860 var hbox = new HBox (false, 12);
861 hbox.pack_start (vbox_left, true, true, 0);
862 hbox.pack_start (vbox_right, false, false, 0);
864 var main_vbox = new VBox (false, 0);
865 main_vbox.set_border_width (12);
866 var menubar = this.ui_manager.get_widget ("/MenuBar");
867 main_vbox.pack_start (menubar, false, false, 0);
868 main_vbox.pack_start (hbox, true, true, 0);
871 main_vbox.show_all();
872 this.vbox_right.hide ();
875 private void set_atk_name_description (Widget widget, string name, string description)
877 var atk_widget = widget.get_accessible ();
879 atk_widget.set_name (name);
880 atk_widget.set_description (description);
883 private void connect_signals()
885 this.destroy.connect (Gtk.main_quit);
888 private void init_ipc_server ()
891 /* Errors will currently be sent via g_log - ie. to an
892 * obtrusive message box, on Windows
894 this.ipc_server = MoonshotServer.get_instance ();
895 MoonshotServer.start (this);
898 var conn = DBus.Bus.get (DBus.BusType.SESSION);
899 dynamic DBus.Object bus = conn.get_object ("org.freedesktop.DBus",
900 "/org/freedesktop/DBus",
901 "org.freedesktop.DBus");
903 // try to register service in session bus
904 uint reply = bus.request_name ("org.janet.Moonshot", (uint) 0);
905 assert (reply == DBus.RequestNameReply.PRIMARY_OWNER);
907 this.ipc_server = new MoonshotServer (this);
908 conn.register_object ("/org/janet/moonshot", ipc_server);
913 stderr.printf ("%s\n", e.message);
918 public static int main(string[] args)
923 // Force specific theme settings on Windows without requiring a gtkrc file
924 Gtk.Settings settings = Gtk.Settings.get_default ();
925 settings.set_string_property ("gtk-theme-name", "ms-windows", "moonshot");
926 settings.set_long_property ("gtk-menu-images", 0, "moonshot");
929 Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
930 Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
931 Intl.textdomain (Config.GETTEXT_PACKAGE);
933 var window = new MainWindow();