/* nautilus-rename-file-popover-controller.c * * Copyright (C) 2016 the Nautilus developers * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, see . * */ #include #include #include "nautilus-rename-file-popover-controller.h" #include "nautilus-directory.h" #include "nautilus-file-private.h" #define RENAME_ENTRY_MIN_CHARS 30 #define RENAME_ENTRY_MAX_CHARS 50 struct _NautilusRenameFilePopoverController { NautilusFileNameWidgetController parent_instance; NautilusFile *target_file; gboolean target_is_folder; GtkWidget *rename_file_popover; GtkWidget *name_entry; GtkWidget *title_label; gulong closed_handler_id; gulong file_changed_handler_id; gulong key_press_event_handler_id; }; G_DEFINE_TYPE (NautilusRenameFilePopoverController, nautilus_rename_file_popover_controller, NAUTILUS_TYPE_FILE_NAME_WIDGET_CONTROLLER) static void disconnect_signal_handlers (NautilusRenameFilePopoverController *self) { g_assert (NAUTILUS_IS_RENAME_FILE_POPOVER_CONTROLLER (self)); g_clear_signal_handler (&self->closed_handler_id, self->rename_file_popover); g_clear_signal_handler (&self->file_changed_handler_id, self->target_file); g_clear_signal_handler (&self->key_press_event_handler_id, self->name_entry); } static void reset_state (NautilusRenameFilePopoverController *self) { g_assert (NAUTILUS_IS_RENAME_FILE_POPOVER_CONTROLLER (self)); disconnect_signal_handlers (self); g_clear_object (&self->target_file); gtk_popover_popdown (GTK_POPOVER (self->rename_file_popover)); } static void rename_file_popover_controller_on_closed (GtkPopover *popover, gpointer user_data) { NautilusRenameFilePopoverController *controller; controller = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (user_data); reset_state (controller); g_signal_emit_by_name (controller, "cancelled"); } static gboolean nautilus_rename_file_popover_controller_name_is_valid (NautilusFileNameWidgetController *controller, gchar *name, gchar **error_message) { NautilusRenameFilePopoverController *self; gboolean is_valid; self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (controller); is_valid = TRUE; if (strlen (name) == 0) { is_valid = FALSE; } else if (strstr (name, "/") != NULL) { is_valid = FALSE; if (self->target_is_folder) { *error_message = _("Folder names cannot contain “/”."); } else { *error_message = _("File names cannot contain “/”."); } } else if (strcmp (name, ".") == 0) { is_valid = FALSE; if (self->target_is_folder) { *error_message = _("A folder cannot be called “.”."); } else { *error_message = _("A file cannot be called “.”."); } } else if (strcmp (name, "..") == 0) { is_valid = FALSE; if (self->target_is_folder) { *error_message = _("A folder cannot be called “..”."); } else { *error_message = _("A file cannot be called “..”."); } } else if (nautilus_file_name_widget_controller_is_name_too_long (controller, name)) { is_valid = FALSE; if (self->target_is_folder) { *error_message = _("Folder name is too long."); } else { *error_message = _("File name is too long."); } } if (is_valid && g_str_has_prefix (name, ".")) { /* We must warn about the side effect */ if (self->target_is_folder) { *error_message = _("Folders with “.” at the beginning of their name are hidden."); } else { *error_message = _("Files with “.” at the beginning of their name are hidden."); } } return is_valid; } static gboolean nautilus_rename_file_popover_controller_ignore_existing_file (NautilusFileNameWidgetController *controller, NautilusFile *existing_file) { NautilusRenameFilePopoverController *self; g_autofree gchar *display_name = NULL; self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (controller); display_name = nautilus_file_get_display_name (existing_file); return nautilus_file_compare_display_name (self->target_file, display_name) == 0; } static gboolean name_entry_on_f2_pressed (GtkWidget *widget, NautilusRenameFilePopoverController *self) { guint text_length; gint start_pos; gint end_pos; gboolean all_selected; text_length = (guint) gtk_entry_get_text_length (GTK_ENTRY (widget)); if (text_length == 0) { return GDK_EVENT_STOP; } gtk_editable_get_selection_bounds (GTK_EDITABLE (widget), &start_pos, &end_pos); all_selected = (start_pos == 0) && ((guint) end_pos == text_length); if (!all_selected || !nautilus_file_is_regular_file (self->target_file)) { gtk_editable_select_region (GTK_EDITABLE (widget), 0, -1); } else { gint start_offset; gint end_offset; /* Select the name part without the file extension */ eel_filename_get_rename_region (gtk_editable_get_text (GTK_EDITABLE (widget)), &start_offset, &end_offset); gtk_editable_select_region (GTK_EDITABLE (widget), start_offset, end_offset); } return GDK_EVENT_STOP; } static gboolean name_entry_on_undo (GtkWidget *widget, NautilusRenameFilePopoverController *self) { g_autofree gchar *edit_name = NULL; edit_name = nautilus_file_get_edit_name (self->target_file); gtk_editable_set_text (GTK_EDITABLE (widget), edit_name); gtk_editable_select_region (GTK_EDITABLE (widget), 0, -1); return GDK_EVENT_STOP; } static gboolean on_event_controller_key_key_pressed (GtkEventControllerKey *controller, guint keyval, guint keycode, GdkModifierType state, gpointer user_data) { GtkWidget *widget; NautilusRenameFilePopoverController *self; widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (controller)); self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (user_data); if (keyval == GDK_KEY_F2) { return name_entry_on_f2_pressed (widget, self); } else if (keyval == GDK_KEY_z && (state & GDK_CONTROL_MASK) != 0) { return name_entry_on_undo (widget, self); } return GDK_EVENT_PROPAGATE; } static void target_file_on_changed (NautilusFile *file, gpointer user_data) { NautilusRenameFilePopoverController *controller; controller = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (user_data); if (nautilus_file_is_gone (file)) { reset_state (controller); g_signal_emit_by_name (controller, "cancelled"); } } NautilusRenameFilePopoverController * nautilus_rename_file_popover_controller_new (GtkWidget *relative_to) { NautilusRenameFilePopoverController *self; g_autoptr (GtkBuilder) builder = NULL; GtkWidget *rename_file_popover; GtkWidget *error_revealer; GtkWidget *error_label; GtkWidget *name_entry; GtkWidget *activate_button; GtkWidget *title_label; builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-rename-file-popover.ui"); rename_file_popover = GTK_WIDGET (gtk_builder_get_object (builder, "rename_file_popover")); error_revealer = GTK_WIDGET (gtk_builder_get_object (builder, "error_revealer")); error_label = GTK_WIDGET (gtk_builder_get_object (builder, "error_label")); name_entry = GTK_WIDGET (gtk_builder_get_object (builder, "name_entry")); activate_button = GTK_WIDGET (gtk_builder_get_object (builder, "rename_button")); title_label = GTK_WIDGET (gtk_builder_get_object (builder, "title_label")); self = g_object_new (NAUTILUS_TYPE_RENAME_FILE_POPOVER_CONTROLLER, "error-revealer", error_revealer, "error-label", error_label, "name-entry", name_entry, "activate-button", activate_button, NULL); self->rename_file_popover = g_object_ref_sink (rename_file_popover); self->name_entry = name_entry; self->title_label = title_label; gtk_popover_set_default_widget (GTK_POPOVER (rename_file_popover), name_entry); gtk_widget_set_parent (rename_file_popover, relative_to); return self; } NautilusFile * nautilus_rename_file_popover_controller_get_target_file (NautilusRenameFilePopoverController *self) { g_return_val_if_fail (NAUTILUS_IS_RENAME_FILE_POPOVER_CONTROLLER (self), NULL); return self->target_file; } void nautilus_rename_file_popover_controller_show_for_file (NautilusRenameFilePopoverController *self, NautilusFile *target_file, GdkRectangle *pointing_to) { g_autoptr (NautilusDirectory) containing_directory = NULL; GtkEventController *controller; g_autofree gchar *edit_name = NULL; gint n_chars; g_assert (NAUTILUS_IS_RENAME_FILE_POPOVER_CONTROLLER (self)); g_assert (NAUTILUS_IS_FILE (target_file)); reset_state (self); self->target_file = g_object_ref (target_file); if (!nautilus_file_is_self_owned (self->target_file)) { g_autoptr (NautilusFile) parent = NULL; parent = nautilus_file_get_parent (self->target_file); containing_directory = nautilus_directory_get_for_file (parent); } else { containing_directory = nautilus_directory_get_for_file (self->target_file); } nautilus_file_name_widget_controller_set_containing_directory (NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (self), containing_directory); self->target_is_folder = nautilus_file_is_directory (self->target_file); self->closed_handler_id = g_signal_connect (self->rename_file_popover, "closed", G_CALLBACK (rename_file_popover_controller_on_closed), self); self->file_changed_handler_id = g_signal_connect (self->target_file, "changed", G_CALLBACK (target_file_on_changed), self); controller = gtk_event_controller_key_new (); gtk_widget_add_controller (self->name_entry, controller); g_signal_connect (controller, "key-pressed", G_CALLBACK (on_event_controller_key_key_pressed), self); gtk_label_set_text (GTK_LABEL (self->title_label), self->target_is_folder ? _("Rename Folder") : _("Rename File")); edit_name = nautilus_file_get_edit_name (self->target_file); gtk_editable_set_text (GTK_EDITABLE (self->name_entry), edit_name); gtk_popover_set_pointing_to (GTK_POPOVER (self->rename_file_popover), pointing_to); gtk_popover_popup (GTK_POPOVER (self->rename_file_popover)); if (nautilus_file_is_regular_file (self->target_file)) { gint start_offset; gint end_offset; /* Select the name part without the file extension */ eel_filename_get_rename_region (edit_name, &start_offset, &end_offset); gtk_editable_select_region (GTK_EDITABLE (self->name_entry), start_offset, end_offset); } n_chars = g_utf8_strlen (edit_name, -1); gtk_editable_set_width_chars (GTK_EDITABLE (self->name_entry), MIN (MAX (n_chars, RENAME_ENTRY_MIN_CHARS), RENAME_ENTRY_MAX_CHARS)); } static void on_name_accepted (NautilusFileNameWidgetController *controller) { NautilusRenameFilePopoverController *self; self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (controller); reset_state (self); } static void nautilus_rename_file_popover_controller_init (NautilusRenameFilePopoverController *self) { g_signal_connect_after (self, "name-accepted", G_CALLBACK (on_name_accepted), self); } static void nautilus_rename_file_popover_controller_finalize (GObject *object) { NautilusRenameFilePopoverController *self; self = NAUTILUS_RENAME_FILE_POPOVER_CONTROLLER (object); reset_state (self); g_clear_pointer (&self->rename_file_popover, gtk_widget_unparent); G_OBJECT_CLASS (nautilus_rename_file_popover_controller_parent_class)->finalize (object); } static void nautilus_rename_file_popover_controller_class_init (NautilusRenameFilePopoverControllerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); NautilusFileNameWidgetControllerClass *parent_class = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_CLASS (klass); object_class->finalize = nautilus_rename_file_popover_controller_finalize; parent_class->name_is_valid = nautilus_rename_file_popover_controller_name_is_valid; parent_class->ignore_existing_file = nautilus_rename_file_popover_controller_ignore_existing_file; }