/* nautilus-file-name-widget-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 "nautilus-file-name-widget-controller.h" #include "nautilus-file-utilities.h" #define FILE_NAME_DUPLICATED_LABEL_TIMEOUT 500 typedef struct { GtkWidget *error_revealer; GtkWidget *error_label; GtkWidget *name_entry; GtkWidget *activate_button; NautilusDirectory *containing_directory; gboolean duplicated_is_folder; gint duplicated_label_timeout_id; } NautilusFileNameWidgetControllerPrivate; enum { NAME_ACCEPTED, CANCELLED, LAST_SIGNAL }; enum { PROP_ERROR_REVEALER = 1, PROP_ERROR_LABEL, PROP_NAME_ENTRY, PROP_ACTION_BUTTON, PROP_CONTAINING_DIRECTORY, NUM_PROPERTIES }; static guint signals[LAST_SIGNAL]; G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (NautilusFileNameWidgetController, nautilus_file_name_widget_controller, G_TYPE_OBJECT) gchar * nautilus_file_name_widget_controller_get_new_name (NautilusFileNameWidgetController *self) { return NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_GET_CLASS (self)->get_new_name (self); } void nautilus_file_name_widget_controller_set_containing_directory (NautilusFileNameWidgetController *self, NautilusDirectory *directory) { g_assert (NAUTILUS_IS_DIRECTORY (directory)); g_object_set (self, "containing-directory", directory, NULL); } gboolean nautilus_file_name_widget_controller_is_name_too_long (NautilusFileNameWidgetController *self, gchar *name) { NautilusFileNameWidgetControllerPrivate *priv; size_t name_length; g_autoptr (GFile) location = NULL; glong max_name_length; priv = nautilus_file_name_widget_controller_get_instance_private (self); name_length = strlen (name); location = nautilus_directory_get_location (priv->containing_directory); max_name_length = nautilus_get_max_child_name_length_for_location (location); if (max_name_length == -1) { /* We don't know, so let's give it a chance */ return FALSE; } else { return name_length > max_name_length + 1; } } static gboolean nautilus_file_name_widget_controller_name_is_valid (NautilusFileNameWidgetController *self, gchar *name, gchar **error_message) { return NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_GET_CLASS (self)->name_is_valid (self, name, error_message); } static gboolean nautilus_file_name_widget_controller_ignore_existing_file (NautilusFileNameWidgetController *self, NautilusFile *existing_file) { return NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_GET_CLASS (self)->ignore_existing_file (self, existing_file); } static gchar * real_get_new_name (NautilusFileNameWidgetController *self) { NautilusFileNameWidgetControllerPrivate *priv; priv = nautilus_file_name_widget_controller_get_instance_private (self); return g_strstrip (g_strdup (gtk_editable_get_text (GTK_EDITABLE (priv->name_entry)))); } static gboolean real_name_is_valid (NautilusFileNameWidgetController *self, gchar *name, gchar **error_message) { gboolean is_valid; is_valid = TRUE; if (strlen (name) == 0) { is_valid = FALSE; } else if (strstr (name, "/") != NULL) { is_valid = FALSE; *error_message = _("File names cannot contain “/”."); } else if (strcmp (name, ".") == 0) { is_valid = FALSE; *error_message = _("A file cannot be called “.”."); } else if (strcmp (name, "..") == 0) { is_valid = FALSE; *error_message = _("A file cannot be called “..”."); } else if (nautilus_file_name_widget_controller_is_name_too_long (self, name)) { is_valid = FALSE; *error_message = _("File name is too long."); } if (is_valid && g_str_has_prefix (name, ".")) { /* We must warn about the side effect */ *error_message = _("Files with “.” at the beginning of their name are hidden."); } return is_valid; } static gboolean real_ignore_existing_file (NautilusFileNameWidgetController *self, NautilusFile *existing_file) { return FALSE; } static gboolean duplicated_file_label_show (NautilusFileNameWidgetController *self) { NautilusFileNameWidgetControllerPrivate *priv; priv = nautilus_file_name_widget_controller_get_instance_private (self); if (priv->duplicated_is_folder) { gtk_label_set_label (GTK_LABEL (priv->error_label), _("A folder with that name already exists.")); } else { gtk_label_set_label (GTK_LABEL (priv->error_label), _("A file with that name already exists.")); } gtk_revealer_set_reveal_child (GTK_REVEALER (priv->error_revealer), TRUE); priv->duplicated_label_timeout_id = 0; return G_SOURCE_REMOVE; } static void file_name_widget_controller_process_new_name (NautilusFileNameWidgetController *controller, gboolean *duplicated_name, gboolean *valid_name) { NautilusFileNameWidgetControllerPrivate *priv; g_autofree gchar *name = NULL; gchar *error_message = NULL; NautilusFile *existing_file; priv = nautilus_file_name_widget_controller_get_instance_private (controller); g_return_if_fail (NAUTILUS_IS_DIRECTORY (priv->containing_directory)); name = nautilus_file_name_widget_controller_get_new_name (controller); *valid_name = nautilus_file_name_widget_controller_name_is_valid (controller, name, &error_message); gtk_label_set_label (GTK_LABEL (priv->error_label), error_message); gtk_revealer_set_reveal_child (GTK_REVEALER (priv->error_revealer), error_message != NULL); existing_file = nautilus_directory_get_file_by_name (priv->containing_directory, name); *duplicated_name = existing_file != NULL && !nautilus_file_name_widget_controller_ignore_existing_file (controller, existing_file); gtk_widget_set_sensitive (priv->activate_button, *valid_name && !*duplicated_name); if (priv->duplicated_label_timeout_id != 0) { g_source_remove (priv->duplicated_label_timeout_id); priv->duplicated_label_timeout_id = 0; } if (*duplicated_name) { priv->duplicated_is_folder = nautilus_file_is_directory (existing_file); } if (existing_file != NULL) { nautilus_file_unref (existing_file); } } static void file_name_widget_controller_on_changed_directory_info_ready (NautilusDirectory *directory, GList *files, gpointer user_data) { NautilusFileNameWidgetController *controller; NautilusFileNameWidgetControllerPrivate *priv; gboolean duplicated_name; gboolean valid_name; controller = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (user_data); priv = nautilus_file_name_widget_controller_get_instance_private (controller); file_name_widget_controller_process_new_name (controller, &duplicated_name, &valid_name); /* Report duplicated file only if not other message shown (for instance, * folders like "." or ".." will always exists, but we consider it as an * error, not as a duplicated file or if the name is the same as the file * we are renaming also don't report as a duplicated */ if (duplicated_name && valid_name) { priv->duplicated_label_timeout_id = g_timeout_add (FILE_NAME_DUPLICATED_LABEL_TIMEOUT, (GSourceFunc) duplicated_file_label_show, controller); } } static void file_name_widget_controller_on_changed (gpointer user_data) { NautilusFileNameWidgetController *controller; NautilusFileNameWidgetControllerPrivate *priv; controller = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (user_data); priv = nautilus_file_name_widget_controller_get_instance_private (controller); nautilus_directory_call_when_ready (priv->containing_directory, NAUTILUS_FILE_ATTRIBUTE_INFO, TRUE, file_name_widget_controller_on_changed_directory_info_ready, controller); } static void file_name_widget_controller_on_activate_directory_info_ready (NautilusDirectory *directory, GList *files, gpointer user_data) { NautilusFileNameWidgetController *controller; gboolean duplicated_name; gboolean valid_name; controller = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (user_data); file_name_widget_controller_process_new_name (controller, &duplicated_name, &valid_name); if (valid_name && !duplicated_name) { g_signal_emit (controller, signals[NAME_ACCEPTED], 0); } else { /* Report duplicated file only if not other message shown (for instance, * folders like "." or ".." will always exists, but we consider it as an * error, not as a duplicated file) */ if (duplicated_name && valid_name) { /* Show it inmediatily since the user tried to trigger the action */ duplicated_file_label_show (controller); } } } static void file_name_widget_controller_on_activate (gpointer user_data) { NautilusFileNameWidgetController *controller; NautilusFileNameWidgetControllerPrivate *priv; controller = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (user_data); priv = nautilus_file_name_widget_controller_get_instance_private (controller); nautilus_directory_call_when_ready (priv->containing_directory, NAUTILUS_FILE_ATTRIBUTE_INFO, TRUE, file_name_widget_controller_on_activate_directory_info_ready, controller); } static void nautilus_file_name_widget_controller_init (NautilusFileNameWidgetController *self) { NautilusFileNameWidgetControllerPrivate *priv; priv = nautilus_file_name_widget_controller_get_instance_private (self); priv->containing_directory = NULL; } static void nautilus_file_name_widget_controller_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NautilusFileNameWidgetController *controller; NautilusFileNameWidgetControllerPrivate *priv; controller = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (object); priv = nautilus_file_name_widget_controller_get_instance_private (controller); switch (prop_id) { case PROP_ERROR_REVEALER: { priv->error_revealer = GTK_WIDGET (g_value_get_object (value)); } break; case PROP_ERROR_LABEL: { priv->error_label = GTK_WIDGET (g_value_get_object (value)); } break; case PROP_NAME_ENTRY: { priv->name_entry = GTK_WIDGET (g_value_get_object (value)); g_signal_connect_swapped (G_OBJECT (priv->name_entry), "activate", (GCallback) file_name_widget_controller_on_activate, controller); g_signal_connect_swapped (G_OBJECT (priv->name_entry), "changed", (GCallback) file_name_widget_controller_on_changed, controller); } break; case PROP_ACTION_BUTTON: { priv->activate_button = GTK_WIDGET (g_value_get_object (value)); g_signal_connect_swapped (G_OBJECT (priv->activate_button), "clicked", (GCallback) file_name_widget_controller_on_activate, controller); } break; case PROP_CONTAINING_DIRECTORY: { g_clear_object (&priv->containing_directory); priv->containing_directory = NAUTILUS_DIRECTORY (g_value_dup_object (value)); } break; default: { G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } break; } } static void nautilus_file_name_widget_controller_finalize (GObject *object) { NautilusFileNameWidgetController *self; NautilusFileNameWidgetControllerPrivate *priv; self = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER (object); priv = nautilus_file_name_widget_controller_get_instance_private (self); if (priv->containing_directory != NULL) { nautilus_directory_cancel_callback (priv->containing_directory, file_name_widget_controller_on_changed_directory_info_ready, self); nautilus_directory_cancel_callback (priv->containing_directory, file_name_widget_controller_on_activate_directory_info_ready, self); g_clear_object (&priv->containing_directory); } if (priv->duplicated_label_timeout_id > 0) { g_source_remove (priv->duplicated_label_timeout_id); priv->duplicated_label_timeout_id = 0; } G_OBJECT_CLASS (nautilus_file_name_widget_controller_parent_class)->finalize (object); } static void nautilus_file_name_widget_controller_class_init (NautilusFileNameWidgetControllerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->set_property = nautilus_file_name_widget_controller_set_property; object_class->finalize = nautilus_file_name_widget_controller_finalize; klass->get_new_name = real_get_new_name; klass->name_is_valid = real_name_is_valid; klass->ignore_existing_file = real_ignore_existing_file; signals[NAME_ACCEPTED] = g_signal_new ("name-accepted", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (NautilusFileNameWidgetControllerClass, name_accepted), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 0); signals[CANCELLED] = g_signal_new ("cancelled", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 0); g_object_class_install_property ( object_class, PROP_ERROR_REVEALER, g_param_spec_object ("error-revealer", "Error Revealer", "The error label revealer", GTK_TYPE_WIDGET, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property ( object_class, PROP_ERROR_LABEL, g_param_spec_object ("error-label", "Error Label", "The label used for displaying errors", GTK_TYPE_WIDGET, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property ( object_class, PROP_NAME_ENTRY, g_param_spec_object ("name-entry", "Name Entry", "The entry for the file name", GTK_TYPE_WIDGET, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property ( object_class, PROP_ACTION_BUTTON, g_param_spec_object ("activate-button", "Activate Button", "The activate button of the widget", GTK_TYPE_WIDGET, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property ( object_class, PROP_CONTAINING_DIRECTORY, g_param_spec_object ("containing-directory", "Containing Directory", "The directory used to check for duplicate names", NAUTILUS_TYPE_DIRECTORY, G_PARAM_WRITABLE)); }