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 // Don't leave passwords in memory longer than necessary.
563 // (This may not actually clear the data, but it's the best we can do.)
564 dialog.clear_password();
565 identity.store_password = dialog.remember;
567 identity.temporary = false;
568 retval = model.update_card(identity);
574 // Do this again, in case OK button wasn't selected.
575 dialog.clear_password();
582 private void send_identity_cb(IdCard id)
584 return_if_fail(this.selection_in_progress());
586 var request = this.request_queue.pop_head();
587 var identity = check_add_password(id, request, identities_manager);
588 send_button.set_sensitive(false);
592 if (!this.selection_in_progress())
595 clear_selection_prompts();
596 if (!parent_app.explicitly_launched) {
597 // The following occasionally causes the app to exit without sending the dbus
598 // reply, so for now we just don't exit
604 IdentityRequest next = this.request_queue.peek_head();
605 candidates = next.candidates;
606 set_prompting_service(next.service);
609 redraw_id_card_widgets();
611 if ((identity != null) && (!identity.is_no_identity()))
612 parent_app.default_id_card = identity;
614 request.return_identity(identity, remember_identity_binding.active);
616 remember_identity_binding.active = false;
617 remember_identity_binding.hide();
620 private void on_about_action()
622 string copyright = "Copyright (c) 2011, %d JANET".printf(LATEST_EDIT_YEAR);
626 Copyright (c) 2011, %d JANET(UK)
629 Redistribution and use in source and binary forms, with or without
630 modification, are permitted provided that the following conditions
633 1. Redistributions of source code must retain the above copyright
634 notice, this list of conditions and the following disclaimer.
636 2. Redistributions in binary form must reproduce the above copyright
637 notice, this list of conditions and the following disclaimer in the
638 documentation and/or other materials provided with the distribution.
640 3. Neither the name of JANET(UK) nor the names of its contributors
641 may be used to endorse or promote products derived from this software
642 without specific prior written permission.
644 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
645 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
646 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
647 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
648 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
649 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
650 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
651 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
652 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
653 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
655 """.printf(LATEST_EDIT_YEAR);
657 AboutDialog about = new AboutDialog();
659 about.set_comments(_("Moonshot project UI"));
660 about.set_copyright(copyright);
661 about.set_website(Config.PACKAGE_URL);
662 about.set_website_label(_("Visit the Moonshot project web site"));
664 // Note: The package version is configured at the top of moonshot/ui/configure.ac
665 about.set_version(Config.PACKAGE_VERSION);
666 about.set_license(license);
667 about.set_modal(true);
668 about.set_transient_for(this);
669 about.response.connect((a, b) => {about.destroy();});
675 private Gtk.ActionEntry[] create_actions() {
676 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
678 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
683 // Pick up the translated version of the name, if any
684 helpmenu.label = dgettext(null, helpmenu.label);
687 Gtk.ActionEntry about = { "AboutAction",
695 N_("About this application"),
698 about.label = dgettext(null, about.label);
705 private void create_ui_manager()
707 Gtk.ActionGroup action_group = new Gtk.ActionGroup("GeneralActionGroup");
708 action_group.add_actions(create_actions(), this);
709 ui_manager.insert_action_group(action_group, 0);
712 ui_manager.add_ui_from_string(menu_layout, -1);
716 stderr.printf("%s\n", e.message);
717 logger.error("create_ui_manager: Caught error: " + e.message);
719 ui_manager.ensure_update();
722 private void build_ui()
730 int button_width = 1;
732 Table top_table = new Table(num_rows, 10, false);
733 top_table.set_border_width(12);
735 AttachOptions fill_and_expand = AttachOptions.EXPAND | AttachOptions.FILL;
736 AttachOptions fill = AttachOptions.FILL;
739 service_prompt_vbox = new VBox(false, 0);
740 top_table.attach(service_prompt_vbox, 0, 1, row, row + 1, fill_and_expand, fill_and_expand, 12, 0);
743 string search_tooltip_text = _("Search for an identity or service");
744 this.search_entry = new Entry();
746 set_atk_name_description(search_entry, _("Search entry"), _("Search for a specific ID Card"));
747 this.search_entry.set_icon_from_pixbuf(EntryIconPosition.SECONDARY,
748 find_icon_sized("edit-find", Gtk.IconSize.MENU));
749 this.search_entry.set_icon_tooltip_text(EntryIconPosition.SECONDARY,
750 search_tooltip_text);
752 this.search_entry.set_tooltip_text(search_tooltip_text);
754 this.search_entry.set_icon_sensitive(EntryIconPosition.SECONDARY, false);
756 this.search_entry.notify["text"].connect(search_entry_text_changed_cb);
757 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
758 this.search_entry.set_width_chars(24);
760 var search_label_markup ="<small>" + search_tooltip_text + "</small>";
761 var full_search_label = new Label(null);
762 full_search_label.set_markup(search_label_markup);
763 full_search_label.set_alignment(1, 0);
765 var search_vbox = new VBox(false, 0);
766 search_vbox.pack_start(search_entry, false, false, 0);
767 var search_spacer = new Alignment(0, 0, 0, 0);
768 search_spacer.set_size_request(0, 2);
769 search_vbox.pack_start(search_spacer, false, false, 0);
770 search_vbox.pack_start(full_search_label, false, false, 0);
772 // Overlap with the service_prompt_box
773 top_table.attach(search_vbox, 5, num_cols - button_width, row - 1, row + 1, fill_and_expand, fill, 0, 12);
776 this.custom_vbox = new CustomVBox(this, false, 2);
778 var viewport = new Viewport(null, null);
779 viewport.set_border_width(2);
780 viewport.set_shadow_type(ShadowType.NONE);
781 viewport.add(custom_vbox);
782 var id_scrollwin = new ScrolledWindow(null, null);
783 id_scrollwin.set_policy(PolicyType.NEVER, PolicyType.AUTOMATIC);
784 id_scrollwin.set_shadow_type(ShadowType.IN);
785 id_scrollwin.add_with_viewport(viewport);
786 top_table.attach(id_scrollwin, 0, num_cols - 1, row, num_rows - 1, fill_and_expand, fill_and_expand, 6, 0);
788 // Right below id_scrollwin:
789 remember_identity_binding = new CheckButton.with_label(_("Remember my identity choice for this service"));
790 remember_identity_binding.active = true;
791 top_table.attach(remember_identity_binding, 0, num_cols / 2, num_rows - 1, num_rows, fill_and_expand, fill_and_expand, 3, 0);
793 var add_button = new Button.with_label(_("Add"));
794 add_button.clicked.connect((w) => {add_identity_cb();});
795 top_table.attach(make_rigid(add_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
798 var import_button = new Button.with_label(_("Import"));
799 import_button.clicked.connect((w) => {import_identities_cb();});
800 top_table.attach(make_rigid(import_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
803 this.edit_button = new Button.with_label(_("Edit"));
804 edit_button.clicked.connect((w) => {edit_identity_cb(this.selected_card);});
805 edit_button.set_sensitive(false);
806 top_table.attach(make_rigid(edit_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
809 this.remove_button = new Button.with_label(_("Remove"));
810 remove_button.clicked.connect((w) => {remove_identity_cb(this.selected_card);});
811 remove_button.set_sensitive(false);
812 top_table.attach(make_rigid(remove_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
815 // push the send button down another row.
817 this.send_button = new Button.with_label(_("Send"));
818 send_button.clicked.connect((w) => {send_identity_cb(this.selected_card);});
819 // send_button.set_visible(false);
820 send_button.set_sensitive(false);
821 top_table.attach(make_rigid(send_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
824 var main_vbox = new VBox(false, 0);
827 // hide the File | Quit menu item which is now on the Mac Menu
828 // Gtk.Widget quit_item = this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
831 Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
833 osxApp.set_menu_bar(menushell);
834 osxApp.set_use_quartz_accelerators(true);
835 osxApp.sync_menu_bar();
838 var menubar = this.ui_manager.get_widget("/MenuBar");
839 main_vbox.pack_start(menubar, false, false, 0);
840 set_bg_color(menubar);
842 main_vbox.pack_start(top_table, true, true, 6);
845 main_vbox.show_all();
847 if (!this.selection_in_progress())
848 remember_identity_binding.hide();
851 internal bool selection_in_progress() {
852 return !this.request_queue.is_empty();
855 private void set_atk_name_description(Widget widget, string name, string description)
857 var atk_widget = widget.get_accessible();
859 atk_widget.set_name(name);
860 atk_widget.set_description(description);
863 private void connect_signals()
865 this.destroy.connect(() => {
866 logger.trace("Destroy event; calling Gtk.main_quit()");
869 this.identities_manager.card_list_changed.connect(this.on_card_list_changed);
870 this.delete_event.connect(() => {return confirm_quit();});
873 private bool confirm_quit() {
874 logger.trace("delete_event intercepted; selection_in_progress()=" + selection_in_progress().to_string());
876 if (selection_in_progress()) {
877 var result = WarningDialog.confirm(this,
878 Markup.printf_escaped(
879 "<span font-weight='heavy'>" + _("Do you wish to use the %s service?") + "</span>",
880 this.request_queue.peek_head().service)
881 + "\n\n" + _("Select Yes to select an ID for this service, or No to cancel"),
882 "close_moonshot_window");
884 // Prevent other handlers from handling this event; this keeps the window open.
889 // Allow the window deletion to proceed.
893 private static Widget make_rigid(Button button)
895 // Hack to prevent the button from growing vertically
896 VBox fixed_height = new VBox(false, 0);
897 fixed_height.pack_start(button, false, false, 0);
902 private void import_identities_cb() {
903 var dialog = new FileChooserDialog(_("Import File"),
905 FileChooserAction.OPEN,
906 _("Cancel"),ResponseType.CANCEL,
907 _("Open"), ResponseType.ACCEPT,
910 if (import_directory != null) {
911 dialog.set_current_folder(import_directory);
914 if (dialog.run() == ResponseType.ACCEPT)
916 // Save the parent directory to use as default for next save
917 string filename = dialog.get_filename();
918 var file = File.new_for_path(filename);
919 import_directory = file.get_parent().get_path();
921 int import_count = 0;
923 var webp = new Parser(filename);
926 logger.trace(@"import_identities_cb: Have $(webp.cards.length) IdCards");
927 foreach (IdCard card in webp.cards)
931 logger.trace(@"import_identities_cb: Skipping null IdCard");
935 if (!card.trust_anchor.is_empty()) {
936 string ta_datetime_added = TrustAnchor.format_datetime_now();
937 card.trust_anchor.set_datetime_added(ta_datetime_added);
938 logger.trace("import_identities_cb : Set ta_datetime_added for '%s' to '%s'; ca_cert='%s'; server_cert='%s'"
939 .printf(card.display_name, ta_datetime_added, card.trust_anchor.ca_cert, card.trust_anchor.server_cert));
943 bool result = add_identity(card, use_flat_file_store);
945 logger.trace(@"import_identities_cb: Added or updated '$(card.display_name)'");
949 logger.trace(@"import_identities_cb: Did not add or update '$(card.display_name)'");
952 if (import_count == 0) {
953 var msg_dialog = new Gtk.MessageDialog(this,
954 Gtk.DialogFlags.DESTROY_WITH_PARENT,
955 Gtk.MessageType.INFO,
957 _("Import completed. No identities were added or updated."));
959 msg_dialog.destroy();