4 class IdentityManagerView : Window {
5 private const int WINDOW_WIDTH = 400;
6 private const int WINDOW_HEIGHT = 500;
7 protected IdentityManagerApp parent_app;
9 public OSXApplication osxApp;
11 private UIManager ui_manager = new UIManager();
12 private Entry search_entry;
13 private VBox vbox_right;
14 private CustomVBox custom_vbox;
15 private VBox services_internal_vbox;
17 private Entry username_entry;
18 private Entry password_entry;
20 private ListStore* listmodel;
21 private TreeModelFilter filter;
23 public IdentityManagerModel identities_manager;
24 private SList<IdCard> candidates;
26 private IdCard default_id_card;
27 public GLib.Queue<IdentityRequest> request_queue;
29 private HashTable<Gtk.Button, string> service_button_map;
41 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' />" +
54 public IdentityManagerView(IdentityManagerApp app) {
57 osxApp = OSXApplication.get_instance();
59 identities_manager = parent_app.model;
60 request_queue = new GLib.Queue<IdentityRequest>();
61 service_button_map = new HashTable<Gtk.Button, string> (direct_hash, direct_equal);
62 this.title = "Moonshoot";
63 this.set_position (WindowPosition.CENTER);
64 set_default_size (WINDOW_WIDTH, WINDOW_HEIGHT);
74 public void on_card_list_changed () {
78 public void add_candidate (IdCard idcard)
80 candidates.append (idcard);
83 private bool visible_func (TreeModel model, TreeIter iter)
88 Columns.IDCARD_COL, out id_card);
93 if (candidates != null)
95 bool is_candidate = false;
96 foreach (IdCard candidate in candidates)
98 if (candidate == id_card)
105 string entry_text = search_entry.get_text ();
106 if (entry_text == null || entry_text == "")
111 foreach (string search_text in entry_text.split(" "))
113 if (search_text == "")
117 string search_text_casefold = search_text.casefold ();
119 if (id_card.issuer != null)
121 string issuer_casefold = id_card.issuer;
123 if (issuer_casefold.contains (search_text_casefold))
127 if (id_card.display_name != null)
129 string display_name_casefold = id_card.display_name.casefold ();
131 if (display_name_casefold.contains (search_text_casefold))
135 if (id_card.services.length > 0)
137 foreach (string service in id_card.services)
139 string service_casefold = service.casefold ();
141 if (service_casefold.contains (search_text_casefold))
149 private void setup_list_model ()
151 this.listmodel = new ListStore (Columns.N_COLUMNS, typeof (IdCard),
156 this.filter = new TreeModelFilter (listmodel, null);
158 filter.set_visible_func (visible_func);
161 private void search_entry_icon_press_cb (EntryIconPosition pos, Gdk.Event event)
163 if (pos == EntryIconPosition.PRIMARY)
165 print ("Search entry icon pressed\n");
169 this.search_entry.set_text ("");
173 private void search_entry_text_changed_cb ()
175 this.filter.refilter ();
176 redraw_id_card_widgets ();
178 var has_text = this.search_entry.get_text_length () > 0;
179 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, has_text);
180 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, has_text);
182 this.vbox_right.set_visible (false);
185 private bool search_entry_key_press_event_cb (Gdk.EventKey e)
187 if(Gdk.keyval_name(e.keyval) == "Escape")
188 this.search_entry.set_text("");
190 // Continue processing this event, since the
191 // text entry functionality needs to see it too.
195 private void load_id_cards () {
196 var children = this.custom_vbox.get_children ();
197 foreach (var id_card_widget in children) {
198 remove_id_card_widget((IdCardWidget)id_card_widget);
201 this.default_id_card = null;
202 LinkedList<IdCard> card_list = identities_manager.get_card_list() ;
203 if (card_list == null) {
207 foreach (IdCard id_card in card_list) {
208 add_id_card_data (id_card);
209 add_id_card_widget (id_card);
212 if (card_list.size > 0){
213 this.default_id_card = card_list.first();
217 private void fill_details (IdCardWidget id_card_widget)
219 var id_card = id_card_widget.id_card;
220 this.username_entry.set_text (id_card.username);
221 this.password_entry.set_text (id_card.password ?? "");
223 var children = this.services_internal_vbox.get_children ();
224 foreach (var hbox in children)
226 fill_services_vbox (id_card_widget.id_card);
227 // identities_manager.store_id_cards();
230 private void show_details (IdCard id_card)
232 this.vbox_right.set_visible (!vbox_right.get_visible ());
234 if (this.vbox_right.get_visible () == false)
236 this.resize (WINDOW_WIDTH, WINDOW_HEIGHT);
240 private void details_identity_cb (IdCardWidget id_card_widget)
242 fill_details (id_card_widget);
243 show_details (id_card_widget.id_card);
246 private IdCard get_id_card_data (AddIdentityDialog dialog)
248 var id_card = new IdCard ();
250 id_card.display_name = dialog.display_name;
251 id_card.issuer = dialog.issuer;
252 if (id_card.issuer == "")
253 id_card.issuer = "Issuer";
254 id_card.username = dialog.username;
255 id_card.password = dialog.password;
256 id_card.services = {};
257 id_card.set_data("pixbuf", find_icon ("avatar-default", 48));
262 private void add_id_card_data (IdCard id_card)
266 this.listmodel->append (out iter);
267 pixbuf = id_card.get_data("pixbuf");
268 listmodel->set (iter,
269 Columns.IDCARD_COL, id_card,
270 Columns.LOGO_COL, pixbuf,
271 Columns.ISSUER_COL, id_card.issuer,
272 Columns.USERNAME_COL, id_card.username,
273 Columns.PASSWORD_COL, id_card.password);
276 private void remove_id_card_data (IdCard id_card)
281 if (listmodel->get_iter_first (out iter))
285 listmodel->get (iter,
286 Columns.ISSUER_COL, out issuer);
288 if (id_card.issuer == issuer)
290 listmodel->remove (iter);
294 while (listmodel->iter_next (ref iter));
298 private void add_id_card_widget (IdCard id_card)
300 var id_card_widget = new IdCardWidget (id_card);
301 this.custom_vbox.add_id_card_widget (id_card_widget);
302 id_card_widget.details_id.connect (details_identity_cb);
303 id_card_widget.remove_id.connect (remove_identity_cb);
304 id_card_widget.send_id.connect ((w) => send_identity_cb (w.id_card));
305 id_card_widget.expanded.connect (this.custom_vbox.receive_expanded_event);
306 id_card_widget.expanded.connect (fill_details);
309 /* This method finds a valid display name */
310 public bool display_name_is_valid (string name,
311 out string? candidate)
313 foreach (IdCard id_card in identities_manager.get_card_list())
315 if (id_card.display_name == name)
317 if (&candidate != null)
319 for (int i=0; i<1000; i++)
321 string tmp = "%s %d".printf (name, i);
322 if (display_name_is_valid (tmp, null))
336 public void insert_id_card (IdCard id_card)
340 if (!display_name_is_valid (id_card.display_name, out candidate))
342 id_card.display_name = candidate;
345 this.identities_manager.add_card(id_card);
348 public bool add_identity (IdCard id_card)
350 /* TODO: Check if display name already exists */
352 var dialog = new Gtk.MessageDialog (this,
353 Gtk.DialogFlags.DESTROY_WITH_PARENT,
354 Gtk.MessageType.QUESTION,
355 Gtk.ButtonsType.YES_NO,
356 _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
357 id_card.display_name);
360 var ret = dialog.run ();
363 if (ret == Gtk.ResponseType.YES) {
364 id_card.set_data ("pixbuf", find_icon ("avatar-default", 48));
365 this.insert_id_card (id_card);
372 private void add_identity_manual_cb ()
374 var dialog = new AddIdentityDialog ();
375 var result = dialog.run ();
378 case ResponseType.OK:
379 insert_id_card (get_id_card_data (dialog));
387 private void remove_id_card_widget (IdCardWidget id_card_widget) {
388 this.custom_vbox.remove_id_card_widget (id_card_widget);
391 private void remove_identity (IdCardWidget id_card_widget)
393 var id_card = id_card_widget.id_card;
394 remove_id_card_widget (id_card_widget);
396 this.identities_manager.remove_card(id_card);
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,
427 MessageType.QUESTION,
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.get_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 bool has_service = false;
496 foreach (string srv in id.services)
498 if (srv == request.service)
503 services_list.append (srv);
509 if (services_list.length () == 0)
515 string[] services = new string[services_list.length ()];
516 foreach (string srv in services_list)
522 id.services = services;
526 // identities_manager.store_id_cards ();
528 /* If there are no candidates we use the service matching rules */
529 if (candidates.length () == 0)
531 foreach (IdCard id in identities_manager.get_card_list())
533 foreach (Rule rule in id.rules)
535 if (!match_service_pattern (request.service, rule.pattern))
538 candidates.append (id);
540 if (rule.always_confirm == "true")
546 if (candidates.length () > 1)
548 if (has_nai && nai_provided != null)
550 identity = nai_provided;
557 identity = candidates.nth_data (0);
559 /* TODO: If candidate list empty return fail */
564 redraw_id_card_widgets ();
569 // Send back the identity (we can't directly run the
570 // callback because we may be being called from a 'yield')
571 Idle.add (() => { send_identity_cb (identity); return false; });
575 private bool match_service_pattern (string service, string pattern)
577 var pspec = new PatternSpec (pattern);
578 return pspec.match_string (service);
581 public void send_identity_cb (IdCard identity)
583 return_if_fail (request_queue.length > 0);
585 var request = this.request_queue.pop_head ();
586 bool reset_password = false;
588 if (request.service != null && request.service != "")
590 string[] services = new string[identity.services.length + 1];
592 for (int i = 0; i < identity.services.length; i++)
593 services[i] = identity.services[i];
595 services[identity.services.length] = request.service;
597 identity.services = services;
599 // identities_manager.store_id_cards();
602 if (identity.password == null)
604 var dialog = new AddPasswordDialog ();
605 var result = dialog.run ();
608 case ResponseType.OK:
609 identity.password = dialog.password;
610 reset_password = ! dialog.remember;
620 if (this.request_queue.is_empty())
623 if (identity != null)
624 this.default_id_card = identity;
626 request.return_identity (identity);
629 identity.password = null;
634 private void label_make_bold (Label label)
636 var font_desc = new Pango.FontDescription ();
638 font_desc.set_weight (Pango.Weight.BOLD);
640 /* This will only affect the weight of the font, the rest is
641 * from the current state of the widget, which comes from the
642 * theme or user prefs, since the font desc only has the
643 * weight flag turned on.
645 label.modify_font (font_desc);
648 private void fill_services_vbox (IdCard id_card)
651 var n_columns = id_card.services.length;
653 var services_table = new Table (n_columns, 2, false);
654 services_table.set_col_spacings (10);
655 services_table.set_row_spacings (10);
656 this.services_internal_vbox.add (services_table);
658 service_button_map.remove_all ();
660 foreach (string service in id_card.services)
662 var label = new Label (service);
663 label.set_alignment (0, (float) 0.5);
665 var remove_button = new Button.from_stock (Stock.REMOVE);
667 var remove_button = new Button.from_stock (STOCK_REMOVE);
671 service_button_map.insert (remove_button, service);
673 remove_button.clicked.connect ((remove_button) =>
675 var dialog = new Gtk.MessageDialog (this,
676 Gtk.DialogFlags.DESTROY_WITH_PARENT,
677 Gtk.MessageType.QUESTION,
678 Gtk.ButtonsType.YES_NO,
679 _("Are you sure you want to stop '%s' ID Card to use %s?"),
680 custom_vbox.current_idcard.id_card.display_name);
681 var ret = dialog.run();
684 if (ret == Gtk.ResponseType.YES)
686 IdCard idcard = custom_vbox.current_idcard.id_card;
687 var candidate = service_button_map.lookup (remove_button);
689 SList<string> services = new SList<string>();
691 foreach (string srv in idcard.services)
693 if (srv == candidate)
695 services.append (srv);
698 idcard.services = new string[services.length()];
699 for (int j=0; j<idcard.services.length; j++)
701 idcard.services[j] = services.nth_data(j);
704 var children = services_internal_vbox.get_children ();
705 foreach (var hbox in children)
708 fill_services_vbox (idcard);
709 custom_vbox.current_idcard.update_id_card_label ();
713 services_table.attach_defaults (label, 0, 1, i, i+1);
714 services_table.attach_defaults (remove_button, 1, 2, i, i+1);
717 this.services_internal_vbox.show_all ();
720 private void on_about_action ()
723 "Javier Jardón <jjardon@codethink.co.uk>",
724 "Sam Thursfield <samthursfield@codethink.co.uk>",
725 "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
729 string copyright = "Copyright 2011 JANET";
733 Copyright (c) 2011, JANET(UK)
736 Redistribution and use in source and binary forms, with or without
737 modification, are permitted provided that the following conditions
740 1. Redistributions of source code must retain the above copyright
741 notice, this list of conditions and the following disclaimer.
743 2. Redistributions in binary form must reproduce the above copyright
744 notice, this list of conditions and the following disclaimer in the
745 documentation and/or other materials provided with the distribution.
747 3. Neither the name of JANET(UK) nor the names of its contributors
748 may be used to endorse or promote products derived from this software
749 without specific prior written permission.
751 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
752 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
753 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
754 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
755 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
756 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
757 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
758 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
759 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
760 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
764 Gtk.show_about_dialog (this,
765 "comments", _("Moonshot project UI"),
766 "copyright", copyright,
767 "website", "http://www.project-moonshot.org/",
769 "website-label", _("Visit the Moonshot project web site"),
771 "translator-credits", _("translator-credits"),
776 private Gtk.ActionEntry[] create_actions() {
777 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
779 Gtk.ActionEntry filemenu = { "FileMenuAction",
784 Gtk.ActionEntry add = { "AddIdCardAction",
792 N_("Add a new ID Card"),
793 add_identity_manual_cb };
795 Gtk.ActionEntry quit = { "QuitAction",
803 N_("Quit the application"),
807 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
812 Gtk.ActionEntry about = { "AboutAction",
820 N_("About this application"),
828 private void create_ui_manager ()
830 Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
831 action_group.add_actions (create_actions (), this);
832 ui_manager.insert_action_group (action_group, 0);
835 ui_manager.add_ui_from_string (layout, -1);
839 stderr.printf ("%s\n", e.message);
841 ui_manager.ensure_update ();
844 private void build_ui()
846 create_ui_manager ();
848 this.search_entry = new Entry();
850 set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
851 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
852 find_icon_sized ("edit-find", Gtk.IconSize.MENU));
853 // find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
854 this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
855 _("Search identity or service"));
856 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
858 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
859 find_icon_sized ("process-stop", Gtk.IconSize.MENU));
860 // find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
861 this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
862 _("Clear the current search"));
863 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
866 this.search_entry.icon_press.connect (search_entry_icon_press_cb);
867 this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
868 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
870 this.custom_vbox = new CustomVBox (this, false, 6);
872 var viewport = new Viewport (null, null);
873 viewport.set_border_width (6);
874 viewport.set_shadow_type (ShadowType.NONE);
875 viewport.add (custom_vbox);
876 var scroll = new ScrolledWindow (null, null);
877 scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
878 scroll.set_shadow_type (ShadowType.IN);
879 scroll.add_with_viewport (viewport);
881 var vbox_left = new VBox (false, 0);
882 vbox_left.pack_start (search_entry, false, false, 6);
883 vbox_left.pack_start (scroll, true, true, 0);
884 vbox_left.set_size_request (WINDOW_WIDTH, 0);
886 var login_vbox_title = new Label (_("Login: "));
887 label_make_bold (login_vbox_title);
888 login_vbox_title.set_alignment (0, (float) 0.5);
889 var username_label = new Label (_("Username:"));
890 username_label.set_alignment (1, (float) 0.5);
891 this.username_entry = new Entry ();
892 var password_label = new Label (_("Password:"));
893 password_label.set_alignment (1, (float) 0.5);
894 this.password_entry = new Entry ();
895 password_entry.set_invisible_char ('*');
896 password_entry.set_visibility (false);
897 var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
898 var login_table = new Table (3, 3, false);
899 login_table.set_col_spacings (10);
900 login_table.set_row_spacings (10);
901 login_table.attach_defaults (username_label, 0, 1, 0, 1);
902 login_table.attach_defaults (username_entry, 1, 2, 0, 1);
903 login_table.attach_defaults (password_label, 0, 1, 1, 2);
904 login_table.attach_defaults (password_entry, 1, 2, 1, 2);
905 login_table.attach_defaults (remember_checkbutton, 1, 2, 2, 3);
906 var login_vbox_alignment = new Alignment (0, 0, 0, 0);
907 login_vbox_alignment.set_padding (0, 0, 12, 0);
908 login_vbox_alignment.add (login_table);
909 var login_vbox = new VBox (false, 6);
910 login_vbox.pack_start (login_vbox_title, false, true, 0);
911 login_vbox.pack_start (login_vbox_alignment, false, true, 0);
913 var services_vbox_title = new Label (_("Services:"));
914 label_make_bold (services_vbox_title);
915 services_vbox_title.set_alignment (0, (float) 0.5);
916 var services_vbox_alignment = new Alignment (0, 0, 0, 0);
917 services_vbox_alignment.set_padding (0, 0, 12, 0);
918 this.services_internal_vbox = new VBox (true, 6);
919 services_vbox_alignment.add (services_internal_vbox);
920 var services_vbox = new VBox (false, 6);
921 services_vbox.pack_start (services_vbox_title, false, true, 0);
922 services_vbox.pack_start (services_vbox_alignment, false, true, 0);
924 this.vbox_right = new VBox (false, 18);
925 vbox_right.pack_start (login_vbox, false, true, 0);
926 vbox_right.pack_start (services_vbox, false, true, 0);
928 var hbox = new HBox (false, 12);
929 hbox.pack_start (vbox_left, true, true, 0);
930 hbox.pack_start (vbox_right, false, false, 0);
932 var main_vbox = new VBox (false, 0);
933 main_vbox.set_border_width (12);
934 var menubar = this.ui_manager.get_widget ("/MenuBar");
935 main_vbox.pack_start (menubar, false, false, 0);
936 main_vbox.pack_start (hbox, true, true, 0);
938 var menushell = this.ui_manager.get_widget("/Menu") as Gtk.MenuShell;
939 osxApp.set_menu_bar(menushell);
943 main_vbox.show_all();
944 this.vbox_right.hide ();
947 private void set_atk_name_description (Widget widget, string name, string description)
949 var atk_widget = widget.get_accessible ();
951 atk_widget.set_name (name);
952 atk_widget.set_description (description);
955 private void connect_signals()
957 this.destroy.connect (Gtk.main_quit);
958 this.identities_manager.card_list_changed.connect(this.on_card_list_changed);