mirror of
https://gitlab.gnome.org/GNOME/nautilus
synced 2024-06-30 23:46:35 +00:00
Compare commits
5 Commits
ea0f3e462c
...
80abefd369
Author | SHA1 | Date | |
---|---|---|---|
|
80abefd369 | ||
|
adb8aeafe9 | ||
|
e73a8e88ec | ||
|
98296d71cc | ||
|
b1d7eaf666 |
|
@ -27,6 +27,7 @@ src/nautilus-directory.c
|
||||||
src/nautilus-dnd.c
|
src/nautilus-dnd.c
|
||||||
src/nautilus-error-reporting.c
|
src/nautilus-error-reporting.c
|
||||||
src/nautilus-file.c
|
src/nautilus-file.c
|
||||||
|
src/nautilus-file-chooser.c
|
||||||
src/nautilus-file-conflict-dialog.c
|
src/nautilus-file-conflict-dialog.c
|
||||||
src/nautilus-filename-validator.c
|
src/nautilus-filename-validator.c
|
||||||
src/nautilus-file-operations.c
|
src/nautilus-file-operations.c
|
||||||
|
|
|
@ -1128,23 +1128,11 @@ nautilus_application_startup (GApplication *app)
|
||||||
|
|
||||||
g_application_set_resource_base_path (G_APPLICATION (self), "/org/gnome/nautilus");
|
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()
|
/* Chain up to the GtkApplication implementation early, so that gtk_init()
|
||||||
* is called for us.
|
* is called for us.
|
||||||
*/
|
*/
|
||||||
G_APPLICATION_CLASS (nautilus_application_parent_class)->startup (G_APPLICATION (self));
|
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);
|
gtk_window_set_default_icon_name (APPLICATION_ID);
|
||||||
|
|
||||||
/* initialize preferences and create the global GSettings objects */
|
/* initialize preferences and create the global GSettings objects */
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "nautilus-file-chooser.h"
|
#include "nautilus-file-chooser.h"
|
||||||
|
|
||||||
#include <config.h>
|
#include <config.h>
|
||||||
|
#include <glib/gi18n.h>
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
#include "gtk/nautilusgtkplacessidebarprivate.h"
|
#include "gtk/nautilusgtkplacessidebarprivate.h"
|
||||||
|
@ -18,12 +19,18 @@
|
||||||
#include "nautilus-directory.h"
|
#include "nautilus-directory.h"
|
||||||
#include "nautilus-enum-types.h"
|
#include "nautilus-enum-types.h"
|
||||||
#include "nautilus-file.h"
|
#include "nautilus-file.h"
|
||||||
|
#include "nautilus-filename-validator.h"
|
||||||
#include "nautilus-scheme.h"
|
#include "nautilus-scheme.h"
|
||||||
#include "nautilus-shortcut-manager.h"
|
#include "nautilus-shortcut-manager.h"
|
||||||
#include "nautilus-toolbar.h"
|
#include "nautilus-toolbar.h"
|
||||||
#include "nautilus-view-item-filter.h"
|
#include "nautilus-view-item-filter.h"
|
||||||
#include "nautilus-window-slot.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
|
struct _NautilusFileChooser
|
||||||
{
|
{
|
||||||
AdwWindow parent_instance;
|
AdwWindow parent_instance;
|
||||||
|
@ -38,9 +45,19 @@ struct _NautilusFileChooser
|
||||||
GtkDropDown *filters_dropdown;
|
GtkDropDown *filters_dropdown;
|
||||||
GtkWidget *choices_menu_button;
|
GtkWidget *choices_menu_button;
|
||||||
GtkWidget *read_only_checkbox;
|
GtkWidget *read_only_checkbox;
|
||||||
|
GtkWidget *selector_cancel_button;
|
||||||
GtkWidget *selector_accept_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;
|
NautilusDirectory *current_directory;
|
||||||
|
NautilusFilenameValidator *validator;
|
||||||
};
|
};
|
||||||
|
|
||||||
G_DEFINE_FINAL_TYPE (NautilusFileChooser, nautilus_file_chooser, ADW_TYPE_WINDOW)
|
G_DEFINE_FINAL_TYPE (NautilusFileChooser, nautilus_file_chooser, ADW_TYPE_WINDOW)
|
||||||
|
@ -173,22 +190,10 @@ selector_can_accept (NautilusFileChooser *self,
|
||||||
|
|
||||||
static void
|
static void
|
||||||
emit_accepted (NautilusFileChooser *self,
|
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));
|
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,
|
g_signal_emit (self, signals[SIGNAL_ACCEPTED], 0,
|
||||||
file_locations,
|
file_locations,
|
||||||
GTK_FILE_FILTER (gtk_drop_down_get_selected_item (self->filters_dropdown)),
|
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_return_if_fail (G_IS_FILE (location));
|
||||||
|
|
||||||
g_autoptr (NautilusDirectory) directory = nautilus_directory_get (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);
|
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]);
|
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
|
static void
|
||||||
on_selector_accept_button_clicked (NautilusFileChooser *self)
|
on_selector_accept_button_clicked (NautilusFileChooser *self)
|
||||||
{
|
{
|
||||||
GList *selection = nautilus_window_slot_get_selection (self->slot);
|
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
|
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
|
static void
|
||||||
update_cursor (NautilusFileChooser *self)
|
update_cursor (NautilusFileChooser *self)
|
||||||
{
|
{
|
||||||
|
@ -243,10 +399,41 @@ on_slot_activate_files (NautilusFileChooser *self,
|
||||||
{
|
{
|
||||||
if (mode_can_accept_files (self->mode, files))
|
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
|
static gboolean
|
||||||
on_key_pressed_bubble (GtkEventControllerKey *controller,
|
on_key_pressed_bubble (GtkEventControllerKey *controller,
|
||||||
unsigned int keyval,
|
unsigned int keyval,
|
||||||
|
@ -270,6 +457,8 @@ nautilus_file_chooser_dispose (GObject *object)
|
||||||
{
|
{
|
||||||
NautilusFileChooser *self = (NautilusFileChooser *) object;
|
NautilusFileChooser *self = (NautilusFileChooser *) object;
|
||||||
|
|
||||||
|
g_clear_object (&self->width_animation);
|
||||||
|
g_clear_object (&self->height_animation);
|
||||||
g_clear_object (&self->current_directory);
|
g_clear_object (&self->current_directory);
|
||||||
|
|
||||||
if (self->slot != NULL)
|
if (self->slot != NULL)
|
||||||
|
@ -344,6 +533,7 @@ nautilus_file_chooser_constructed (GObject *object)
|
||||||
G_OBJECT_CLASS (nautilus_file_chooser_parent_class)->constructed (object);
|
G_OBJECT_CLASS (nautilus_file_chooser_parent_class)->constructed (object);
|
||||||
|
|
||||||
NautilusFileChooser *self = (NautilusFileChooser *) object;
|
NautilusFileChooser *self = (NautilusFileChooser *) object;
|
||||||
|
gboolean start_with_namer = (self->mode == NAUTILUS_MODE_SAVE_FILE);
|
||||||
|
|
||||||
/* Setup slot.
|
/* Setup slot.
|
||||||
* We hold a reference to control its lifetime with relation to bindings. */
|
* 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,
|
gtk_widget_set_visible (self->choices_menu_button,
|
||||||
nautilus_mode_is_open (self->mode));
|
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
|
static void
|
||||||
nautilus_file_chooser_init (NautilusFileChooser *self)
|
nautilus_file_chooser_init (NautilusFileChooser *self)
|
||||||
{
|
{
|
||||||
|
g_type_ensure (NAUTILUS_TYPE_FILENAME_VALIDATOR);
|
||||||
g_type_ensure (NAUTILUS_TYPE_TOOLBAR);
|
g_type_ensure (NAUTILUS_TYPE_TOOLBAR);
|
||||||
g_type_ensure (NAUTILUS_TYPE_GTK_PLACES_SIDEBAR);
|
g_type_ensure (NAUTILUS_TYPE_GTK_PLACES_SIDEBAR);
|
||||||
g_type_ensure (NAUTILUS_TYPE_SHORTCUT_MANAGER);
|
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);
|
gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_BUBBLE);
|
||||||
g_signal_connect (controller, "key-pressed",
|
g_signal_connect (controller, "key-pressed",
|
||||||
G_CALLBACK (on_key_pressed_bubble), self);
|
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
|
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, filters_dropdown);
|
||||||
gtk_widget_class_bind_template_child (widget_class, NautilusFileChooser, choices_menu_button);
|
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, 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, 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, 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_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] =
|
properties[PROP_CURRENT_DIRECTORY] =
|
||||||
g_param_spec_object ("current-directory", NULL, NULL,
|
g_param_spec_object ("current-directory", NULL, NULL,
|
||||||
|
@ -444,7 +669,14 @@ void
|
||||||
nautilus_file_chooser_set_accept_label (NautilusFileChooser *self,
|
nautilus_file_chooser_set_accept_label (NautilusFileChooser *self,
|
||||||
const char *accept_label)
|
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
|
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);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -34,5 +34,8 @@ nautilus_file_chooser_set_filters (NautilusFileChooser *self,
|
||||||
void
|
void
|
||||||
nautilus_file_chooser_set_starting_location (NautilusFileChooser *self,
|
nautilus_file_chooser_set_starting_location (NautilusFileChooser *self,
|
||||||
GFile *starting_location);
|
GFile *starting_location);
|
||||||
|
void
|
||||||
|
nautilus_file_chooser_set_suggested_name (NautilusFileChooser *self,
|
||||||
|
const char *suggested_name);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
|
@ -17,6 +17,7 @@ struct _NautilusFilenameValidator
|
||||||
|
|
||||||
NautilusDirectory *containing_directory;
|
NautilusDirectory *containing_directory;
|
||||||
gboolean target_is_folder;
|
gboolean target_is_folder;
|
||||||
|
gboolean allow_override;
|
||||||
char *original_name;
|
char *original_name;
|
||||||
|
|
||||||
const char *feedback_text;
|
const char *feedback_text;
|
||||||
|
@ -43,6 +44,7 @@ enum
|
||||||
PROP_NEW_NAME,
|
PROP_NEW_NAME,
|
||||||
PROP_CONTAINING_DIRECTORY,
|
PROP_CONTAINING_DIRECTORY,
|
||||||
PROP_PASSED,
|
PROP_PASSED,
|
||||||
|
PROP_ALLOW_OVERRIDE,
|
||||||
PROP_TARGET_IS_FOLDER,
|
PROP_TARGET_IS_FOLDER,
|
||||||
NUM_PROPERTIES
|
NUM_PROPERTIES
|
||||||
};
|
};
|
||||||
|
@ -200,7 +202,8 @@ filename_validator_process_new_name (NautilusFilenameValidator *self)
|
||||||
!nautilus_filename_validator_ignore_existing_file (self,
|
!nautilus_filename_validator_ignore_existing_file (self,
|
||||||
existing_file);
|
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)
|
if (self->passed != passed)
|
||||||
{
|
{
|
||||||
|
@ -249,7 +252,11 @@ void
|
||||||
nautilus_filename_validator_validate (NautilusFilenameValidator *self)
|
nautilus_filename_validator_validate (NautilusFilenameValidator *self)
|
||||||
{
|
{
|
||||||
g_return_if_fail (NAUTILUS_IS_FILENAME_VALIDATOR (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_directory_call_when_ready (self->containing_directory,
|
||||||
NAUTILUS_FILE_ATTRIBUTE_INFO,
|
NAUTILUS_FILE_ATTRIBUTE_INFO,
|
||||||
|
@ -378,6 +385,12 @@ nautilus_filename_validator_set_property (GObject *object,
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PROP_ALLOW_OVERRIDE:
|
||||||
|
{
|
||||||
|
self->allow_override = g_value_get_boolean (value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case PROP_TARGET_IS_FOLDER:
|
case PROP_TARGET_IS_FOLDER:
|
||||||
{
|
{
|
||||||
self->target_is_folder = g_value_get_boolean (value);
|
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,
|
g_param_spec_object ("containing-directory", NULL, NULL,
|
||||||
NAUTILUS_TYPE_DIRECTORY,
|
NAUTILUS_TYPE_DIRECTORY,
|
||||||
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
|
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] =
|
properties[PROP_TARGET_IS_FOLDER] =
|
||||||
g_param_spec_boolean ("target-is-folder", NULL, NULL,
|
g_param_spec_boolean ("target-is-folder", NULL, NULL,
|
||||||
FALSE,
|
FALSE,
|
||||||
|
@ -460,3 +477,9 @@ nautilus_filename_validator_class_init (NautilusFilenameValidatorClass *klass)
|
||||||
|
|
||||||
g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -24,3 +24,4 @@ void nautilus_filename_validator_set_original_name (NautilusFilenameVa
|
||||||
const char *original_name);
|
const char *original_name);
|
||||||
void nautilus_filename_validator_validate (NautilusFilenameValidator *self);
|
void nautilus_filename_validator_validate (NautilusFilenameValidator *self);
|
||||||
void nautilus_filename_validator_try_accept (NautilusFilenameValidator *self);
|
void nautilus_filename_validator_try_accept (NautilusFilenameValidator *self);
|
||||||
|
gboolean nautilus_filename_validator_will_override (NautilusFilenameValidator *self);
|
||||||
|
|
|
@ -354,6 +354,7 @@ handle_file_chooser_methods (XdpImplFileChooser *object,
|
||||||
nautilus_file_chooser_set_filters (window, G_LIST_MODEL (filters));
|
nautilus_file_chooser_set_filters (window, G_LIST_MODEL (filters));
|
||||||
nautilus_file_chooser_set_current_filter (window, current_filter_position);
|
nautilus_file_chooser_set_current_filter (window, current_filter_position);
|
||||||
nautilus_file_chooser_set_starting_location (window, starting_location);
|
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);
|
gtk_window_set_title (GTK_WINDOW (window), arg_title);
|
||||||
|
|
||||||
g_signal_connect_swapped (window, "close-request",
|
g_signal_connect_swapped (window, "close-request",
|
||||||
|
@ -364,24 +365,7 @@ handle_file_chooser_methods (XdpImplFileChooser *object,
|
||||||
/* Show window */
|
/* Show window */
|
||||||
if (arg_parent_window != NULL)
|
if (arg_parent_window != NULL)
|
||||||
{
|
{
|
||||||
data->external_parent = create_external_window_from_handle (arg_parent_window);
|
g_message ("No parenting. Testing purposes only.");
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gtk_window_present (data->window);
|
gtk_window_present (data->window);
|
||||||
|
|
|
@ -327,3 +327,7 @@ label.encrypted_zip {
|
||||||
rubberband {
|
rubberband {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#NautilusFileChooser preferencesgroup.move-up-12-px {
|
||||||
|
margin-top: -12px;
|
||||||
|
}
|
||||||
|
|
|
@ -2,13 +2,11 @@
|
||||||
<interface>
|
<interface>
|
||||||
<requires lib="gtk" version="4.0"/>
|
<requires lib="gtk" version="4.0"/>
|
||||||
<template class="NautilusFileChooser" parent="AdwWindow">
|
<template class="NautilusFileChooser" parent="AdwWindow">
|
||||||
|
<property name="name">NautilusFileChooser</property>
|
||||||
<property name="width-request">360</property>
|
<property name="width-request">360</property>
|
||||||
<property name="height-request">348</property>
|
<property name="height-request">348</property>
|
||||||
<property name="default-width">890</property>
|
<property name="default-width">890</property>
|
||||||
<property name="default-height">550</property>
|
<property name="default-height">550</property>
|
||||||
<style>
|
|
||||||
<class name="view"/>
|
|
||||||
</style>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="NautilusShortcutManager">
|
<object class="NautilusShortcutManager">
|
||||||
<property name="child">
|
<property name="child">
|
||||||
|
@ -21,6 +19,9 @@
|
||||||
<property name="max-sidebar-width">240</property>
|
<property name="max-sidebar-width">240</property>
|
||||||
<property name="sidebar-width-unit">px</property>
|
<property name="sidebar-width-unit">px</property>
|
||||||
<property name="sidebar-width-fraction">0.2</property>
|
<property name="sidebar-width-fraction">0.2</property>
|
||||||
|
<style>
|
||||||
|
<class name="view"/>
|
||||||
|
</style>
|
||||||
<property name="sidebar">
|
<property name="sidebar">
|
||||||
<object class="AdwToolbarView">
|
<object class="AdwToolbarView">
|
||||||
<child type="top">
|
<child type="top">
|
||||||
|
@ -117,24 +118,41 @@
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child type="center">
|
<child type="end">
|
||||||
<object class="GtkButton" id="selector_accept_button">
|
<object class="GtkBox">
|
||||||
<property name="use-underline">true</property>
|
<property name="spacing">12</property>
|
||||||
<binding name="sensitive">
|
<child>
|
||||||
<closure type="gboolean" function="selector_can_accept">
|
<object class="GtkButton" id="selector_cancel_button">
|
||||||
<lookup type="NautilusWindowSlot" name="selection">
|
<property name="visible">false</property>
|
||||||
<lookup name="content">slot_container</lookup>
|
<property name="label">_Cancel</property>
|
||||||
</lookup>
|
<property name="use-underline">true</property>
|
||||||
<lookup type="NautilusWindowSlot" name="location">
|
<property name="action-name">window.close</property>
|
||||||
<lookup name="content">slot_container</lookup>
|
<style>
|
||||||
</lookup>
|
<class name="pill"/>
|
||||||
</closure>
|
</style>
|
||||||
</binding>
|
</object>
|
||||||
<signal name="clicked" handler="on_selector_accept_button_clicked" swapped="yes"/>
|
</child>
|
||||||
<style>
|
<child>
|
||||||
<class name="pill"/>
|
<object class="GtkButton" id="selector_accept_button">
|
||||||
<class name="suggested-action"/>
|
<property name="label">_Select</property>
|
||||||
</style>
|
<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>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
@ -145,6 +163,126 @@
|
||||||
</property>
|
</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</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>
|
</object>
|
||||||
</property>
|
</property>
|
||||||
</object>
|
</object>
|
||||||
|
@ -171,4 +309,10 @@
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</template>
|
</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>
|
</interface>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user