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;
24 private CheckButton remember_checkbutton;
26 private ListStore* listmodel;
27 private TreeModelFilter filter;
29 public IdentityManagerModel identities_manager;
30 private unowned SList<IdCard> candidates;
32 public GLib.Queue<IdentityRequest> request_queue;
34 private HashTable<Gtk.Button, string> service_button_map;
46 private const string layout =
47 "<menubar name='MenuBar'>" +
48 " <menu name='FileMenu' action='FileMenuAction'>" +
49 " <menuitem name='AddIdCard' action='AddIdCardAction' />" +
51 " <menuitem name='Quit' action='QuitAction' />" +
54 " <menu name='HelpMenu' action='HelpMenuAction'>" +
55 " <menuitem name='About' action='AboutAction' />" +
59 public IdentityManagerView(IdentityManagerApp app) {
62 osxApp = OSXApplication.get_instance();
64 identities_manager = parent_app.model;
65 request_queue = new GLib.Queue<IdentityRequest>();
66 service_button_map = new HashTable<Gtk.Button, string> (direct_hash, direct_equal);
67 this.title = "Moonshot Identity Selector";
68 this.set_position (WindowPosition.CENTER);
69 set_default_size (WINDOW_WIDTH, WINDOW_HEIGHT);
76 public void on_card_list_changed () {
80 private bool visible_func (TreeModel model, TreeIter iter)
85 Columns.IDCARD_COL, out id_card);
90 if (candidates != null)
92 bool is_candidate = false;
93 foreach (IdCard candidate in candidates)
95 if (candidate == id_card)
102 string entry_text = search_entry.get_text ();
103 if (entry_text == null || entry_text == "")
108 foreach (string search_text in entry_text.split(" "))
110 if (search_text == "")
114 string search_text_casefold = search_text.casefold ();
116 if (id_card.issuer != null)
118 string issuer_casefold = id_card.issuer;
120 if (issuer_casefold.contains (search_text_casefold))
124 if (id_card.display_name != null)
126 string display_name_casefold = id_card.display_name.casefold ();
128 if (display_name_casefold.contains (search_text_casefold))
132 if (id_card.services.length > 0)
134 foreach (string service in id_card.services)
136 string service_casefold = service.casefold ();
138 if (service_casefold.contains (search_text_casefold))
146 private void setup_list_model ()
148 this.listmodel = new ListStore (Columns.N_COLUMNS, typeof (IdCard),
153 this.filter = new TreeModelFilter (listmodel, null);
155 filter.set_visible_func (visible_func);
158 private void search_entry_icon_press_cb (EntryIconPosition pos, Gdk.Event event)
160 if (pos == EntryIconPosition.PRIMARY)
162 print ("Search entry icon pressed\n");
166 this.search_entry.set_text ("");
170 private void search_entry_text_changed_cb ()
172 this.filter.refilter ();
173 redraw_id_card_widgets ();
175 var has_text = this.search_entry.get_text_length () > 0;
176 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, has_text);
177 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, has_text);
179 this.vbox_right.set_visible (false);
182 private bool search_entry_key_press_event_cb (Gdk.EventKey e)
184 if(Gdk.keyval_name(e.keyval) == "Escape")
185 this.search_entry.set_text("");
187 // Continue processing this event, since the
188 // text entry functionality needs to see it too.
192 private void load_id_cards () {
193 var children = this.custom_vbox.get_children ();
194 foreach (var id_card_widget in children) {
195 remove_id_card_widget((IdCardWidget)id_card_widget);
197 this.listmodel->clear();
198 LinkedList<IdCard> card_list = identities_manager.get_card_list() ;
199 if (card_list == null) {
203 foreach (IdCard id_card in card_list) {
204 add_id_card_data (id_card);
205 add_id_card_widget (id_card);
209 private void fill_details (IdCardWidget id_card_widget)
211 var id_card = id_card_widget.id_card;
212 var vr_children = this.vbox_right.get_children();
213 foreach (var vr_child in vr_children)
214 this.vbox_right.remove(vr_child);
215 if (id_card.display_name == IdCard.NO_IDENTITY) {
216 this.vbox_right.pack_start(no_identity_title, false, true, 0);
218 this.username_entry.set_text (id_card.username);
219 this.password_entry.set_text (id_card.password ?? "");
220 this.vbox_right.pack_start(login_vbox, false, true, 0);
221 this.remember_checkbutton.active = id_card.store_password;
223 this.vbox_right.pack_start (services_vbox, false, true, 0);
225 var children = this.services_internal_vbox.get_children ();
226 foreach (var hbox in children)
227 services_internal_vbox.remove(hbox);
228 fill_services_vbox (id_card_widget.id_card);
229 // identities_manager.store_id_cards();
232 private void show_details (IdCard id_card)
234 this.vbox_right.set_visible (!vbox_right.get_visible ());
236 if (this.vbox_right.get_visible () == false)
238 this.resize (WINDOW_WIDTH, WINDOW_HEIGHT);
242 private void details_identity_cb (IdCardWidget id_card_widget)
244 fill_details (id_card_widget);
245 show_details (id_card_widget.id_card);
248 private IdCard get_id_card_data (AddIdentityDialog dialog)
250 var id_card = new IdCard ();
252 id_card.display_name = dialog.display_name;
253 id_card.issuer = dialog.issuer;
254 if (id_card.issuer == "")
255 id_card.issuer = "Issuer";
256 id_card.username = dialog.username;
257 id_card.password = dialog.password;
258 id_card.store_password = dialog.store_password;
259 id_card.services = {};
260 id_card.set_data("pixbuf", find_icon ("avatar-default", 48));
265 private void add_id_card_data (IdCard id_card)
269 this.listmodel->append (out iter);
270 pixbuf = id_card.get_data("pixbuf");
271 listmodel->set (iter,
272 Columns.IDCARD_COL, id_card,
273 Columns.LOGO_COL, pixbuf,
274 Columns.ISSUER_COL, id_card.issuer,
275 Columns.USERNAME_COL, id_card.username,
276 Columns.PASSWORD_COL, id_card.password);
279 private void remove_id_card_data (IdCard id_card)
284 if (listmodel->get_iter_first (out iter))
288 listmodel->get (iter,
289 Columns.ISSUER_COL, out issuer);
291 if (id_card.issuer == issuer)
293 listmodel->remove (iter);
297 while (listmodel->iter_next (ref iter));
301 private void add_id_card_widget (IdCard id_card)
303 var id_card_widget = new IdCardWidget (id_card);
304 this.custom_vbox.add_id_card_widget (id_card_widget);
305 id_card_widget.details_id.connect (details_identity_cb);
306 id_card_widget.remove_id.connect (remove_identity_cb);
307 id_card_widget.send_id.connect ((w) => send_identity_cb (w.id_card));
308 id_card_widget.expanded.connect (this.custom_vbox.receive_expanded_event);
309 id_card_widget.expanded.connect (fill_details);
312 public bool add_identity (IdCard id_card, bool force_flat_file_store)
316 * TODO: We should have a confirmation dialog, but currently it will crash on Mac OS
317 * so for now we will install silently
319 var ret = Gtk.ResponseType.YES;
322 var dialog = new Gtk.MessageDialog (this,
323 Gtk.DialogFlags.DESTROY_WITH_PARENT,
324 Gtk.MessageType.QUESTION,
325 Gtk.ButtonsType.YES_NO,
326 _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
327 id_card.display_name);
329 var ret = dialog.run ();
333 if (ret == Gtk.ResponseType.YES) {
334 id_card.set_data ("pixbuf", find_icon ("avatar-default", 48));
335 this.identities_manager.add_card (id_card, force_flat_file_store);
342 private void add_identity_manual_cb ()
344 var dialog = new AddIdentityDialog ();
345 var result = dialog.run ();
348 case ResponseType.OK:
349 this.identities_manager.add_card (get_id_card_data (dialog), false);
357 private void remove_id_card_widget (IdCardWidget id_card_widget) {
358 this.custom_vbox.remove_id_card_widget (id_card_widget);
361 private void remove_identity (IdCardWidget id_card_widget)
363 var id_card = id_card_widget.id_card;
364 remove_id_card_widget (id_card_widget);
366 this.identities_manager.remove_card(id_card);
369 private void redraw_id_card_widgets ()
374 var children = this.custom_vbox.get_children ();
375 foreach (var id_card_widget in children)
376 remove_id_card_widget((IdCardWidget )id_card_widget); //id_card_widget.destroy();
378 if (filter.get_iter_first (out iter))
383 Columns.IDCARD_COL, out id_card);
385 add_id_card_widget (id_card);
387 while (filter.iter_next (ref iter));
391 private void remove_identity_cb (IdCardWidget id_card_widget)
393 var id_card = id_card_widget.id_card;
395 var dialog = new MessageDialog (this,
396 DialogFlags.DESTROY_WITH_PARENT,
397 MessageType.QUESTION,
398 Gtk.ButtonsType.YES_NO,
399 _("Are you sure you want to delete %s ID Card?"), id_card.issuer);
400 var result = dialog.run ();
402 case ResponseType.YES:
403 remove_identity (id_card_widget);
411 public void set_prompting_service(string service)
413 prompting_service.set_label( _("Identity requested for service: %s").printf(service) );
416 public void queue_identity_request(IdentityRequest request)
418 if (this.request_queue.is_empty())
419 { /* setup widgets */
420 candidates = request.candidates;
422 redraw_id_card_widgets ();
423 set_prompting_service(request.service);
426 this.request_queue.push_tail (request);
429 public void check_add_password(IdCard identity, IdentityRequest request, IdentityManagerModel model)
431 if ((identity.password == "") && !identity.IsNoIdentity())
433 var dialog = new AddPasswordDialog (identity, request);
434 var result = dialog.run ();
437 case ResponseType.OK:
438 identity.password = dialog.password;
439 identity.store_password = dialog.remember;
440 model.update_card(identity);
451 public void send_identity_cb (IdCard identity)
453 return_if_fail (request_queue.length > 0);
456 var request = this.request_queue.pop_head ();
457 check_add_password(identity, request, identities_manager);
458 if (this.request_queue.is_empty())
461 prompting_service.set_label(_(""));
462 if (!parent_app.explicitly_launched) {
463 // The following occasionally causes the app to exit without sending the dbus
464 // reply, so for now we just don't exit
470 IdentityRequest next = this.request_queue.peek_head();
471 candidates = next.candidates;
472 set_prompting_service(next.service);
475 redraw_id_card_widgets ();
477 if (identity != null)
478 parent_app.default_id_card = identity;
480 request.return_identity (identity);
483 private void label_make_bold (Label label)
485 var font_desc = new Pango.FontDescription ();
487 font_desc.set_weight (Pango.Weight.BOLD);
489 /* This will only affect the weight of the font, the rest is
490 * from the current state of the widget, which comes from the
491 * theme or user prefs, since the font desc only has the
492 * weight flag turned on.
494 label.modify_font (font_desc);
497 private void fill_services_vbox (IdCard id_card)
500 var n_columns = id_card.services.length;
502 var services_table = new Table (n_columns, 2, false);
503 services_table.set_col_spacings (10);
504 services_table.set_row_spacings (10);
505 this.services_internal_vbox.add (services_table);
507 service_button_map.remove_all ();
509 foreach (string service in id_card.services)
511 var label = new Label (service);
512 label.set_alignment (0, (float) 0.5);
514 var remove_button = new Button.from_stock (Stock.REMOVE);
516 var remove_button = new Button.from_stock (STOCK_REMOVE);
520 service_button_map.insert (remove_button, service);
522 remove_button.clicked.connect ((remove_button) =>
524 var dialog = new Gtk.MessageDialog (this,
525 Gtk.DialogFlags.DESTROY_WITH_PARENT,
526 Gtk.MessageType.QUESTION,
527 Gtk.ButtonsType.YES_NO,
528 _("Are you sure you want to stop '%s' ID Card from being used with %s?"),
529 custom_vbox.current_idcard.id_card.display_name,
531 var ret = dialog.run();
534 if (ret == Gtk.ResponseType.YES)
536 IdCard idcard = custom_vbox.current_idcard.id_card;
537 var candidate = service_button_map.lookup (remove_button);
539 SList<string> services = new SList<string>();
541 foreach (string srv in idcard.services)
543 if (srv == candidate)
545 services.append (srv);
548 idcard.services = new string[services.length()];
549 for (int j=0; j<idcard.services.length; j++)
551 idcard.services[j] = services.nth_data(j);
554 var children = services_internal_vbox.get_children ();
555 foreach (var hbox in children)
556 services_internal_vbox.remove(hbox);
558 fill_services_vbox (idcard);
559 custom_vbox.current_idcard.update_id_card_label ();
560 identities_manager.update_card(idcard);
564 services_table.attach_defaults (label, 0, 1, i, i+1);
565 services_table.attach_defaults (remove_button, 1, 2, i, i+1);
568 this.services_internal_vbox.show_all ();
571 private void on_about_action ()
574 "Javier Jardón <jjardon@codethink.co.uk>",
575 "Sam Thursfield <samthursfield@codethink.co.uk>",
576 "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
580 string copyright = "Copyright 2011 JANET";
584 Copyright (c) 2011, JANET(UK)
587 Redistribution and use in source and binary forms, with or without
588 modification, are permitted provided that the following conditions
591 1. Redistributions of source code must retain the above copyright
592 notice, this list of conditions and the following disclaimer.
594 2. Redistributions in binary form must reproduce the above copyright
595 notice, this list of conditions and the following disclaimer in the
596 documentation and/or other materials provided with the distribution.
598 3. Neither the name of JANET(UK) nor the names of its contributors
599 may be used to endorse or promote products derived from this software
600 without specific prior written permission.
602 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
603 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
604 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
605 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
606 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
607 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
608 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
609 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
610 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
611 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
615 Gtk.show_about_dialog (this,
616 "comments", _("Moonshot project UI"),
617 "copyright", copyright,
618 "website", Config.PACKAGE_URL,
619 "version", Config.PACKAGE_VERSION,
621 "website-label", _("Visit the Moonshot project web site"),
623 "translator-credits", _("translator-credits"),
628 private Gtk.ActionEntry[] create_actions() {
629 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
631 Gtk.ActionEntry filemenu = { "FileMenuAction",
636 Gtk.ActionEntry add = { "AddIdCardAction",
644 N_("Add a new ID Card"),
645 add_identity_manual_cb };
647 Gtk.ActionEntry quit = { "QuitAction",
655 N_("Quit the application"),
659 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
664 Gtk.ActionEntry about = { "AboutAction",
672 N_("About this application"),
680 private void create_ui_manager ()
682 Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
683 action_group.add_actions (create_actions (), this);
684 ui_manager.insert_action_group (action_group, 0);
687 ui_manager.add_ui_from_string (layout, -1);
691 stderr.printf ("%s\n", e.message);
693 ui_manager.ensure_update ();
696 private void build_ui()
698 create_ui_manager ();
700 this.search_entry = new Entry();
702 set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
703 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
704 find_icon_sized ("edit-find", Gtk.IconSize.MENU));
705 // find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
706 this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
707 _("Search identity or service"));
708 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
710 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
711 find_icon_sized ("process-stop", Gtk.IconSize.MENU));
712 // find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
713 this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
714 _("Clear the current search"));
715 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
718 this.search_entry.icon_press.connect (search_entry_icon_press_cb);
719 this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
720 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
722 this.custom_vbox = new CustomVBox (this, false, 6);
724 var viewport = new Viewport (null, null);
725 viewport.set_border_width (6);
726 viewport.set_shadow_type (ShadowType.NONE);
727 viewport.add (custom_vbox);
728 var scroll = new ScrolledWindow (null, null);
729 scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
730 scroll.set_shadow_type (ShadowType.IN);
731 scroll.add_with_viewport (viewport);
732 this.prompting_service = new Label (_(""));
734 prompting_service.set_alignment(0, (float )0.5);
736 var vbox_left = new VBox (false, 0);
737 vbox_left.pack_start (search_entry, false, false, 6);
738 vbox_left.pack_start (scroll, true, true, 0);
739 vbox_left.pack_start (prompting_service, false, false, 6);
740 vbox_left.set_size_request (WINDOW_WIDTH, 0);
742 this.no_identity_title = new Label (_("No Identity: Send this identity to services which should not use Moonshot"));
743 no_identity_title.set_alignment(0, (float ) 0.5);
744 no_identity_title.set_size_request(RIGHT_PANE_WIDTH, -1);
745 no_identity_title.set_line_wrap(true);
746 no_identity_title.show();
748 var login_vbox_title = new Label (_("Login: "));
749 label_make_bold (login_vbox_title);
750 login_vbox_title.set_alignment (0, (float) 0.5);
751 var username_label = new Label (_("Username:"));
752 username_label.set_alignment (1, (float) 0.5);
753 this.username_entry = new Entry ();
754 var password_label = new Label (_("Password:"));
755 password_label.set_alignment (1, (float) 0.5);
756 this.password_entry = new Entry ();
757 password_entry.set_invisible_char ('*');
758 password_entry.set_visibility (false);
759 this.remember_checkbutton = new CheckButton.with_label (_("Remember password"));
760 var login_table = new Table (3, 3, false);
761 login_table.set_col_spacings (10);
762 login_table.set_row_spacings (10);
763 login_table.attach_defaults (username_label, 0, 1, 0, 1);
764 login_table.attach_defaults (username_entry, 1, 2, 0, 1);
765 login_table.attach_defaults (password_label, 0, 1, 1, 2);
766 login_table.attach_defaults (password_entry, 1, 2, 1, 2);
767 login_table.attach_defaults (remember_checkbutton, 1, 2, 2, 3);
768 var login_vbox_alignment = new Alignment (0, 0, 0, 0);
769 login_vbox_alignment.set_padding (0, 0, 12, 0);
770 login_vbox_alignment.add (login_table);
771 this.login_vbox = new VBox (false, 6);
772 login_vbox.pack_start (login_vbox_title, false, true, 0);
773 login_vbox.pack_start (login_vbox_alignment, false, true, 0);
775 var services_vbox_title = new Label (_("Services:"));
776 label_make_bold (services_vbox_title);
777 services_vbox_title.set_alignment (0, (float) 0.5);
778 var services_vbox_alignment = new Alignment (0, 0, 0, 0);
779 services_vbox_alignment.set_padding (0, 0, 12, 0);
780 this.services_internal_vbox = new VBox (true, 6);
781 services_vbox_alignment.add (services_internal_vbox);
782 this.services_vbox = new VBox (false, 6);
783 services_vbox.pack_start (services_vbox_title, false, true, 0);
784 services_vbox.pack_start (services_vbox_alignment, false, true, 0);
786 this.vbox_right = new VBox (false, 18);
787 vbox_right.pack_start (login_vbox, false, true, 0);
788 vbox_right.pack_start (services_vbox, false, true, 0);
789 vbox_right.set_size_request( RIGHT_PANE_WIDTH, -1 );
791 var hbox = new HBox (false, 12);
792 hbox.pack_start (vbox_left, true, true, 0);
793 hbox.pack_start (vbox_right, false, false, 0);
795 var main_vbox = new VBox (false, 0);
796 main_vbox.set_border_width (12);
799 // hide the File | Quit menu item which is now on the Mac Menu
800 Gtk.Widget quit_item = this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
803 Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
804 osxApp.set_menu_bar(menushell);
805 osxApp.set_use_quartz_accelerators(true);
806 osxApp.sync_menu_bar();
809 var menubar = this.ui_manager.get_widget ("/MenuBar");
810 main_vbox.pack_start (menubar, false, false, 0);
812 main_vbox.pack_start (hbox, true, true, 0);
814 main_vbox.show_all();
815 this.vbox_right.hide ();
818 private void set_atk_name_description (Widget widget, string name, string description)
820 var atk_widget = widget.get_accessible ();
822 atk_widget.set_name (name);
823 atk_widget.set_description (description);
826 private void connect_signals()
828 this.destroy.connect (Gtk.main_quit);
829 this.identities_manager.card_list_changed.connect(this.on_card_list_changed);