4 class IdentityManagerView : Window {
5 private const int WINDOW_WIDTH = 400;
6 private const int WINDOW_HEIGHT = 500;
7 protected IdentityManagerApp parent_app;
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 IdentityManagerModel identities_manager;
21 private SList<IdCard> candidates;
23 private IdCard default_id_card;
24 public GLib.Queue<IdentityRequest> request_queue;
26 private HashTable<Gtk.Button, string> service_button_map;
38 private const string layout =
39 "<menubar name='MenuBar'>" +
40 " <menu name='FileMenu' action='FileMenuAction'>" +
41 " <menuitem name='AddIdCard' action='AddIdCardAction' />" +
43 " <menuitem name='Quit' action='QuitAction' />" +
46 " <menu name='HelpMenu' action='HelpMenuAction'>" +
47 " <menuitem name='About' action='AboutAction' />" +
51 public IdentityManagerView(IdentityManagerApp app) {
53 identities_manager = parent_app.model;
54 request_queue = new GLib.Queue<IdentityRequest>();
55 service_button_map = new HashTable<Gtk.Button, string> (direct_hash, direct_equal);
56 this.title = "Moonshoot";
57 this.set_position (WindowPosition.CENTER);
58 set_default_size (WINDOW_WIDTH, WINDOW_HEIGHT);
65 public void on_card_list_changed () {
69 public void add_candidate (IdCard idcard)
71 candidates.append (idcard);
74 private bool visible_func (TreeModel model, TreeIter iter)
79 Columns.IDCARD_COL, out id_card);
84 if (candidates != null)
86 bool is_candidate = false;
87 foreach (IdCard candidate in candidates)
89 if (candidate == id_card)
96 string entry_text = search_entry.get_text ();
97 if (entry_text == null || entry_text == "")
102 foreach (string search_text in entry_text.split(" "))
104 if (search_text == "")
108 string search_text_casefold = search_text.casefold ();
110 if (id_card.issuer != null)
112 string issuer_casefold = id_card.issuer;
114 if (issuer_casefold.contains (search_text_casefold))
118 if (id_card.display_name != null)
120 string display_name_casefold = id_card.display_name.casefold ();
122 if (display_name_casefold.contains (search_text_casefold))
126 if (id_card.services.length > 0)
128 foreach (string service in id_card.services)
130 string service_casefold = service.casefold ();
132 if (service_casefold.contains (search_text_casefold))
140 private void setup_list_model ()
142 this.listmodel = new ListStore (Columns.N_COLUMNS, typeof (IdCard),
147 this.filter = new TreeModelFilter (listmodel, null);
149 filter.set_visible_func (visible_func);
152 private void search_entry_icon_press_cb (EntryIconPosition pos, Gdk.Event event)
154 if (pos == EntryIconPosition.PRIMARY)
156 print ("Search entry icon pressed\n");
160 this.search_entry.set_text ("");
164 private void search_entry_text_changed_cb ()
166 this.filter.refilter ();
167 redraw_id_card_widgets ();
169 var has_text = this.search_entry.get_text_length () > 0;
170 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, has_text);
171 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, has_text);
173 this.vbox_right.set_visible (false);
176 private bool search_entry_key_press_event_cb (Gdk.EventKey e)
178 if(Gdk.keyval_name(e.keyval) == "Escape")
179 this.search_entry.set_text("");
181 // Continue processing this event, since the
182 // text entry functionality needs to see it too.
186 private void load_id_cards () {
187 var children = this.custom_vbox.get_children ();
188 foreach (var id_card_widget in children) {
189 remove_id_card_widget((IdCardWidget)id_card_widget);
192 this.default_id_card = null;
193 LinkedList<IdCard> card_list = identities_manager.get_card_list() ;
194 if (card_list == null) {
198 foreach (IdCard id_card in card_list) {
199 add_id_card_data (id_card);
200 add_id_card_widget (id_card);
203 if (card_list.size > 0){
204 this.default_id_card = card_list.first();
208 private void fill_details (IdCardWidget id_card_widget)
210 var id_card = id_card_widget.id_card;
211 this.username_entry.set_text (id_card.username);
212 this.password_entry.set_text (id_card.password ?? "");
214 var children = this.services_internal_vbox.get_children ();
215 foreach (var hbox in children)
217 fill_services_vbox (id_card_widget.id_card);
218 // identities_manager.store_id_cards();
221 private void show_details (IdCard id_card)
223 this.vbox_right.set_visible (!vbox_right.get_visible ());
225 if (this.vbox_right.get_visible () == false)
227 this.resize (WINDOW_WIDTH, WINDOW_HEIGHT);
231 private void details_identity_cb (IdCardWidget id_card_widget)
233 fill_details (id_card_widget);
234 show_details (id_card_widget.id_card);
237 private IdCard get_id_card_data (AddIdentityDialog dialog)
239 var id_card = new IdCard ();
241 id_card.display_name = dialog.display_name;
242 id_card.issuer = dialog.issuer;
243 if (id_card.issuer == "")
244 id_card.issuer = "Issuer";
245 id_card.username = dialog.username;
246 id_card.password = dialog.password;
247 id_card.services = {};
248 id_card.set_data("pixbuf", find_icon ("avatar-default", 48));
253 private void add_id_card_data (IdCard id_card)
257 this.listmodel->append (out iter);
258 pixbuf = id_card.get_data("pixbuf");
259 listmodel->set (iter,
260 Columns.IDCARD_COL, id_card,
261 Columns.LOGO_COL, pixbuf,
262 Columns.ISSUER_COL, id_card.issuer,
263 Columns.USERNAME_COL, id_card.username,
264 Columns.PASSWORD_COL, id_card.password);
267 private void remove_id_card_data (IdCard id_card)
272 if (listmodel->get_iter_first (out iter))
276 listmodel->get (iter,
277 Columns.ISSUER_COL, out issuer);
279 if (id_card.issuer == issuer)
281 listmodel->remove (iter);
285 while (listmodel->iter_next (ref iter));
289 private void add_id_card_widget (IdCard id_card)
291 var id_card_widget = new IdCardWidget (id_card);
292 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.get_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.add_card(id_card);
339 public bool add_identity (IdCard id_card)
341 /* TODO: Check if display name already exists */
343 var dialog = new Gtk.MessageDialog (this,
344 Gtk.DialogFlags.DESTROY_WITH_PARENT,
345 Gtk.MessageType.QUESTION,
346 Gtk.ButtonsType.YES_NO,
347 _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
348 id_card.display_name);
351 var ret = dialog.run ();
354 if (ret == Gtk.ResponseType.YES) {
355 id_card.set_data ("pixbuf", find_icon ("avatar-default", 48));
356 this.insert_id_card (id_card);
363 private void add_identity_manual_cb ()
365 var dialog = new AddIdentityDialog ();
366 var result = dialog.run ();
369 case ResponseType.OK:
370 insert_id_card (get_id_card_data (dialog));
378 private void remove_id_card_widget (IdCardWidget id_card_widget) {
379 this.custom_vbox.remove_id_card_widget (id_card_widget);
382 private void remove_identity (IdCardWidget id_card_widget)
384 var id_card = id_card_widget.id_card;
385 remove_id_card_widget (id_card_widget);
387 this.identities_manager.remove_card(id_card);
390 private void redraw_id_card_widgets ()
395 var children = this.custom_vbox.get_children ();
396 foreach (var id_card_widget in children)
397 id_card_widget.destroy();
399 if (filter.get_iter_first (out iter))
404 Columns.IDCARD_COL, out id_card);
406 add_id_card_widget (id_card);
408 while (filter.iter_next (ref iter));
412 private void remove_identity_cb (IdCardWidget id_card_widget)
414 var id_card = id_card_widget.id_card;
416 var dialog = new MessageDialog (null,
417 DialogFlags.DESTROY_WITH_PARENT,
419 Gtk.ButtonsType.YES_NO,
420 _("Are you sure you want to delete %s ID Card?"), id_card.issuer);
421 var result = dialog.run ();
423 case ResponseType.YES:
424 remove_identity (id_card_widget);
432 public void select_identity (IdentityRequest request)
434 IdCard identity = null;
436 this.request_queue.push_tail (request);
438 if (custom_vbox.current_idcard != null &&
439 custom_vbox.current_idcard.send_button != null)
440 custom_vbox.current_idcard.send_button.set_sensitive (true);
442 if (request.select_default)
444 identity = default_id_card;
447 if (identity == null)
449 bool has_nai = request.nai != null && request.nai != "";
450 bool has_srv = request.service != null && request.service != "";
451 bool confirm = false;
452 IdCard nai_provided = null;
454 foreach (IdCard id in identities_manager.get_card_list())
456 /* If NAI matches we add id card to the candidate list */
457 if (has_nai && request.nai == id.nai)
464 /* If any service matches we add id card to the candidate list */
467 foreach (string srv in id.services)
469 if (request.service == srv)
478 /* If more than one candidate we dissasociate service from all ids */
479 if (has_srv && candidates.length() > 1)
481 foreach (IdCard id in candidates)
484 SList<string> services_list = null;
485 bool has_service = false;
487 foreach (string srv in id.services)
489 if (srv == request.service)
494 services_list.append (srv);
500 if (services_list.length () == 0)
506 string[] services = new string[services_list.length ()];
507 foreach (string srv in services_list)
513 id.services = services;
517 // identities_manager.store_id_cards ();
519 /* If there are no candidates we use the service matching rules */
520 if (candidates.length () == 0)
522 foreach (IdCard id in identities_manager.get_card_list())
524 foreach (Rule rule in id.rules)
526 if (!match_service_pattern (request.service, rule.pattern))
529 candidates.append (id);
531 if (rule.always_confirm == "true")
537 if (candidates.length () > 1)
539 if (has_nai && nai_provided != null)
541 identity = nai_provided;
548 identity = candidates.nth_data (0);
550 /* TODO: If candidate list empty return fail */
555 redraw_id_card_widgets ();
560 // Send back the identity (we can't directly run the
561 // callback because we may be being called from a 'yield')
562 Idle.add (() => { send_identity_cb (identity); return false; });
566 private bool match_service_pattern (string service, string pattern)
568 var pspec = new PatternSpec (pattern);
569 return pspec.match_string (service);
572 public void send_identity_cb (IdCard identity)
574 return_if_fail (request_queue.length > 0);
576 var request = this.request_queue.pop_head ();
577 bool reset_password = false;
579 if (request.service != null && request.service != "")
581 string[] services = new string[identity.services.length + 1];
583 for (int i = 0; i < identity.services.length; i++)
584 services[i] = identity.services[i];
586 services[identity.services.length] = request.service;
588 identity.services = services;
590 // identities_manager.store_id_cards();
593 if (identity.password == null)
595 var dialog = new AddPasswordDialog ();
596 var result = dialog.run ();
599 case ResponseType.OK:
600 identity.password = dialog.password;
601 reset_password = ! dialog.remember;
611 if (this.request_queue.is_empty())
614 if (identity != null)
615 this.default_id_card = identity;
617 request.return_identity (identity);
620 identity.password = null;
625 private void label_make_bold (Label label)
627 var font_desc = new Pango.FontDescription ();
629 font_desc.set_weight (Pango.Weight.BOLD);
631 /* This will only affect the weight of the font, the rest is
632 * from the current state of the widget, which comes from the
633 * theme or user prefs, since the font desc only has the
634 * weight flag turned on.
636 label.modify_font (font_desc);
639 private void fill_services_vbox (IdCard id_card)
642 var n_columns = id_card.services.length;
644 var services_table = new Table (n_columns, 2, false);
645 services_table.set_col_spacings (10);
646 services_table.set_row_spacings (10);
647 this.services_internal_vbox.add (services_table);
649 service_button_map.remove_all ();
651 foreach (string service in id_card.services)
653 var label = new Label (service);
654 label.set_alignment (0, (float) 0.5);
656 var remove_button = new Button.from_stock (Stock.REMOVE);
658 var remove_button = new Button.from_stock (STOCK_REMOVE);
662 service_button_map.insert (remove_button, service);
664 remove_button.clicked.connect ((remove_button) =>
666 var dialog = new Gtk.MessageDialog (this,
667 Gtk.DialogFlags.DESTROY_WITH_PARENT,
668 Gtk.MessageType.QUESTION,
669 Gtk.ButtonsType.YES_NO,
670 _("Are you sure you want to stop '%s' ID Card to use %s?"),
671 custom_vbox.current_idcard.id_card.display_name);
672 var ret = dialog.run();
675 if (ret == Gtk.ResponseType.YES)
677 IdCard idcard = custom_vbox.current_idcard.id_card;
678 var candidate = service_button_map.lookup (remove_button);
680 SList<string> services = new SList<string>();
682 foreach (string srv in idcard.services)
684 if (srv == candidate)
686 services.append (srv);
689 idcard.services = new string[services.length()];
690 for (int j=0; j<idcard.services.length; j++)
692 idcard.services[j] = services.nth_data(j);
695 var children = services_internal_vbox.get_children ();
696 foreach (var hbox in children)
699 fill_services_vbox (idcard);
700 custom_vbox.current_idcard.update_id_card_label ();
704 services_table.attach_defaults (label, 0, 1, i, i+1);
705 services_table.attach_defaults (remove_button, 1, 2, i, i+1);
708 this.services_internal_vbox.show_all ();
711 private void on_about_action ()
714 "Javier Jardón <jjardon@codethink.co.uk>",
715 "Sam Thursfield <samthursfield@codethink.co.uk>",
716 "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
720 string copyright = "Copyright 2011 JANET";
724 Copyright (c) 2011, JANET(UK)
727 Redistribution and use in source and binary forms, with or without
728 modification, are permitted provided that the following conditions
731 1. Redistributions of source code must retain the above copyright
732 notice, this list of conditions and the following disclaimer.
734 2. Redistributions in binary form must reproduce the above copyright
735 notice, this list of conditions and the following disclaimer in the
736 documentation and/or other materials provided with the distribution.
738 3. Neither the name of JANET(UK) nor the names of its contributors
739 may be used to endorse or promote products derived from this software
740 without specific prior written permission.
742 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
743 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
744 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
745 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
746 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
747 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
748 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
749 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
750 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
751 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
755 Gtk.show_about_dialog (this,
756 "comments", _("Moonshot project UI"),
757 "copyright", copyright,
758 "website", "http://www.project-moonshot.org/",
760 "website-label", _("Visit the Moonshot project web site"),
762 "translator-credits", _("translator-credits"),
767 private Gtk.ActionEntry[] create_actions() {
768 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
770 Gtk.ActionEntry filemenu = { "FileMenuAction",
775 Gtk.ActionEntry add = { "AddIdCardAction",
783 N_("Add a new ID Card"),
784 add_identity_manual_cb };
786 Gtk.ActionEntry quit = { "QuitAction",
794 N_("Quit the application"),
798 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
803 Gtk.ActionEntry about = { "AboutAction",
811 N_("About this application"),
819 private void create_ui_manager ()
821 Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
822 action_group.add_actions (create_actions (), this);
823 ui_manager.insert_action_group (action_group, 0);
826 ui_manager.add_ui_from_string (layout, -1);
830 stderr.printf ("%s\n", e.message);
832 ui_manager.ensure_update ();
835 private void build_ui()
837 create_ui_manager ();
839 this.search_entry = new Entry();
841 set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
842 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
843 find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
844 this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
845 _("Search identity or service"));
846 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
848 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
849 find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
850 this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
851 _("Clear the current search"));
852 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
855 this.search_entry.icon_press.connect (search_entry_icon_press_cb);
856 this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
857 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
859 this.custom_vbox = new CustomVBox (this, false, 6);
861 var viewport = new Viewport (null, null);
862 viewport.set_border_width (6);
863 viewport.set_shadow_type (ShadowType.NONE);
864 viewport.add (custom_vbox);
865 var scroll = new ScrolledWindow (null, null);
866 scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
867 scroll.set_shadow_type (ShadowType.IN);
868 scroll.add_with_viewport (viewport);
870 var vbox_left = new VBox (false, 0);
871 vbox_left.pack_start (search_entry, false, false, 6);
872 vbox_left.pack_start (scroll, true, true, 0);
873 vbox_left.set_size_request (WINDOW_WIDTH, 0);
875 var login_vbox_title = new Label (_("Login: "));
876 label_make_bold (login_vbox_title);
877 login_vbox_title.set_alignment (0, (float) 0.5);
878 var username_label = new Label (_("Username:"));
879 username_label.set_alignment (1, (float) 0.5);
880 this.username_entry = new Entry ();
881 var password_label = new Label (_("Password:"));
882 password_label.set_alignment (1, (float) 0.5);
883 this.password_entry = new Entry ();
884 password_entry.set_invisible_char ('*');
885 password_entry.set_visibility (false);
886 var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
887 var login_table = new Table (3, 3, false);
888 login_table.set_col_spacings (10);
889 login_table.set_row_spacings (10);
890 login_table.attach_defaults (username_label, 0, 1, 0, 1);
891 login_table.attach_defaults (username_entry, 1, 2, 0, 1);
892 login_table.attach_defaults (password_label, 0, 1, 1, 2);
893 login_table.attach_defaults (password_entry, 1, 2, 1, 2);
894 login_table.attach_defaults (remember_checkbutton, 1, 2, 2, 3);
895 var login_vbox_alignment = new Alignment (0, 0, 0, 0);
896 login_vbox_alignment.set_padding (0, 0, 12, 0);
897 login_vbox_alignment.add (login_table);
898 var login_vbox = new VBox (false, 6);
899 login_vbox.pack_start (login_vbox_title, false, true, 0);
900 login_vbox.pack_start (login_vbox_alignment, false, true, 0);
902 var services_vbox_title = new Label (_("Services:"));
903 label_make_bold (services_vbox_title);
904 services_vbox_title.set_alignment (0, (float) 0.5);
905 var services_vbox_alignment = new Alignment (0, 0, 0, 0);
906 services_vbox_alignment.set_padding (0, 0, 12, 0);
907 this.services_internal_vbox = new VBox (true, 6);
908 services_vbox_alignment.add (services_internal_vbox);
909 var services_vbox = new VBox (false, 6);
910 services_vbox.pack_start (services_vbox_title, false, true, 0);
911 services_vbox.pack_start (services_vbox_alignment, false, true, 0);
913 this.vbox_right = new VBox (false, 18);
914 vbox_right.pack_start (login_vbox, false, true, 0);
915 vbox_right.pack_start (services_vbox, false, true, 0);
917 var hbox = new HBox (false, 12);
918 hbox.pack_start (vbox_left, true, true, 0);
919 hbox.pack_start (vbox_right, false, false, 0);
921 var main_vbox = new VBox (false, 0);
922 main_vbox.set_border_width (12);
923 var menubar = this.ui_manager.get_widget ("/MenuBar");
924 main_vbox.pack_start (menubar, false, false, 0);
925 main_vbox.pack_start (hbox, true, true, 0);
928 main_vbox.show_all();
929 this.vbox_right.hide ();
932 private void set_atk_name_description (Widget widget, string name, string description)
934 var atk_widget = widget.get_accessible ();
936 atk_widget.set_name (name);
937 atk_widget.set_description (description);
940 private void connect_signals()
942 this.destroy.connect (Gtk.main_quit);
943 this.identities_manager.card_list_changed.connect(this.on_card_list_changed);