/* * Copyright (C) 2024 GNOME Foundation Inc. * * SPDX-License-Identifier: GPL-3.0-or-later * * Original Author: António Fernandes */ #define G_LOG_DOMAIN "nautilus-file-chooser" #include "nautilus-file-chooser.h" #include #include #include #include "nautilus-directory.h" #include "nautilus-enum-types.h" #include "nautilus-file.h" #include "nautilus-file-utilities.h" #include "nautilus-filename-utilities.h" #include "nautilus-filename-validator.h" #include "nautilus-global-preferences.h" #include "nautilus-scheme.h" #include "nautilus-shortcut-manager.h" #include "nautilus-sidebar.h" #include "nautilus-toolbar.h" #include "nautilus-view-item-filter.h" #include "nautilus-window-slot.h" #define FILTER_WIDTH_CHARS 12 struct _NautilusFileChooser { AdwWindow parent_instance; GActionGroup *chooser_action_group; NautilusMode mode; char *accept_label; char *suggested_name; gboolean flag_initial_focus_done; GtkWidget *split_view; GtkWidget *places_sidebar; NautilusToolbar *toolbar; AdwBin *slot_container; NautilusWindowSlot *slot; GtkDropDown *filters_dropdown; GtkWidget *choices_menu_button; GtkWidget *read_only_checkbox; GtkWidget *accept_button; GtkWidget *filename_widget; GtkWidget *filename_button_container; GtkWidget *filename_undo_button; GtkWidget *filename_entry; GtkWidget *new_folder_button; GtkWidget *title_widget; NautilusFilenameValidator *validator; AdwBreakpoint *breakpoint; }; G_DEFINE_FINAL_TYPE (NautilusFileChooser, nautilus_file_chooser, ADW_TYPE_WINDOW) enum { PROP_0, PROP_MODE, N_PROPS }; static GParamSpec *properties[N_PROPS]; enum { SIGNAL_ACCEPTED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; static void open_filename_entry (NautilusFileChooser *self); static gboolean mode_can_accept_files (NautilusMode mode, NautilusFileList *files) { if (files == NULL) { return FALSE; } gboolean exactly_one_item = (files->next == NULL); switch (mode) { case NAUTILUS_MODE_OPEN_FILE: case NAUTILUS_MODE_SAVE_FILE: { gboolean is_folder = nautilus_file_opens_in_view (NAUTILUS_FILE (files->data)); return (exactly_one_item && !is_folder); } case NAUTILUS_MODE_OPEN_FOLDER: { gboolean is_folder = nautilus_file_opens_in_view (NAUTILUS_FILE (files->data)); return (exactly_one_item && is_folder); } case NAUTILUS_MODE_SAVE_FILES: { return FALSE; } case NAUTILUS_MODE_OPEN_FILES: case NAUTILUS_MODE_OPEN_FOLDERS: { for (NautilusFileList *l = files; l != NULL; l = l->next) { gboolean is_folder = nautilus_file_opens_in_view (NAUTILUS_FILE (l->data)); if (mode != (is_folder ? NAUTILUS_MODE_OPEN_FOLDERS : NAUTILUS_MODE_OPEN_FILES)) { return FALSE; } } return TRUE; } default: { g_assert_not_reached (); } } return FALSE; } static gboolean mode_can_accept_current_directory (NautilusMode mode, GFile *location) { if (location == NULL) { return FALSE; } switch (mode) { case NAUTILUS_MODE_OPEN_FOLDER: case NAUTILUS_MODE_OPEN_FOLDERS: case NAUTILUS_MODE_SAVE_FILE: case NAUTILUS_MODE_SAVE_FILES: { g_autofree char *scheme = g_file_get_uri_scheme (location); return !nautilus_scheme_is_internal (scheme); } case NAUTILUS_MODE_OPEN_FILE: case NAUTILUS_MODE_OPEN_FILES: { return FALSE; } default: { g_assert_not_reached (); } } return FALSE; } static gboolean file_chooser_has_filename_entry (NautilusFileChooser *self) { return (self->mode == NAUTILUS_MODE_SAVE_FILE); } static gboolean nautilus_file_chooser_can_accept (NautilusFileChooser *self, NautilusFileList *files, GFile *location, gboolean filename_passed) { if (self->mode == NAUTILUS_MODE_SAVE_FILE) { return (filename_passed && mode_can_accept_current_directory (self->mode, location)); } else { return (mode_can_accept_files (self->mode, files) || mode_can_accept_current_directory (self->mode, location)); } } static void emit_accepted (NautilusFileChooser *self, GList *file_locations) { g_signal_emit (self, signals[SIGNAL_ACCEPTED], 0, file_locations, GTK_FILE_FILTER (gtk_drop_down_get_selected_item (self->filters_dropdown))); } static void on_overwrite_confirm_response (AdwAlertDialog *dialog, GAsyncResult *result, NautilusFileChooser *self) { const char *response = adw_alert_dialog_choose_finish (dialog, result); if (g_strcmp0 (response, "replace") == 0) { GFile *parent_location = nautilus_window_slot_get_location (self->slot); g_autofree char *new_filename = nautilus_filename_validator_get_new_name (self->validator); g_autoptr (GFile) new_file_location = g_file_get_child (parent_location, new_filename); emit_accepted (self, &(GList){ .data = new_file_location }); } else { /* The user probably wants to rename the file name, so focus entry */ open_filename_entry (self); } } static void ask_confirm_overwrite (NautilusFileChooser *self) { AdwDialog *dialog = adw_alert_dialog_new (_("Replace When Saving?"), NULL); g_autofree char *filename = nautilus_filename_validator_get_new_name (self->validator); g_autoptr (NautilusFile) directory_as_file = nautilus_file_get (nautilus_window_slot_get_location (self->slot)); const char *directory_name = nautilus_file_get_display_name (directory_as_file); adw_alert_dialog_format_body (ADW_ALERT_DIALOG (dialog), _("“%s” already exists in “%s”. Saving will replace or overwrite its contents."), filename, directory_name); adw_alert_dialog_add_responses (ADW_ALERT_DIALOG (dialog), "cancel", _("_Cancel"), "replace", _("_Replace"), NULL); adw_alert_dialog_set_response_appearance (ADW_ALERT_DIALOG (dialog), "replace", ADW_RESPONSE_DESTRUCTIVE); adw_alert_dialog_set_default_response (ADW_ALERT_DIALOG (dialog), "cancel"); adw_alert_dialog_set_close_response (ADW_ALERT_DIALOG (dialog), "cancel"); adw_alert_dialog_choose (ADW_ALERT_DIALOG (dialog), GTK_WIDGET (self), NULL, (GAsyncReadyCallback) on_overwrite_confirm_response, self); } static GFile * get_file_chooser_activation_location (NautilusFile *file) { g_autoptr (GFile) location = nautilus_file_get_location (file); const gchar *path = g_file_peek_path (location); if (path != NULL) { return g_steal_pointer (&location); } return nautilus_file_get_activation_location (file); } static void action_accept (GSimpleAction *action, GVariant *parameter, gpointer user_data) { NautilusFileChooser *self = NAUTILUS_FILE_CHOOSER (user_data); if (self->mode == NAUTILUS_MODE_SAVE_FILE) { if (nautilus_filename_validator_get_will_overwrite (self->validator)) { ask_confirm_overwrite (self); } else { GFile *parent_location = nautilus_window_slot_get_location (self->slot); g_autofree char *name = nautilus_filename_validator_get_new_name (self->validator); g_autoptr (GFile) file_location = g_file_get_child (parent_location, name); emit_accepted (self, &(GList){ .data = file_location }); } } else { NautilusFileList *selection = nautilus_window_slot_get_selection (self->slot); if (mode_can_accept_files (self->mode, selection)) { g_autolist (GFile) file_locations = g_list_copy_deep (selection, (GCopyFunc) get_file_chooser_activation_location, NULL); emit_accepted (self, file_locations); } else { GFile *location = nautilus_window_slot_get_location (self->slot); emit_accepted (self, &(GList){ .data = location }); } } } static void action_focus_entry (GSimpleAction *action, GVariant *parameter, gpointer user_data) { NautilusFileChooser *self = user_data; if (file_chooser_has_filename_entry (self)) { open_filename_entry (self); } } static void on_validator_has_feedback_changed (NautilusFileChooser *self) { gboolean has_feedback; g_object_get (self->validator, "has-feedback", &has_feedback, NULL); if (has_feedback) { gtk_widget_add_css_class (self->filename_entry, "warning"); } else { gtk_widget_remove_css_class (self->filename_entry, "warning"); } } static void on_validator_will_overwrite_changed (NautilusFileChooser *self) { if (nautilus_filename_validator_get_will_overwrite (self->validator)) { gtk_widget_remove_css_class (self->accept_button, "suggested-action"); gtk_widget_add_css_class (self->accept_button, "destructive-action"); gtk_button_set_label (GTK_BUTTON (self->accept_button), _("_Replace")); } else { gtk_widget_remove_css_class (self->accept_button, "destructive-action"); gtk_widget_add_css_class (self->accept_button, "suggested-action"); gtk_button_set_label (GTK_BUTTON (self->accept_button), self->accept_label); } } static void update_cursor (NautilusFileChooser *self) { if (self->slot != NULL && nautilus_window_slot_get_allow_stop (self->slot)) { gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "progress"); } else { gtk_widget_set_cursor (GTK_WIDGET (self), NULL); } } static void on_file_drop (GtkDropTarget *target, const GValue *value, gdouble x, gdouble y, gpointer user_data) { GSList *locations = g_value_get_boxed (value); g_return_if_fail (locations != NULL); g_autolist (NautilusFile) selection = NULL; g_autoptr (GFile) location = NULL; NautilusFileChooser *self = user_data; for (GSList *l = locations; l != NULL; l = l->next) { selection = g_list_prepend (selection, nautilus_file_get (l->data)); } selection = g_list_reverse (selection); if (nautilus_file_opens_in_view (selection->data) && self->mode != NAUTILUS_MODE_OPEN_FOLDER && self->mode != NAUTILUS_MODE_OPEN_FOLDERS) { /* If it's a folder go into the folder unless you want to open that folder */ location = g_object_ref (locations->data); } else { location = g_file_get_parent (locations->data); } nautilus_window_slot_open_location_full (self->slot, location, selection); } static void on_slot_activate_files (NautilusFileChooser *self, GList *files) { if (gtk_widget_get_sensitive (self->accept_button) && mode_can_accept_files (self->mode, files)) { gtk_widget_activate (self->accept_button); } } static void on_filename_entry_focus_leave (NautilusFileChooser *self) { /* The filename entry is a transient: it should hide when it loses focus. * * However, if we lose focus because the window itself lost focus, then the * filename entry should persist, because this may happen due to the user * switching keyboard layout/input method; or they may want to copy/drop * an path from another window/app. We detect this case by looking at the * focus widget of the window (GtkRoot). */ GtkWidget *focus_widget = gtk_root_get_focus (gtk_widget_get_root (GTK_WIDGET (self))); if (focus_widget != NULL && gtk_widget_is_ancestor (focus_widget, GTK_WIDGET (self->filename_entry))) { return; } gtk_stack_set_visible_child (GTK_STACK (self->filename_widget), self->filename_button_container); } static void on_filename_entry_changed (NautilusFileChooser *self) { const char *current_text = gtk_editable_get_text (GTK_EDITABLE (self->filename_entry)); gboolean is_not_suggested_text = (g_strcmp0 (self->suggested_name, current_text) != 0 && self->suggested_name != NULL); gtk_widget_set_visible (self->filename_undo_button, is_not_suggested_text); nautilus_filename_validator_validate (self->validator); } static void open_filename_entry (NautilusFileChooser *self) { gtk_stack_set_visible_child (GTK_STACK (self->filename_widget), self->filename_entry); gtk_entry_grab_focus_without_selecting (GTK_ENTRY (self->filename_entry)); const char *filename = gtk_editable_get_text (GTK_EDITABLE (self->filename_entry)); int extension_offset = nautilus_filename_get_extension_char_offset (filename); gtk_editable_select_region (GTK_EDITABLE (self->filename_entry), 0, extension_offset); } static void on_filename_undo_button_clicked (NautilusFileChooser *self) { gtk_editable_set_text (GTK_EDITABLE (self->filename_entry), self->suggested_name); nautilus_window_slot_open_location_full (self->slot, nautilus_window_slot_get_location (self->slot), NULL); } static void on_slot_selection_notify (NautilusFileChooser *self) { g_return_if_fail (self->mode == NAUTILUS_MODE_SAVE_FILE); NautilusSelectionSource selection_source = nautilus_window_slot_get_selection_source (self->slot); if (!selection_source_is_intentional (selection_source)) { /* If the selection is auto, don't override the suggested name with the * auto-selected file. */ return; } NautilusFileList *selection = nautilus_window_slot_get_selection (self->slot); if (mode_can_accept_files (self->mode, selection)) { NautilusFile *file = NAUTILUS_FILE (selection->data); gtk_editable_set_text (GTK_EDITABLE (self->filename_entry), nautilus_file_get_edit_name (file)); } } static void on_location_changed (NautilusFileChooser *self) { if (self->mode != NAUTILUS_MODE_SAVE_FILE) { return; } g_autoptr (NautilusDirectory) directory = NULL; GFile *location = nautilus_window_slot_get_location (self->slot); g_autofree char *scheme = g_file_get_uri_scheme (location); if (nautilus_scheme_is_internal (scheme)) { return; } directory = nautilus_directory_get (location); nautilus_filename_validator_set_containing_directory (self->validator, directory); nautilus_filename_validator_validate (self->validator); } static gboolean on_key_pressed_bubble (GtkEventControllerKey *controller, unsigned int keyval, unsigned int keycode, GdkModifierType state, gpointer user_data) { NautilusFileChooser *self = NAUTILUS_FILE_CHOOSER (user_data); if (self->slot != NULL && nautilus_window_slot_handle_event (self->slot, controller, keyval, state)) { return GDK_EVENT_STOP; } return GDK_EVENT_PROPAGATE; } static void on_click_gesture_pressed (GtkGestureClick *gesture, gint n_press, gdouble x, gdouble y, gpointer user_data) { NautilusFileChooser *self = user_data; guint button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); if (nautilus_global_preferences_get_use_extra_buttons () && (button == nautilus_global_preferences_get_back_button ())) { nautilus_window_slot_navigate (self->slot, -1); } else if (nautilus_global_preferences_get_use_extra_buttons () && (button == nautilus_global_preferences_get_forward_button ())) { nautilus_window_slot_navigate (self->slot, 1); } } static int get_filter_width_chars (GtkListItem *listitem, const char *name) { return MIN (g_utf8_strlen (name, -1), FILTER_WIDTH_CHARS); } static void update_dropdown_checkmark (GtkDropDown *dropdown, GParamSpec *psepc, GtkListItem *list_item) { guint selected = gtk_drop_down_get_selected (dropdown); GtkWidget *cell = gtk_list_item_get_child (list_item); GtkWidget *check_mark = gtk_widget_get_last_child (cell); gdouble opacity = (selected == gtk_list_item_get_position (list_item)) ? 1 : 0; gtk_widget_set_opacity (check_mark, opacity); } static void filters_dropdown_setup (GtkListItemFactory *factory, GtkListItem *list_item, gpointer user_data) { GtkWidget *box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); GtkWidget *label = gtk_label_new (NULL); GtkWidget *icon; gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_box_append (GTK_BOX (box), label); icon = g_object_new (GTK_TYPE_IMAGE, "icon-name", "object-select-symbolic", "accessible-role", GTK_ACCESSIBLE_ROLE_PRESENTATION, NULL); gtk_box_append (GTK_BOX (box), icon); gtk_list_item_set_child (list_item, box); g_assert (gtk_widget_get_first_child (box) == label && gtk_widget_get_last_child (box) == icon); } static void filters_dropdown_bind (GtkListItemFactory *factory, GtkListItem *list_item, gpointer user_data) { NautilusFileChooser *self = user_data; GtkFileFilter *filter = gtk_list_item_get_item (list_item); GtkWidget *cell = gtk_list_item_get_child (list_item); GtkWidget *label = gtk_widget_get_first_child (cell); gtk_label_set_label (GTK_LABEL (label), gtk_file_filter_get_name (filter)); g_signal_connect (self->filters_dropdown, "notify::selected", G_CALLBACK (update_dropdown_checkmark), list_item); update_dropdown_checkmark (self->filters_dropdown, NULL, list_item); } static void filters_dropdown_unbind (GtkListItemFactory *factory, GtkListItem *list_item, gpointer user_data) { NautilusFileChooser *self = user_data; g_signal_handlers_disconnect_by_func (self->filters_dropdown, update_dropdown_checkmark, list_item); } static gboolean title_widget_query_tooltip (GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data) { PangoLayout *layout = gtk_label_get_layout (GTK_LABEL (widget)); if (pango_layout_is_ellipsized (layout)) { const char *label = gtk_label_get_label (GTK_LABEL (widget)); gtk_tooltip_set_text (tooltip, label); return TRUE; } return FALSE; } static void nautilus_file_chooser_dispose (GObject *object) { NautilusFileChooser *self = (NautilusFileChooser *) object; if (self->slot != NULL) { g_assert (adw_bin_get_child (self->slot_container) == GTK_WIDGET (self->slot)); nautilus_window_slot_set_active (self->slot, FALSE); /* Let bindings on AdwToolbarView:content react to the slot being unset * while the slot itself is still alive. */ adw_bin_set_child (self->slot_container, NULL); g_clear_object (&self->slot); } G_OBJECT_CLASS (nautilus_file_chooser_parent_class)->dispose (object); } static void nautilus_file_chooser_finalize (GObject *object) { NautilusFileChooser *self = (NautilusFileChooser *) object; g_free (self->accept_label); g_free (self->suggested_name); G_OBJECT_CLASS (nautilus_file_chooser_parent_class)->finalize (object); } static void nautilus_file_chooser_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NautilusFileChooser *self = NAUTILUS_FILE_CHOOSER (object); switch (prop_id) { case PROP_MODE: { g_value_set_enum (value, self->mode); } break; default: { G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } } static void nautilus_file_chooser_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NautilusFileChooser *self = NAUTILUS_FILE_CHOOSER (object); switch (prop_id) { case PROP_MODE: { self->mode = g_value_get_enum (value); } break; default: { G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } } static gboolean nautilus_file_chooser_grab_focus (GtkWidget *widget) { NautilusFileChooser *self = NAUTILUS_FILE_CHOOSER (widget); if (self->mode == NAUTILUS_MODE_SAVE_FILE && !self->flag_initial_focus_done) { self->flag_initial_focus_done = TRUE; open_filename_entry (self); } else if (self->slot != NULL) { return gtk_widget_grab_focus (GTK_WIDGET (self->slot)); } return GTK_WIDGET_CLASS (nautilus_file_chooser_parent_class)->grab_focus (widget); } static void nautilus_file_chooser_constructed (GObject *object) { G_OBJECT_CLASS (nautilus_file_chooser_parent_class)->constructed (object); NautilusFileChooser *self = (NautilusFileChooser *) object; /* Setup slot. */ self->slot = nautilus_window_slot_new (self->mode); g_signal_connect_swapped (self->slot, "notify::location", G_CALLBACK (on_location_changed), self); adw_bin_set_child (self->slot_container, GTK_WIDGET (self->slot)); nautilus_window_slot_set_active (self->slot, TRUE); g_signal_connect_swapped (self->slot, "notify::allow-stop", G_CALLBACK (update_cursor), self); g_signal_connect_swapped (self->slot, "activate-files", G_CALLBACK (on_slot_activate_files), self); g_autoptr (NautilusViewItemFilter) filter = nautilus_view_item_filter_new (); g_object_bind_property (self->filters_dropdown, "selected-item", filter, "file-filter", G_BINDING_SYNC_CREATE); nautilus_window_slot_set_filter (self->slot, GTK_FILTER (filter)); gtk_widget_set_visible (self->filename_widget, (self->mode == NAUTILUS_MODE_SAVE_FILE)); gtk_widget_set_visible (self->new_folder_button, (self->mode == NAUTILUS_MODE_SAVE_FILE || self->mode == NAUTILUS_MODE_SAVE_FILES || self->mode == NAUTILUS_MODE_OPEN_FOLDER || self->mode == NAUTILUS_MODE_OPEN_FOLDERS)); /* Add the setter here once the new folder property is set */ adw_breakpoint_add_setters (self->breakpoint, G_OBJECT (self->toolbar), "show-new-folder-button", FALSE, NULL); if (self->mode == NAUTILUS_MODE_SAVE_FILE) { g_signal_connect_object (self->slot, "notify::selection", G_CALLBACK (on_slot_selection_notify), self, G_CONNECT_SWAPPED); } if (g_strcmp0 (PROFILE, "") != 0) { gtk_widget_add_css_class (GTK_WIDGET (self), "devel"); } gtk_widget_add_css_class (self->title_widget, "windowtitle"); gtk_widget_add_css_class (self->title_widget, "title"); g_signal_connect (self->title_widget, "query-tooltip", G_CALLBACK (title_widget_query_tooltip), self); int width, height; g_settings_get (nautilus_window_state, NAUTILUS_WINDOW_STATE_INITIAL_SIZE_FILE_CHOOSER, "(ii)", &width, &height); gtk_window_set_default_size (GTK_WINDOW (self), width, height); } const GActionEntry chooser_action_entries[] = { { .name = "accept", .activate = action_accept }, { .name = "focus-entry", .activate = action_focus_entry }, }; static void nautilus_file_chooser_init (NautilusFileChooser *self) { g_type_ensure (NAUTILUS_TYPE_FILENAME_VALIDATOR); g_type_ensure (NAUTILUS_TYPE_TOOLBAR); g_type_ensure (NAUTILUS_TYPE_PLACES_SIDEBAR); g_type_ensure (NAUTILUS_TYPE_SHORTCUT_MANAGER); gtk_widget_init_template (GTK_WIDGET (self)); /* Give the dialog its own window group. Otherwise all such dialogs would * belong to the same default window group, acting like a stack of modals. */ g_autoptr (GtkWindowGroup) window_group = gtk_window_group_new (); gtk_window_group_add_window (window_group, GTK_WINDOW (self)); /* Setup sidebar */ nautilus_sidebar_set_open_flags (NAUTILUS_PLACES_SIDEBAR (self->places_sidebar), NAUTILUS_OPEN_FLAG_NORMAL); nautilus_sidebar_set_show_trash (NAUTILUS_PLACES_SIDEBAR (self->places_sidebar), FALSE); GtkEventController *controller = gtk_event_controller_key_new (); gtk_widget_add_controller (GTK_WIDGET (self), controller); gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE); g_signal_connect (controller, "key-pressed", G_CALLBACK (on_key_pressed_bubble), self); controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); gtk_widget_add_controller (GTK_WIDGET (self), controller); gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE); gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0); g_signal_connect (controller, "pressed", G_CALLBACK (on_click_gesture_pressed), self); /* The factory is set in the ui, but we need to set the popup (list) factory * in code to make the checkmark appear correctly. */ GtkListItemFactory *factory = gtk_signal_list_item_factory_new (); g_signal_connect (factory, "setup", G_CALLBACK (filters_dropdown_setup), self); g_signal_connect (factory, "bind", G_CALLBACK (filters_dropdown_bind), self); g_signal_connect (factory, "unbind", G_CALLBACK (filters_dropdown_unbind), self); gtk_drop_down_set_list_factory (self->filters_dropdown, factory); /* Setup Actions */ self->chooser_action_group = G_ACTION_GROUP (g_simple_action_group_new ()); g_action_map_add_action_entries (G_ACTION_MAP (self->chooser_action_group), chooser_action_entries, G_N_ELEMENTS (chooser_action_entries), self); gtk_widget_insert_action_group (GTK_WIDGET (self), "chooser", G_ACTION_GROUP (self->chooser_action_group)); /* Bind "accept" action enabled to nautilus_file_chooser_can_accept(). */ GAction *action = g_action_map_lookup_action (G_ACTION_MAP (self->chooser_action_group), "accept"); GtkExpression *container_expr, *validator_expr, *slot_expr, **expressions, *closure_expr; container_expr = gtk_constant_expression_new (ADW_TYPE_BIN, self->slot_container); validator_expr = gtk_constant_expression_new (NAUTILUS_TYPE_FILENAME_VALIDATOR, self->validator); slot_expr = gtk_property_expression_new (ADW_TYPE_BIN, container_expr, "child"), expressions = (GtkExpression *[]) { gtk_property_expression_new (NAUTILUS_TYPE_WINDOW_SLOT, gtk_expression_ref (slot_expr), "selection"), gtk_property_expression_new (NAUTILUS_TYPE_WINDOW_SLOT, slot_expr, "location"), gtk_property_expression_new (NAUTILUS_TYPE_FILENAME_VALIDATOR, validator_expr, "passed"), }; closure_expr = gtk_cclosure_expression_new (G_TYPE_BOOLEAN, NULL, 3, expressions, G_CALLBACK (nautilus_file_chooser_can_accept), NULL, NULL); gtk_expression_bind (closure_expr, action, "enabled", self); } static void nautilus_file_chooser_class_init (NautilusFileChooserClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->constructed = nautilus_file_chooser_constructed; object_class->dispose = nautilus_file_chooser_dispose; object_class->finalize = nautilus_file_chooser_finalize; object_class->get_property = nautilus_file_chooser_get_property; object_class->set_property = nautilus_file_chooser_set_property; widget_class->grab_focus = nautilus_file_chooser_grab_focus; gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-file-chooser.ui"); gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, split_view); gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, places_sidebar); gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, toolbar); gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, slot_container); gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, filters_dropdown); gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, choices_menu_button); gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, accept_button); gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, filename_widget); gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, filename_button_container); gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, filename_undo_button); gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, filename_entry); gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, new_folder_button); gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, validator); gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, title_widget); gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, breakpoint); gtk_widget_class_bind_template_callback (widget_class, open_filename_entry); gtk_widget_class_bind_template_callback (widget_class, on_filename_undo_button_clicked); gtk_widget_class_bind_template_callback (widget_class, on_filename_entry_changed); gtk_widget_class_bind_template_callback (widget_class, on_filename_entry_focus_leave); gtk_widget_class_bind_template_callback (widget_class, nautilus_filename_validator_validate); gtk_widget_class_bind_template_callback (widget_class, on_validator_has_feedback_changed); gtk_widget_class_bind_template_callback (widget_class, on_validator_will_overwrite_changed); gtk_widget_class_bind_template_callback (widget_class, on_file_drop); gtk_widget_class_bind_template_callback (widget_class, get_filter_width_chars); gtk_widget_class_add_binding_action (widget_class, GDK_KEY_e, GDK_CONTROL_MASK, "chooser.focus-entry", NULL); /* This shortcut was provided via a mnemonic by the GTK file chooser */ gtk_widget_class_add_binding_action (widget_class, GDK_KEY_n, GDK_ALT_MASK, "chooser.focus-entry", NULL); properties[PROP_MODE] = g_param_spec_enum ("mode", NULL, NULL, NAUTILUS_TYPE_MODE, NAUTILUS_MODE_OPEN_FILE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, N_PROPS, properties); signals[SIGNAL_ACCEPTED] = g_signal_new ("accepted", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_POINTER, GTK_TYPE_FILE_FILTER); } NautilusFileChooser * nautilus_file_chooser_new (NautilusMode mode) { g_assert (mode != NAUTILUS_MODE_BROWSE); return g_object_new (NAUTILUS_TYPE_FILE_CHOOSER, "mode", mode, NULL); } void nautilus_file_chooser_set_accept_label (NautilusFileChooser *self, const char *accept_label) { g_set_str (&self->accept_label, accept_label); gtk_button_set_label (GTK_BUTTON (self->accept_button), accept_label); } void nautilus_file_chooser_set_current_filter (NautilusFileChooser *self, guint position) { gtk_drop_down_set_selected (self->filters_dropdown, position); } void nautilus_file_chooser_set_filters (NautilusFileChooser *self, GListModel *filters) { gboolean has_filters = (filters != NULL && g_list_model_get_n_items (filters) != 0); gtk_drop_down_set_model (self->filters_dropdown, filters); gtk_widget_set_visible (GTK_WIDGET (self->filters_dropdown), has_filters); } void nautilus_file_chooser_set_starting_location (NautilusFileChooser *self, GFile *starting_location) { g_autoptr (GFile) location_to_open = NULL; if (starting_location != NULL) { location_to_open = g_object_ref (starting_location); } else { location_to_open = g_file_new_for_path (g_get_home_dir ()); } nautilus_window_slot_open_location_full (self->slot, location_to_open, NULL); } void nautilus_file_chooser_set_suggested_name (NautilusFileChooser *self, const char *suggested_name) { g_set_str (&self->suggested_name, suggested_name); if (suggested_name != NULL) { gtk_editable_set_text (GTK_EDITABLE (self->filename_entry), suggested_name); } } void nautilus_file_chooser_add_choices (NautilusFileChooser *self, GActionGroup *action_group, GMenuModel *menu) { gboolean visible = g_menu_model_get_n_items (menu) > 0; gtk_widget_insert_action_group (GTK_WIDGET (self), "choices", action_group); gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (self->choices_menu_button), menu); gtk_widget_set_visible (self->choices_menu_button, visible); }