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.update_card (identity);
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 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";
732 const string license =
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", Config.PACKAGE_URL,
769 "version", Config.PACKAGE_VERSION,
771 "website-label", _("Visit the Moonshot project web site"),
773 "translator-credits", _("translator-credits"),
778 private Gtk.ActionEntry[] create_actions() {
779 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
781 Gtk.ActionEntry filemenu = { "FileMenuAction",
786 Gtk.ActionEntry add = { "AddIdCardAction",
794 N_("Add a new ID Card"),
795 add_identity_manual_cb };
797 Gtk.ActionEntry quit = { "QuitAction",
805 N_("Quit the application"),
809 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
814 Gtk.ActionEntry about = { "AboutAction",
822 N_("About this application"),
830 private void create_ui_manager ()
832 Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
833 action_group.add_actions (create_actions (), this);
834 ui_manager.insert_action_group (action_group, 0);
837 ui_manager.add_ui_from_string (layout, -1);
841 stderr.printf ("%s\n", e.message);
843 ui_manager.ensure_update ();
846 private void build_ui()
848 create_ui_manager ();
850 this.search_entry = new Entry();
852 set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
853 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
854 find_icon_sized ("edit-find", Gtk.IconSize.MENU));
855 // find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
856 this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
857 _("Search identity or service"));
858 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
860 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
861 find_icon_sized ("process-stop", Gtk.IconSize.MENU));
862 // find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
863 this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
864 _("Clear the current search"));
865 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
868 this.search_entry.icon_press.connect (search_entry_icon_press_cb);
869 this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
870 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
872 this.custom_vbox = new CustomVBox (this, false, 6);
874 var viewport = new Viewport (null, null);
875 viewport.set_border_width (6);
876 viewport.set_shadow_type (ShadowType.NONE);
877 viewport.add (custom_vbox);
878 var scroll = new ScrolledWindow (null, null);
879 scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
880 scroll.set_shadow_type (ShadowType.IN);
881 scroll.add_with_viewport (viewport);
883 var vbox_left = new VBox (false, 0);
884 vbox_left.pack_start (search_entry, false, false, 6);
885 vbox_left.pack_start (scroll, true, true, 0);
886 vbox_left.set_size_request (WINDOW_WIDTH, 0);
888 var login_vbox_title = new Label (_("Login: "));
889 label_make_bold (login_vbox_title);
890 login_vbox_title.set_alignment (0, (float) 0.5);
891 var username_label = new Label (_("Username:"));
892 username_label.set_alignment (1, (float) 0.5);
893 this.username_entry = new Entry ();
894 var password_label = new Label (_("Password:"));
895 password_label.set_alignment (1, (float) 0.5);
896 this.password_entry = new Entry ();
897 password_entry.set_invisible_char ('*');
898 password_entry.set_visibility (false);
899 var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
900 var login_table = new Table (3, 3, false);
901 login_table.set_col_spacings (10);
902 login_table.set_row_spacings (10);
903 login_table.attach_defaults (username_label, 0, 1, 0, 1);
904 login_table.attach_defaults (username_entry, 1, 2, 0, 1);
905 login_table.attach_defaults (password_label, 0, 1, 1, 2);
906 login_table.attach_defaults (password_entry, 1, 2, 1, 2);
907 login_table.attach_defaults (remember_checkbutton, 1, 2, 2, 3);
908 var login_vbox_alignment = new Alignment (0, 0, 0, 0);
909 login_vbox_alignment.set_padding (0, 0, 12, 0);
910 login_vbox_alignment.add (login_table);
911 var login_vbox = new VBox (false, 6);
912 login_vbox.pack_start (login_vbox_title, false, true, 0);
913 login_vbox.pack_start (login_vbox_alignment, false, true, 0);
915 var services_vbox_title = new Label (_("Services:"));
916 label_make_bold (services_vbox_title);
917 services_vbox_title.set_alignment (0, (float) 0.5);
918 var services_vbox_alignment = new Alignment (0, 0, 0, 0);
919 services_vbox_alignment.set_padding (0, 0, 12, 0);
920 this.services_internal_vbox = new VBox (true, 6);
921 services_vbox_alignment.add (services_internal_vbox);
922 var services_vbox = new VBox (false, 6);
923 services_vbox.pack_start (services_vbox_title, false, true, 0);
924 services_vbox.pack_start (services_vbox_alignment, false, true, 0);
926 this.vbox_right = new VBox (false, 18);
927 vbox_right.pack_start (login_vbox, false, true, 0);
928 vbox_right.pack_start (services_vbox, false, true, 0);
930 var hbox = new HBox (false, 12);
931 hbox.pack_start (vbox_left, true, true, 0);
932 hbox.pack_start (vbox_right, false, false, 0);
934 var main_vbox = new VBox (false, 0);
935 main_vbox.set_border_width (12);
938 // hide the File | Quit menu item which is now on the Mac Menu
939 Gtk.Widget quit_item = this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
942 Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
943 osxApp.set_menu_bar(menushell);
944 osxApp.set_use_quartz_accelerators(true);
945 osxApp.sync_menu_bar();
948 var menubar = this.ui_manager.get_widget ("/MenuBar");
949 main_vbox.pack_start (menubar, false, false, 0);
951 main_vbox.pack_start (hbox, true, true, 0);
953 main_vbox.show_all();
954 this.vbox_right.hide ();
957 private void set_atk_name_description (Widget widget, string name, string description)
959 var atk_widget = widget.get_accessible ();
961 atk_widget.set_name (name);
962 atk_widget.set_description (description);
965 private void connect_signals()
967 this.destroy.connect (Gtk.main_quit);
968 this.identities_manager.card_list_changed.connect(this.on_card_list_changed);