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)
352 * TODO: We should have a confirmation dialog, but currently it will crash on Mac OS
353 * so for now we will install silently
355 var ret = Gtk.ResponseType.YES;
357 public OSXApplication osxApp;
359 var dialog = new Gtk.MessageDialog (this,
360 Gtk.DialogFlags.DESTROY_WITH_PARENT,
361 Gtk.MessageType.QUESTION,
362 Gtk.ButtonsType.YES_NO,
363 _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
364 id_card.display_name);
366 var ret = dialog.run ();
370 if (ret == Gtk.ResponseType.YES) {
371 id_card.set_data ("pixbuf", find_icon ("avatar-default", 48));
372 this.insert_id_card (id_card);
379 private void add_identity_manual_cb ()
381 var dialog = new AddIdentityDialog ();
382 var result = dialog.run ();
385 case ResponseType.OK:
386 insert_id_card (get_id_card_data (dialog));
394 private void remove_id_card_widget (IdCardWidget id_card_widget) {
395 this.custom_vbox.remove_id_card_widget (id_card_widget);
398 private void remove_identity (IdCardWidget id_card_widget)
400 var id_card = id_card_widget.id_card;
401 remove_id_card_widget (id_card_widget);
403 this.identities_manager.remove_card(id_card);
406 private void redraw_id_card_widgets ()
411 var children = this.custom_vbox.get_children ();
412 foreach (var id_card_widget in children)
413 id_card_widget.destroy();
415 if (filter.get_iter_first (out iter))
420 Columns.IDCARD_COL, out id_card);
422 add_id_card_widget (id_card);
424 while (filter.iter_next (ref iter));
428 private void remove_identity_cb (IdCardWidget id_card_widget)
430 var id_card = id_card_widget.id_card;
432 var dialog = new MessageDialog (this,
433 DialogFlags.DESTROY_WITH_PARENT,
434 MessageType.QUESTION,
435 Gtk.ButtonsType.YES_NO,
436 _("Are you sure you want to delete %s ID Card?"), id_card.issuer);
437 var result = dialog.run ();
439 case ResponseType.YES:
440 remove_identity (id_card_widget);
448 public void select_identity (IdentityRequest request)
450 IdCard identity = null;
452 this.request_queue.push_tail (request);
454 if (custom_vbox.current_idcard != null &&
455 custom_vbox.current_idcard.send_button != null)
456 custom_vbox.current_idcard.send_button.set_sensitive (true);
458 if (request.select_default)
460 identity = default_id_card;
463 if (identity == null)
465 bool has_nai = request.nai != null && request.nai != "";
466 bool has_srv = request.service != null && request.service != "";
467 bool confirm = false;
468 IdCard nai_provided = null;
470 foreach (IdCard id in identities_manager.get_card_list())
472 /* If NAI matches we add id card to the candidate list */
473 if (has_nai && request.nai == id.nai)
480 /* If any service matches we add id card to the candidate list */
483 foreach (string srv in id.services)
485 if (request.service == srv)
494 /* If more than one candidate we dissasociate service from all ids */
495 if (has_srv && candidates.length() > 1)
497 foreach (IdCard id in candidates)
500 SList<string> services_list = null;
501 bool has_service = false;
503 foreach (string srv in id.services)
505 if (srv == request.service)
510 services_list.append (srv);
516 if (services_list.length () == 0)
522 string[] services = new string[services_list.length ()];
523 foreach (string srv in services_list)
529 id.services = services;
533 // identities_manager.store_id_cards ();
535 /* If there are no candidates we use the service matching rules */
536 if (candidates.length () == 0)
538 foreach (IdCard id in identities_manager.get_card_list())
540 foreach (Rule rule in id.rules)
542 if (!match_service_pattern (request.service, rule.pattern))
545 candidates.append (id);
547 if (rule.always_confirm == "true")
553 if (candidates.length () > 1)
555 if (has_nai && nai_provided != null)
557 identity = nai_provided;
564 identity = candidates.nth_data (0);
566 /* TODO: If candidate list empty return fail */
571 redraw_id_card_widgets ();
576 // Send back the identity (we can't directly run the
577 // callback because we may be being called from a 'yield')
578 Idle.add (() => { send_identity_cb (identity); return false; });
582 private bool match_service_pattern (string service, string pattern)
584 var pspec = new PatternSpec (pattern);
585 return pspec.match_string (service);
588 public void send_identity_cb (IdCard identity)
590 return_if_fail (request_queue.length > 0);
592 var request = this.request_queue.pop_head ();
593 bool reset_password = false;
595 if (request.service != null && request.service != "")
597 bool duplicate_service = false;
599 foreach (string service in identity.services)
601 if (service == request.service)
602 duplicate_service = true;
604 if (duplicate_service == false)
606 string[] services = new string[identity.services.length + 1];
608 for (int i = 0; i < identity.services.length; i++)
609 services[i] = identity.services[i];
611 services[identity.services.length] = request.service;
612 identity.services = services;
614 identities_manager.update_card (identity);
618 if (identity.password == null)
620 var dialog = new AddPasswordDialog ();
621 var result = dialog.run ();
624 case ResponseType.OK:
625 identity.password = dialog.password;
626 reset_password = ! dialog.remember;
636 if (this.request_queue.is_empty())
639 if (identity != null)
640 this.default_id_card = identity;
642 request.return_identity (identity);
645 identity.password = null;
650 private void label_make_bold (Label label)
652 var font_desc = new Pango.FontDescription ();
654 font_desc.set_weight (Pango.Weight.BOLD);
656 /* This will only affect the weight of the font, the rest is
657 * from the current state of the widget, which comes from the
658 * theme or user prefs, since the font desc only has the
659 * weight flag turned on.
661 label.modify_font (font_desc);
664 private void fill_services_vbox (IdCard id_card)
667 var n_columns = id_card.services.length;
669 var services_table = new Table (n_columns, 2, false);
670 services_table.set_col_spacings (10);
671 services_table.set_row_spacings (10);
672 this.services_internal_vbox.add (services_table);
674 service_button_map.remove_all ();
676 foreach (string service in id_card.services)
678 var label = new Label (service);
679 label.set_alignment (0, (float) 0.5);
681 var remove_button = new Button.from_stock (Stock.REMOVE);
683 var remove_button = new Button.from_stock (STOCK_REMOVE);
687 service_button_map.insert (remove_button, service);
689 remove_button.clicked.connect ((remove_button) =>
691 var dialog = new Gtk.MessageDialog (this,
692 Gtk.DialogFlags.DESTROY_WITH_PARENT,
693 Gtk.MessageType.QUESTION,
694 Gtk.ButtonsType.YES_NO,
695 _("Are you sure you want to stop '%s' ID Card from being used with %s?"),
696 custom_vbox.current_idcard.id_card.display_name,
698 var ret = dialog.run();
701 if (ret == Gtk.ResponseType.YES)
703 IdCard idcard = custom_vbox.current_idcard.id_card;
704 var candidate = service_button_map.lookup (remove_button);
706 SList<string> services = new SList<string>();
708 foreach (string srv in idcard.services)
710 if (srv == candidate)
712 services.append (srv);
715 idcard.services = new string[services.length()];
716 for (int j=0; j<idcard.services.length; j++)
718 idcard.services[j] = services.nth_data(j);
721 var children = services_internal_vbox.get_children ();
722 foreach (var hbox in children)
725 fill_services_vbox (idcard);
726 custom_vbox.current_idcard.update_id_card_label ();
730 services_table.attach_defaults (label, 0, 1, i, i+1);
731 services_table.attach_defaults (remove_button, 1, 2, i, i+1);
734 this.services_internal_vbox.show_all ();
737 private void on_about_action ()
740 "Javier Jardón <jjardon@codethink.co.uk>",
741 "Sam Thursfield <samthursfield@codethink.co.uk>",
742 "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
746 string copyright = "Copyright 2011 JANET";
748 const string license =
750 Copyright (c) 2011, JANET(UK)
753 Redistribution and use in source and binary forms, with or without
754 modification, are permitted provided that the following conditions
757 1. Redistributions of source code must retain the above copyright
758 notice, this list of conditions and the following disclaimer.
760 2. Redistributions in binary form must reproduce the above copyright
761 notice, this list of conditions and the following disclaimer in the
762 documentation and/or other materials provided with the distribution.
764 3. Neither the name of JANET(UK) nor the names of its contributors
765 may be used to endorse or promote products derived from this software
766 without specific prior written permission.
768 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
769 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
770 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
771 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
772 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
773 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
774 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
775 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
776 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
777 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
781 Gtk.show_about_dialog (this,
782 "comments", _("Moonshot project UI"),
783 "copyright", copyright,
784 "website", Config.PACKAGE_URL,
785 "version", Config.PACKAGE_VERSION,
787 "website-label", _("Visit the Moonshot project web site"),
789 "translator-credits", _("translator-credits"),
794 private Gtk.ActionEntry[] create_actions() {
795 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
797 Gtk.ActionEntry filemenu = { "FileMenuAction",
802 Gtk.ActionEntry add = { "AddIdCardAction",
810 N_("Add a new ID Card"),
811 add_identity_manual_cb };
813 Gtk.ActionEntry quit = { "QuitAction",
821 N_("Quit the application"),
825 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
830 Gtk.ActionEntry about = { "AboutAction",
838 N_("About this application"),
846 private void create_ui_manager ()
848 Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
849 action_group.add_actions (create_actions (), this);
850 ui_manager.insert_action_group (action_group, 0);
853 ui_manager.add_ui_from_string (layout, -1);
857 stderr.printf ("%s\n", e.message);
859 ui_manager.ensure_update ();
862 private void build_ui()
864 create_ui_manager ();
866 this.search_entry = new Entry();
868 set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
869 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
870 find_icon_sized ("edit-find", Gtk.IconSize.MENU));
871 // find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
872 this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
873 _("Search identity or service"));
874 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
876 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
877 find_icon_sized ("process-stop", Gtk.IconSize.MENU));
878 // find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
879 this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
880 _("Clear the current search"));
881 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
884 this.search_entry.icon_press.connect (search_entry_icon_press_cb);
885 this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
886 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
888 this.custom_vbox = new CustomVBox (this, false, 6);
890 var viewport = new Viewport (null, null);
891 viewport.set_border_width (6);
892 viewport.set_shadow_type (ShadowType.NONE);
893 viewport.add (custom_vbox);
894 var scroll = new ScrolledWindow (null, null);
895 scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
896 scroll.set_shadow_type (ShadowType.IN);
897 scroll.add_with_viewport (viewport);
899 var vbox_left = new VBox (false, 0);
900 vbox_left.pack_start (search_entry, false, false, 6);
901 vbox_left.pack_start (scroll, true, true, 0);
902 vbox_left.set_size_request (WINDOW_WIDTH, 0);
904 var login_vbox_title = new Label (_("Login: "));
905 label_make_bold (login_vbox_title);
906 login_vbox_title.set_alignment (0, (float) 0.5);
907 var username_label = new Label (_("Username:"));
908 username_label.set_alignment (1, (float) 0.5);
909 this.username_entry = new Entry ();
910 var password_label = new Label (_("Password:"));
911 password_label.set_alignment (1, (float) 0.5);
912 this.password_entry = new Entry ();
913 password_entry.set_invisible_char ('*');
914 password_entry.set_visibility (false);
915 var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
916 var login_table = new Table (3, 3, false);
917 login_table.set_col_spacings (10);
918 login_table.set_row_spacings (10);
919 login_table.attach_defaults (username_label, 0, 1, 0, 1);
920 login_table.attach_defaults (username_entry, 1, 2, 0, 1);
921 login_table.attach_defaults (password_label, 0, 1, 1, 2);
922 login_table.attach_defaults (password_entry, 1, 2, 1, 2);
923 login_table.attach_defaults (remember_checkbutton, 1, 2, 2, 3);
924 var login_vbox_alignment = new Alignment (0, 0, 0, 0);
925 login_vbox_alignment.set_padding (0, 0, 12, 0);
926 login_vbox_alignment.add (login_table);
927 var login_vbox = new VBox (false, 6);
928 login_vbox.pack_start (login_vbox_title, false, true, 0);
929 login_vbox.pack_start (login_vbox_alignment, false, true, 0);
931 var services_vbox_title = new Label (_("Services:"));
932 label_make_bold (services_vbox_title);
933 services_vbox_title.set_alignment (0, (float) 0.5);
934 var services_vbox_alignment = new Alignment (0, 0, 0, 0);
935 services_vbox_alignment.set_padding (0, 0, 12, 0);
936 this.services_internal_vbox = new VBox (true, 6);
937 services_vbox_alignment.add (services_internal_vbox);
938 var services_vbox = new VBox (false, 6);
939 services_vbox.pack_start (services_vbox_title, false, true, 0);
940 services_vbox.pack_start (services_vbox_alignment, false, true, 0);
942 this.vbox_right = new VBox (false, 18);
943 vbox_right.pack_start (login_vbox, false, true, 0);
944 vbox_right.pack_start (services_vbox, false, true, 0);
946 var hbox = new HBox (false, 12);
947 hbox.pack_start (vbox_left, true, true, 0);
948 hbox.pack_start (vbox_right, false, false, 0);
950 var main_vbox = new VBox (false, 0);
951 main_vbox.set_border_width (12);
954 // hide the File | Quit menu item which is now on the Mac Menu
955 Gtk.Widget quit_item = this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
958 Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
959 osxApp.set_menu_bar(menushell);
960 osxApp.set_use_quartz_accelerators(true);
961 osxApp.sync_menu_bar();
964 var menubar = this.ui_manager.get_widget ("/MenuBar");
965 main_vbox.pack_start (menubar, false, false, 0);
967 main_vbox.pack_start (hbox, true, true, 0);
969 main_vbox.show_all();
970 this.vbox_right.hide ();
973 private void set_atk_name_description (Widget widget, string name, string description)
975 var atk_widget = widget.get_accessible ();
977 atk_widget.set_name (name);
978 atk_widget.set_description (description);
981 private void connect_signals()
983 this.destroy.connect (Gtk.main_quit);
984 this.identities_manager.card_list_changed.connect(this.on_card_list_changed);