4 public 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;
19 private Label prompting_service;
21 private ListStore* listmodel;
22 private TreeModelFilter filter;
24 public IdentityManagerModel identities_manager;
25 private unowned SList<IdCard> candidates;
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 = "Moonshot Identity Selector";
63 this.set_position (WindowPosition.CENTER);
64 set_default_size (WINDOW_WIDTH, WINDOW_HEIGHT);
71 public void on_card_list_changed () {
75 private bool visible_func (TreeModel model, TreeIter iter)
80 Columns.IDCARD_COL, out id_card);
85 if (candidates != null)
87 bool is_candidate = false;
88 foreach (IdCard candidate in candidates)
90 if (candidate == id_card)
97 string entry_text = search_entry.get_text ();
98 if (entry_text == null || entry_text == "")
103 foreach (string search_text in entry_text.split(" "))
105 if (search_text == "")
109 string search_text_casefold = search_text.casefold ();
111 if (id_card.issuer != null)
113 string issuer_casefold = id_card.issuer;
115 if (issuer_casefold.contains (search_text_casefold))
119 if (id_card.display_name != null)
121 string display_name_casefold = id_card.display_name.casefold ();
123 if (display_name_casefold.contains (search_text_casefold))
127 if (id_card.services.length > 0)
129 foreach (string service in id_card.services)
131 string service_casefold = service.casefold ();
133 if (service_casefold.contains (search_text_casefold))
141 private void setup_list_model ()
143 this.listmodel = new ListStore (Columns.N_COLUMNS, typeof (IdCard),
148 this.filter = new TreeModelFilter (listmodel, null);
150 filter.set_visible_func (visible_func);
153 private void search_entry_icon_press_cb (EntryIconPosition pos, Gdk.Event event)
155 if (pos == EntryIconPosition.PRIMARY)
157 print ("Search entry icon pressed\n");
161 this.search_entry.set_text ("");
165 private void search_entry_text_changed_cb ()
167 this.filter.refilter ();
168 redraw_id_card_widgets ();
170 var has_text = this.search_entry.get_text_length () > 0;
171 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, has_text);
172 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, has_text);
174 this.vbox_right.set_visible (false);
177 private bool search_entry_key_press_event_cb (Gdk.EventKey e)
179 if(Gdk.keyval_name(e.keyval) == "Escape")
180 this.search_entry.set_text("");
182 // Continue processing this event, since the
183 // text entry functionality needs to see it too.
187 private void load_id_cards () {
188 var children = this.custom_vbox.get_children ();
189 foreach (var id_card_widget in children) {
190 remove_id_card_widget((IdCardWidget)id_card_widget);
192 this.listmodel->clear();
193 LinkedList<IdCard> card_list = identities_manager.get_card_list() ;
194 if (card_list == null) {
198 foreach (IdCard id_card in card_list) {
199 add_id_card_data (id_card);
200 add_id_card_widget (id_card);
204 private void fill_details (IdCardWidget id_card_widget)
206 var id_card = id_card_widget.id_card;
207 this.username_entry.set_text (id_card.username);
208 this.password_entry.set_text (id_card.password ?? "");
210 var children = this.services_internal_vbox.get_children ();
211 foreach (var hbox in children)
212 services_internal_vbox.remove(hbox);
213 fill_services_vbox (id_card_widget.id_card);
214 // identities_manager.store_id_cards();
217 private void show_details (IdCard id_card)
219 this.vbox_right.set_visible (!vbox_right.get_visible ());
221 if (this.vbox_right.get_visible () == false)
223 this.resize (WINDOW_WIDTH, WINDOW_HEIGHT);
227 private void details_identity_cb (IdCardWidget id_card_widget)
229 fill_details (id_card_widget);
230 show_details (id_card_widget.id_card);
233 private IdCard get_id_card_data (AddIdentityDialog dialog)
235 var id_card = new IdCard ();
237 id_card.display_name = dialog.display_name;
238 id_card.issuer = dialog.issuer;
239 if (id_card.issuer == "")
240 id_card.issuer = "Issuer";
241 id_card.username = dialog.username;
242 id_card.password = dialog.password;
243 id_card.services = {};
244 id_card.set_data("pixbuf", find_icon ("avatar-default", 48));
249 private void add_id_card_data (IdCard id_card)
253 this.listmodel->append (out iter);
254 pixbuf = id_card.get_data("pixbuf");
255 listmodel->set (iter,
256 Columns.IDCARD_COL, id_card,
257 Columns.LOGO_COL, pixbuf,
258 Columns.ISSUER_COL, id_card.issuer,
259 Columns.USERNAME_COL, id_card.username,
260 Columns.PASSWORD_COL, id_card.password);
263 private void remove_id_card_data (IdCard id_card)
268 if (listmodel->get_iter_first (out iter))
272 listmodel->get (iter,
273 Columns.ISSUER_COL, out issuer);
275 if (id_card.issuer == issuer)
277 listmodel->remove (iter);
281 while (listmodel->iter_next (ref iter));
285 private void add_id_card_widget (IdCard id_card)
287 var id_card_widget = new IdCardWidget (id_card);
288 this.custom_vbox.add_id_card_widget (id_card_widget);
289 id_card_widget.details_id.connect (details_identity_cb);
290 id_card_widget.remove_id.connect (remove_identity_cb);
291 id_card_widget.send_id.connect ((w) => send_identity_cb (w.id_card));
292 id_card_widget.expanded.connect (this.custom_vbox.receive_expanded_event);
293 id_card_widget.expanded.connect (fill_details);
296 /* This method finds a valid display name */
297 public bool display_name_is_valid (string name,
298 out string? candidate)
300 foreach (IdCard id_card in identities_manager.get_card_list())
302 if (id_card.display_name == name)
304 if (&candidate != null)
306 for (int i=0; i<1000; i++)
308 string tmp = "%s %d".printf (name, i);
309 if (display_name_is_valid (tmp, null))
323 public void insert_id_card (IdCard id_card)
327 if (!display_name_is_valid (id_card.display_name, out candidate))
329 id_card.display_name = candidate;
332 this.identities_manager.add_card(id_card);
335 public bool add_identity (IdCard id_card)
339 * TODO: We should have a confirmation dialog, but currently it will crash on Mac OS
340 * so for now we will install silently
342 var ret = Gtk.ResponseType.YES;
345 var dialog = new Gtk.MessageDialog (this,
346 Gtk.DialogFlags.DESTROY_WITH_PARENT,
347 Gtk.MessageType.QUESTION,
348 Gtk.ButtonsType.YES_NO,
349 _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
350 id_card.display_name);
352 var ret = dialog.run ();
356 if (ret == Gtk.ResponseType.YES) {
357 id_card.set_data ("pixbuf", find_icon ("avatar-default", 48));
358 this.insert_id_card (id_card);
365 private void add_identity_manual_cb ()
367 var dialog = new AddIdentityDialog ();
368 var result = dialog.run ();
371 case ResponseType.OK:
372 insert_id_card (get_id_card_data (dialog));
380 private void remove_id_card_widget (IdCardWidget id_card_widget) {
381 this.custom_vbox.remove_id_card_widget (id_card_widget);
384 private void remove_identity (IdCardWidget id_card_widget)
386 var id_card = id_card_widget.id_card;
387 remove_id_card_widget (id_card_widget);
389 this.identities_manager.remove_card(id_card);
392 private void redraw_id_card_widgets ()
397 var children = this.custom_vbox.get_children ();
398 foreach (var id_card_widget in children)
399 remove_id_card_widget((IdCardWidget )id_card_widget); //id_card_widget.destroy();
401 if (filter.get_iter_first (out iter))
406 Columns.IDCARD_COL, out id_card);
408 add_id_card_widget (id_card);
410 while (filter.iter_next (ref iter));
414 private void remove_identity_cb (IdCardWidget id_card_widget)
416 var id_card = id_card_widget.id_card;
418 var dialog = new MessageDialog (this,
419 DialogFlags.DESTROY_WITH_PARENT,
420 MessageType.QUESTION,
421 Gtk.ButtonsType.YES_NO,
422 _("Are you sure you want to delete %s ID Card?"), id_card.issuer);
423 var result = dialog.run ();
425 case ResponseType.YES:
426 remove_identity (id_card_widget);
434 public void set_prompting_service(string service)
436 prompting_service.set_label( _("Identity requested for service: %s").printf(service) );
439 public void queue_identity_request(IdentityRequest request)
441 if (this.request_queue.is_empty())
442 { /* setup widgets */
443 candidates = request.candidates;
445 redraw_id_card_widgets ();
446 set_prompting_service(request.service);
449 this.request_queue.push_tail (request);
452 public void send_identity_cb (IdCard identity)
454 return_if_fail (request_queue.length > 0);
457 var request = this.request_queue.pop_head ();
458 bool reset_password = false;
460 if (identity.password == null)
462 var dialog = new AddPasswordDialog ();
463 var result = dialog.run ();
466 case ResponseType.OK:
467 identity.password = dialog.password;
468 reset_password = ! dialog.remember;
478 if (this.request_queue.is_empty())
481 prompting_service.set_label(_(""));
482 if (!parent_app.explicitly_launched) {
483 // The following occasionally causes the app to exit without sending the dbus
484 // reply, so for now we just don't exit
490 IdentityRequest next = this.request_queue.peek_head();
491 candidates = next.candidates;
492 set_prompting_service(next.service);
495 redraw_id_card_widgets ();
497 if (identity != null)
498 parent_app.default_id_card = identity;
500 request.return_identity (identity);
503 identity.password = null;
507 private void label_make_bold (Label label)
509 var font_desc = new Pango.FontDescription ();
511 font_desc.set_weight (Pango.Weight.BOLD);
513 /* This will only affect the weight of the font, the rest is
514 * from the current state of the widget, which comes from the
515 * theme or user prefs, since the font desc only has the
516 * weight flag turned on.
518 label.modify_font (font_desc);
521 private void fill_services_vbox (IdCard id_card)
524 var n_columns = id_card.services.length;
526 var services_table = new Table (n_columns, 2, false);
527 services_table.set_col_spacings (10);
528 services_table.set_row_spacings (10);
529 this.services_internal_vbox.add (services_table);
531 service_button_map.remove_all ();
533 foreach (string service in id_card.services)
535 var label = new Label (service);
536 label.set_alignment (0, (float) 0.5);
538 var remove_button = new Button.from_stock (Stock.REMOVE);
540 var remove_button = new Button.from_stock (STOCK_REMOVE);
544 service_button_map.insert (remove_button, service);
546 remove_button.clicked.connect ((remove_button) =>
548 var dialog = new Gtk.MessageDialog (this,
549 Gtk.DialogFlags.DESTROY_WITH_PARENT,
550 Gtk.MessageType.QUESTION,
551 Gtk.ButtonsType.YES_NO,
552 _("Are you sure you want to stop '%s' ID Card from being used with %s?"),
553 custom_vbox.current_idcard.id_card.display_name,
555 var ret = dialog.run();
558 if (ret == Gtk.ResponseType.YES)
560 IdCard idcard = custom_vbox.current_idcard.id_card;
561 var candidate = service_button_map.lookup (remove_button);
563 SList<string> services = new SList<string>();
565 foreach (string srv in idcard.services)
567 if (srv == candidate)
569 services.append (srv);
572 idcard.services = new string[services.length()];
573 for (int j=0; j<idcard.services.length; j++)
575 idcard.services[j] = services.nth_data(j);
578 var children = services_internal_vbox.get_children ();
579 foreach (var hbox in children)
580 services_internal_vbox.remove(hbox);
582 fill_services_vbox (idcard);
583 custom_vbox.current_idcard.update_id_card_label ();
584 identities_manager.update_card(idcard);
588 services_table.attach_defaults (label, 0, 1, i, i+1);
589 services_table.attach_defaults (remove_button, 1, 2, i, i+1);
592 this.services_internal_vbox.show_all ();
595 private void on_about_action ()
598 "Javier Jardón <jjardon@codethink.co.uk>",
599 "Sam Thursfield <samthursfield@codethink.co.uk>",
600 "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
604 string copyright = "Copyright 2011 JANET";
608 Copyright (c) 2011, JANET(UK)
611 Redistribution and use in source and binary forms, with or without
612 modification, are permitted provided that the following conditions
615 1. Redistributions of source code must retain the above copyright
616 notice, this list of conditions and the following disclaimer.
618 2. Redistributions in binary form must reproduce the above copyright
619 notice, this list of conditions and the following disclaimer in the
620 documentation and/or other materials provided with the distribution.
622 3. Neither the name of JANET(UK) nor the names of its contributors
623 may be used to endorse or promote products derived from this software
624 without specific prior written permission.
626 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
627 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
628 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
629 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
630 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
631 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
632 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
633 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
634 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
635 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
639 Gtk.show_about_dialog (this,
640 "comments", _("Moonshot project UI"),
641 "copyright", copyright,
642 "website", Config.PACKAGE_URL,
643 "version", Config.PACKAGE_VERSION,
645 "website-label", _("Visit the Moonshot project web site"),
647 "translator-credits", _("translator-credits"),
652 private Gtk.ActionEntry[] create_actions() {
653 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
655 Gtk.ActionEntry filemenu = { "FileMenuAction",
660 Gtk.ActionEntry add = { "AddIdCardAction",
668 N_("Add a new ID Card"),
669 add_identity_manual_cb };
671 Gtk.ActionEntry quit = { "QuitAction",
679 N_("Quit the application"),
683 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
688 Gtk.ActionEntry about = { "AboutAction",
696 N_("About this application"),
704 private void create_ui_manager ()
706 Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
707 action_group.add_actions (create_actions (), this);
708 ui_manager.insert_action_group (action_group, 0);
711 ui_manager.add_ui_from_string (layout, -1);
715 stderr.printf ("%s\n", e.message);
717 ui_manager.ensure_update ();
720 private void build_ui()
722 create_ui_manager ();
724 this.search_entry = new Entry();
726 set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
727 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
728 find_icon_sized ("edit-find", Gtk.IconSize.MENU));
729 // find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
730 this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
731 _("Search identity or service"));
732 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
734 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
735 find_icon_sized ("process-stop", Gtk.IconSize.MENU));
736 // find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
737 this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
738 _("Clear the current search"));
739 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
742 this.search_entry.icon_press.connect (search_entry_icon_press_cb);
743 this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
744 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
746 this.custom_vbox = new CustomVBox (this, false, 6);
748 var viewport = new Viewport (null, null);
749 viewport.set_border_width (6);
750 viewport.set_shadow_type (ShadowType.NONE);
751 viewport.add (custom_vbox);
752 var scroll = new ScrolledWindow (null, null);
753 scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
754 scroll.set_shadow_type (ShadowType.IN);
755 scroll.add_with_viewport (viewport);
756 this.prompting_service = new Label (_(""));
758 prompting_service.set_alignment(0, (float )0.5);
760 var vbox_left = new VBox (false, 0);
761 vbox_left.pack_start (search_entry, false, false, 6);
762 vbox_left.pack_start (scroll, true, true, 0);
763 vbox_left.pack_start (prompting_service, false, false, 6);
764 vbox_left.set_size_request (WINDOW_WIDTH, 0);
766 var login_vbox_title = new Label (_("Login: "));
767 label_make_bold (login_vbox_title);
768 login_vbox_title.set_alignment (0, (float) 0.5);
769 var username_label = new Label (_("Username:"));
770 username_label.set_alignment (1, (float) 0.5);
771 this.username_entry = new Entry ();
772 var password_label = new Label (_("Password:"));
773 password_label.set_alignment (1, (float) 0.5);
774 this.password_entry = new Entry ();
775 password_entry.set_invisible_char ('*');
776 password_entry.set_visibility (false);
777 var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
778 var login_table = new Table (3, 3, false);
779 login_table.set_col_spacings (10);
780 login_table.set_row_spacings (10);
781 login_table.attach_defaults (username_label, 0, 1, 0, 1);
782 login_table.attach_defaults (username_entry, 1, 2, 0, 1);
783 login_table.attach_defaults (password_label, 0, 1, 1, 2);
784 login_table.attach_defaults (password_entry, 1, 2, 1, 2);
785 login_table.attach_defaults (remember_checkbutton, 1, 2, 2, 3);
786 var login_vbox_alignment = new Alignment (0, 0, 0, 0);
787 login_vbox_alignment.set_padding (0, 0, 12, 0);
788 login_vbox_alignment.add (login_table);
789 var login_vbox = new VBox (false, 6);
790 login_vbox.pack_start (login_vbox_title, false, true, 0);
791 login_vbox.pack_start (login_vbox_alignment, false, true, 0);
793 var services_vbox_title = new Label (_("Services:"));
794 label_make_bold (services_vbox_title);
795 services_vbox_title.set_alignment (0, (float) 0.5);
796 var services_vbox_alignment = new Alignment (0, 0, 0, 0);
797 services_vbox_alignment.set_padding (0, 0, 12, 0);
798 this.services_internal_vbox = new VBox (true, 6);
799 services_vbox_alignment.add (services_internal_vbox);
800 var services_vbox = new VBox (false, 6);
801 services_vbox.pack_start (services_vbox_title, false, true, 0);
802 services_vbox.pack_start (services_vbox_alignment, false, true, 0);
804 this.vbox_right = new VBox (false, 18);
805 vbox_right.pack_start (login_vbox, false, true, 0);
806 vbox_right.pack_start (services_vbox, false, true, 0);
808 var hbox = new HBox (false, 12);
809 hbox.pack_start (vbox_left, true, true, 0);
810 hbox.pack_start (vbox_right, false, false, 0);
812 var main_vbox = new VBox (false, 0);
813 main_vbox.set_border_width (12);
816 // hide the File | Quit menu item which is now on the Mac Menu
817 Gtk.Widget quit_item = this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
820 Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
821 osxApp.set_menu_bar(menushell);
822 osxApp.set_use_quartz_accelerators(true);
823 osxApp.sync_menu_bar();
826 var menubar = this.ui_manager.get_widget ("/MenuBar");
827 main_vbox.pack_start (menubar, false, false, 0);
829 main_vbox.pack_start (hbox, true, true, 0);
831 main_vbox.show_all();
832 this.vbox_right.hide ();
835 private void set_atk_name_description (Widget widget, string name, string description)
837 var atk_widget = widget.get_accessible ();
839 atk_widget.set_name (name);
840 atk_widget.set_description (description);
843 private void connect_signals()
845 this.destroy.connect (Gtk.main_quit);
846 this.identities_manager.card_list_changed.connect(this.on_card_list_changed);