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)
486 /* If more than one candidate we dissasociate service from all ids */
487 if (has_srv && candidates.length() > 1)
489 foreach (IdCard id in candidates)
492 SList<string> services_list = null;
493 bool has_service = false;
494 // this is problematic -
495 foreach (string srv in id.services)
497 if (srv == request.service)
502 services_list.append (srv);
508 if (services_list.length () == 0)
514 string[] services = new string[services_list.length ()];
515 foreach (string srv in services_list)
521 id.services = services;
525 // identities_manager.store_id_cards ();
527 /* If there are no candidates we use the service matching rules */
528 if (candidates.length () == 0)
530 foreach (IdCard id in identities_manager.get_card_list())
532 foreach (Rule rule in id.rules)
534 if (!match_service_pattern (request.service, rule.pattern))
537 candidates.append (id);
539 if (rule.always_confirm == "true")
545 if (candidates.length () > 1)
547 if (has_nai && nai_provided != null)
549 identity = nai_provided;
556 identity = candidates.nth_data (0);
558 /* TODO: If candidate list empty return fail */
563 redraw_id_card_widgets ();
568 // Send back the identity (we can't directly run the
569 // callback because we may be being called from a 'yield')
570 Idle.add (() => { send_identity_cb (identity); return false; });
574 private bool match_service_pattern (string service, string pattern)
576 var pspec = new PatternSpec (pattern);
577 return pspec.match_string (service);
580 public void send_identity_cb (IdCard? identity) //identity will be null if no matching card was found
582 return_if_fail (request_queue.length > 0);
584 var request = this.request_queue.pop_head ();
585 bool reset_password = false;
587 if (identity != null)
589 if (request.service != null && request.service != "")
591 string[] services = new string[identity.services.length + 1];
593 for (int i = 0; i < identity.services.length; i++)
594 services[i] = identity.services[i];
596 services[identity.services.length] = request.service;
598 identity.services = services;
600 // identities_manager.store_id_cards();
603 if (identity.password == null)
605 var dialog = new AddPasswordDialog ();
606 var result = dialog.run ();
609 case ResponseType.OK:
610 identity.password = dialog.password;
611 reset_password = ! dialog.remember;
620 this.default_id_card = identity;
623 if (this.request_queue.is_empty())
626 request.return_identity (identity);
628 if (reset_password && identity != null)
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 from being used with %s?"),
680 custom_vbox.current_idcard.id_card.display_name,
682 var ret = dialog.run();
685 if (ret == Gtk.ResponseType.YES)
687 IdCard idcard = custom_vbox.current_idcard.id_card;
688 var candidate = service_button_map.lookup (remove_button);
690 SList<string> services = new SList<string>();
692 foreach (string srv in idcard.services)
694 if (srv == candidate)
696 services.append (srv);
699 idcard.services = new string[services.length()];
700 for (int j=0; j<idcard.services.length; j++)
702 idcard.services[j] = services.nth_data(j);
705 var children = services_internal_vbox.get_children ();
706 foreach (var hbox in children)
709 fill_services_vbox (idcard);
710 custom_vbox.current_idcard.update_id_card_label ();
714 services_table.attach_defaults (label, 0, 1, i, i+1);
715 services_table.attach_defaults (remove_button, 1, 2, i, i+1);
718 this.services_internal_vbox.show_all ();
721 private void on_about_action ()
724 "Javier Jardón <jjardon@codethink.co.uk>",
725 "Sam Thursfield <samthursfield@codethink.co.uk>",
726 "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
730 string copyright = "Copyright 2011 JANET";
734 Copyright (c) 2011, JANET(UK)
737 Redistribution and use in source and binary forms, with or without
738 modification, are permitted provided that the following conditions
741 1. Redistributions of source code must retain the above copyright
742 notice, this list of conditions and the following disclaimer.
744 2. Redistributions in binary form must reproduce the above copyright
745 notice, this list of conditions and the following disclaimer in the
746 documentation and/or other materials provided with the distribution.
748 3. Neither the name of JANET(UK) nor the names of its contributors
749 may be used to endorse or promote products derived from this software
750 without specific prior written permission.
752 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
753 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
754 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
755 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
756 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
757 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
758 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
759 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
760 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
761 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
765 Gtk.show_about_dialog (this,
766 "comments", _("Moonshot project UI"),
767 "copyright", copyright,
768 "website", "http://www.project-moonshot.org/",
770 "website-label", _("Visit the Moonshot project web site"),
772 "translator-credits", _("translator-credits"),
777 private Gtk.ActionEntry[] create_actions() {
778 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
780 Gtk.ActionEntry filemenu = { "FileMenuAction",
785 Gtk.ActionEntry add = { "AddIdCardAction",
793 N_("Add a new ID Card"),
794 add_identity_manual_cb };
796 Gtk.ActionEntry quit = { "QuitAction",
804 N_("Quit the application"),
808 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
813 Gtk.ActionEntry about = { "AboutAction",
821 N_("About this application"),
829 private void create_ui_manager ()
831 Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
832 action_group.add_actions (create_actions (), this);
833 ui_manager.insert_action_group (action_group, 0);
836 ui_manager.add_ui_from_string (layout, -1);
840 stderr.printf ("%s\n", e.message);
842 ui_manager.ensure_update ();
845 private void build_ui()
847 create_ui_manager ();
849 this.search_entry = new Entry();
851 set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
852 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
853 find_icon_sized ("edit-find", Gtk.IconSize.MENU));
854 // find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
855 this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
856 _("Search identity or service"));
857 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
859 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
860 find_icon_sized ("process-stop", Gtk.IconSize.MENU));
861 // find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
862 this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
863 _("Clear the current search"));
864 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
867 this.search_entry.icon_press.connect (search_entry_icon_press_cb);
868 this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
869 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
871 this.custom_vbox = new CustomVBox (this, false, 6);
873 var viewport = new Viewport (null, null);
874 viewport.set_border_width (6);
875 viewport.set_shadow_type (ShadowType.NONE);
876 viewport.add (custom_vbox);
877 var scroll = new ScrolledWindow (null, null);
878 scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
879 scroll.set_shadow_type (ShadowType.IN);
880 scroll.add_with_viewport (viewport);
882 var vbox_left = new VBox (false, 0);
883 vbox_left.pack_start (search_entry, false, false, 6);
884 vbox_left.pack_start (scroll, true, true, 0);
885 vbox_left.set_size_request (WINDOW_WIDTH, 0);
887 var login_vbox_title = new Label (_("Login: "));
888 label_make_bold (login_vbox_title);
889 login_vbox_title.set_alignment (0, (float) 0.5);
890 var username_label = new Label (_("Username:"));
891 username_label.set_alignment (1, (float) 0.5);
892 this.username_entry = new Entry ();
893 var password_label = new Label (_("Password:"));
894 password_label.set_alignment (1, (float) 0.5);
895 this.password_entry = new Entry ();
896 password_entry.set_invisible_char ('*');
897 password_entry.set_visibility (false);
898 var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
899 var login_table = new Table (3, 3, false);
900 login_table.set_col_spacings (10);
901 login_table.set_row_spacings (10);
902 login_table.attach_defaults (username_label, 0, 1, 0, 1);
903 login_table.attach_defaults (username_entry, 1, 2, 0, 1);
904 login_table.attach_defaults (password_label, 0, 1, 1, 2);
905 login_table.attach_defaults (password_entry, 1, 2, 1, 2);
906 login_table.attach_defaults (remember_checkbutton, 1, 2, 2, 3);
907 var login_vbox_alignment = new Alignment (0, 0, 0, 0);
908 login_vbox_alignment.set_padding (0, 0, 12, 0);
909 login_vbox_alignment.add (login_table);
910 var login_vbox = new VBox (false, 6);
911 login_vbox.pack_start (login_vbox_title, false, true, 0);
912 login_vbox.pack_start (login_vbox_alignment, false, true, 0);
914 var services_vbox_title = new Label (_("Services:"));
915 label_make_bold (services_vbox_title);
916 services_vbox_title.set_alignment (0, (float) 0.5);
917 var services_vbox_alignment = new Alignment (0, 0, 0, 0);
918 services_vbox_alignment.set_padding (0, 0, 12, 0);
919 this.services_internal_vbox = new VBox (true, 6);
920 services_vbox_alignment.add (services_internal_vbox);
921 var services_vbox = new VBox (false, 6);
922 services_vbox.pack_start (services_vbox_title, false, true, 0);
923 services_vbox.pack_start (services_vbox_alignment, false, true, 0);
925 this.vbox_right = new VBox (false, 18);
926 vbox_right.pack_start (login_vbox, false, true, 0);
927 vbox_right.pack_start (services_vbox, false, true, 0);
929 var hbox = new HBox (false, 12);
930 hbox.pack_start (vbox_left, true, true, 0);
931 hbox.pack_start (vbox_right, false, false, 0);
933 var main_vbox = new VBox (false, 0);
934 main_vbox.set_border_width (12);
937 // hide the File | Quit menu item which is now on the Mac Menu
938 Gtk.Widget quit_item = this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
941 Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
942 osxApp.set_menu_bar(menushell);
943 osxApp.set_use_quartz_accelerators(true);
944 osxApp.sync_menu_bar();
947 var menubar = this.ui_manager.get_widget ("/MenuBar");
948 main_vbox.pack_start (menubar, false, false, 0);
950 main_vbox.pack_start (hbox, true, true, 0);
952 main_vbox.show_all();
953 this.vbox_right.hide ();
956 private void set_atk_name_description (Widget widget, string name, string description)
958 var atk_widget = widget.get_accessible ();
960 atk_widget.set_name (name);
961 atk_widget.set_description (description);
964 private void connect_signals()
966 this.destroy.connect (Gtk.main_quit);
967 this.identities_manager.card_list_changed.connect(this.on_card_list_changed);