1
0
mirror of https://gitlab.gnome.org/GNOME/nautilus synced 2024-06-28 14:35:28 +00:00

Compare commits

...

5 Commits

Author SHA1 Message Date
António Fernandes
80abefd369 Skip patched-mutter shenaningans for testing purposes 2024-06-24 13:43:42 +01:00
António Fernandes
adb8aeafe9 SAVE_FILE 2024-06-24 13:27:31 +01:00
António Fernandes
e73a8e88ec FIXUP-FC 2024-06-24 13:27:31 +01:00
António Fernandes
98296d71cc filename-validator: Support NULL current-directory
There is no need to treat this as a programming error. Simply don't
validade filename without a directory.

This is going to be needed by the FileChooser in SAVE_FILE mode, if
neither the client app nor the user has set a target directory yet.
2024-06-24 13:27:31 +01:00
António Fernandes
b1d7eaf666 filename-validator: Introduce :allow-override
This property is going to be set to TRUE by the upcoming FileChooser.

If turned on, the validator will still provide feedback on duplated
names, but it will accept them nonetheless if otherwise valid. It's
up to the FileChooser to confirm the user wants to override the
existing file.
2024-06-24 13:27:31 +01:00
9 changed files with 461 additions and 71 deletions

View File

@ -27,6 +27,7 @@ src/nautilus-directory.c
src/nautilus-dnd.c
src/nautilus-error-reporting.c
src/nautilus-file.c
src/nautilus-file-chooser.c
src/nautilus-file-conflict-dialog.c
src/nautilus-filename-validator.c
src/nautilus-file-operations.c

View File

@ -1128,23 +1128,11 @@ nautilus_application_startup (GApplication *app)
g_application_set_resource_base_path (G_APPLICATION (self), "/org/gnome/nautilus");
/* Initialize GDK display (for wayland-x11-interop protocol) before GTK does
* it during the chain-up. */
g_autoptr (GError) error = NULL;
GdkDisplay *display = init_external_window_display (&error);
if (error != NULL)
{
g_message ("Failed to initialize display server connection: %s",
error->message);
}
/* Chain up to the GtkApplication implementation early, so that gtk_init()
* is called for us.
*/
G_APPLICATION_CLASS (nautilus_application_parent_class)->startup (G_APPLICATION (self));
g_assert (gdk_display_get_default () == display);
gtk_window_set_default_icon_name (APPLICATION_ID);
/* initialize preferences and create the global GSettings objects */

View File

@ -11,6 +11,7 @@
#include "nautilus-file-chooser.h"
#include <config.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "gtk/nautilusgtkplacessidebarprivate.h"
@ -18,12 +19,18 @@
#include "nautilus-directory.h"
#include "nautilus-enum-types.h"
#include "nautilus-file.h"
#include "nautilus-filename-validator.h"
#include "nautilus-scheme.h"
#include "nautilus-shortcut-manager.h"
#include "nautilus-toolbar.h"
#include "nautilus-view-item-filter.h"
#include "nautilus-window-slot.h"
#define SELECTOR_DEFAULT_WIDTH 890
#define NAMER_DEFAULT_WIDTH 400
#define SELECTOR_DEFAULT_HEIGHT 550
#define NAMER_DEFAULT_HEIGHT 360
struct _NautilusFileChooser
{
AdwWindow parent_instance;
@ -38,9 +45,19 @@ struct _NautilusFileChooser
GtkDropDown *filters_dropdown;
GtkWidget *choices_menu_button;
GtkWidget *read_only_checkbox;
GtkWidget *selector_cancel_button;
GtkWidget *selector_accept_button;
GtkWidget *namer_view;
GtkWidget *name_group;
GtkWidget *name_entry;
GtkWidget *location_row;
GtkWidget *namer_accept_button;
AdwAnimation *width_animation;
AdwAnimation *height_animation;
NautilusDirectory *current_directory;
NautilusFilenameValidator *validator;
};
G_DEFINE_FINAL_TYPE (NautilusFileChooser, nautilus_file_chooser, ADW_TYPE_WINDOW)
@ -173,22 +190,10 @@ selector_can_accept (NautilusFileChooser *self,
static void
emit_accepted (NautilusFileChooser *self,
GList *files)
GList *file_locations)
{
g_autolist (GFile) file_locations = NULL;
gboolean writable = !gtk_check_button_get_active (GTK_CHECK_BUTTON (self->read_only_checkbox));
if (files != NULL)
{
file_locations = g_list_copy_deep (files, (GCopyFunc) nautilus_file_get_activation_location, NULL);
}
else
{
GFile *location = nautilus_window_slot_get_location (self->slot);
file_locations = g_list_prepend (NULL, g_object_ref (location));
}
g_signal_emit (self, signals[SIGNAL_ACCEPTED], 0,
file_locations,
GTK_FILE_FILTER (gtk_drop_down_get_selected_item (self->filters_dropdown)),
@ -202,27 +207,178 @@ update_current_directory (NautilusFileChooser *self,
g_return_if_fail (G_IS_FILE (location));
g_autoptr (NautilusDirectory) directory = nautilus_directory_get (location);
g_autofree char *parse_name = g_file_get_parse_name (location);
g_set_object (&self->current_directory, directory);
adw_action_row_set_subtitle (ADW_ACTION_ROW (self->location_row), parse_name);
gtk_widget_add_css_class (self->location_row, "property");
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CURRENT_DIRECTORY]);
}
static void
animate_switch_to_stack_child (NautilusFileChooser *self,
GtkWidget *page)
{
gboolean to_namer = (page == self->namer_view);
adw_timed_animation_set_value_from (ADW_TIMED_ANIMATION (self->width_animation),
gtk_widget_get_width (GTK_WIDGET (self)));
adw_timed_animation_set_value_from (ADW_TIMED_ANIMATION (self->height_animation),
gtk_widget_get_height (GTK_WIDGET (self)));
adw_timed_animation_set_value_to (ADW_TIMED_ANIMATION (self->width_animation),
(to_namer ? NAMER_DEFAULT_WIDTH : SELECTOR_DEFAULT_WIDTH));
adw_timed_animation_set_value_to (ADW_TIMED_ANIMATION (self->height_animation),
(to_namer ? NAMER_DEFAULT_HEIGHT : SELECTOR_DEFAULT_HEIGHT));
adw_animation_play (self->width_animation);
adw_animation_play (self->height_animation);
gtk_stack_set_visible_child (self->stack, page);
}
static void
on_selector_accept_button_clicked (NautilusFileChooser *self)
{
GList *selection = nautilus_window_slot_get_selection (self->slot);
if (mode_can_accept_files (self->mode, selection))
if (self->mode == NAUTILUS_MODE_SAVE_FILE)
{
emit_accepted (self, selection);
if (mode_can_accept_files (self->mode, selection))
{
g_autoptr (GFile) selected_location = NULL;
NautilusFile *file = NAUTILUS_FILE (selection->data);
if (nautilus_file_opens_in_view (file))
{
selected_location = nautilus_file_get_location (file);
}
else
{
gtk_editable_set_text (GTK_EDITABLE (self->name_entry),
nautilus_file_get_edit_name (file));
selected_location = nautilus_file_get_parent_location (file);
}
update_current_directory (self, selected_location);
}
else
{
update_current_directory (self, nautilus_window_slot_get_location (self->slot));
}
animate_switch_to_stack_child (self, self->namer_view);
}
else
{
emit_accepted (self, NULL);
if (mode_can_accept_files (self->mode, selection))
{
g_autolist (GFile) file_locations = g_list_copy_deep (selection, (GCopyFunc) nautilus_file_get_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
on_override_confirm_response (AdwAlertDialog *dialog,
GAsyncResult *result,
NautilusFileChooser *self)
{
const char *response = adw_alert_dialog_choose_finish (dialog, result);
if (g_strcmp0 (response, "replace") == 0)
{
g_autofree char *name = nautilus_filename_validator_get_new_name (self->validator);
g_autoptr (GFile) parent_location = nautilus_directory_get_location (self->current_directory);
g_autoptr (GFile) file_location = g_file_get_child (parent_location, name);
emit_accepted (self, &(GList){ .data = file_location });
}
}
static void
ask_confirm_override (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_directory_get_corresponding_file (self->current_directory);
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 override 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_override_confirm_response, self);
}
static void
on_namer_accept_button_clicked (NautilusFileChooser *self)
{
if (nautilus_filename_validator_will_override (self->validator))
{
ask_confirm_override (self);
}
else
{
g_autofree char *name = nautilus_filename_validator_get_new_name (self->validator);
g_autoptr (GFile) parent_location = nautilus_directory_get_location (self->current_directory);
g_autoptr (GFile) file_location = g_file_get_child (parent_location, name);
emit_accepted (self, &(GList){ .data = file_location });
}
}
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->namer_accept_button, "warning");
gtk_widget_add_css_class (self->name_group, "warning");
}
else
{
gtk_widget_remove_css_class (self->namer_accept_button, "warning");
gtk_widget_remove_css_class (self->name_group, "warning");
}
}
static void
action_pick_location (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
{
NautilusFileChooser *self = NAUTILUS_FILE_CHOOSER (widget);
animate_switch_to_stack_child (self, self->selector_view);
}
static void
update_cursor (NautilusFileChooser *self)
{
@ -243,10 +399,41 @@ on_slot_activate_files (NautilusFileChooser *self,
{
if (mode_can_accept_files (self->mode, files))
{
emit_accepted (self, files);
if (self->mode == NAUTILUS_MODE_SAVE_FILE)
{
NautilusFile *file = NAUTILUS_FILE (files->data);
gtk_editable_set_text (GTK_EDITABLE (self->name_entry),
nautilus_file_get_edit_name (file));
g_autoptr (GFile) location = nautilus_file_get_activation_location (file);
g_autoptr (GFile) parent_location = g_file_get_parent (location);
update_current_directory (self, parent_location);
animate_switch_to_stack_child (self, self->namer_view);
}
else
{
g_autolist (GFile) file_locations = g_list_copy_deep (files, (GCopyFunc) nautilus_file_get_activation_location, NULL);
emit_accepted (self, file_locations);
}
}
}
static gboolean
on_close_request (NautilusFileChooser *self)
{
if (self->mode == NAUTILUS_MODE_SAVE_FILE &&
gtk_stack_get_visible_child (self->stack) == self->selector_view)
{
animate_switch_to_stack_child (self, self->namer_view);
return TRUE;
}
return FALSE;
}
static gboolean
on_key_pressed_bubble (GtkEventControllerKey *controller,
unsigned int keyval,
@ -270,6 +457,8 @@ nautilus_file_chooser_dispose (GObject *object)
{
NautilusFileChooser *self = (NautilusFileChooser *) object;
g_clear_object (&self->width_animation);
g_clear_object (&self->height_animation);
g_clear_object (&self->current_directory);
if (self->slot != NULL)
@ -344,6 +533,7 @@ nautilus_file_chooser_constructed (GObject *object)
G_OBJECT_CLASS (nautilus_file_chooser_parent_class)->constructed (object);
NautilusFileChooser *self = (NautilusFileChooser *) object;
gboolean start_with_namer = (self->mode == NAUTILUS_MODE_SAVE_FILE);
/* Setup slot.
* We hold a reference to control its lifetime with relation to bindings. */
@ -364,11 +554,22 @@ nautilus_file_chooser_constructed (GObject *object)
gtk_widget_set_visible (self->choices_menu_button,
nautilus_mode_is_open (self->mode));
gtk_widget_set_visible (self->selector_cancel_button, start_with_namer);
gtk_stack_set_visible_child (self->stack, (start_with_namer ?
self->namer_view :
self->selector_view));
gtk_window_set_default_size (GTK_WINDOW (self),
(start_with_namer ? NAMER_DEFAULT_WIDTH : SELECTOR_DEFAULT_WIDTH),
(start_with_namer ? NAMER_DEFAULT_HEIGHT : SELECTOR_DEFAULT_HEIGHT));
}
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_GTK_PLACES_SIDEBAR);
g_type_ensure (NAUTILUS_TYPE_SHORTCUT_MANAGER);
@ -383,6 +584,18 @@ nautilus_file_chooser_init (NautilusFileChooser *self)
gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
g_signal_connect (controller, "key-pressed",
G_CALLBACK (on_key_pressed_bubble), self);
self->width_animation = adw_timed_animation_new (GTK_WIDGET (self),
0.0, 0.0, 500,
adw_property_animation_target_new (G_OBJECT (self),
"default-width"));
self->height_animation = adw_timed_animation_new (GTK_WIDGET (self),
0.0, 0.0, 500,
adw_property_animation_target_new (G_OBJECT (self),
"default-height"));
g_signal_connect_swapped (self, "close-request", G_CALLBACK (on_close_request), self);
}
static void
@ -406,10 +619,22 @@ nautilus_file_chooser_class_init (NautilusFileChooserClass *klass)
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, read_only_checkbox);
gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, selector_cancel_button);
gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, selector_accept_button);
gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, namer_view);
gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, name_group);
gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, name_entry);
gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, location_row);
gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, namer_accept_button);
gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, validator);
gtk_widget_class_bind_template_callback (widget_class, selector_can_accept);
gtk_widget_class_bind_template_callback (widget_class, on_selector_accept_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, on_namer_accept_button_clicked);
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_install_action (widget_class, "filechooser.pick-location", NULL, action_pick_location);
properties[PROP_CURRENT_DIRECTORY] =
g_param_spec_object ("current-directory", NULL, NULL,
@ -444,7 +669,14 @@ void
nautilus_file_chooser_set_accept_label (NautilusFileChooser *self,
const char *accept_label)
{
gtk_button_set_label (GTK_BUTTON (self->selector_accept_button), accept_label);
if (self->mode == NAUTILUS_MODE_SAVE_FILE)
{
gtk_button_set_label (GTK_BUTTON (self->namer_accept_button), accept_label);
}
else
{
gtk_button_set_label (GTK_BUTTON (self->selector_accept_button), accept_label);
}
}
void
@ -482,3 +714,13 @@ nautilus_file_chooser_set_starting_location (NautilusFileChooser *self,
nautilus_window_slot_open_location_full (self->slot, location_to_open, 0, NULL);
}
void
nautilus_file_chooser_set_suggested_name (NautilusFileChooser *self,
const char *suggested_name)
{
if (suggested_name != NULL)
{
gtk_editable_set_text (GTK_EDITABLE (self->name_entry), suggested_name);
}
}

View File

@ -34,5 +34,8 @@ nautilus_file_chooser_set_filters (NautilusFileChooser *self,
void
nautilus_file_chooser_set_starting_location (NautilusFileChooser *self,
GFile *starting_location);
void
nautilus_file_chooser_set_suggested_name (NautilusFileChooser *self,
const char *suggested_name);
G_END_DECLS

View File

@ -17,6 +17,7 @@ struct _NautilusFilenameValidator
NautilusDirectory *containing_directory;
gboolean target_is_folder;
gboolean allow_override;
char *original_name;
const char *feedback_text;
@ -43,6 +44,7 @@ enum
PROP_NEW_NAME,
PROP_CONTAINING_DIRECTORY,
PROP_PASSED,
PROP_ALLOW_OVERRIDE,
PROP_TARGET_IS_FOLDER,
NUM_PROPERTIES
};
@ -200,7 +202,8 @@ filename_validator_process_new_name (NautilusFilenameValidator *self)
!nautilus_filename_validator_ignore_existing_file (self,
existing_file);
gboolean passed = (self->valid_name && !self->duplicated_name);
gboolean passed = (self->valid_name &&
(self->allow_override || !self->duplicated_name));
if (self->passed != passed)
{
@ -249,7 +252,11 @@ void
nautilus_filename_validator_validate (NautilusFilenameValidator *self)
{
g_return_if_fail (NAUTILUS_IS_FILENAME_VALIDATOR (self));
g_return_if_fail (NAUTILUS_IS_DIRECTORY (self->containing_directory));
if (self->containing_directory == NULL)
{
return;
}
nautilus_directory_call_when_ready (self->containing_directory,
NAUTILUS_FILE_ATTRIBUTE_INFO,
@ -378,6 +385,12 @@ nautilus_filename_validator_set_property (GObject *object,
}
break;
case PROP_ALLOW_OVERRIDE:
{
self->allow_override = g_value_get_boolean (value);
}
break;
case PROP_TARGET_IS_FOLDER:
{
self->target_is_folder = g_value_get_boolean (value);
@ -453,6 +466,10 @@ nautilus_filename_validator_class_init (NautilusFilenameValidatorClass *klass)
g_param_spec_object ("containing-directory", NULL, NULL,
NAUTILUS_TYPE_DIRECTORY,
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
properties[PROP_ALLOW_OVERRIDE] =
g_param_spec_boolean ("allow-override", NULL, NULL,
FALSE,
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
properties[PROP_TARGET_IS_FOLDER] =
g_param_spec_boolean ("target-is-folder", NULL, NULL,
FALSE,
@ -460,3 +477,9 @@ nautilus_filename_validator_class_init (NautilusFilenameValidatorClass *klass)
g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
}
gboolean
nautilus_filename_validator_will_override (NautilusFilenameValidator *self)
{
return (self->allow_override && self->passed && self->duplicated_name);
}

View File

@ -24,3 +24,4 @@ void nautilus_filename_validator_set_original_name (NautilusFilenameVa
const char *original_name);
void nautilus_filename_validator_validate (NautilusFilenameValidator *self);
void nautilus_filename_validator_try_accept (NautilusFilenameValidator *self);
gboolean nautilus_filename_validator_will_override (NautilusFilenameValidator *self);

View File

@ -354,6 +354,7 @@ handle_file_chooser_methods (XdpImplFileChooser *object,
nautilus_file_chooser_set_filters (window, G_LIST_MODEL (filters));
nautilus_file_chooser_set_current_filter (window, current_filter_position);
nautilus_file_chooser_set_starting_location (window, starting_location);
nautilus_file_chooser_set_suggested_name (window, suggested_filename);
gtk_window_set_title (GTK_WINDOW (window), arg_title);
g_signal_connect_swapped (window, "close-request",
@ -364,24 +365,7 @@ handle_file_chooser_methods (XdpImplFileChooser *object,
/* Show window */
if (arg_parent_window != NULL)
{
data->external_parent = create_external_window_from_handle (arg_parent_window);
if (data->external_parent == NULL)
{
g_warning ("Failed to associate portal window with parent window %s",
arg_parent_window);
}
else
{
gtk_widget_realize (GTK_WIDGET (window));
GdkSurface *surface = gtk_native_get_surface (GTK_NATIVE (window));
gboolean modal = TRUE;
(void) g_variant_lookup (arg_options, "modal", "b", &modal);
external_window_set_parent_of (data->external_parent, surface);
gtk_window_set_modal (data->window, modal);
}
g_message ("No parenting. Testing purposes only.");
}
gtk_window_present (data->window);

View File

@ -327,3 +327,7 @@ label.encrypted_zip {
rubberband {
border-radius: 6px;
}
#NautilusFileChooser preferencesgroup.move-up-12-px {
margin-top: -12px;
}

View File

@ -2,13 +2,11 @@
<interface>
<requires lib="gtk" version="4.0"/>
<template class="NautilusFileChooser" parent="AdwWindow">
<property name="name">NautilusFileChooser</property>
<property name="width-request">360</property>
<property name="height-request">348</property>
<property name="default-width">890</property>
<property name="default-height">550</property>
<style>
<class name="view"/>
</style>
<child>
<object class="NautilusShortcutManager">
<property name="child">
@ -21,6 +19,9 @@
<property name="max-sidebar-width">240</property>
<property name="sidebar-width-unit">px</property>
<property name="sidebar-width-fraction">0.2</property>
<style>
<class name="view"/>
</style>
<property name="sidebar">
<object class="AdwToolbarView">
<child type="top">
@ -117,24 +118,41 @@
</child>
</object>
</child>
<child type="center">
<object class="GtkButton" id="selector_accept_button">
<property name="use-underline">true</property>
<binding name="sensitive">
<closure type="gboolean" function="selector_can_accept">
<lookup type="NautilusWindowSlot" name="selection">
<lookup name="content">slot_container</lookup>
</lookup>
<lookup type="NautilusWindowSlot" name="location">
<lookup name="content">slot_container</lookup>
</lookup>
</closure>
</binding>
<signal name="clicked" handler="on_selector_accept_button_clicked" swapped="yes"/>
<style>
<class name="pill"/>
<class name="suggested-action"/>
</style>
<child type="end">
<object class="GtkBox">
<property name="spacing">12</property>
<child>
<object class="GtkButton" id="selector_cancel_button">
<property name="visible">false</property>
<property name="label">_Cancel</property>
<property name="use-underline">true</property>
<property name="action-name">window.close</property>
<style>
<class name="pill"/>
</style>
</object>
</child>
<child>
<object class="GtkButton" id="selector_accept_button">
<property name="label">_Select</property>
<property name="use-underline">true</property>
<binding name="sensitive">
<closure type="gboolean" function="selector_can_accept">
<lookup type="NautilusWindowSlot" name="selection">
<lookup name="content">slot_container</lookup>
</lookup>
<lookup type="NautilusWindowSlot" name="location">
<lookup name="content">slot_container</lookup>
</lookup>
</closure>
</binding>
<signal name="clicked" handler="on_selector_accept_button_clicked" swapped="yes"/>
<style>
<class name="pill"/>
<class name="suggested-action"/>
</style>
</object>
</child>
</object>
</child>
</object>
@ -145,6 +163,126 @@
</property>
</object>
</child>
<child>
<object class="AdwToolbarView" id="namer_view">
<child type="top">
<object class="AdwHeaderBar">
<property name="show-end-title-buttons">false</property>
<property name="show-start-title-buttons">false</property>
</object>
</child>
<property name="content">
<object class="AdwPreferencesPage">
<child>
<object class="AdwPreferencesGroup" id="name_group">
<style>
<class name="move-up-12-px"/>
</style>
<child>
<object class="AdwEntryRow" id="name_entry">
<property name="title" translatable="yes">File _Name</property>
<property name="use-underline">True</property>
<signal name="changed" handler="nautilus_filename_validator_validate" object="validator" swapped="yes"/>
</object>
</child>
<child>
<object class="GtkRevealer">
<property name="reveal-child" bind-source="validator" bind-property="has-feedback"/>
<property name="child">
<object class="GtkLabel">
<property name="label" bind-source="validator" bind-property="feedback-text"/>
<property name="margin_top">6</property>
<property name="xalign">0</property>
<style>
<class name="caption"/>
</style>
</object>
</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<property name="separate-rows">true</property>
<style>
<class name="move-up-12-px"/>
</style>
<child>
<object class="AdwComboRow">
<property name="title" translatable="yes">File _Type</property>
<property name="use-underline">True</property>
<!-- Share state with the dropdown. -->
<property name="visible"
bind-source="filters_dropdown" bind-property="visible"
bind-flags="sync-create"/>
<property name="expression"
bind-source="filters_dropdown" bind-property="expression"
bind-flags="sync-create"/>
<property name="model"
bind-source="filters_dropdown" bind-property="model"
bind-flags="sync-create"/>
<property name="selected"
bind-source="filters_dropdown" bind-property="selected"
bind-flags="bidirectional|sync-create"/>
</object>
</child>
<child>
<object class="AdwActionRow" id="location_row">
<property name="title" translatable="yes">Save _Location</property>
<property name="subtitle" translatable="yes">Set location</property>
<property name="use-underline">True</property>
<property name="activatable">True</property>
<property name="action-name">filechooser.pick-location</property>
<child>
<object class="GtkImage">
<property name="icon-name">folder-open-symbolic</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</property>
<child type="bottom">
<object class="AdwClamp">
<property name="child">
<object class="GtkCenterBox">
<property name="margin-top">6</property>
<property name="margin-bottom">6</property>
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<style>
<class name="toolbar"/>
</style>
<child type="start">
<object class="GtkButton">
<property name="label">_Cancel</property>
<property name="use-underline">true</property>
<property name="action-name">window.close</property>
<style>
<class name="pill"/>
</style>
</object>
</child>
<child type="end">
<object class="GtkButton" id="namer_accept_button">
<property name="use-underline">true</property>
<property name="sensitive" bind-source="validator" bind-property="passed" bind-flags="sync-create"/>
<signal name="clicked" handler="on_namer_accept_button_clicked" swapped="yes"/>
<style>
<class name="pill"/>
<class name="suggested-action"/>
</style>
</object>
</child>
</object>
</property>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
@ -171,4 +309,10 @@
</object>
</child>
</template>
<object class="NautilusFilenameValidator" id="validator">
<property name="allow-override">true</property>
<property name="containing-directory" bind-source="NautilusFileChooser" bind-property="current-directory"/>
<property name="new-name" bind-source="name_entry" bind-property="text"/>
<signal name="notify::has-feedback" handler="on_validator_has_feedback_changed" swapped="yes"/>
</object>
</interface>