/* 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_entry_get_text (GTK_ENTRY (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));
}