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,
129 msg_dialog.destroy();
133 private void on_card_list_changed() {
134 logger.trace("on_card_list_changed");
138 private bool visible_func(TreeModel model, TreeIter iter)
143 Columns.IDCARD_COL, out id_card);
148 if (candidates != null)
150 bool is_candidate = false;
151 foreach (IdCard candidate in candidates)
153 if (candidate == id_card)
160 string entry_text = search_entry.get_text();
161 if (entry_text == null || entry_text == "")
166 foreach (string search_text in entry_text.split(" "))
168 if (search_text == "")
172 string search_text_casefold = search_text.casefold();
174 if (id_card.issuer != null)
176 string issuer_casefold = id_card.issuer;
178 if (issuer_casefold.contains(search_text_casefold))
182 if (id_card.display_name != null)
184 string display_name_casefold = id_card.display_name.casefold();
186 if (display_name_casefold.contains(search_text_casefold))
190 if (id_card.services.size > 0)
192 foreach (string service in id_card.services)
194 string service_casefold = service.casefold();
196 if (service_casefold.contains(search_text_casefold))
204 private void setup_list_model()
206 this.listmodel = new Gtk.ListStore(Columns.N_COLUMNS, typeof(IdCard),
211 this.filter = new TreeModelFilter(listmodel, null);
213 filter.set_visible_func(visible_func);
216 private void search_entry_text_changed_cb()
218 this.filter.refilter();
219 redraw_id_card_widgets();
222 private bool search_entry_key_press_event_cb(Gdk.EventKey e)
224 if(Gdk.keyval_name(e.keyval) == "Escape")
225 this.search_entry.set_text("");
227 // Continue processing this event, since the
228 // text entry functionality needs to see it too.
232 private void load_id_cards() {
233 logger.trace("load_id_cards");
236 this.listmodel->clear();
237 LinkedList<IdCard> card_list = identities_manager.get_card_list() ;
238 if (card_list == null) {
242 foreach (IdCard id_card in card_list) {
243 logger.trace(@"load_id_cards: Loading card with display name '$(id_card.display_name)'");
244 add_id_card_data(id_card);
245 add_id_card_widget(id_card);
249 private IdCard update_id_card_data(IdentityDialog dialog, IdCard id_card)
251 id_card.display_name = dialog.display_name;
252 id_card.issuer = dialog.issuer;
253 id_card.username = dialog.username;
254 id_card.password = dialog.password;
255 id_card.store_password = dialog.store_password;
257 id_card.update_services_from_list(dialog.get_services());
259 if (dialog.clear_trust_anchor) {
260 id_card.clear_trust_anchor();
266 private void add_id_card_data(IdCard id_card)
270 this.listmodel->append(out iter);
271 pixbuf = get_pixbuf(id_card);
273 Columns.IDCARD_COL, id_card,
274 Columns.LOGO_COL, pixbuf,
275 Columns.ISSUER_COL, id_card.issuer,
276 Columns.USERNAME_COL, id_card.username,
277 Columns.PASSWORD_COL, id_card.password);
280 private IdCardWidget add_id_card_widget(IdCard id_card)
282 if (id_card == null) {
283 logger.trace("add_id_card_widget: id_card == null; returning.");
287 logger.trace("add_id_card_widget: id_card.nai='%s'; selected nai='%s'"
289 this.selected_card == null ? "[null selection]" : this.selected_card.nai));
292 var id_card_widget = new IdCardWidget(id_card, this);
293 this.custom_vbox.add_id_card_widget(id_card_widget);
294 id_card_widget.expanded.connect(this.widget_selected_cb);
295 id_card_widget.collapsed.connect(this.widget_unselected_cb);
297 if (this.selected_card != null && this.selected_card.nai == id_card.nai) {
298 logger.trace(@"add_id_card_widget: Expanding selected idcard widget");
299 id_card_widget.expand();
301 // After a card is added, modified, or deleted, we reload all the cards.
302 // (I'm not sure why, or if it's necessary to do this.) This means that the
303 // selected_card may now point to a card instance that's not in the current list.
304 // Hence the only way to carry the selection across reloads is to identify
305 // the selected card by its NAI. And hence we need to reset what our idea of the
306 // "selected card" is.
307 // There should be a better way to do this, especially since we're not great
308 // at preventing duplicate NAIs.
309 this.selected_card = id_card;
311 return id_card_widget;
314 private void widget_selected_cb(IdCardWidget id_card_widget)
316 logger.trace(@"widget_selected_cb: id_card_widget.id_card.display_name='$(id_card_widget.id_card.display_name)'");
318 this.selected_card = id_card_widget.id_card;
319 bool allow_removes = !id_card_widget.id_card.is_no_identity();
320 this.remove_button.set_sensitive(allow_removes);
321 this.edit_button.set_sensitive(true);
322 this.custom_vbox.receive_expanded_event(id_card_widget);
324 if (this.selection_in_progress())
325 this.send_button.set_sensitive(true);
328 private void widget_unselected_cb(IdCardWidget id_card_widget)
330 logger.trace(@"widget_unselected_cb: id_card_widget.id_card.display_name='$(id_card_widget.id_card.display_name)'");
332 this.selected_card = null;
333 this.remove_button.set_sensitive(false);
334 this.edit_button.set_sensitive(false);
335 this.custom_vbox.receive_collapsed_event(id_card_widget);
337 this.send_button.set_sensitive(false);
340 public bool add_identity(IdCard id_card, bool force_flat_file_store, out ArrayList<IdCard>? old_duplicates=null)
344 * TODO: We should have a confirmation dialog, but currently it will crash on Mac OS
345 * so for now we will install silently
347 var ret = Gtk.ResponseType.YES;
349 Gtk.MessageDialog dialog;
350 IdCard? prev_id = identities_manager.find_id_card(id_card.nai, force_flat_file_store);
351 logger.trace("add_identity(flat=%s, card='%s'): find_id_card returned %s"
352 .printf(force_flat_file_store.to_string(), id_card.display_name, (prev_id != null ? prev_id.display_name : "null")));
354 int flags = prev_id.Compare(id_card);
355 logger.trace("add_identity: compare returned " + flags.to_string());
357 if (&old_duplicates != null) {
358 old_duplicates = new ArrayList<IdCard>();
361 return false; // no changes, no need to update
362 } else if ((flags & (1 << IdCard.DiffFlags.DISPLAY_NAME)) != 0) {
363 dialog = new Gtk.MessageDialog(this,
364 Gtk.DialogFlags.DESTROY_WITH_PARENT,
365 Gtk.MessageType.QUESTION,
366 Gtk.ButtonsType.YES_NO,
367 _("Would you like to replace ID Card '%s' using nai '%s' with the new ID Card '%s'?"),
368 prev_id.display_name,
370 id_card.display_name);
372 dialog = new Gtk.MessageDialog(this,
373 Gtk.DialogFlags.DESTROY_WITH_PARENT,
374 Gtk.MessageType.QUESTION,
375 Gtk.ButtonsType.YES_NO,
376 _("Would you like to update ID Card '%s' using nai '%s'?"),
377 id_card.display_name,
381 dialog = new Gtk.MessageDialog(this,
382 Gtk.DialogFlags.DESTROY_WITH_PARENT,
383 Gtk.MessageType.QUESTION,
384 Gtk.ButtonsType.YES_NO,
385 _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
386 id_card.display_name);
388 var ret = dialog.run();
392 if (ret == Gtk.ResponseType.YES) {
393 this.identities_manager.add_card(id_card, force_flat_file_store, out old_duplicates);
397 if (&old_duplicates != null) {
398 old_duplicates = new ArrayList<IdCard>();
404 private void add_identity_cb()
406 var dialog = new IdentityDialog(this);
407 int result = ResponseType.CANCEL;
408 while (!dialog.complete)
409 result = dialog.run();
412 case ResponseType.OK:
413 this.identities_manager.add_card(update_id_card_data(dialog, new IdCard()), false);
421 private void edit_identity_cb(IdCard card)
423 var dialog = new IdentityDialog.with_idcard(card, _("Edit Identity"), this);
424 int result = ResponseType.CANCEL;
425 while (!dialog.complete)
426 result = dialog.run();
429 case ResponseType.OK:
430 this.identities_manager.update_card(update_id_card_data(dialog, card));
432 // Make sure we haven't created a duplicate NAI via this update.
433 report_duplicate_nais();
441 private void remove_identity(IdCard id_card)
443 logger.trace(@"remove_identity: id_card.display_name='$(id_card.display_name)'");
445 this.selected_card = null;
446 this.identities_manager.remove_card(id_card);
448 // Nothing is selected, so disable buttons
449 this.edit_button.set_sensitive(false);
450 this.remove_button.set_sensitive(false);
451 this.send_button.set_sensitive(false);
454 private void redraw_id_card_widgets()
459 this.custom_vbox.clear();
461 if (filter.get_iter_first(out iter))
466 Columns.IDCARD_COL, out id_card);
468 add_id_card_widget(id_card);
470 while (filter.iter_next(ref iter));
474 private void remove_identity_cb(IdCard id_card)
476 bool remove = WarningDialog.confirm(this,
477 Markup.printf_escaped(
478 "<span font-weight='heavy'>" + _("You are about to remove the identity '%s'.") + "</span>",
479 id_card.display_name)
480 + "\n\n" + _("Are you sure you want to do this?"),
483 remove_identity(id_card);
486 private void set_prompting_service(string service)
488 clear_selection_prompts();
490 var prompting_service = new Label(_("Identity requested for service:\n%s").printf(service));
491 prompting_service.set_line_wrap(true);
494 prompting_service.set_alignment(0, (float )0.5);
496 var selection_prompt = new Label(_("Select your identity:"));
497 selection_prompt.set_alignment(0, 1);
499 this.service_prompt_vbox.pack_start(prompting_service, false, false, 12);
500 this.service_prompt_vbox.pack_start(selection_prompt, false, false, 2);
501 this.service_prompt_vbox.show_all();
504 private void clear_selection_prompts()
506 var list = service_prompt_vbox.get_children();
507 foreach (Widget w in list)
509 service_prompt_vbox.remove(w);
514 public void queue_identity_request(IdentityRequest request)
516 bool queue_was_empty = !this.selection_in_progress();
517 this.request_queue.push_tail(request);
520 { /* setup widgets */
521 candidates = request.candidates;
523 redraw_id_card_widgets();
524 set_prompting_service(request.service);
525 remember_identity_binding.show();
527 if (this.custom_vbox.find_idcard_widget(this.selected_card) != null) {
528 // A widget is already selected, and has not been filtered out of the display via search
529 send_button.set_sensitive(true);
537 /** Makes the window visible, or at least, notifies the user that the window
538 * wants to be visible.
540 * This differs from show() in that show() does not guarantee that the
541 * window will be moved to the foreground. Actually, neither does this
542 * method, because the user's settings and window manager may affect the
543 * behavior significantly.
545 public void make_visible()
547 set_urgency_hint(true);
551 public IdCard check_add_password(IdCard identity, IdentityRequest request, IdentityManagerModel model)
553 logger.trace(@"check_add_password");
554 IdCard retval = identity;
555 bool idcard_has_pw = (identity.password != null) && (identity.password != "");
556 bool request_has_pw = (request.password != null) && (request.password != "");
557 if ((!idcard_has_pw) && (!identity.is_no_identity())) {
558 if (request_has_pw) {
559 identity.password = request.password;
560 retval = model.update_card(identity);
562 var dialog = new AddPasswordDialog(identity, request);
563 var result = dialog.run();
566 case ResponseType.OK:
567 identity.password = dialog.password;
568 // Don't leave passwords in memory longer than necessary.
569 // (This may not actually clear the data, but it's the best we can do.)
570 dialog.clear_password();
571 identity.store_password = dialog.remember;
573 identity.temporary = false;
574 retval = model.update_card(identity);
580 // Do this again, in case OK button wasn't selected.
581 dialog.clear_password();
588 private void send_identity_cb(IdCard id)
590 return_if_fail(this.selection_in_progress());
592 var request = this.request_queue.pop_head();
593 var identity = check_add_password(id, request, identities_manager);
594 send_button.set_sensitive(false);
598 if (!this.selection_in_progress())
601 clear_selection_prompts();
602 if (!parent_app.explicitly_launched) {
603 // The following occasionally causes the app to exit without sending the dbus
604 // reply, so for now we just don't exit
610 IdentityRequest next = this.request_queue.peek_head();
611 candidates = next.candidates;
612 set_prompting_service(next.service);
615 redraw_id_card_widgets();
617 if ((identity != null) && (!identity.is_no_identity()))
618 parent_app.default_id_card = identity;
620 request.return_identity(identity, remember_identity_binding.active);
622 remember_identity_binding.active = true;
623 remember_identity_binding.hide();
626 private void on_about_action()
628 string copyright = "Copyright (c) 2011, %d JANET".printf(LATEST_EDIT_YEAR);
632 Copyright (c) 2011, %d JANET(UK)
635 Redistribution and use in source and binary forms, with or without
636 modification, are permitted provided that the following conditions
639 1. Redistributions of source code must retain the above copyright
640 notice, this list of conditions and the following disclaimer.
642 2. Redistributions in binary form must reproduce the above copyright
643 notice, this list of conditions and the following disclaimer in the
644 documentation and/or other materials provided with the distribution.
646 3. Neither the name of JANET(UK) nor the names of its contributors
647 may be used to endorse or promote products derived from this software
648 without specific prior written permission.
650 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
651 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
652 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
653 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
654 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
655 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
656 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
657 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
658 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
659 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
661 """.printf(LATEST_EDIT_YEAR);
663 AboutDialog about = new AboutDialog();
665 about.set_comments(_("Moonshot project UI"));
666 about.set_copyright(copyright);
667 about.set_website(Config.PACKAGE_URL);
668 about.set_website_label(_("Visit the Moonshot project web site"));
670 // Note: The package version is configured at the top of moonshot/ui/configure.ac
671 about.set_version(Config.PACKAGE_VERSION);
672 about.set_license(license);
673 about.set_modal(true);
674 about.set_transient_for(this);
675 about.response.connect((a, b) => {about.destroy();});
681 private Gtk.ActionEntry[] create_actions() {
682 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
684 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
689 // Pick up the translated version of the name, if any
690 helpmenu.label = dgettext(null, helpmenu.label);
693 Gtk.ActionEntry about = { "AboutAction",
701 N_("About this application"),
704 about.label = dgettext(null, about.label);
711 private void create_ui_manager()
713 Gtk.ActionGroup action_group = new Gtk.ActionGroup("GeneralActionGroup");
714 action_group.add_actions(create_actions(), this);
715 ui_manager.insert_action_group(action_group, 0);
718 ui_manager.add_ui_from_string(menu_layout, -1);
722 stderr.printf("%s\n", e.message);
723 logger.error("create_ui_manager: Caught error: " + e.message);
725 ui_manager.ensure_update();
728 private void build_ui()
736 int button_width = 1;
738 Table top_table = new Table(num_rows, 10, false);
739 top_table.set_border_width(12);
741 AttachOptions fill_and_expand = AttachOptions.EXPAND | AttachOptions.FILL;
742 AttachOptions fill = AttachOptions.FILL;
745 service_prompt_vbox = new VBox(false, 0);
746 top_table.attach(service_prompt_vbox, 0, 1, row, row + 1, fill_and_expand, fill_and_expand, 12, 0);
749 string search_tooltip_text = _("Search for an identity or service");
750 this.search_entry = new Entry();
752 set_atk_name_description(search_entry, _("Search entry"), _("Search for a specific ID Card"));
753 this.search_entry.set_icon_from_pixbuf(EntryIconPosition.SECONDARY,
754 find_icon_sized("edit-find", Gtk.IconSize.MENU));
755 this.search_entry.set_icon_tooltip_text(EntryIconPosition.SECONDARY,
756 search_tooltip_text);
758 this.search_entry.set_tooltip_text(search_tooltip_text);
760 this.search_entry.set_icon_sensitive(EntryIconPosition.SECONDARY, false);
762 this.search_entry.notify["text"].connect(search_entry_text_changed_cb);
763 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
764 this.search_entry.set_width_chars(24);
766 var search_label_markup ="<small>" + search_tooltip_text + "</small>";
767 var full_search_label = new Label(null);
768 full_search_label.set_markup(search_label_markup);
769 full_search_label.set_alignment(1, 0);
771 var search_vbox = new VBox(false, 0);
772 search_vbox.pack_start(search_entry, false, false, 0);
773 var search_spacer = new Alignment(0, 0, 0, 0);
774 search_spacer.set_size_request(0, 2);
775 search_vbox.pack_start(search_spacer, false, false, 0);
776 search_vbox.pack_start(full_search_label, false, false, 0);
778 // Overlap with the service_prompt_box
779 top_table.attach(search_vbox, 5, num_cols - button_width, row - 1, row + 1, fill_and_expand, fill, 0, 12);
782 this.custom_vbox = new CustomVBox(this, false, 2);
784 var viewport = new Viewport(null, null);
785 viewport.set_border_width(2);
786 viewport.set_shadow_type(ShadowType.NONE);
787 viewport.add(custom_vbox);
788 var id_scrollwin = new ScrolledWindow(null, null);
789 id_scrollwin.set_policy(PolicyType.NEVER, PolicyType.AUTOMATIC);
790 id_scrollwin.set_shadow_type(ShadowType.IN);
791 id_scrollwin.add_with_viewport(viewport);
792 top_table.attach(id_scrollwin, 0, num_cols - 1, row, num_rows - 1, fill_and_expand, fill_and_expand, 6, 0);
794 // Right below id_scrollwin:
795 remember_identity_binding = new CheckButton.with_label(_("Remember my identity choice for this service"));
796 remember_identity_binding.active = true;
797 top_table.attach(remember_identity_binding, 0, num_cols / 2, num_rows - 1, num_rows, fill_and_expand, fill_and_expand, 3, 0);
799 var add_button = new Button.with_label(_("Add"));
800 add_button.clicked.connect((w) => {add_identity_cb();});
801 top_table.attach(make_rigid(add_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
804 var import_button = new Button.with_label(_("Import"));
805 import_button.clicked.connect((w) => {import_identities_cb();});
806 top_table.attach(make_rigid(import_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
809 this.edit_button = new Button.with_label(_("Edit"));
810 edit_button.clicked.connect((w) => {edit_identity_cb(this.selected_card);});
811 edit_button.set_sensitive(false);
812 top_table.attach(make_rigid(edit_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
815 this.remove_button = new Button.with_label(_("Remove"));
816 remove_button.clicked.connect((w) => {remove_identity_cb(this.selected_card);});
817 remove_button.set_sensitive(false);
818 top_table.attach(make_rigid(remove_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
821 // push the send button down another row.
823 this.send_button = new Button.with_label(_("Send"));
824 send_button.clicked.connect((w) => {send_identity_cb(this.selected_card);});
825 // send_button.set_visible(false);
826 send_button.set_sensitive(false);
827 top_table.attach(make_rigid(send_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
830 var main_vbox = new VBox(false, 0);
833 // hide the File | Quit menu item which is now on the Mac Menu
834 // Gtk.Widget quit_item = this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
837 Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
839 osxApp.set_menu_bar(menushell);
840 osxApp.set_use_quartz_accelerators(true);
841 osxApp.sync_menu_bar();
844 var menubar = this.ui_manager.get_widget("/MenuBar");
845 main_vbox.pack_start(menubar, false, false, 0);
846 set_bg_color(menubar);
848 main_vbox.pack_start(top_table, true, true, 6);
851 main_vbox.show_all();
853 if (!this.selection_in_progress())
854 remember_identity_binding.hide();
857 internal bool selection_in_progress() {
858 return !this.request_queue.is_empty();
861 private void set_atk_name_description(Widget widget, string name, string description)
863 var atk_widget = widget.get_accessible();
865 atk_widget.set_name(name);
866 atk_widget.set_description(description);
869 private void connect_signals()
871 this.destroy.connect(() => {
872 logger.trace("Destroy event; calling Gtk.main_quit()");
875 this.identities_manager.card_list_changed.connect(this.on_card_list_changed);
876 this.delete_event.connect(() => {return confirm_quit();});
879 private bool confirm_quit() {
880 logger.trace("delete_event intercepted; selection_in_progress()=" + selection_in_progress().to_string());
882 if (selection_in_progress()) {
883 var result = WarningDialog.confirm(this,
884 Markup.printf_escaped(
885 "<span font-weight='heavy'>" + _("Do you wish to use the %s service?") + "</span>",
886 this.request_queue.peek_head().service)
887 + "\n\n" + _("Select Yes to select an ID for this service, or No to cancel"),
888 "close_moonshot_window");
890 // Prevent other handlers from handling this event; this keeps the window open.
895 // Allow the window deletion to proceed.
899 private static Widget make_rigid(Button button)
901 // Hack to prevent the button from growing vertically
902 VBox fixed_height = new VBox(false, 0);
903 fixed_height.pack_start(button, false, false, 0);
908 private void import_identities_cb() {
909 var dialog = new FileChooserDialog(_("Import File"),
911 FileChooserAction.OPEN,
912 _("Cancel"),ResponseType.CANCEL,
913 _("Open"), ResponseType.ACCEPT,
916 if (import_directory != null) {
917 dialog.set_current_folder(import_directory);
920 if (dialog.run() == ResponseType.ACCEPT)
922 // Save the parent directory to use as default for next save
923 string filename = dialog.get_filename();
924 var file = File.new_for_path(filename);
925 import_directory = file.get_parent().get_path();
927 int import_count = 0;
929 var webp = new Parser(filename);
932 logger.trace(@"import_identities_cb: Have $(webp.cards.length) IdCards");
933 foreach (IdCard card in webp.cards)
937 logger.trace(@"import_identities_cb: Skipping null IdCard");
941 if (!card.trust_anchor.is_empty()) {
942 string ta_datetime_added = TrustAnchor.format_datetime_now();
943 card.trust_anchor.set_datetime_added(ta_datetime_added);
944 logger.trace("import_identities_cb : Set ta_datetime_added for '%s' to '%s'; ca_cert='%s'; server_cert='%s'"
945 .printf(card.display_name, ta_datetime_added, card.trust_anchor.ca_cert, card.trust_anchor.server_cert));
949 bool result = add_identity(card, use_flat_file_store);
951 logger.trace(@"import_identities_cb: Added or updated '$(card.display_name)'");
955 logger.trace(@"import_identities_cb: Did not add or update '$(card.display_name)'");
958 if (import_count == 0) {
959 var msg_dialog = new Gtk.MessageDialog(this,
960 Gtk.DialogFlags.DESTROY_WITH_PARENT,
961 Gtk.MessageType.INFO,
964 _("Import completed. No identities were added or updated."));
966 msg_dialog.destroy();