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, out ArrayList<IdCard>? old_duplicates=null)
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 if (&old_duplicates != null) {
320 old_duplicates = new ArrayList<IdCard>();
323 return false; // no changes, no need to update
324 } else if ((flags & (1 << IdCard.DiffFlags.DISPLAY_NAME)) != 0) {
325 dialog = new Gtk.MessageDialog(this,
326 Gtk.DialogFlags.DESTROY_WITH_PARENT,
327 Gtk.MessageType.QUESTION,
328 Gtk.ButtonsType.YES_NO,
329 _("Would you like to replace ID Card '%s' using nai '%s' with the new ID Card '%s'?"),
330 prev_id.display_name,
332 id_card.display_name);
334 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 update ID Card '%s' using nai '%s'?"),
339 id_card.display_name,
343 dialog = new Gtk.MessageDialog(this,
344 Gtk.DialogFlags.DESTROY_WITH_PARENT,
345 Gtk.MessageType.QUESTION,
346 Gtk.ButtonsType.YES_NO,
347 _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
348 id_card.display_name);
350 var ret = dialog.run();
354 if (ret == Gtk.ResponseType.YES) {
355 this.identities_manager.add_card(id_card, force_flat_file_store, out old_duplicates);
359 if (&old_duplicates != null) {
360 old_duplicates = new ArrayList<IdCard>();
366 private void add_identity_cb()
368 var dialog = new IdentityDialog(this);
369 int result = ResponseType.CANCEL;
370 while (!dialog.complete)
371 result = dialog.run();
374 case ResponseType.OK:
375 this.identities_manager.add_card(update_id_card_data(dialog, new IdCard()), false);
383 private void edit_identity_cb(IdCard card)
385 var dialog = new IdentityDialog.with_idcard(card, _("Edit Identity"), this);
386 int result = ResponseType.CANCEL;
387 while (!dialog.complete)
388 result = dialog.run();
391 case ResponseType.OK:
392 this.identities_manager.update_card(update_id_card_data(dialog, card));
400 private void remove_identity(IdCard id_card)
402 logger.trace(@"remove_identity: id_card.display_name='$(id_card.display_name)'");
403 if (id_card != this.selected_idcard) {
404 logger.error("remove_identity: id_card != this.selected_idcard!");
407 this.selected_idcard = null;
408 this.identities_manager.remove_card(id_card);
410 // Nothing is selected, so disable buttons
411 this.edit_button.set_sensitive(false);
412 this.remove_button.set_sensitive(false);
413 this.send_button.set_sensitive(false);
416 private void redraw_id_card_widgets()
421 this.custom_vbox.clear();
423 if (filter.get_iter_first(out iter))
428 Columns.IDCARD_COL, out id_card);
430 add_id_card_widget(id_card);
432 while (filter.iter_next(ref iter));
436 private void remove_identity_cb(IdCard id_card)
438 bool remove = WarningDialog.confirm(this,
439 Markup.printf_escaped(
440 "<span font-weight='heavy'>" + _("You are about to remove the identity '%s'.") + "</span>",
441 id_card.display_name)
442 + "\n\n" + _("Are you sure you want to do this?"),
445 remove_identity(id_card);
448 private void set_prompting_service(string service)
450 clear_selection_prompts();
452 var prompting_service = new Label(_("Identity requested for service:\n%s").printf(service));
453 prompting_service.set_line_wrap(true);
456 prompting_service.set_alignment(0, (float )0.5);
458 var selection_prompt = new Label(_("Select your identity:"));
459 selection_prompt.set_alignment(0, 1);
461 this.service_prompt_vbox.pack_start(prompting_service, false, false, 12);
462 this.service_prompt_vbox.pack_start(selection_prompt, false, false, 2);
463 this.service_prompt_vbox.show_all();
466 private void clear_selection_prompts()
468 var list = service_prompt_vbox.get_children();
469 foreach (Widget w in list)
471 service_prompt_vbox.remove(w);
476 public void queue_identity_request(IdentityRequest request)
478 bool queue_was_empty = !this.selection_in_progress();
479 this.request_queue.push_tail(request);
482 { /* setup widgets */
483 candidates = request.candidates;
485 redraw_id_card_widgets();
486 set_prompting_service(request.service);
487 remember_identity_binding.show();
489 if (this.selected_idcard != null
490 && this.custom_vbox.find_idcard_widget(this.selected_idcard) != null) {
491 // A widget is already selected, and has not been filtered out of the display via search
492 send_button.set_sensitive(true);
500 /** Makes the window visible, or at least, notifies the user that the window
501 * wants to be visible.
503 * This differs from show() in that show() does not guarantee that the
504 * window will be moved to the foreground. Actually, neither does this
505 * method, because the user's settings and window manager may affect the
506 * behavior significantly.
508 public void make_visible()
510 set_urgency_hint(true);
514 public IdCard check_add_password(IdCard identity, IdentityRequest request, IdentityManagerModel model)
516 logger.trace(@"check_add_password");
517 IdCard retval = identity;
518 bool idcard_has_pw = (identity.password != null) && (identity.password != "");
519 bool request_has_pw = (request.password != null) && (request.password != "");
520 if ((!idcard_has_pw) && (!identity.is_no_identity())) {
521 if (request_has_pw) {
522 identity.password = request.password;
523 retval = model.update_card(identity);
525 var dialog = new AddPasswordDialog(identity, request);
526 var result = dialog.run();
529 case ResponseType.OK:
530 identity.password = dialog.password;
531 identity.store_password = dialog.remember;
533 identity.temporary = false;
534 retval = model.update_card(identity);
546 private void send_identity_cb(IdCard id)
548 return_if_fail(this.selection_in_progress());
550 if (!check_and_confirm_trust_anchor(id)) {
551 // Allow user to pick again
555 var request = this.request_queue.pop_head();
556 var identity = check_add_password(id, request, identities_manager);
557 send_button.set_sensitive(false);
561 if (!this.selection_in_progress())
564 clear_selection_prompts();
565 if (!parent_app.explicitly_launched) {
566 // The following occasionally causes the app to exit without sending the dbus
567 // reply, so for now we just don't exit
573 IdentityRequest next = this.request_queue.peek_head();
574 candidates = next.candidates;
575 set_prompting_service(next.service);
578 redraw_id_card_widgets();
580 if ((identity != null) && (!identity.is_no_identity()))
581 parent_app.default_id_card = identity;
583 request.return_identity(identity, remember_identity_binding.active);
585 remember_identity_binding.active = false;
586 remember_identity_binding.hide();
589 private bool check_and_confirm_trust_anchor(IdCard id)
591 if (!id.trust_anchor.is_empty() && id.trust_anchor.get_anchor_type() == TrustAnchor.TrustAnchorType.SERVER_CERT) {
592 if (!id.trust_anchor.user_verified) {
595 int result = ResponseType.CANCEL;
596 var dialog = new TrustAnchorDialog(id, this);
597 while (!dialog.complete)
598 result = dialog.run();
601 case ResponseType.OK:
602 id.trust_anchor.user_verified = true;
616 private void on_about_action()
618 string copyright = "Copyright (c) 2011, %d JANET".printf(LATEST_EDIT_YEAR);
622 Copyright (c) 2011, %d JANET(UK)
625 Redistribution and use in source and binary forms, with or without
626 modification, are permitted provided that the following conditions
629 1. Redistributions of source code must retain the above copyright
630 notice, this list of conditions and the following disclaimer.
632 2. Redistributions in binary form must reproduce the above copyright
633 notice, this list of conditions and the following disclaimer in the
634 documentation and/or other materials provided with the distribution.
636 3. Neither the name of JANET(UK) nor the names of its contributors
637 may be used to endorse or promote products derived from this software
638 without specific prior written permission.
640 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
641 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
642 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
643 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
644 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
645 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
646 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
647 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
648 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
649 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
651 """.printf(LATEST_EDIT_YEAR);
653 AboutDialog about = new AboutDialog();
655 about.set_comments(_("Moonshot project UI"));
656 about.set_copyright(copyright);
657 about.set_website(Config.PACKAGE_URL);
658 about.set_website_label(_("Visit the Moonshot project web site"));
660 // Note: The package version is configured at the top of moonshot/ui/configure.ac
661 about.set_version(Config.PACKAGE_VERSION);
662 about.set_license(license);
663 about.set_modal(true);
664 about.set_transient_for(this);
665 about.response.connect((a, b) => {about.destroy();});
666 about.modify_bg(StateType.NORMAL, white);
671 private Gtk.ActionEntry[] create_actions() {
672 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
674 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
679 // Pick up the translated version of the name, if any
680 helpmenu.label = dgettext(null, helpmenu.label);
683 Gtk.ActionEntry about = { "AboutAction",
691 N_("About this application"),
694 about.label = dgettext(null, about.label);
701 private void create_ui_manager()
703 Gtk.ActionGroup action_group = new Gtk.ActionGroup("GeneralActionGroup");
704 action_group.add_actions(create_actions(), this);
705 ui_manager.insert_action_group(action_group, 0);
708 ui_manager.add_ui_from_string(menu_layout, -1);
712 stderr.printf("%s\n", e.message);
713 logger.error("create_ui_manager: Caught error: " + e.message);
715 ui_manager.ensure_update();
718 private void build_ui()
720 // Note: On Debian7/Gtk+2, the menu bar remains gray. This doesn't happen on Debian8/Gtk+3.
721 this.modify_bg(StateType.NORMAL, white);
727 int button_width = 1;
729 Table top_table = new Table(num_rows, 10, false);
730 top_table.set_border_width(12);
732 AttachOptions fill_and_expand = AttachOptions.EXPAND | AttachOptions.FILL;
733 AttachOptions fill = AttachOptions.FILL;
736 service_prompt_vbox = new VBox(false, 0);
737 top_table.attach(service_prompt_vbox, 0, 1, row, row + 1, fill_and_expand, fill_and_expand, 12, 0);
740 string search_tooltip_text = _("Search for an identity or service");
741 this.search_entry = new Entry();
743 set_atk_name_description(search_entry, _("Search entry"), _("Search for a specific ID Card"));
744 this.search_entry.set_icon_from_pixbuf(EntryIconPosition.SECONDARY,
745 find_icon_sized("edit-find", Gtk.IconSize.MENU));
746 this.search_entry.set_icon_tooltip_text(EntryIconPosition.SECONDARY,
747 search_tooltip_text);
749 this.search_entry.set_tooltip_text(search_tooltip_text);
751 this.search_entry.set_icon_sensitive(EntryIconPosition.SECONDARY, false);
753 this.search_entry.notify["text"].connect(search_entry_text_changed_cb);
754 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
755 this.search_entry.set_width_chars(24);
757 var search_label_markup ="<small>" + search_tooltip_text + "</small>";
758 var full_search_label = new Label(null);
759 full_search_label.set_markup(search_label_markup);
760 full_search_label.set_alignment(1, 0);
762 var search_vbox = new VBox(false, 0);
763 search_vbox.pack_start(search_entry, false, false, 0);
764 var search_spacer = new Alignment(0, 0, 0, 0);
765 search_spacer.set_size_request(0, 2);
766 search_vbox.pack_start(search_spacer, false, false, 0);
767 search_vbox.pack_start(full_search_label, false, false, 0);
769 // Overlap with the service_prompt_box
770 top_table.attach(search_vbox, 5, num_cols - button_width, row - 1, row + 1, fill_and_expand, fill, 0, 12);
773 this.custom_vbox = new CustomVBox(this, false, 2);
775 var viewport = new Viewport(null, null);
776 viewport.set_border_width(2);
777 viewport.set_shadow_type(ShadowType.NONE);
778 viewport.add(custom_vbox);
779 var id_scrollwin = new ScrolledWindow(null, null);
780 id_scrollwin.set_policy(PolicyType.NEVER, PolicyType.AUTOMATIC);
781 id_scrollwin.set_shadow_type(ShadowType.IN);
782 id_scrollwin.add_with_viewport(viewport);
783 top_table.attach(id_scrollwin, 0, num_cols - 1, row, num_rows - 1, fill_and_expand, fill_and_expand, 6, 0);
785 // Right below id_scrollwin:
786 remember_identity_binding = new CheckButton.with_label(_("Remember my identity choice for this service"));
787 remember_identity_binding.active = false;
788 top_table.attach(remember_identity_binding, 0, num_cols / 2, num_rows - 1, num_rows, fill_and_expand, fill_and_expand, 3, 0);
790 var add_button = new Button.with_label(_("Add"));
791 add_button.clicked.connect((w) => {add_identity_cb();});
792 top_table.attach(make_rigid(add_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
795 var import_button = new Button.with_label(_("Import"));
796 import_button.clicked.connect((w) => {import_identities_cb();});
797 top_table.attach(make_rigid(import_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
800 this.edit_button = new Button.with_label(_("Edit"));
801 edit_button.clicked.connect((w) => {edit_identity_cb(this.selected_idcard);});
802 edit_button.set_sensitive(false);
803 top_table.attach(make_rigid(edit_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
806 this.remove_button = new Button.with_label(_("Remove"));
807 remove_button.clicked.connect((w) => {remove_identity_cb(this.selected_idcard);});
808 remove_button.set_sensitive(false);
809 top_table.attach(make_rigid(remove_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
812 // push the send button down another row.
814 this.send_button = new Button.with_label(_("Send"));
815 send_button.clicked.connect((w) => {send_identity_cb(this.selected_idcard);});
816 // send_button.set_visible(false);
817 send_button.set_sensitive(false);
818 top_table.attach(make_rigid(send_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
821 var main_vbox = new VBox(false, 0);
824 // hide the File | Quit menu item which is now on the Mac Menu
825 // Gtk.Widget quit_item = this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
828 Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
829 menushell.modify_bg(StateType.NORMAL, white);
831 osxApp.set_menu_bar(menushell);
832 osxApp.set_use_quartz_accelerators(true);
833 osxApp.sync_menu_bar();
836 var menubar = this.ui_manager.get_widget("/MenuBar");
837 main_vbox.pack_start(menubar, false, false, 0);
838 menubar.modify_bg(StateType.NORMAL, white);
840 main_vbox.pack_start(top_table, true, true, 6);
843 main_vbox.show_all();
845 if (!this.selection_in_progress())
846 remember_identity_binding.hide();
849 internal bool selection_in_progress() {
850 return !this.request_queue.is_empty();
853 private void set_atk_name_description(Widget widget, string name, string description)
855 var atk_widget = widget.get_accessible();
857 atk_widget.set_name(name);
858 atk_widget.set_description(description);
861 private void connect_signals()
863 this.destroy.connect(() => {
864 logger.trace("Destroy event; calling Gtk.main_quit()");
867 this.identities_manager.card_list_changed.connect(this.on_card_list_changed);
868 this.delete_event.connect(() => {return confirm_quit();});
871 private bool confirm_quit() {
872 logger.trace("delete_event intercepted; selection_in_progress()=" + selection_in_progress().to_string());
874 if (selection_in_progress()) {
875 var result = WarningDialog.confirm(this,
876 Markup.printf_escaped(
877 "<span font-weight='heavy'>" + _("Do you wish to use the %s service?") + "</span>",
878 this.request_queue.peek_head().service)
879 + "\n\n" + _("Select Yes to select an ID for this service, or No to cancel"),
880 "close_moonshot_window");
882 // Prevent other handlers from handling this event; this keeps the window open.
887 // Allow the window deletion to proceed.
891 private static Widget make_rigid(Button button)
893 // Hack to prevent the button from growing vertically
894 VBox fixed_height = new VBox(false, 0);
895 fixed_height.pack_start(button, false, false, 0);
900 private void import_identities_cb() {
901 var dialog = new FileChooserDialog(_("Import File"),
903 FileChooserAction.OPEN,
904 _("Cancel"),ResponseType.CANCEL,
905 _("Open"), ResponseType.ACCEPT,
908 if (import_directory != null) {
909 dialog.set_current_folder(import_directory);
912 if (dialog.run() == ResponseType.ACCEPT)
914 // Save the parent directory to use as default for next save
915 string filename = dialog.get_filename();
916 var file = File.new_for_path(filename);
917 import_directory = file.get_parent().get_path();
919 int import_count = 0;
921 var webp = new Parser(filename);
924 logger.trace(@"import_identities_cb: Have $(webp.cards.length) IdCards");
925 foreach (IdCard card in webp.cards)
929 logger.trace(@"import_identities_cb: Skipping null IdCard");
933 if (!card.trust_anchor.is_empty()) {
934 string ta_datetime_added = TrustAnchor.format_datetime_now();
935 card.trust_anchor.set_datetime_added(ta_datetime_added);
936 logger.trace("import_identities_cb : Set ta_datetime_added for '%s' to '%s'; ca_cert='%s'; server_cert='%s'"
937 .printf(card.display_name, ta_datetime_added, card.trust_anchor.ca_cert, card.trust_anchor.server_cert));
941 bool result = add_identity(card, use_flat_file_store);
943 logger.trace(@"import_identities_cb: Added or updated '$(card.display_name)'");
947 logger.trace(@"import_identities_cb: Did not add or update '$(card.display_name)'");
950 var msg_dialog = new Gtk.MessageDialog(this,
951 Gtk.DialogFlags.DESTROY_WITH_PARENT,
952 Gtk.MessageType.INFO,
954 _("Import completed. %d Identities were added or updated."),
957 msg_dialog.destroy();