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 string current_idcard_nai = null;
194 if (this.custom_vbox.current_idcard != null) {
195 current_idcard_nai = custom_vbox.current_idcard.id_card.nai;
196 custom_vbox.current_idcard = null;
198 var children = this.custom_vbox.get_children ();
199 foreach (var id_card_widget in children) {
200 remove_id_card_widget((IdCardWidget)id_card_widget);
202 this.listmodel->clear();
203 LinkedList<IdCard> card_list = identities_manager.get_card_list() ;
204 if (card_list == null) {
208 foreach (IdCard id_card in card_list) {
209 add_id_card_data (id_card);
210 IdCardWidget id_card_widget = add_id_card_widget (id_card);
211 if (id_card_widget.id_card.nai == current_idcard_nai) {
212 fill_details(id_card_widget);
213 id_card_widget.expand();
216 if (custom_vbox.current_idcard == null)
220 private void fill_details (IdCardWidget? id_card_widget)
222 var vr_children = this.vbox_right.get_children();
223 foreach (var vr_child in vr_children)
224 this.vbox_right.remove(vr_child);
225 if (id_card_widget != null) {
226 var id_card = id_card_widget.id_card;
227 if (id_card.display_name == IdCard.NO_IDENTITY) {
228 this.vbox_right.pack_start(no_identity_title, false, true, 0);
230 this.username_entry.set_text (id_card.username);
231 this.password_entry.set_text (id_card.password ?? "");
232 this.vbox_right.pack_start(login_vbox, false, true, 0);
233 this.remember_checkbutton.active = id_card.store_password;
235 this.vbox_right.pack_start (services_vbox, false, true, 0);
237 var children = this.services_internal_vbox.get_children ();
238 foreach (var hbox in children)
239 services_internal_vbox.remove(hbox);
240 fill_services_vbox (id_card_widget.id_card);
244 private void show_details (IdCard id_card)
246 this.vbox_right.set_visible (!vbox_right.get_visible ());
248 if (this.vbox_right.get_visible () == false)
250 this.resize (WINDOW_WIDTH, WINDOW_HEIGHT);
254 private void details_identity_cb (IdCardWidget id_card_widget)
256 fill_details (id_card_widget);
257 show_details (id_card_widget.id_card);
260 private IdCard get_id_card_data (AddIdentityDialog dialog)
262 var id_card = new IdCard ();
264 id_card.display_name = dialog.display_name;
265 id_card.issuer = dialog.issuer;
266 if (id_card.issuer == "")
267 id_card.issuer = "Issuer";
268 id_card.username = dialog.username;
269 id_card.password = dialog.password;
270 id_card.store_password = dialog.store_password;
271 id_card.services = {};
276 private void add_id_card_data (IdCard id_card)
280 this.listmodel->append (out iter);
281 pixbuf = get_pixbuf(id_card);
282 listmodel->set (iter,
283 Columns.IDCARD_COL, id_card,
284 Columns.LOGO_COL, pixbuf,
285 Columns.ISSUER_COL, id_card.issuer,
286 Columns.USERNAME_COL, id_card.username,
287 Columns.PASSWORD_COL, id_card.password);
290 private void remove_id_card_data (IdCard id_card)
295 if (listmodel->get_iter_first (out iter))
299 listmodel->get (iter,
300 Columns.ISSUER_COL, out issuer);
302 if (id_card.issuer == issuer)
304 listmodel->remove (iter);
308 while (listmodel->iter_next (ref iter));
312 private IdCardWidget add_id_card_widget (IdCard id_card)
314 var id_card_widget = new IdCardWidget (id_card);
315 this.custom_vbox.add_id_card_widget (id_card_widget);
316 id_card_widget.details_id.connect (details_identity_cb);
317 id_card_widget.remove_id.connect (remove_identity_cb);
318 id_card_widget.send_id.connect ((w) => send_identity_cb (w.id_card));
319 id_card_widget.expanded.connect (this.custom_vbox.receive_expanded_event);
320 id_card_widget.expanded.connect (fill_details);
321 return id_card_widget;
324 public bool add_identity (IdCard id_card, bool force_flat_file_store)
328 * TODO: We should have a confirmation dialog, but currently it will crash on Mac OS
329 * so for now we will install silently
331 var ret = Gtk.ResponseType.YES;
334 var dialog = new Gtk.MessageDialog (this,
335 Gtk.DialogFlags.DESTROY_WITH_PARENT,
336 Gtk.MessageType.QUESTION,
337 Gtk.ButtonsType.YES_NO,
338 _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
339 id_card.display_name);
341 var ret = dialog.run ();
345 if (ret == Gtk.ResponseType.YES) {
346 this.identities_manager.add_card (id_card, force_flat_file_store);
353 private void add_identity_manual_cb ()
355 var dialog = new AddIdentityDialog ();
356 var result = dialog.run ();
359 case ResponseType.OK:
360 this.identities_manager.add_card (get_id_card_data (dialog), false);
368 private void remove_id_card_widget (IdCardWidget id_card_widget) {
369 this.custom_vbox.remove_id_card_widget (id_card_widget);
372 private void remove_identity (IdCardWidget id_card_widget)
374 var id_card = id_card_widget.id_card;
375 remove_id_card_widget (id_card_widget);
377 this.identities_manager.remove_card(id_card);
380 private void redraw_id_card_widgets ()
385 var children = this.custom_vbox.get_children ();
386 foreach (var id_card_widget in children)
387 remove_id_card_widget((IdCardWidget )id_card_widget); //id_card_widget.destroy();
389 if (filter.get_iter_first (out iter))
394 Columns.IDCARD_COL, out id_card);
396 add_id_card_widget (id_card);
398 while (filter.iter_next (ref iter));
402 private void remove_identity_cb (IdCardWidget id_card_widget)
404 var id_card = id_card_widget.id_card;
406 var dialog = new MessageDialog (this,
407 DialogFlags.DESTROY_WITH_PARENT,
408 MessageType.QUESTION,
409 Gtk.ButtonsType.YES_NO,
410 _("Are you sure you want to delete %s ID Card?"), id_card.issuer);
411 var result = dialog.run ();
413 case ResponseType.YES:
414 remove_identity (id_card_widget);
422 public void set_prompting_service(string service)
424 prompting_service.set_label( _("Identity requested for service: %s").printf(service) );
427 public void queue_identity_request(IdentityRequest request)
429 if (this.request_queue.is_empty())
430 { /* setup widgets */
431 candidates = request.candidates;
433 redraw_id_card_widgets ();
434 set_prompting_service(request.service);
437 this.request_queue.push_tail (request);
440 public void check_add_password(IdCard identity, IdentityRequest request, IdentityManagerModel model)
442 if ((identity.password == "") && !identity.IsNoIdentity())
444 var dialog = new AddPasswordDialog (identity, request);
445 var result = dialog.run ();
448 case ResponseType.OK:
449 identity.password = dialog.password;
450 identity.store_password = dialog.remember;
451 model.update_card(identity);
462 public void send_identity_cb (IdCard identity)
464 return_if_fail (request_queue.length > 0);
467 var request = this.request_queue.pop_head ();
468 check_add_password(identity, request, identities_manager);
469 if (this.request_queue.is_empty())
472 prompting_service.set_label(_(""));
473 if (!parent_app.explicitly_launched) {
474 // The following occasionally causes the app to exit without sending the dbus
475 // reply, so for now we just don't exit
481 IdentityRequest next = this.request_queue.peek_head();
482 candidates = next.candidates;
483 set_prompting_service(next.service);
486 redraw_id_card_widgets ();
488 if (identity != null)
489 parent_app.default_id_card = identity;
491 request.return_identity (identity);
494 private void label_make_bold (Label label)
496 var font_desc = new Pango.FontDescription ();
498 font_desc.set_weight (Pango.Weight.BOLD);
500 /* This will only affect the weight of the font, the rest is
501 * from the current state of the widget, which comes from the
502 * theme or user prefs, since the font desc only has the
503 * weight flag turned on.
505 label.modify_font (font_desc);
508 private void fill_services_vbox (IdCard id_card)
511 var n_columns = id_card.services.length;
513 var services_table = new Table (n_columns, 2, false);
514 services_table.set_col_spacings (10);
515 services_table.set_row_spacings (10);
516 this.services_internal_vbox.add (services_table);
518 service_button_map.remove_all ();
520 foreach (string service in id_card.services)
522 var label = new Label (service);
523 label.set_alignment (0, (float) 0.5);
525 var remove_button = new Button.from_stock (Stock.REMOVE);
527 var remove_button = new Button.from_stock (STOCK_REMOVE);
531 service_button_map.insert (remove_button, service);
533 remove_button.clicked.connect ((remove_button) =>
535 var candidate = service_button_map.lookup (remove_button);
536 if (candidate == null)
538 var dialog = new Gtk.MessageDialog (this,
539 Gtk.DialogFlags.DESTROY_WITH_PARENT,
540 Gtk.MessageType.QUESTION,
541 Gtk.ButtonsType.YES_NO,
542 _("Are you sure you want to stop '%s' ID Card from being used with %s?"),
543 custom_vbox.current_idcard.id_card.display_name,
545 var ret = dialog.run();
548 if (ret == Gtk.ResponseType.YES)
550 IdCard idcard = custom_vbox.current_idcard.id_card;
551 if (idcard != null) {
552 SList<string> services = new SList<string>();
554 foreach (string srv in idcard.services)
556 if (srv == candidate)
558 services.append (srv);
561 idcard.services = new string[services.length()];
562 for (int j=0; j<idcard.services.length; j++)
564 idcard.services[j] = services.nth_data(j);
567 identities_manager.update_card(idcard);
572 services_table.attach_defaults (label, 0, 1, i, i+1);
573 services_table.attach_defaults (remove_button, 1, 2, i, i+1);
576 this.services_internal_vbox.show_all ();
579 private void on_about_action ()
582 "Javier Jardón <jjardon@codethink.co.uk>",
583 "Sam Thursfield <samthursfield@codethink.co.uk>",
584 "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
588 string copyright = "Copyright 2011 JANET";
592 Copyright (c) 2011, JANET(UK)
595 Redistribution and use in source and binary forms, with or without
596 modification, are permitted provided that the following conditions
599 1. Redistributions of source code must retain the above copyright
600 notice, this list of conditions and the following disclaimer.
602 2. Redistributions in binary form must reproduce the above copyright
603 notice, this list of conditions and the following disclaimer in the
604 documentation and/or other materials provided with the distribution.
606 3. Neither the name of JANET(UK) nor the names of its contributors
607 may be used to endorse or promote products derived from this software
608 without specific prior written permission.
610 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
611 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
612 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
613 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
614 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
615 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
616 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
617 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
618 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
619 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
623 Gtk.show_about_dialog (this,
624 "comments", _("Moonshot project UI"),
625 "copyright", copyright,
626 "website", Config.PACKAGE_URL,
627 "version", Config.PACKAGE_VERSION,
629 "website-label", _("Visit the Moonshot project web site"),
631 "translator-credits", _("translator-credits"),
636 private Gtk.ActionEntry[] create_actions() {
637 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
639 Gtk.ActionEntry filemenu = { "FileMenuAction",
644 Gtk.ActionEntry add = { "AddIdCardAction",
652 N_("Add a new ID Card"),
653 add_identity_manual_cb };
655 Gtk.ActionEntry quit = { "QuitAction",
663 N_("Quit the application"),
667 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
672 Gtk.ActionEntry about = { "AboutAction",
680 N_("About this application"),
688 private void create_ui_manager ()
690 Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
691 action_group.add_actions (create_actions (), this);
692 ui_manager.insert_action_group (action_group, 0);
695 ui_manager.add_ui_from_string (layout, -1);
699 stderr.printf ("%s\n", e.message);
701 ui_manager.ensure_update ();
704 private void build_ui()
706 create_ui_manager ();
708 this.search_entry = new Entry();
710 set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
711 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
712 find_icon_sized ("edit-find", Gtk.IconSize.MENU));
713 // find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
714 this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
715 _("Search identity or service"));
716 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
718 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
719 find_icon_sized ("process-stop", Gtk.IconSize.MENU));
720 // find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
721 this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
722 _("Clear the current search"));
723 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
726 this.search_entry.icon_press.connect (search_entry_icon_press_cb);
727 this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
728 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
730 this.custom_vbox = new CustomVBox (this, false, 6);
732 var viewport = new Viewport (null, null);
733 viewport.set_border_width (6);
734 viewport.set_shadow_type (ShadowType.NONE);
735 viewport.add (custom_vbox);
736 var scroll = new ScrolledWindow (null, null);
737 scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
738 scroll.set_shadow_type (ShadowType.IN);
739 scroll.add_with_viewport (viewport);
740 this.prompting_service = new Label (_(""));
742 prompting_service.set_alignment(0, (float )0.5);
744 var vbox_left = new VBox (false, 0);
745 vbox_left.pack_start (search_entry, false, false, 6);
746 vbox_left.pack_start (scroll, true, true, 0);
747 vbox_left.pack_start (prompting_service, false, false, 6);
748 vbox_left.set_size_request (WINDOW_WIDTH, 0);
750 this.no_identity_title = new Label (_("No Identity: Send this identity to services which should not use Moonshot"));
751 no_identity_title.set_alignment(0, (float ) 0.5);
752 no_identity_title.set_size_request(RIGHT_PANE_WIDTH, -1);
753 no_identity_title.set_line_wrap(true);
754 no_identity_title.show();
756 var login_vbox_title = new Label (_("Login: "));
757 label_make_bold (login_vbox_title);
758 login_vbox_title.set_alignment (0, (float) 0.5);
759 var username_label = new Label (_("Username:"));
760 username_label.set_alignment (1, (float) 0.5);
761 this.username_entry = new Entry ();
762 var password_label = new Label (_("Password:"));
763 password_label.set_alignment (1, (float) 0.5);
764 this.password_entry = new Entry ();
765 password_entry.set_invisible_char ('*');
766 password_entry.set_visibility (false);
767 this.remember_checkbutton = new CheckButton.with_label (_("Remember password"));
768 var login_table = new Table (3, 3, false);
769 login_table.set_col_spacings (10);
770 login_table.set_row_spacings (10);
771 login_table.attach_defaults (username_label, 0, 1, 0, 1);
772 login_table.attach_defaults (username_entry, 1, 2, 0, 1);
773 login_table.attach_defaults (password_label, 0, 1, 1, 2);
774 login_table.attach_defaults (password_entry, 1, 2, 1, 2);
775 login_table.attach_defaults (remember_checkbutton, 1, 2, 2, 3);
776 var login_vbox_alignment = new Alignment (0, 0, 0, 0);
777 login_vbox_alignment.set_padding (0, 0, 12, 0);
778 login_vbox_alignment.add (login_table);
779 this.login_vbox = new VBox (false, 6);
780 login_vbox.pack_start (login_vbox_title, false, true, 0);
781 login_vbox.pack_start (login_vbox_alignment, false, true, 0);
783 var services_vbox_title = new Label (_("Services:"));
784 label_make_bold (services_vbox_title);
785 services_vbox_title.set_alignment (0, (float) 0.5);
786 var services_vbox_alignment = new Alignment (0, 0, 0, 0);
787 services_vbox_alignment.set_padding (0, 0, 12, 0);
788 this.services_internal_vbox = new VBox (true, 6);
789 services_vbox_alignment.add (services_internal_vbox);
790 this.services_vbox = new VBox (false, 6);
791 services_vbox.pack_start (services_vbox_title, false, true, 0);
792 services_vbox.pack_start (services_vbox_alignment, false, true, 0);
794 this.vbox_right = new VBox (false, 18);
795 vbox_right.pack_start (login_vbox, false, true, 0);
796 vbox_right.pack_start (services_vbox, false, true, 0);
797 vbox_right.set_size_request( RIGHT_PANE_WIDTH, -1 );
799 var hbox = new HBox (false, 12);
800 hbox.pack_start (vbox_left, true, true, 0);
801 hbox.pack_start (vbox_right, false, false, 0);
803 var main_vbox = new VBox (false, 0);
804 main_vbox.set_border_width (12);
807 // hide the File | Quit menu item which is now on the Mac Menu
808 Gtk.Widget quit_item = this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
811 Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
812 osxApp.set_menu_bar(menushell);
813 osxApp.set_use_quartz_accelerators(true);
814 osxApp.sync_menu_bar();
817 var menubar = this.ui_manager.get_widget ("/MenuBar");
818 main_vbox.pack_start (menubar, false, false, 0);
820 main_vbox.pack_start (hbox, true, true, 0);
822 main_vbox.show_all();
823 this.vbox_right.hide ();
826 private void set_atk_name_description (Widget widget, string name, string description)
828 var atk_widget = widget.get_accessible ();
830 atk_widget.set_name (name);
831 atk_widget.set_description (description);
834 private void connect_signals()
836 this.destroy.connect (Gtk.main_quit);
837 this.identities_manager.card_list_changed.connect(this.on_card_list_changed);