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_card = 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);
108 report_duplicate_nais();
111 private void report_duplicate_nais() {
112 ArrayList<ArrayList<IdCard>> duplicates;
113 identities_manager.find_duplicate_nai_sets(out duplicates);
114 foreach (ArrayList<IdCard> list in duplicates) {
115 string message = _("The following identities use the same Network Access Identifier (NAI),\n'%s'.").printf(list.get(0).nai)
116 + _("\n\nDuplicate NAIs are not allowed. Please remove identities you don't need, or modify")
117 + _(" user ID or issuer fields so that they are no longer the same NAI.");
119 foreach (var card in list) {
120 message += "\n\nDisplay Name: '%s'\nServices:\n %s".printf(card.display_name, card.get_services_string(",\n "));
122 var msg_dialog = new Gtk.MessageDialog(this,
123 Gtk.DialogFlags.DESTROY_WITH_PARENT,
124 Gtk.MessageType.INFO,
128 msg_dialog.destroy();
132 private void on_card_list_changed() {
133 logger.trace("on_card_list_changed");
137 private bool visible_func(TreeModel model, TreeIter iter)
142 Columns.IDCARD_COL, out id_card);
147 if (candidates != null)
149 bool is_candidate = false;
150 foreach (IdCard candidate in candidates)
152 if (candidate == id_card)
159 string entry_text = search_entry.get_text();
160 if (entry_text == null || entry_text == "")
165 foreach (string search_text in entry_text.split(" "))
167 if (search_text == "")
171 string search_text_casefold = search_text.casefold();
173 if (id_card.issuer != null)
175 string issuer_casefold = id_card.issuer;
177 if (issuer_casefold.contains(search_text_casefold))
181 if (id_card.display_name != null)
183 string display_name_casefold = id_card.display_name.casefold();
185 if (display_name_casefold.contains(search_text_casefold))
189 if (id_card.services.size > 0)
191 foreach (string service in id_card.services)
193 string service_casefold = service.casefold();
195 if (service_casefold.contains(search_text_casefold))
203 private void setup_list_model()
205 this.listmodel = new Gtk.ListStore(Columns.N_COLUMNS, typeof(IdCard),
210 this.filter = new TreeModelFilter(listmodel, null);
212 filter.set_visible_func(visible_func);
215 private void search_entry_text_changed_cb()
217 this.filter.refilter();
218 redraw_id_card_widgets();
221 private bool search_entry_key_press_event_cb(Gdk.EventKey e)
223 if(Gdk.keyval_name(e.keyval) == "Escape")
224 this.search_entry.set_text("");
226 // Continue processing this event, since the
227 // text entry functionality needs to see it too.
231 private void load_id_cards() {
232 logger.trace("load_id_cards");
235 this.listmodel->clear();
236 LinkedList<IdCard> card_list = identities_manager.get_card_list() ;
237 if (card_list == null) {
241 foreach (IdCard id_card in card_list) {
242 logger.trace(@"load_id_cards: Loading card with display name '$(id_card.display_name)'");
243 add_id_card_data(id_card);
244 add_id_card_widget(id_card);
248 private IdCard update_id_card_data(IdentityDialog dialog, IdCard id_card)
250 id_card.display_name = dialog.display_name;
251 id_card.issuer = dialog.issuer;
252 id_card.username = dialog.username;
253 id_card.password = dialog.password;
254 id_card.store_password = dialog.store_password;
256 id_card.update_services_from_list(dialog.get_services());
258 if (dialog.clear_trust_anchor) {
259 id_card.clear_trust_anchor();
265 private void add_id_card_data(IdCard id_card)
269 this.listmodel->append(out iter);
270 pixbuf = get_pixbuf(id_card);
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 IdCardWidget add_id_card_widget(IdCard id_card)
281 logger.trace("add_id_card_widget: id_card.nai='%s'; selected nai='%s'"
283 this.selected_card == null ? "[null selection]" : this.selected_card.nai));
286 var id_card_widget = new IdCardWidget(id_card, this);
287 this.custom_vbox.add_id_card_widget(id_card_widget);
288 id_card_widget.expanded.connect(this.widget_selected_cb);
289 id_card_widget.collapsed.connect(this.widget_unselected_cb);
291 if (this.selected_card != null && this.selected_card.nai == id_card.nai) {
292 logger.trace(@"add_id_card_widget: Expanding selected idcard widget");
293 id_card_widget.expand();
295 // After a card is added, modified, or deleted, we reload all the cards.
296 // (I'm not sure why, or if it's necessary to do this.) This means that the
297 // selected_card may now point to a card instance that's not in the current list.
298 // Hence the only way to carry the selection across reloads is to identify
299 // the selected card by its NAI. And hence we need to reset what our idea of the
300 // "selected card" is.
301 // There should be a better way to do this, especially since we're not great
302 // at preventing duplicate NAIs.
303 this.selected_card = id_card;
305 return id_card_widget;
308 private void widget_selected_cb(IdCardWidget id_card_widget)
310 logger.trace(@"widget_selected_cb: id_card_widget.id_card.display_name='$(id_card_widget.id_card.display_name)'");
312 this.selected_card = id_card_widget.id_card;
313 bool allow_removes = !id_card_widget.id_card.is_no_identity();
314 this.remove_button.set_sensitive(allow_removes);
315 this.edit_button.set_sensitive(true);
316 this.custom_vbox.receive_expanded_event(id_card_widget);
318 if (this.selection_in_progress())
319 this.send_button.set_sensitive(true);
322 private void widget_unselected_cb(IdCardWidget id_card_widget)
324 logger.trace(@"widget_unselected_cb: id_card_widget.id_card.display_name='$(id_card_widget.id_card.display_name)'");
326 this.selected_card = null;
327 this.remove_button.set_sensitive(false);
328 this.edit_button.set_sensitive(false);
329 this.custom_vbox.receive_collapsed_event(id_card_widget);
331 this.send_button.set_sensitive(false);
334 public bool add_identity(IdCard id_card, bool force_flat_file_store, out ArrayList<IdCard>? old_duplicates=null)
338 * TODO: We should have a confirmation dialog, but currently it will crash on Mac OS
339 * so for now we will install silently
341 var ret = Gtk.ResponseType.YES;
343 Gtk.MessageDialog dialog;
344 IdCard? prev_id = identities_manager.find_id_card(id_card.nai, force_flat_file_store);
345 logger.trace("add_identity(flat=%s, card='%s'): find_id_card returned %s"
346 .printf(force_flat_file_store.to_string(), id_card.display_name, (prev_id != null ? prev_id.display_name : "null")));
348 int flags = prev_id.Compare(id_card);
349 logger.trace("add_identity: compare returned " + flags.to_string());
351 if (&old_duplicates != null) {
352 old_duplicates = new ArrayList<IdCard>();
355 return false; // no changes, no need to update
356 } else if ((flags & (1 << IdCard.DiffFlags.DISPLAY_NAME)) != 0) {
357 dialog = new Gtk.MessageDialog(this,
358 Gtk.DialogFlags.DESTROY_WITH_PARENT,
359 Gtk.MessageType.QUESTION,
360 Gtk.ButtonsType.YES_NO,
361 _("Would you like to replace ID Card '%s' using nai '%s' with the new ID Card '%s'?"),
362 prev_id.display_name,
364 id_card.display_name);
366 dialog = new Gtk.MessageDialog(this,
367 Gtk.DialogFlags.DESTROY_WITH_PARENT,
368 Gtk.MessageType.QUESTION,
369 Gtk.ButtonsType.YES_NO,
370 _("Would you like to update ID Card '%s' using nai '%s'?"),
371 id_card.display_name,
375 dialog = new Gtk.MessageDialog(this,
376 Gtk.DialogFlags.DESTROY_WITH_PARENT,
377 Gtk.MessageType.QUESTION,
378 Gtk.ButtonsType.YES_NO,
379 _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
380 id_card.display_name);
382 var ret = dialog.run();
386 if (ret == Gtk.ResponseType.YES) {
387 this.identities_manager.add_card(id_card, force_flat_file_store, out old_duplicates);
391 if (&old_duplicates != null) {
392 old_duplicates = new ArrayList<IdCard>();
398 private void add_identity_cb()
400 var dialog = new IdentityDialog(this);
401 int result = ResponseType.CANCEL;
402 while (!dialog.complete)
403 result = dialog.run();
406 case ResponseType.OK:
407 this.identities_manager.add_card(update_id_card_data(dialog, new IdCard()), false);
415 private void edit_identity_cb(IdCard card)
417 var dialog = new IdentityDialog.with_idcard(card, _("Edit Identity"), this);
418 int result = ResponseType.CANCEL;
419 while (!dialog.complete)
420 result = dialog.run();
423 case ResponseType.OK:
424 this.identities_manager.update_card(update_id_card_data(dialog, card));
426 // Make sure we haven't created a duplicate NAI via this update.
427 report_duplicate_nais();
435 private void remove_identity(IdCard id_card)
437 logger.trace(@"remove_identity: id_card.display_name='$(id_card.display_name)'");
439 this.selected_card = null;
440 this.identities_manager.remove_card(id_card);
442 // Nothing is selected, so disable buttons
443 this.edit_button.set_sensitive(false);
444 this.remove_button.set_sensitive(false);
445 this.send_button.set_sensitive(false);
448 private void redraw_id_card_widgets()
453 this.custom_vbox.clear();
455 if (filter.get_iter_first(out iter))
460 Columns.IDCARD_COL, out id_card);
462 add_id_card_widget(id_card);
464 while (filter.iter_next(ref iter));
468 private void remove_identity_cb(IdCard id_card)
470 bool remove = WarningDialog.confirm(this,
471 Markup.printf_escaped(
472 "<span font-weight='heavy'>" + _("You are about to remove the identity '%s'.") + "</span>",
473 id_card.display_name)
474 + "\n\n" + _("Are you sure you want to do this?"),
477 remove_identity(id_card);
480 private void set_prompting_service(string service)
482 clear_selection_prompts();
484 var prompting_service = new Label(_("Identity requested for service:\n%s").printf(service));
485 prompting_service.set_line_wrap(true);
488 prompting_service.set_alignment(0, (float )0.5);
490 var selection_prompt = new Label(_("Select your identity:"));
491 selection_prompt.set_alignment(0, 1);
493 this.service_prompt_vbox.pack_start(prompting_service, false, false, 12);
494 this.service_prompt_vbox.pack_start(selection_prompt, false, false, 2);
495 this.service_prompt_vbox.show_all();
498 private void clear_selection_prompts()
500 var list = service_prompt_vbox.get_children();
501 foreach (Widget w in list)
503 service_prompt_vbox.remove(w);
508 public void queue_identity_request(IdentityRequest request)
510 bool queue_was_empty = !this.selection_in_progress();
511 this.request_queue.push_tail(request);
514 { /* setup widgets */
515 candidates = request.candidates;
517 redraw_id_card_widgets();
518 set_prompting_service(request.service);
519 remember_identity_binding.show();
521 if (this.custom_vbox.find_idcard_widget(this.selected_card) != null) {
522 // A widget is already selected, and has not been filtered out of the display via search
523 send_button.set_sensitive(true);
531 /** Makes the window visible, or at least, notifies the user that the window
532 * wants to be visible.
534 * This differs from show() in that show() does not guarantee that the
535 * window will be moved to the foreground. Actually, neither does this
536 * method, because the user's settings and window manager may affect the
537 * behavior significantly.
539 public void make_visible()
541 set_urgency_hint(true);
545 public IdCard check_add_password(IdCard identity, IdentityRequest request, IdentityManagerModel model)
547 logger.trace(@"check_add_password");
548 IdCard retval = identity;
549 bool idcard_has_pw = (identity.password != null) && (identity.password != "");
550 bool request_has_pw = (request.password != null) && (request.password != "");
551 if ((!idcard_has_pw) && (!identity.is_no_identity())) {
552 if (request_has_pw) {
553 identity.password = request.password;
554 retval = model.update_card(identity);
556 var dialog = new AddPasswordDialog(identity, request);
557 var result = dialog.run();
560 case ResponseType.OK:
561 identity.password = dialog.password;
562 identity.store_password = dialog.remember;
564 identity.temporary = false;
565 retval = model.update_card(identity);
577 private void send_identity_cb(IdCard id)
579 return_if_fail(this.selection_in_progress());
581 var request = this.request_queue.pop_head();
582 var identity = check_add_password(id, request, identities_manager);
583 send_button.set_sensitive(false);
587 if (!this.selection_in_progress())
590 clear_selection_prompts();
591 if (!parent_app.explicitly_launched) {
592 // The following occasionally causes the app to exit without sending the dbus
593 // reply, so for now we just don't exit
599 IdentityRequest next = this.request_queue.peek_head();
600 candidates = next.candidates;
601 set_prompting_service(next.service);
604 redraw_id_card_widgets();
606 if ((identity != null) && (!identity.is_no_identity()))
607 parent_app.default_id_card = identity;
609 request.return_identity(identity, remember_identity_binding.active);
611 remember_identity_binding.active = false;
612 remember_identity_binding.hide();
615 private void on_about_action()
617 string copyright = "Copyright (c) 2011, %d JANET".printf(LATEST_EDIT_YEAR);
621 Copyright (c) 2011, %d 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
650 """.printf(LATEST_EDIT_YEAR);
652 AboutDialog about = new AboutDialog();
654 about.set_comments(_("Moonshot project UI"));
655 about.set_copyright(copyright);
656 about.set_website(Config.PACKAGE_URL);
657 about.set_website_label(_("Visit the Moonshot project web site"));
659 // Note: The package version is configured at the top of moonshot/ui/configure.ac
660 about.set_version(Config.PACKAGE_VERSION);
661 about.set_license(license);
662 about.set_modal(true);
663 about.set_transient_for(this);
664 about.response.connect((a, b) => {about.destroy();});
670 private Gtk.ActionEntry[] create_actions() {
671 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
673 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
678 // Pick up the translated version of the name, if any
679 helpmenu.label = dgettext(null, helpmenu.label);
682 Gtk.ActionEntry about = { "AboutAction",
690 N_("About this application"),
693 about.label = dgettext(null, about.label);
700 private void create_ui_manager()
702 Gtk.ActionGroup action_group = new Gtk.ActionGroup("GeneralActionGroup");
703 action_group.add_actions(create_actions(), this);
704 ui_manager.insert_action_group(action_group, 0);
707 ui_manager.add_ui_from_string(menu_layout, -1);
711 stderr.printf("%s\n", e.message);
712 logger.error("create_ui_manager: Caught error: " + e.message);
714 ui_manager.ensure_update();
717 private void build_ui()
725 int button_width = 1;
727 Table top_table = new Table(num_rows, 10, false);
728 top_table.set_border_width(12);
730 AttachOptions fill_and_expand = AttachOptions.EXPAND | AttachOptions.FILL;
731 AttachOptions fill = AttachOptions.FILL;
734 service_prompt_vbox = new VBox(false, 0);
735 top_table.attach(service_prompt_vbox, 0, 1, row, row + 1, fill_and_expand, fill_and_expand, 12, 0);
738 string search_tooltip_text = _("Search for an identity or service");
739 this.search_entry = new Entry();
741 set_atk_name_description(search_entry, _("Search entry"), _("Search for a specific ID Card"));
742 this.search_entry.set_icon_from_pixbuf(EntryIconPosition.SECONDARY,
743 find_icon_sized("edit-find", Gtk.IconSize.MENU));
744 this.search_entry.set_icon_tooltip_text(EntryIconPosition.SECONDARY,
745 search_tooltip_text);
747 this.search_entry.set_tooltip_text(search_tooltip_text);
749 this.search_entry.set_icon_sensitive(EntryIconPosition.SECONDARY, false);
751 this.search_entry.notify["text"].connect(search_entry_text_changed_cb);
752 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
753 this.search_entry.set_width_chars(24);
755 var search_label_markup ="<small>" + search_tooltip_text + "</small>";
756 var full_search_label = new Label(null);
757 full_search_label.set_markup(search_label_markup);
758 full_search_label.set_alignment(1, 0);
760 var search_vbox = new VBox(false, 0);
761 search_vbox.pack_start(search_entry, false, false, 0);
762 var search_spacer = new Alignment(0, 0, 0, 0);
763 search_spacer.set_size_request(0, 2);
764 search_vbox.pack_start(search_spacer, false, false, 0);
765 search_vbox.pack_start(full_search_label, false, false, 0);
767 // Overlap with the service_prompt_box
768 top_table.attach(search_vbox, 5, num_cols - button_width, row - 1, row + 1, fill_and_expand, fill, 0, 12);
771 this.custom_vbox = new CustomVBox(this, false, 2);
773 var viewport = new Viewport(null, null);
774 viewport.set_border_width(2);
775 viewport.set_shadow_type(ShadowType.NONE);
776 viewport.add(custom_vbox);
777 var id_scrollwin = new ScrolledWindow(null, null);
778 id_scrollwin.set_policy(PolicyType.NEVER, PolicyType.AUTOMATIC);
779 id_scrollwin.set_shadow_type(ShadowType.IN);
780 id_scrollwin.add_with_viewport(viewport);
781 top_table.attach(id_scrollwin, 0, num_cols - 1, row, num_rows - 1, fill_and_expand, fill_and_expand, 6, 0);
783 // Right below id_scrollwin:
784 remember_identity_binding = new CheckButton.with_label(_("Remember my identity choice for this service"));
785 remember_identity_binding.active = false;
786 top_table.attach(remember_identity_binding, 0, num_cols / 2, num_rows - 1, num_rows, fill_and_expand, fill_and_expand, 3, 0);
788 var add_button = new Button.with_label(_("Add"));
789 add_button.clicked.connect((w) => {add_identity_cb();});
790 top_table.attach(make_rigid(add_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
793 var import_button = new Button.with_label(_("Import"));
794 import_button.clicked.connect((w) => {import_identities_cb();});
795 top_table.attach(make_rigid(import_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
798 this.edit_button = new Button.with_label(_("Edit"));
799 edit_button.clicked.connect((w) => {edit_identity_cb(this.selected_card);});
800 edit_button.set_sensitive(false);
801 top_table.attach(make_rigid(edit_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
804 this.remove_button = new Button.with_label(_("Remove"));
805 remove_button.clicked.connect((w) => {remove_identity_cb(this.selected_card);});
806 remove_button.set_sensitive(false);
807 top_table.attach(make_rigid(remove_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
810 // push the send button down another row.
812 this.send_button = new Button.with_label(_("Send"));
813 send_button.clicked.connect((w) => {send_identity_cb(this.selected_card);});
814 // send_button.set_visible(false);
815 send_button.set_sensitive(false);
816 top_table.attach(make_rigid(send_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
819 var main_vbox = new VBox(false, 0);
822 // hide the File | Quit menu item which is now on the Mac Menu
823 // Gtk.Widget quit_item = this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
826 Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
828 osxApp.set_menu_bar(menushell);
829 osxApp.set_use_quartz_accelerators(true);
830 osxApp.sync_menu_bar();
833 var menubar = this.ui_manager.get_widget("/MenuBar");
834 main_vbox.pack_start(menubar, false, false, 0);
835 set_bg_color(menubar);
837 main_vbox.pack_start(top_table, true, true, 6);
840 main_vbox.show_all();
842 if (!this.selection_in_progress())
843 remember_identity_binding.hide();
846 internal bool selection_in_progress() {
847 return !this.request_queue.is_empty();
850 private void set_atk_name_description(Widget widget, string name, string description)
852 var atk_widget = widget.get_accessible();
854 atk_widget.set_name(name);
855 atk_widget.set_description(description);
858 private void connect_signals()
860 this.destroy.connect(() => {
861 logger.trace("Destroy event; calling Gtk.main_quit()");
864 this.identities_manager.card_list_changed.connect(this.on_card_list_changed);
865 this.delete_event.connect(() => {return confirm_quit();});
868 private bool confirm_quit() {
869 logger.trace("delete_event intercepted; selection_in_progress()=" + selection_in_progress().to_string());
871 if (selection_in_progress()) {
872 var result = WarningDialog.confirm(this,
873 Markup.printf_escaped(
874 "<span font-weight='heavy'>" + _("Do you wish to use the %s service?") + "</span>",
875 this.request_queue.peek_head().service)
876 + "\n\n" + _("Select Yes to select an ID for this service, or No to cancel"),
877 "close_moonshot_window");
879 // Prevent other handlers from handling this event; this keeps the window open.
884 // Allow the window deletion to proceed.
888 private static Widget make_rigid(Button button)
890 // Hack to prevent the button from growing vertically
891 VBox fixed_height = new VBox(false, 0);
892 fixed_height.pack_start(button, false, false, 0);
897 private void import_identities_cb() {
898 var dialog = new FileChooserDialog(_("Import File"),
900 FileChooserAction.OPEN,
901 _("Cancel"),ResponseType.CANCEL,
902 _("Open"), ResponseType.ACCEPT,
905 if (import_directory != null) {
906 dialog.set_current_folder(import_directory);
909 if (dialog.run() == ResponseType.ACCEPT)
911 // Save the parent directory to use as default for next save
912 string filename = dialog.get_filename();
913 var file = File.new_for_path(filename);
914 import_directory = file.get_parent().get_path();
916 int import_count = 0;
918 var webp = new Parser(filename);
921 logger.trace(@"import_identities_cb: Have $(webp.cards.length) IdCards");
922 foreach (IdCard card in webp.cards)
926 logger.trace(@"import_identities_cb: Skipping null IdCard");
930 if (!card.trust_anchor.is_empty()) {
931 string ta_datetime_added = TrustAnchor.format_datetime_now();
932 card.trust_anchor.set_datetime_added(ta_datetime_added);
933 logger.trace("import_identities_cb : Set ta_datetime_added for '%s' to '%s'; ca_cert='%s'; server_cert='%s'"
934 .printf(card.display_name, ta_datetime_added, card.trust_anchor.ca_cert, card.trust_anchor.server_cert));
938 bool result = add_identity(card, use_flat_file_store);
940 logger.trace(@"import_identities_cb: Added or updated '$(card.display_name)'");
944 logger.trace(@"import_identities_cb: Did not add or update '$(card.display_name)'");
947 if (import_count == 0) {
948 var msg_dialog = new Gtk.MessageDialog(this,
949 Gtk.DialogFlags.DESTROY_WITH_PARENT,
950 Gtk.MessageType.INFO,
952 _("Import completed. No identities were added or updated."));
954 msg_dialog.destroy();