4 public class IdentityManagerView : Window {
5 private const int WINDOW_WIDTH = 400;
6 private const int WINDOW_HEIGHT = 500;
7 private const int RIGHT_PANE_WIDTH = 275;
8 protected IdentityManagerApp parent_app;
10 public OSXApplication osxApp;
12 private UIManager ui_manager = new UIManager();
13 private Entry search_entry;
14 private VBox vbox_right;
15 private VBox login_vbox;
16 private VBox services_vbox;
17 private CustomVBox custom_vbox;
18 private VBox services_internal_vbox;
20 private Entry username_entry;
21 private Entry password_entry;
22 private Label prompting_service;
23 private Label no_identity_title;
25 private ListStore* listmodel;
26 private TreeModelFilter filter;
28 public IdentityManagerModel identities_manager;
29 private unowned SList<IdCard> candidates;
31 public GLib.Queue<IdentityRequest> request_queue;
33 private HashTable<Gtk.Button, string> service_button_map;
45 private const string layout =
46 "<menubar name='MenuBar'>" +
47 " <menu name='FileMenu' action='FileMenuAction'>" +
48 " <menuitem name='AddIdCard' action='AddIdCardAction' />" +
50 " <menuitem name='Quit' action='QuitAction' />" +
53 " <menu name='HelpMenu' action='HelpMenuAction'>" +
54 " <menuitem name='About' action='AboutAction' />" +
58 public IdentityManagerView(IdentityManagerApp app) {
61 osxApp = OSXApplication.get_instance();
63 identities_manager = parent_app.model;
64 request_queue = new GLib.Queue<IdentityRequest>();
65 service_button_map = new HashTable<Gtk.Button, string> (direct_hash, direct_equal);
66 this.title = "Moonshot Identity Selector";
67 this.set_position (WindowPosition.CENTER);
68 set_default_size (WINDOW_WIDTH, WINDOW_HEIGHT);
75 public void on_card_list_changed () {
79 private bool visible_func (TreeModel model, TreeIter iter)
84 Columns.IDCARD_COL, out id_card);
89 if (candidates != null)
91 bool is_candidate = false;
92 foreach (IdCard candidate in candidates)
94 if (candidate == id_card)
101 string entry_text = search_entry.get_text ();
102 if (entry_text == null || entry_text == "")
107 foreach (string search_text in entry_text.split(" "))
109 if (search_text == "")
113 string search_text_casefold = search_text.casefold ();
115 if (id_card.issuer != null)
117 string issuer_casefold = id_card.issuer;
119 if (issuer_casefold.contains (search_text_casefold))
123 if (id_card.display_name != null)
125 string display_name_casefold = id_card.display_name.casefold ();
127 if (display_name_casefold.contains (search_text_casefold))
131 if (id_card.services.length > 0)
133 foreach (string service in id_card.services)
135 string service_casefold = service.casefold ();
137 if (service_casefold.contains (search_text_casefold))
145 private void setup_list_model ()
147 this.listmodel = new ListStore (Columns.N_COLUMNS, typeof (IdCard),
152 this.filter = new TreeModelFilter (listmodel, null);
154 filter.set_visible_func (visible_func);
157 private void search_entry_icon_press_cb (EntryIconPosition pos, Gdk.Event event)
159 if (pos == EntryIconPosition.PRIMARY)
161 print ("Search entry icon pressed\n");
165 this.search_entry.set_text ("");
169 private void search_entry_text_changed_cb ()
171 this.filter.refilter ();
172 redraw_id_card_widgets ();
174 var has_text = this.search_entry.get_text_length () > 0;
175 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, has_text);
176 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, has_text);
178 this.vbox_right.set_visible (false);
181 private bool search_entry_key_press_event_cb (Gdk.EventKey e)
183 if(Gdk.keyval_name(e.keyval) == "Escape")
184 this.search_entry.set_text("");
186 // Continue processing this event, since the
187 // text entry functionality needs to see it too.
191 private void load_id_cards () {
192 var children = this.custom_vbox.get_children ();
193 foreach (var id_card_widget in children) {
194 remove_id_card_widget((IdCardWidget)id_card_widget);
196 this.listmodel->clear();
197 LinkedList<IdCard> card_list = identities_manager.get_card_list() ;
198 if (card_list == null) {
202 foreach (IdCard id_card in card_list) {
203 add_id_card_data (id_card);
204 add_id_card_widget (id_card);
208 private void fill_details (IdCardWidget id_card_widget)
210 var id_card = id_card_widget.id_card;
211 var vr_children = this.vbox_right.get_children();
212 foreach (var vr_child in vr_children)
213 this.vbox_right.remove(vr_child);
214 if (id_card.display_name == IdCard.NO_IDENTITY) {
215 this.vbox_right.pack_start(no_identity_title, false, true, 0);
217 this.username_entry.set_text (id_card.username);
218 this.password_entry.set_text (id_card.password ?? "");
219 this.vbox_right.pack_start(login_vbox, false, true, 0);
221 this.vbox_right.pack_start (services_vbox, false, true, 0);
223 var children = this.services_internal_vbox.get_children ();
224 foreach (var hbox in children)
225 services_internal_vbox.remove(hbox);
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;
358 var dialog = new Gtk.MessageDialog (this,
359 Gtk.DialogFlags.DESTROY_WITH_PARENT,
360 Gtk.MessageType.QUESTION,
361 Gtk.ButtonsType.YES_NO,
362 _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
363 id_card.display_name);
365 var ret = dialog.run ();
369 if (ret == Gtk.ResponseType.YES) {
370 id_card.set_data ("pixbuf", find_icon ("avatar-default", 48));
371 this.insert_id_card (id_card);
378 private void add_identity_manual_cb ()
380 var dialog = new AddIdentityDialog ();
381 var result = dialog.run ();
384 case ResponseType.OK:
385 insert_id_card (get_id_card_data (dialog));
393 private void remove_id_card_widget (IdCardWidget id_card_widget) {
394 this.custom_vbox.remove_id_card_widget (id_card_widget);
397 private void remove_identity (IdCardWidget id_card_widget)
399 var id_card = id_card_widget.id_card;
400 remove_id_card_widget (id_card_widget);
402 this.identities_manager.remove_card(id_card);
405 private void redraw_id_card_widgets ()
410 var children = this.custom_vbox.get_children ();
411 foreach (var id_card_widget in children)
412 remove_id_card_widget((IdCardWidget )id_card_widget); //id_card_widget.destroy();
414 if (filter.get_iter_first (out iter))
419 Columns.IDCARD_COL, out id_card);
421 add_id_card_widget (id_card);
423 while (filter.iter_next (ref iter));
427 private void remove_identity_cb (IdCardWidget id_card_widget)
429 var id_card = id_card_widget.id_card;
431 var dialog = new MessageDialog (this,
432 DialogFlags.DESTROY_WITH_PARENT,
433 MessageType.QUESTION,
434 Gtk.ButtonsType.YES_NO,
435 _("Are you sure you want to delete %s ID Card?"), id_card.issuer);
436 var result = dialog.run ();
438 case ResponseType.YES:
439 remove_identity (id_card_widget);
447 public void set_prompting_service(string service)
449 prompting_service.set_label( _("Identity requested for service: %s").printf(service) );
452 public void queue_identity_request(IdentityRequest request)
454 if (this.request_queue.is_empty())
455 { /* setup widgets */
456 candidates = request.candidates;
458 redraw_id_card_widgets ();
459 set_prompting_service(request.service);
462 this.request_queue.push_tail (request);
465 public void send_identity_cb (IdCard identity)
467 return_if_fail (request_queue.length > 0);
470 var request = this.request_queue.pop_head ();
471 bool reset_password = false;
473 if (identity.password == null)
475 var dialog = new AddPasswordDialog ();
476 var result = dialog.run ();
479 case ResponseType.OK:
480 identity.password = dialog.password;
481 reset_password = ! dialog.remember;
491 if (this.request_queue.is_empty())
494 prompting_service.set_label(_(""));
495 if (!parent_app.explicitly_launched) {
496 // The following occasionally causes the app to exit without sending the dbus
497 // reply, so for now we just don't exit
503 IdentityRequest next = this.request_queue.peek_head();
504 candidates = next.candidates;
505 set_prompting_service(next.service);
508 redraw_id_card_widgets ();
510 if (identity != null)
511 parent_app.default_id_card = identity;
513 request.return_identity (identity);
516 identity.password = null;
520 private void label_make_bold (Label label)
522 var font_desc = new Pango.FontDescription ();
524 font_desc.set_weight (Pango.Weight.BOLD);
526 /* This will only affect the weight of the font, the rest is
527 * from the current state of the widget, which comes from the
528 * theme or user prefs, since the font desc only has the
529 * weight flag turned on.
531 label.modify_font (font_desc);
534 private void fill_services_vbox (IdCard id_card)
537 var n_columns = id_card.services.length;
539 var services_table = new Table (n_columns, 2, false);
540 services_table.set_col_spacings (10);
541 services_table.set_row_spacings (10);
542 this.services_internal_vbox.add (services_table);
544 service_button_map.remove_all ();
546 foreach (string service in id_card.services)
548 var label = new Label (service);
549 label.set_alignment (0, (float) 0.5);
551 var remove_button = new Button.from_stock (Stock.REMOVE);
553 var remove_button = new Button.from_stock (STOCK_REMOVE);
557 service_button_map.insert (remove_button, service);
559 remove_button.clicked.connect ((remove_button) =>
561 var dialog = new Gtk.MessageDialog (this,
562 Gtk.DialogFlags.DESTROY_WITH_PARENT,
563 Gtk.MessageType.QUESTION,
564 Gtk.ButtonsType.YES_NO,
565 _("Are you sure you want to stop '%s' ID Card from being used with %s?"),
566 custom_vbox.current_idcard.id_card.display_name,
568 var ret = dialog.run();
571 if (ret == Gtk.ResponseType.YES)
573 IdCard idcard = custom_vbox.current_idcard.id_card;
574 var candidate = service_button_map.lookup (remove_button);
576 SList<string> services = new SList<string>();
578 foreach (string srv in idcard.services)
580 if (srv == candidate)
582 services.append (srv);
585 idcard.services = new string[services.length()];
586 for (int j=0; j<idcard.services.length; j++)
588 idcard.services[j] = services.nth_data(j);
591 var children = services_internal_vbox.get_children ();
592 foreach (var hbox in children)
593 services_internal_vbox.remove(hbox);
595 fill_services_vbox (idcard);
596 custom_vbox.current_idcard.update_id_card_label ();
597 identities_manager.update_card(idcard);
601 services_table.attach_defaults (label, 0, 1, i, i+1);
602 services_table.attach_defaults (remove_button, 1, 2, i, i+1);
605 this.services_internal_vbox.show_all ();
608 private void on_about_action ()
611 "Javier Jardón <jjardon@codethink.co.uk>",
612 "Sam Thursfield <samthursfield@codethink.co.uk>",
613 "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
617 string copyright = "Copyright 2011 JANET";
621 Copyright (c) 2011, JANET(UK)
624 Redistribution and use in source and binary forms, with or without
625 modification, are permitted provided that the following conditions
628 1. Redistributions of source code must retain the above copyright
629 notice, this list of conditions and the following disclaimer.
631 2. Redistributions in binary form must reproduce the above copyright
632 notice, this list of conditions and the following disclaimer in the
633 documentation and/or other materials provided with the distribution.
635 3. Neither the name of JANET(UK) nor the names of its contributors
636 may be used to endorse or promote products derived from this software
637 without specific prior written permission.
639 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
640 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
641 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
642 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
643 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
644 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
645 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
646 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
647 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
648 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
652 Gtk.show_about_dialog (this,
653 "comments", _("Moonshot project UI"),
654 "copyright", copyright,
655 "website", Config.PACKAGE_URL,
656 "version", Config.PACKAGE_VERSION,
658 "website-label", _("Visit the Moonshot project web site"),
660 "translator-credits", _("translator-credits"),
665 private Gtk.ActionEntry[] create_actions() {
666 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
668 Gtk.ActionEntry filemenu = { "FileMenuAction",
673 Gtk.ActionEntry add = { "AddIdCardAction",
681 N_("Add a new ID Card"),
682 add_identity_manual_cb };
684 Gtk.ActionEntry quit = { "QuitAction",
692 N_("Quit the application"),
696 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
701 Gtk.ActionEntry about = { "AboutAction",
709 N_("About this application"),
717 private void create_ui_manager ()
719 Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
720 action_group.add_actions (create_actions (), this);
721 ui_manager.insert_action_group (action_group, 0);
724 ui_manager.add_ui_from_string (layout, -1);
728 stderr.printf ("%s\n", e.message);
730 ui_manager.ensure_update ();
733 private void build_ui()
735 create_ui_manager ();
737 this.search_entry = new Entry();
739 set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
740 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
741 find_icon_sized ("edit-find", Gtk.IconSize.MENU));
742 // find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
743 this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
744 _("Search identity or service"));
745 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
747 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
748 find_icon_sized ("process-stop", Gtk.IconSize.MENU));
749 // find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
750 this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
751 _("Clear the current search"));
752 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
755 this.search_entry.icon_press.connect (search_entry_icon_press_cb);
756 this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
757 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
759 this.custom_vbox = new CustomVBox (this, false, 6);
761 var viewport = new Viewport (null, null);
762 viewport.set_border_width (6);
763 viewport.set_shadow_type (ShadowType.NONE);
764 viewport.add (custom_vbox);
765 var scroll = new ScrolledWindow (null, null);
766 scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
767 scroll.set_shadow_type (ShadowType.IN);
768 scroll.add_with_viewport (viewport);
769 this.prompting_service = new Label (_(""));
771 prompting_service.set_alignment(0, (float )0.5);
773 var vbox_left = new VBox (false, 0);
774 vbox_left.pack_start (search_entry, false, false, 6);
775 vbox_left.pack_start (scroll, true, true, 0);
776 vbox_left.pack_start (prompting_service, false, false, 6);
777 vbox_left.set_size_request (WINDOW_WIDTH, 0);
779 this.no_identity_title = new Label (_("No Identity: Send this identity to services which should not use Moonshot"));
780 no_identity_title.set_alignment(0, (float ) 0.5);
781 no_identity_title.set_size_request(RIGHT_PANE_WIDTH, -1);
782 no_identity_title.set_line_wrap(true);
783 no_identity_title.show();
785 var login_vbox_title = new Label (_("Login: "));
786 label_make_bold (login_vbox_title);
787 login_vbox_title.set_alignment (0, (float) 0.5);
788 var username_label = new Label (_("Username:"));
789 username_label.set_alignment (1, (float) 0.5);
790 this.username_entry = new Entry ();
791 var password_label = new Label (_("Password:"));
792 password_label.set_alignment (1, (float) 0.5);
793 this.password_entry = new Entry ();
794 password_entry.set_invisible_char ('*');
795 password_entry.set_visibility (false);
796 var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
797 var login_table = new Table (3, 3, false);
798 login_table.set_col_spacings (10);
799 login_table.set_row_spacings (10);
800 login_table.attach_defaults (username_label, 0, 1, 0, 1);
801 login_table.attach_defaults (username_entry, 1, 2, 0, 1);
802 login_table.attach_defaults (password_label, 0, 1, 1, 2);
803 login_table.attach_defaults (password_entry, 1, 2, 1, 2);
804 login_table.attach_defaults (remember_checkbutton, 1, 2, 2, 3);
805 var login_vbox_alignment = new Alignment (0, 0, 0, 0);
806 login_vbox_alignment.set_padding (0, 0, 12, 0);
807 login_vbox_alignment.add (login_table);
808 this.login_vbox = new VBox (false, 6);
809 login_vbox.pack_start (login_vbox_title, false, true, 0);
810 login_vbox.pack_start (login_vbox_alignment, false, true, 0);
812 var services_vbox_title = new Label (_("Services:"));
813 label_make_bold (services_vbox_title);
814 services_vbox_title.set_alignment (0, (float) 0.5);
815 var services_vbox_alignment = new Alignment (0, 0, 0, 0);
816 services_vbox_alignment.set_padding (0, 0, 12, 0);
817 this.services_internal_vbox = new VBox (true, 6);
818 services_vbox_alignment.add (services_internal_vbox);
819 this.services_vbox = new VBox (false, 6);
820 services_vbox.pack_start (services_vbox_title, false, true, 0);
821 services_vbox.pack_start (services_vbox_alignment, false, true, 0);
823 this.vbox_right = new VBox (false, 18);
824 vbox_right.pack_start (login_vbox, false, true, 0);
825 vbox_right.pack_start (services_vbox, false, true, 0);
826 vbox_right.set_size_request( RIGHT_PANE_WIDTH, -1 );
828 var hbox = new HBox (false, 12);
829 hbox.pack_start (vbox_left, true, true, 0);
830 hbox.pack_start (vbox_right, false, false, 0);
832 var main_vbox = new VBox (false, 0);
833 main_vbox.set_border_width (12);
836 // hide the File | Quit menu item which is now on the Mac Menu
837 Gtk.Widget quit_item = this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
840 Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
841 osxApp.set_menu_bar(menushell);
842 osxApp.set_use_quartz_accelerators(true);
843 osxApp.sync_menu_bar();
846 var menubar = this.ui_manager.get_widget ("/MenuBar");
847 main_vbox.pack_start (menubar, false, false, 0);
849 main_vbox.pack_start (hbox, true, true, 0);
851 main_vbox.show_all();
852 this.vbox_right.hide ();
855 private void set_atk_name_description (Widget widget, string name, string description)
857 var atk_widget = widget.get_accessible ();
859 atk_widget.set_name (name);
860 atk_widget.set_description (description);
863 private void connect_signals()
865 this.destroy.connect (Gtk.main_quit);
866 this.identities_manager.card_list_changed.connect(this.on_card_list_changed);