SAVE_FILE

This commit is contained in:
António Fernandes 2024-06-24 13:27:01 +01:00
parent e73a8e88ec
commit adb8aeafe9
6 changed files with 408 additions and 21 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

@ -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)
@ -190,12 +207,37 @@ 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)
{
@ -203,7 +245,30 @@ on_selector_accept_button_clicked (NautilusFileChooser *self)
if (self->mode == NAUTILUS_MODE_SAVE_FILE)
{
/* TODO */
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
{
@ -222,6 +287,98 @@ on_selector_accept_button_clicked (NautilusFileChooser *self)
}
}
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)
{
@ -244,7 +401,16 @@ on_slot_activate_files (NautilusFileChooser *self,
{
if (self->mode == NAUTILUS_MODE_SAVE_FILE)
{
/* TODO */
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
{
@ -255,6 +421,19 @@ on_slot_activate_files (NautilusFileChooser *self,
}
}
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,
@ -278,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)
@ -352,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. */
@ -372,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);
@ -391,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
@ -414,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,
@ -451,9 +668,16 @@ nautilus_file_chooser_new (NautilusMode mode)
void
nautilus_file_chooser_set_accept_label (NautilusFileChooser *self,
const char *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
nautilus_file_chooser_set_current_filter (NautilusFileChooser *self,
@ -490,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

@ -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",

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,6 +2,7 @@
<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>
@ -117,8 +118,23 @@
</child>
</object>
</child>
<child type="center">
<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">
@ -142,9 +158,131 @@
</object>
</child>
</object>
</child>
</object>
</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>