2 * Copyright (c) 2011-2016, JANET(UK)
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of JANET(UK) nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 using WebProvisioning;
36 public class IdentityManagerView : Window {
37 static MoonshotLogger logger = get_logger("IdentityManagerView");
39 bool use_flat_file_store = false;
41 // The latest year in which Moonshot sources were modified.
42 private static int LATEST_EDIT_YEAR = 2016;
44 public static Gdk.Color white = make_color(65535, 65535, 65535);
46 private const int WINDOW_WIDTH = 700;
47 private const int WINDOW_HEIGHT = 500;
48 protected IdentityManagerApp parent_app;
50 public OSXApplication osxApp;
52 private UIManager ui_manager = new UIManager();
53 private Entry search_entry;
54 private CustomVBox custom_vbox;
55 private VBox service_prompt_vbox;
56 private Button edit_button;
57 private Button remove_button;
59 private Button send_button;
61 private Gtk.ListStore* listmodel;
62 private TreeModelFilter filter;
64 internal IdentityManagerModel identities_manager;
65 private unowned SList<IdCard> candidates;
67 private GLib.Queue<IdentityRequest> request_queue;
69 internal CheckButton remember_identity_binding = null;
71 private IdCard selected_idcard = null;
73 private string import_directory = null;
85 private const string menu_layout =
86 "<menubar name='MenuBar'>" +
87 " <menu name='HelpMenu' action='HelpMenuAction'>" +
88 " <menuitem name='About' action='AboutAction' />" +
92 public IdentityManagerView(IdentityManagerApp app, bool use_flat_file_store) {
94 this.use_flat_file_store = use_flat_file_store;
97 osxApp = OSXApplication.get_instance();
99 identities_manager = parent_app.model;
100 request_queue = new GLib.Queue<IdentityRequest>();
101 this.title = "Moonshot Identity Selector";
102 this.set_position(WindowPosition.CENTER);
103 set_default_size(WINDOW_WIDTH, WINDOW_HEIGHT);
110 private void on_card_list_changed() {
111 logger.trace("on_card_list_changed");
115 private bool visible_func(TreeModel model, TreeIter iter)
120 Columns.IDCARD_COL, out id_card);
125 if (candidates != null)
127 bool is_candidate = false;
128 foreach (IdCard candidate in candidates)
130 if (candidate == id_card)
137 string entry_text = search_entry.get_text();
138 if (entry_text == null || entry_text == "")
143 foreach (string search_text in entry_text.split(" "))
145 if (search_text == "")
149 string search_text_casefold = search_text.casefold();
151 if (id_card.issuer != null)
153 string issuer_casefold = id_card.issuer;
155 if (issuer_casefold.contains(search_text_casefold))
159 if (id_card.display_name != null)
161 string display_name_casefold = id_card.display_name.casefold();
163 if (display_name_casefold.contains(search_text_casefold))
167 if (id_card.services.size > 0)
169 foreach (string service in id_card.services)
171 string service_casefold = service.casefold();
173 if (service_casefold.contains(search_text_casefold))
181 private void setup_list_model()
183 this.listmodel = new Gtk.ListStore(Columns.N_COLUMNS, typeof(IdCard),
188 this.filter = new TreeModelFilter(listmodel, null);
190 filter.set_visible_func(visible_func);
193 private void search_entry_text_changed_cb()
195 this.filter.refilter();
196 redraw_id_card_widgets();
199 private bool search_entry_key_press_event_cb(Gdk.EventKey e)
201 if(Gdk.keyval_name(e.keyval) == "Escape")
202 this.search_entry.set_text("");
204 // Continue processing this event, since the
205 // text entry functionality needs to see it too.
209 private void load_id_cards() {
210 logger.trace("load_id_cards");
213 this.listmodel->clear();
214 LinkedList<IdCard> card_list = identities_manager.get_card_list() ;
215 if (card_list == null) {
219 foreach (IdCard id_card in card_list) {
220 logger.trace(@"load_id_cards: Loading card with display name '$(id_card.display_name)'");
221 add_id_card_data(id_card);
222 add_id_card_widget(id_card);
226 private IdCard update_id_card_data(IdentityDialog dialog, IdCard id_card)
228 id_card.display_name = dialog.display_name;
229 id_card.issuer = dialog.issuer;
230 id_card.username = dialog.username;
231 id_card.password = dialog.password;
232 id_card.store_password = dialog.store_password;
234 id_card.update_services_from_list(dialog.get_services());
236 if (dialog.clear_trust_anchor) {
237 id_card.clear_trust_anchor();
243 private void add_id_card_data(IdCard id_card)
247 this.listmodel->append(out iter);
248 pixbuf = get_pixbuf(id_card);
250 Columns.IDCARD_COL, id_card,
251 Columns.LOGO_COL, pixbuf,
252 Columns.ISSUER_COL, id_card.issuer,
253 Columns.USERNAME_COL, id_card.username,
254 Columns.PASSWORD_COL, id_card.password);
257 private IdCardWidget add_id_card_widget(IdCard id_card)
259 logger.trace("add_id_card_widget: id_card.nai='%s'; selected nai='%s'"
261 this.selected_idcard == null ? "[null selection]" : this.selected_idcard.nai));
264 var id_card_widget = new IdCardWidget(id_card, this);
265 this.custom_vbox.add_id_card_widget(id_card_widget);
266 id_card_widget.expanded.connect(this.widget_selected_cb);
267 id_card_widget.collapsed.connect(this.widget_unselected_cb);
269 if (this.selected_idcard != null && this.selected_idcard.nai == id_card.nai) {
270 logger.trace(@"add_id_card_widget: Expanding selected idcard widget");
271 id_card_widget.expand();
273 return id_card_widget;
276 private void widget_selected_cb(IdCardWidget id_card_widget)
278 logger.trace(@"widget_selected_cb: id_card_widget.id_card.display_name='$(id_card_widget.id_card.display_name)'");
280 this.selected_idcard = id_card_widget.id_card;
281 bool allow_removes = !id_card_widget.id_card.is_no_identity();
282 this.remove_button.set_sensitive(allow_removes);
283 this.edit_button.set_sensitive(true);
284 this.custom_vbox.receive_expanded_event(id_card_widget);
286 if (this.selection_in_progress())
287 this.send_button.set_sensitive(true);
290 private void widget_unselected_cb(IdCardWidget id_card_widget)
292 logger.trace(@"widget_unselected_cb: id_card_widget.id_card.display_name='$(id_card_widget.id_card.display_name)'");
294 this.selected_idcard = null;
295 this.remove_button.set_sensitive(false);
296 this.edit_button.set_sensitive(false);
297 this.custom_vbox.receive_collapsed_event(id_card_widget);
299 this.send_button.set_sensitive(false);
302 public bool add_identity(IdCard id_card, bool force_flat_file_store)
306 * TODO: We should have a confirmation dialog, but currently it will crash on Mac OS
307 * so for now we will install silently
309 var ret = Gtk.ResponseType.YES;
311 Gtk.MessageDialog dialog;
312 IdCard? prev_id = identities_manager.find_id_card(id_card.nai, force_flat_file_store);
313 logger.trace("add_identity(flat=%s, card='%s'): find_id_card returned %s"
314 .printf(force_flat_file_store.to_string(), id_card.display_name, (prev_id != null ? prev_id.display_name : "null")));
316 int flags = prev_id.Compare(id_card);
317 logger.trace("add_identity: compare returned " + flags.to_string());
319 return false; // no changes, no need to update
320 } else if ((flags & (1 << IdCard.DiffFlags.DISPLAY_NAME)) != 0) {
321 dialog = new Gtk.MessageDialog(this,
322 Gtk.DialogFlags.DESTROY_WITH_PARENT,
323 Gtk.MessageType.QUESTION,
324 Gtk.ButtonsType.YES_NO,
325 _("Would you like to replace ID Card '%s' using nai '%s' with the new ID Card '%s'?"),
326 prev_id.display_name,
328 id_card.display_name);
330 dialog = new Gtk.MessageDialog(this,
331 Gtk.DialogFlags.DESTROY_WITH_PARENT,
332 Gtk.MessageType.QUESTION,
333 Gtk.ButtonsType.YES_NO,
334 _("Would you like to update ID Card '%s' using nai '%s'?"),
335 id_card.display_name,
339 dialog = new Gtk.MessageDialog(this,
340 Gtk.DialogFlags.DESTROY_WITH_PARENT,
341 Gtk.MessageType.QUESTION,
342 Gtk.ButtonsType.YES_NO,
343 _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
344 id_card.display_name);
346 var ret = dialog.run();
350 if (ret == Gtk.ResponseType.YES) {
351 this.identities_manager.add_card(id_card, force_flat_file_store);
357 private void add_identity_cb()
359 var dialog = new IdentityDialog(this);
360 int result = ResponseType.CANCEL;
361 while (!dialog.complete)
362 result = dialog.run();
365 case ResponseType.OK:
366 this.identities_manager.add_card(update_id_card_data(dialog, new IdCard()), false);
374 private void edit_identity_cb(IdCard card)
376 var dialog = new IdentityDialog.with_idcard(card, _("Edit Identity"), this);
377 int result = ResponseType.CANCEL;
378 while (!dialog.complete)
379 result = dialog.run();
382 case ResponseType.OK:
383 this.identities_manager.update_card(update_id_card_data(dialog, card));
391 private void remove_identity(IdCard id_card)
393 logger.trace(@"remove_identity: id_card.display_name='$(id_card.display_name)'");
394 if (id_card != this.selected_idcard) {
395 logger.error("remove_identity: id_card != this.selected_idcard!");
398 this.selected_idcard = null;
399 this.identities_manager.remove_card(id_card);
401 // Nothing is selected, so disable buttons
402 this.edit_button.set_sensitive(false);
403 this.remove_button.set_sensitive(false);
404 this.send_button.set_sensitive(false);
407 private void redraw_id_card_widgets()
412 this.custom_vbox.clear();
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(IdCard id_card)
429 bool remove = WarningDialog.confirm(this,
430 Markup.printf_escaped(
431 "<span font-weight='heavy'>You are about to remove the identity '%s'.</span>",
432 id_card.display_name)
433 + "\n\nAre you sure you want to do this?",
436 remove_identity(id_card);
439 private void set_prompting_service(string service)
441 clear_selection_prompts();
443 var prompting_service = new Label(_("Identity requested for service:\n%s").printf(service));
444 prompting_service.set_line_wrap(true);
447 prompting_service.set_alignment(0, (float )0.5);
449 var selection_prompt = new Label(_("Select your identity:"));
450 selection_prompt.set_alignment(0, 1);
452 this.service_prompt_vbox.pack_start(prompting_service, false, false, 12);
453 this.service_prompt_vbox.pack_start(selection_prompt, false, false, 2);
454 this.service_prompt_vbox.show_all();
457 private void clear_selection_prompts()
459 var list = service_prompt_vbox.get_children();
460 foreach (Widget w in list)
462 service_prompt_vbox.remove(w);
467 public void queue_identity_request(IdentityRequest request)
469 bool queue_was_empty = !this.selection_in_progress();
470 this.request_queue.push_tail(request);
473 { /* setup widgets */
474 candidates = request.candidates;
476 redraw_id_card_widgets();
477 set_prompting_service(request.service);
478 remember_identity_binding.show();
480 if (this.selected_idcard != null
481 && this.custom_vbox.find_idcard_widget(this.selected_idcard) != null) {
482 // A widget is already selected, and has not been filtered out of the display via search
483 send_button.set_sensitive(true);
491 /** Makes the window visible, or at least, notifies the user that the window
492 * wants to be visible.
494 * This differs from show() in that show() does not guarantee that the
495 * window will be moved to the foreground. Actually, neither does this
496 * method, because the user's settings and window manager may affect the
497 * behavior significantly.
499 public void make_visible()
501 set_urgency_hint(true);
505 public IdCard check_add_password(IdCard identity, IdentityRequest request, IdentityManagerModel model)
507 logger.trace(@"check_add_password");
508 IdCard retval = identity;
509 bool idcard_has_pw = (identity.password != null) && (identity.password != "");
510 bool request_has_pw = (request.password != null) && (request.password != "");
511 if ((!idcard_has_pw) && (!identity.is_no_identity())) {
512 if (request_has_pw) {
513 identity.password = request.password;
514 retval = model.update_card(identity);
516 var dialog = new AddPasswordDialog(identity, request);
517 var result = dialog.run();
520 case ResponseType.OK:
521 identity.password = dialog.password;
522 identity.store_password = dialog.remember;
524 identity.temporary = false;
525 retval = model.update_card(identity);
537 private void send_identity_cb(IdCard id)
539 return_if_fail(this.selection_in_progress());
541 if (!check_and_confirm_trust_anchor(id)) {
542 // Allow user to pick again
546 var request = this.request_queue.pop_head();
547 var identity = check_add_password(id, request, identities_manager);
548 send_button.set_sensitive(false);
552 if (!this.selection_in_progress())
555 clear_selection_prompts();
556 if (!parent_app.explicitly_launched) {
557 // The following occasionally causes the app to exit without sending the dbus
558 // reply, so for now we just don't exit
564 IdentityRequest next = this.request_queue.peek_head();
565 candidates = next.candidates;
566 set_prompting_service(next.service);
569 redraw_id_card_widgets();
571 if ((identity != null) && (!identity.is_no_identity()))
572 parent_app.default_id_card = identity;
574 request.return_identity(identity, remember_identity_binding.active);
576 remember_identity_binding.active = false;
577 remember_identity_binding.hide();
580 private bool check_and_confirm_trust_anchor(IdCard id)
582 if (!id.trust_anchor.is_empty() && id.trust_anchor.get_anchor_type() == TrustAnchor.TrustAnchorType.SERVER_CERT) {
583 if (!id.trust_anchor.user_verified) {
586 int result = ResponseType.CANCEL;
587 var dialog = new TrustAnchorDialog(id, this);
588 while (!dialog.complete)
589 result = dialog.run();
592 case ResponseType.OK:
593 id.trust_anchor.user_verified = true;
607 private void on_about_action()
609 string copyright = "Copyright (c) 2011, %d JANET".printf(LATEST_EDIT_YEAR);
613 Copyright (c) 2011, %d JANET(UK)
616 Redistribution and use in source and binary forms, with or without
617 modification, are permitted provided that the following conditions
620 1. Redistributions of source code must retain the above copyright
621 notice, this list of conditions and the following disclaimer.
623 2. Redistributions in binary form must reproduce the above copyright
624 notice, this list of conditions and the following disclaimer in the
625 documentation and/or other materials provided with the distribution.
627 3. Neither the name of JANET(UK) nor the names of its contributors
628 may be used to endorse or promote products derived from this software
629 without specific prior written permission.
631 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
632 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
633 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
634 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
635 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
636 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
637 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
638 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
639 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
640 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
642 """.printf(LATEST_EDIT_YEAR);
644 AboutDialog about = new AboutDialog();
646 about.set_comments(_("Moonshot project UI"));
647 about.set_copyright(copyright);
648 about.set_website(Config.PACKAGE_URL);
649 about.set_website_label(_("Visit the Moonshot project web site"));
651 // Note: The package version is configured at the top of moonshot/ui/configure.ac
652 about.set_version(Config.PACKAGE_VERSION);
653 about.set_license(license);
654 about.set_modal(true);
655 about.set_transient_for(this);
656 about.response.connect((a, b) => {about.destroy();});
657 about.modify_bg(StateType.NORMAL, white);
662 private Gtk.ActionEntry[] create_actions() {
663 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
665 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
670 Gtk.ActionEntry about = { "AboutAction",
678 N_("About this application"),
686 private void create_ui_manager()
688 Gtk.ActionGroup action_group = new Gtk.ActionGroup("GeneralActionGroup");
689 action_group.add_actions(create_actions(), this);
690 ui_manager.insert_action_group(action_group, 0);
693 ui_manager.add_ui_from_string(menu_layout, -1);
697 stderr.printf("%s\n", e.message);
698 logger.error("create_ui_manager: Caught error: " + e.message);
700 ui_manager.ensure_update();
703 private void build_ui()
705 // Note: On Debian7/Gtk+2, the menu bar remains gray. This doesn't happen on Debian8/Gtk+3.
706 this.modify_bg(StateType.NORMAL, white);
712 int button_width = 1;
714 Table top_table = new Table(num_rows, 10, false);
715 top_table.set_border_width(12);
717 AttachOptions fill_and_expand = AttachOptions.EXPAND | AttachOptions.FILL;
718 AttachOptions fill = AttachOptions.FILL;
721 service_prompt_vbox = new VBox(false, 0);
722 top_table.attach(service_prompt_vbox, 0, 1, row, row + 1, fill_and_expand, fill_and_expand, 12, 0);
725 string search_tooltip_text = _("Search for an identity or service");
726 this.search_entry = new Entry();
728 set_atk_name_description(search_entry, _("Search entry"), _("Search for a specific ID Card"));
729 this.search_entry.set_icon_from_pixbuf(EntryIconPosition.SECONDARY,
730 find_icon_sized("edit-find", Gtk.IconSize.MENU));
731 this.search_entry.set_icon_tooltip_text(EntryIconPosition.SECONDARY,
732 search_tooltip_text);
734 this.search_entry.set_tooltip_text(search_tooltip_text);
736 this.search_entry.set_icon_sensitive(EntryIconPosition.SECONDARY, false);
738 this.search_entry.notify["text"].connect(search_entry_text_changed_cb);
739 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
740 this.search_entry.set_width_chars(24);
742 var search_label_markup =_("<small>") + search_tooltip_text + _("</small>");
743 var full_search_label = new Label(null);
744 full_search_label.set_markup(search_label_markup);
745 full_search_label.set_alignment(1, 0);
747 var search_vbox = new VBox(false, 0);
748 search_vbox.pack_start(search_entry, false, false, 0);
749 var search_spacer = new Alignment(0, 0, 0, 0);
750 search_spacer.set_size_request(0, 2);
751 search_vbox.pack_start(search_spacer, false, false, 0);
752 search_vbox.pack_start(full_search_label, false, false, 0);
754 // Overlap with the service_prompt_box
755 top_table.attach(search_vbox, 5, num_cols - button_width, row - 1, row + 1, fill_and_expand, fill, 0, 12);
758 this.custom_vbox = new CustomVBox(this, false, 2);
760 var viewport = new Viewport(null, null);
761 viewport.set_border_width(2);
762 viewport.set_shadow_type(ShadowType.NONE);
763 viewport.add(custom_vbox);
764 var id_scrollwin = new ScrolledWindow(null, null);
765 id_scrollwin.set_policy(PolicyType.NEVER, PolicyType.AUTOMATIC);
766 id_scrollwin.set_shadow_type(ShadowType.IN);
767 id_scrollwin.add_with_viewport(viewport);
768 top_table.attach(id_scrollwin, 0, num_cols - 1, row, num_rows - 1, fill_and_expand, fill_and_expand, 6, 0);
770 // Right below id_scrollwin:
771 remember_identity_binding = new CheckButton.with_label(_("Remember my identity choice for this service"));
772 remember_identity_binding.active = false;
773 top_table.attach(remember_identity_binding, 0, num_cols / 2, num_rows - 1, num_rows, fill_and_expand, fill_and_expand, 3, 0);
775 var add_button = new Button.with_label(_("Add"));
776 add_button.clicked.connect((w) => {add_identity_cb();});
777 top_table.attach(make_rigid(add_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
780 var import_button = new Button.with_label(_("Import"));
781 import_button.clicked.connect((w) => {import_identities_cb();});
782 top_table.attach(make_rigid(import_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
785 this.edit_button = new Button.with_label(_("Edit"));
786 edit_button.clicked.connect((w) => {edit_identity_cb(this.selected_idcard);});
787 edit_button.set_sensitive(false);
788 top_table.attach(make_rigid(edit_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
791 this.remove_button = new Button.with_label(_("Remove"));
792 remove_button.clicked.connect((w) => {remove_identity_cb(this.selected_idcard);});
793 remove_button.set_sensitive(false);
794 top_table.attach(make_rigid(remove_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
797 // push the send button down another row.
799 this.send_button = new Button.with_label(_("Send"));
800 send_button.clicked.connect((w) => {send_identity_cb(this.selected_idcard);});
801 // send_button.set_visible(false);
802 send_button.set_sensitive(false);
803 top_table.attach(make_rigid(send_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
806 var main_vbox = new VBox(false, 0);
809 // hide the File | Quit menu item which is now on the Mac Menu
810 // Gtk.Widget quit_item = this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
813 Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
814 menushell.modify_bg(StateType.NORMAL, white);
816 osxApp.set_menu_bar(menushell);
817 osxApp.set_use_quartz_accelerators(true);
818 osxApp.sync_menu_bar();
821 var menubar = this.ui_manager.get_widget("/MenuBar");
822 main_vbox.pack_start(menubar, false, false, 0);
823 menubar.modify_bg(StateType.NORMAL, white);
825 main_vbox.pack_start(top_table, true, true, 6);
828 main_vbox.show_all();
830 if (!this.selection_in_progress())
831 remember_identity_binding.hide();
834 internal bool selection_in_progress() {
835 return !this.request_queue.is_empty();
838 private void set_atk_name_description(Widget widget, string name, string description)
840 var atk_widget = widget.get_accessible();
842 atk_widget.set_name(name);
843 atk_widget.set_description(description);
846 private void connect_signals()
848 this.destroy.connect(() => {
849 logger.trace("Destroy event; calling Gtk.main_quit()");
852 this.identities_manager.card_list_changed.connect(this.on_card_list_changed);
853 this.delete_event.connect(() => {return confirm_quit();});
856 private bool confirm_quit() {
857 logger.trace("delete_event intercepted; selection_in_progress()=" + selection_in_progress().to_string());
859 if (selection_in_progress()) {
860 var result = WarningDialog.confirm(this,
861 Markup.printf_escaped(
862 _("<span font-weight='heavy'>Do you wish to use the %s service?</span>"),
863 this.request_queue.peek_head().service)
864 + _("\n\nSelect Yes to select an ID for this service, or No to cancel"),
865 "close_moonshot_window");
867 // Prevent other handlers from handling this event; this keeps the window open.
872 // Allow the window deletion to proceed.
876 private static Widget make_rigid(Button button)
878 // Hack to prevent the button from growing vertically
879 VBox fixed_height = new VBox(false, 0);
880 fixed_height.pack_start(button, false, false, 0);
885 private void import_identities_cb() {
886 var dialog = new FileChooserDialog("Import File",
888 FileChooserAction.OPEN,
889 _("Cancel"),ResponseType.CANCEL,
890 _("Save"), ResponseType.ACCEPT,
893 if (import_directory != null) {
894 dialog.set_current_folder(import_directory);
897 if (dialog.run() == ResponseType.ACCEPT)
899 // Save the parent directory to use as default for next save
900 string filename = dialog.get_filename();
901 var file = File.new_for_path(filename);
902 import_directory = file.get_parent().get_path();
904 int import_count = 0;
906 var webp = new Parser(filename);
909 logger.trace(@"import_identities_cb: Have $(webp.cards.length) IdCards");
910 foreach (IdCard card in webp.cards)
914 logger.trace(@"import_identities_cb: Skipping null IdCard");
918 bool result = add_identity(card, use_flat_file_store);
920 logger.trace(@"import_identities_cb: Added or updated '$(card.display_name)'");
924 logger.trace(@"import_identities_cb: Did not add or update '$(card.display_name)'");
927 var msg_dialog = new Gtk.MessageDialog(this,
928 Gtk.DialogFlags.DESTROY_WITH_PARENT,
929 Gtk.MessageType.INFO,
931 _("Import completed. %d Identities were added or updated."),
934 msg_dialog.destroy();