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 bool duplicate_service = false;
592 foreach (string service in identity.services)
594 if (service == request.service)
595 duplicate_service = true;
597 if (duplicate_service == false)
599 string[] services = new string[identity.services.length + 1];
601 for (int i = 0; i < identity.services.length; i++)
602 services[i] = identity.services[i];
604 services[identity.services.length] = request.service;
605 identity.services = services;
607 identities_manager.update_card (identity);
611 if (identity.password == null)
613 var dialog = new AddPasswordDialog ();
614 var result = dialog.run ();
617 case ResponseType.OK:
618 identity.password = dialog.password;
619 reset_password = ! dialog.remember;
629 if (this.request_queue.is_empty())
632 if (identity != null)
633 this.default_id_card = identity;
635 request.return_identity (identity);
638 identity.password = null;
643 private void label_make_bold (Label label)
645 var font_desc = new Pango.FontDescription ();
647 font_desc.set_weight (Pango.Weight.BOLD);
649 /* This will only affect the weight of the font, the rest is
650 * from the current state of the widget, which comes from the
651 * theme or user prefs, since the font desc only has the
652 * weight flag turned on.
654 label.modify_font (font_desc);
657 private void fill_services_vbox (IdCard id_card)
660 var n_columns = id_card.services.length;
662 var services_table = new Table (n_columns, 2, false);
663 services_table.set_col_spacings (10);
664 services_table.set_row_spacings (10);
665 this.services_internal_vbox.add (services_table);
667 service_button_map.remove_all ();
669 foreach (string service in id_card.services)
671 var label = new Label (service);
672 label.set_alignment (0, (float) 0.5);
674 var remove_button = new Button.from_stock (Stock.REMOVE);
676 var remove_button = new Button.from_stock (STOCK_REMOVE);
680 service_button_map.insert (remove_button, service);
682 remove_button.clicked.connect ((remove_button) =>
684 var dialog = new Gtk.MessageDialog (this,
685 Gtk.DialogFlags.DESTROY_WITH_PARENT,
686 Gtk.MessageType.QUESTION,
687 Gtk.ButtonsType.YES_NO,
688 _("Are you sure you want to stop '%s' ID Card from being used with %s?"),
689 custom_vbox.current_idcard.id_card.display_name,
691 var ret = dialog.run();
694 if (ret == Gtk.ResponseType.YES)
696 IdCard idcard = custom_vbox.current_idcard.id_card;
697 var candidate = service_button_map.lookup (remove_button);
699 SList<string> services = new SList<string>();
701 foreach (string srv in idcard.services)
703 if (srv == candidate)
705 services.append (srv);
708 idcard.services = new string[services.length()];
709 for (int j=0; j<idcard.services.length; j++)
711 idcard.services[j] = services.nth_data(j);
714 var children = services_internal_vbox.get_children ();
715 foreach (var hbox in children)
718 fill_services_vbox (idcard);
719 custom_vbox.current_idcard.update_id_card_label ();
723 services_table.attach_defaults (label, 0, 1, i, i+1);
724 services_table.attach_defaults (remove_button, 1, 2, i, i+1);
727 this.services_internal_vbox.show_all ();
730 private void on_about_action ()
733 "Javier Jardón <jjardon@codethink.co.uk>",
734 "Sam Thursfield <samthursfield@codethink.co.uk>",
735 "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
739 string copyright = "Copyright 2011 JANET";
741 const string license =
743 Copyright (c) 2011, JANET(UK)
746 Redistribution and use in source and binary forms, with or without
747 modification, are permitted provided that the following conditions
750 1. Redistributions of source code must retain the above copyright
751 notice, this list of conditions and the following disclaimer.
753 2. Redistributions in binary form must reproduce the above copyright
754 notice, this list of conditions and the following disclaimer in the
755 documentation and/or other materials provided with the distribution.
757 3. Neither the name of JANET(UK) nor the names of its contributors
758 may be used to endorse or promote products derived from this software
759 without specific prior written permission.
761 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
762 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
763 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
764 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
765 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
766 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
767 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
768 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
769 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
770 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
774 Gtk.show_about_dialog (this,
775 "comments", _("Moonshot project UI"),
776 "copyright", copyright,
777 "website", Config.PACKAGE_URL,
778 "version", Config.PACKAGE_VERSION,
780 "website-label", _("Visit the Moonshot project web site"),
782 "translator-credits", _("translator-credits"),
787 private Gtk.ActionEntry[] create_actions() {
788 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
790 Gtk.ActionEntry filemenu = { "FileMenuAction",
795 Gtk.ActionEntry add = { "AddIdCardAction",
803 N_("Add a new ID Card"),
804 add_identity_manual_cb };
806 Gtk.ActionEntry quit = { "QuitAction",
814 N_("Quit the application"),
818 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
823 Gtk.ActionEntry about = { "AboutAction",
831 N_("About this application"),
839 private void create_ui_manager ()
841 Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
842 action_group.add_actions (create_actions (), this);
843 ui_manager.insert_action_group (action_group, 0);
846 ui_manager.add_ui_from_string (layout, -1);
850 stderr.printf ("%s\n", e.message);
852 ui_manager.ensure_update ();
855 private void build_ui()
857 create_ui_manager ();
859 this.search_entry = new Entry();
861 set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
862 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
863 find_icon_sized ("edit-find", Gtk.IconSize.MENU));
864 // find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
865 this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
866 _("Search identity or service"));
867 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
869 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
870 find_icon_sized ("process-stop", Gtk.IconSize.MENU));
871 // find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
872 this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
873 _("Clear the current search"));
874 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
877 this.search_entry.icon_press.connect (search_entry_icon_press_cb);
878 this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
879 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
881 this.custom_vbox = new CustomVBox (this, false, 6);
883 var viewport = new Viewport (null, null);
884 viewport.set_border_width (6);
885 viewport.set_shadow_type (ShadowType.NONE);
886 viewport.add (custom_vbox);
887 var scroll = new ScrolledWindow (null, null);
888 scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
889 scroll.set_shadow_type (ShadowType.IN);
890 scroll.add_with_viewport (viewport);
892 var vbox_left = new VBox (false, 0);
893 vbox_left.pack_start (search_entry, false, false, 6);
894 vbox_left.pack_start (scroll, true, true, 0);
895 vbox_left.set_size_request (WINDOW_WIDTH, 0);
897 var login_vbox_title = new Label (_("Login: "));
898 label_make_bold (login_vbox_title);
899 login_vbox_title.set_alignment (0, (float) 0.5);
900 var username_label = new Label (_("Username:"));
901 username_label.set_alignment (1, (float) 0.5);
902 this.username_entry = new Entry ();
903 var password_label = new Label (_("Password:"));
904 password_label.set_alignment (1, (float) 0.5);
905 this.password_entry = new Entry ();
906 password_entry.set_invisible_char ('*');
907 password_entry.set_visibility (false);
908 var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
909 var login_table = new Table (3, 3, false);
910 login_table.set_col_spacings (10);
911 login_table.set_row_spacings (10);
912 login_table.attach_defaults (username_label, 0, 1, 0, 1);
913 login_table.attach_defaults (username_entry, 1, 2, 0, 1);
914 login_table.attach_defaults (password_label, 0, 1, 1, 2);
915 login_table.attach_defaults (password_entry, 1, 2, 1, 2);
916 login_table.attach_defaults (remember_checkbutton, 1, 2, 2, 3);
917 var login_vbox_alignment = new Alignment (0, 0, 0, 0);
918 login_vbox_alignment.set_padding (0, 0, 12, 0);
919 login_vbox_alignment.add (login_table);
920 var login_vbox = new VBox (false, 6);
921 login_vbox.pack_start (login_vbox_title, false, true, 0);
922 login_vbox.pack_start (login_vbox_alignment, false, true, 0);
924 var services_vbox_title = new Label (_("Services:"));
925 label_make_bold (services_vbox_title);
926 services_vbox_title.set_alignment (0, (float) 0.5);
927 var services_vbox_alignment = new Alignment (0, 0, 0, 0);
928 services_vbox_alignment.set_padding (0, 0, 12, 0);
929 this.services_internal_vbox = new VBox (true, 6);
930 services_vbox_alignment.add (services_internal_vbox);
931 var services_vbox = new VBox (false, 6);
932 services_vbox.pack_start (services_vbox_title, false, true, 0);
933 services_vbox.pack_start (services_vbox_alignment, false, true, 0);
935 this.vbox_right = new VBox (false, 18);
936 vbox_right.pack_start (login_vbox, false, true, 0);
937 vbox_right.pack_start (services_vbox, false, true, 0);
939 var hbox = new HBox (false, 12);
940 hbox.pack_start (vbox_left, true, true, 0);
941 hbox.pack_start (vbox_right, false, false, 0);
943 var main_vbox = new VBox (false, 0);
944 main_vbox.set_border_width (12);
947 // hide the File | Quit menu item which is now on the Mac Menu
948 Gtk.Widget quit_item = this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
951 Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
952 osxApp.set_menu_bar(menushell);
953 osxApp.set_use_quartz_accelerators(true);
954 osxApp.sync_menu_bar();
957 var menubar = this.ui_manager.get_widget ("/MenuBar");
958 main_vbox.pack_start (menubar, false, false, 0);
960 main_vbox.pack_start (hbox, true, true, 0);
962 main_vbox.show_all();
963 this.vbox_right.hide ();
966 private void set_atk_name_description (Widget widget, string name, string description)
968 var atk_widget = widget.get_accessible ();
970 atk_widget.set_name (name);
971 atk_widget.set_description (description);
974 private void connect_signals()
976 this.destroy.connect (Gtk.main_quit);
977 this.identities_manager.card_list_changed.connect(this.on_card_list_changed);