summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorges Basile Stavracas Neto <georges.stavracas@gmail.com>2015-04-18 22:04:42 -0300
committerGeorges Basile Stavracas Neto <georges.stavracas@gmail.com>2015-04-20 10:39:05 -0300
commit6ae3384aa4a0cba83ff803418fb08054980027d8 (patch)
tree6d54634234c76a5814f73f08d1eecf29ed1c621c
parentc376667159069cee2e6715de2478c1624fc7b9ff (diff)
downloadnautilus-6ae3384aa4a0cba83ff803418fb08054980027d8.tar.gz
view: show "New Folder" dialog
This commit introduces the "New Folder" dialog, which asks the folder name before actually creating it. With the introduced changes, the folder is created with the given name instead of creating it first with the generic "Unamed folder" and then renaming it. The dialog has a "delayed message" logic, where typos are immediatly recognized, but coincident folder names appear ~2s later. This dialog is part of the ongoing effort to modernize Nautilus to better fit GNOME standards, and the latest mockups can be found at [1]. [1] https://raw.githubusercontent.com/gnome-design-team/gnome-mockups/master/nautilus/nautilus-next/create-folder-wires.png https://bugzilla.gnome.org/show_bug.cgi?id=747381
-rw-r--r--libnautilus-private/nautilus-file-operations.c2
-rw-r--r--libnautilus-private/nautilus-file-operations.h1
-rw-r--r--libnautilus-private/nautilus-file-undo-operations.c5
-rw-r--r--src/nautilus-new-folder-dialog.ui86
-rw-r--r--src/nautilus-view.c221
-rw-r--r--src/nautilus.gresource.xml1
6 files changed, 297 insertions, 19 deletions
diff --git a/libnautilus-private/nautilus-file-operations.c b/libnautilus-private/nautilus-file-operations.c
index 0315a4664..acb4c7c61 100644
--- a/libnautilus-private/nautilus-file-operations.c
+++ b/libnautilus-private/nautilus-file-operations.c
@@ -6256,6 +6256,7 @@ void
nautilus_file_operations_new_folder (GtkWidget *parent_view,
GdkPoint *target_point,
const char *parent_dir,
+ const char *folder_name,
NautilusCreateCallback done_callback,
gpointer done_callback_data)
{
@@ -6271,6 +6272,7 @@ nautilus_file_operations_new_folder (GtkWidget *parent_view,
job->done_callback = done_callback;
job->done_callback_data = done_callback_data;
job->dest_dir = g_file_new_for_uri (parent_dir);
+ job->filename = g_strdup (folder_name);
job->make_dir = TRUE;
if (target_point != NULL) {
job->position = *target_point;
diff --git a/libnautilus-private/nautilus-file-operations.h b/libnautilus-private/nautilus-file-operations.h
index 87611b68f..e65ba7d7c 100644
--- a/libnautilus-private/nautilus-file-operations.h
+++ b/libnautilus-private/nautilus-file-operations.h
@@ -64,6 +64,7 @@ void nautilus_file_operations_empty_trash (GtkWidget *parent_vie
void nautilus_file_operations_new_folder (GtkWidget *parent_view,
GdkPoint *target_point,
const char *parent_dir_uri,
+ const char *folder_name,
NautilusCreateCallback done_callback,
gpointer done_callback_data);
void nautilus_file_operations_new_file (GtkWidget *parent_view,
diff --git a/libnautilus-private/nautilus-file-undo-operations.c b/libnautilus-private/nautilus-file-undo-operations.c
index 33a142612..25a49f249 100644
--- a/libnautilus-private/nautilus-file-undo-operations.c
+++ b/libnautilus-private/nautilus-file-undo-operations.c
@@ -726,12 +726,15 @@ create_folder_redo_func (NautilusFileUndoInfoCreate *self,
{
GFile *parent;
gchar *parent_uri;
+ gchar *name;
+ name = g_file_get_basename (self->priv->target_file);
parent = g_file_get_parent (self->priv->target_file);
parent_uri = g_file_get_uri (parent);
- nautilus_file_operations_new_folder (NULL, NULL, parent_uri,
+ nautilus_file_operations_new_folder (NULL, NULL, parent_uri, name,
create_callback, self);
+ g_free (name);
g_free (parent_uri);
g_object_unref (parent);
}
diff --git a/src/nautilus-new-folder-dialog.ui b/src/nautilus-new-folder-dialog.ui
new file mode 100644
index 000000000..c47dbb389
--- /dev/null
+++ b/src/nautilus-new-folder-dialog.ui
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.14"/>
+ <object class="GtkDialog" id="new_folder_dialog">
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="use-header-bar">1</property>
+ <property name="width_request">450</property>
+ <property name="title" translatable="yes">New Folder</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="vbox">
+ <property name="orientation">vertical</property>
+ <property name="margin_top">16</property>
+ <property name="margin_bottom">12</property>
+ <property name="margin_start">24</property>
+ <property name="margin_end">24</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="folder_name_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Folder Name</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <signal name="notify::text" handler="validate_cb" swapped="no" />
+ <signal name="activate" handler="activated_cb" swapped="no" />
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="error_label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="create_button">
+ <property name="label" translatable="yes">Create</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Create the new folder</property>
+ <property name="sensitive">False</property>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="ok" default="true">create_button</action-widget>
+ <action-widget response="cancel">cancel_button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/src/nautilus-view.c b/src/nautilus-view.c
index 1e28f1615..365aab194 100644
--- a/src/nautilus-view.c
+++ b/src/nautilus-view.c
@@ -50,6 +50,7 @@
#include <glib/gstdio.h>
#include <gio/gio.h>
#include <math.h>
+#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
@@ -114,6 +115,9 @@
#define MAX_MENU_LEVELS 5
#define TEMPLATE_LIMIT 30
+/* Time to show the duplicated folder label */
+#define NEW_FOLDER_DIALOG_ERROR_LABEL_TIMEOUT 500
+
enum {
ADD_FILE,
BEGIN_FILE_CHANGES,
@@ -153,6 +157,8 @@ struct NautilusViewDetails
GdkEventButton *pathbar_popup_event;
guint dir_merge_id;
+ gint new_folder_name_timeout_id;
+
gboolean supports_zooming;
GList *scripts_directory_list;
@@ -1415,7 +1421,7 @@ reveal_newly_added_folder (NautilusView *view, NautilusFile *new_file,
g_signal_handlers_disconnect_by_func (view,
G_CALLBACK (reveal_newly_added_folder),
(void *) target_location);
- rename_file (view, new_file);
+ nautilus_view_select_file (view, new_file);
}
g_object_unref (location);
}
@@ -1556,7 +1562,7 @@ new_folder_done (GFile *new_folder,
} else {
if (g_hash_table_lookup_extended (data->added_locations, new_folder, NULL, NULL)) {
/* The file was already added */
- rename_file (directory_view, file);
+ nautilus_view_select_file (directory_view, file);
} else {
/* We need to run after the default handler adds the folder we want to
* operate on. The ADD_FILE signal is registered as G_SIGNAL_RUN_LAST, so we
@@ -1623,31 +1629,210 @@ context_menu_to_file_operation_position (NautilusView *view)
}
}
+typedef struct {
+ NautilusView *view;
+ GtkWidget *dialog;
+ GtkWidget *label;
+} NewFolderDialogData;
+
+static gboolean
+show_has_folder_label (NewFolderDialogData *data)
+{
+ gtk_label_set_label (GTK_LABEL (data->label), _("A file or folder with that name already exists."));
+ data->view->details->new_folder_name_timeout_id = 0;
+ return FALSE;
+}
+
+static void
+nautilus_view_add_file_dialog_validate_name (GObject *object,
+ GParamSpec *params,
+ gpointer user_data)
+{
+ NewFolderDialogData *data = user_data;
+ NautilusFile *file;
+ NautilusView *view;
+ GtkWidget *dialog;
+ gboolean contains_slash;
+ gboolean is_empty;
+ gboolean has_folder;
+ GList *file_list, *node;
+ const gchar *text;
+
+ g_assert (GTK_IS_ENTRY (object));
+ g_assert (user_data);
+ g_assert (NAUTILUS_IS_VIEW (data->view));
+ g_assert (GTK_IS_DIALOG (data->dialog));
+ g_assert (GTK_IS_LABEL (data->label));
+
+ text = gtk_entry_get_text (GTK_ENTRY (object));
+ dialog = gtk_widget_get_toplevel (GTK_WIDGET (object));
+ is_empty = gtk_entry_get_text_length (GTK_ENTRY (object)) == 0;
+ contains_slash = strstr (text, "/") != NULL;
+
+ /* Check whether current location already has
+ * a folder with the proposed name.
+ */
+ view = data->view;
+ has_folder = FALSE;
+ file_list = nautilus_directory_get_file_list (view->details->model);
+
+ for (node = file_list; node != NULL; node = node->next) {
+ file = node->data;
+
+ if (nautilus_file_compare_display_name (file, text) == 0) {
+ has_folder = TRUE;
+ break;
+ }
+ }
+
+ nautilus_file_list_free (file_list);
+
+ /* Remove any sources left behind by
+ * previous calls of this function.
+ */
+ if (view->details->new_folder_name_timeout_id > 0) {
+ g_source_remove (view->details->new_folder_name_timeout_id);
+ view->details->new_folder_name_timeout_id = 0;
+ }
+
+ if (has_folder && !contains_slash && !is_empty) {
+ /* Before showing the dup folder label, clear out the
+ * previous message to stop showing any previous errors,
+ * considering that there are other possible error
+ * labels.
+ */
+ gtk_label_set_label (GTK_LABEL (data->label), NULL);
+
+ view->details->new_folder_name_timeout_id = g_timeout_add (NEW_FOLDER_DIALOG_ERROR_LABEL_TIMEOUT,
+ (GSourceFunc)show_has_folder_label,
+ user_data);
+ } else if (contains_slash) {
+ /* If the user types forbidden characters,
+ * immediately shows the error label.
+ */
+ gtk_label_set_label (GTK_LABEL (data->label), _("Folder names cannot contain \"/\"."));
+ } else {
+ /* No errors detected, empty the label */
+ gtk_label_set_label (GTK_LABEL (data->label), NULL);
+ }
+
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+ GTK_RESPONSE_OK,
+ !is_empty && !contains_slash && !has_folder);
+}
+
+static void
+nautilus_view_add_file_dialog_entry_activate (GtkWidget *entry,
+ gpointer user_data)
+{
+ NewFolderDialogData *data = user_data;
+ GtkWidget *create_button;
+
+ g_assert (GTK_IS_ENTRY (entry));
+ g_assert (user_data);
+ g_assert (NAUTILUS_IS_VIEW (data->view));
+ g_assert (GTK_IS_DIALOG (data->dialog));
+ g_assert (GTK_IS_LABEL (data->label));
+
+ create_button = gtk_dialog_get_widget_for_response (GTK_DIALOG (data->dialog),
+ GTK_RESPONSE_OK);
+
+ /* nautilus_view_add_file_dialog_validate_content performs
+ * all the necessary validation, and it's not needed to check
+ * it all again. Checking if the "Create" button is sensitive
+ * is enough.
+ */
+ if (gtk_widget_get_sensitive (create_button)) {
+ gtk_dialog_response (GTK_DIALOG (data->dialog),
+ GTK_RESPONSE_OK);
+ } else {
+ NautilusView *view = data->view;
+
+ /* Since typos are immediately shown, only
+ * handle name collisions here.
+ */
+ if (view->details->new_folder_name_timeout_id > 0) {
+ g_source_remove (view->details->new_folder_name_timeout_id);
+ show_has_folder_label (data);
+ }
+ }
+}
+
static void
nautilus_view_new_folder (NautilusView *directory_view,
gboolean with_selection)
{
- char *parent_uri;
- NewFolderData *data;
- GdkPoint *pos;
+ NewFolderDialogData *dialog_data;
+ GtkBuilder *builder;
+ GtkWindow *dialog;
+ GtkEntry *entry;
+ gint response;
- data = new_folder_data_new (directory_view, with_selection);
+ builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/nautilus-new-folder-dialog.ui");
+ dialog = GTK_WINDOW (gtk_builder_get_object (builder, "new_folder_dialog"));
+ entry = GTK_ENTRY (gtk_builder_get_object (builder, "name_entry"));
- g_signal_connect_data (directory_view,
- "add-file",
- G_CALLBACK (track_newly_added_locations),
- data,
- (GClosureNotify)NULL,
- G_CONNECT_AFTER);
+ /* build up dialog fields */
+ dialog_data = g_new0 (NewFolderDialogData, 1);
+ dialog_data->view = directory_view;
+ dialog_data->dialog = GTK_WIDGET (dialog);
+ dialog_data->label = GTK_WIDGET (gtk_builder_get_object (builder, "error_label"));
- pos = context_menu_to_file_operation_position (directory_view);
+ gtk_window_set_transient_for (dialog,
+ GTK_WINDOW (nautilus_view_get_window (directory_view)));
+
+ /* Connect signals */
+ gtk_builder_add_callback_symbols (builder,
+ "validate_cb",
+ G_CALLBACK (nautilus_view_add_file_dialog_validate_name),
+ "activated_cb",
+ G_CALLBACK (nautilus_view_add_file_dialog_entry_activate),
+ NULL);
+
+ gtk_builder_connect_signals (builder, dialog_data);
+
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ if (response == GTK_RESPONSE_OK) {
+ NewFolderData *data;
+ GdkPoint *pos;
+ char *parent_uri;
+ gchar *name;
+
+ data = new_folder_data_new (directory_view, with_selection);
+ name = g_strdup (gtk_entry_get_text (entry));
+ g_strstrip (name);
+
+ g_signal_connect_data (directory_view,
+ "add-file",
+ G_CALLBACK (track_newly_added_locations),
+ data,
+ (GClosureNotify)NULL,
+ G_CONNECT_AFTER);
+
+ pos = context_menu_to_file_operation_position (directory_view);
+
+ parent_uri = nautilus_view_get_backing_uri (directory_view);
+ nautilus_file_operations_new_folder (GTK_WIDGET (directory_view),
+ pos, parent_uri, name,
+ new_folder_done, data);
+
+ g_free (parent_uri);
+ g_free (name);
+ }
- parent_uri = nautilus_view_get_backing_uri (directory_view);
- nautilus_file_operations_new_folder (GTK_WIDGET (directory_view),
- pos, parent_uri,
- new_folder_done, data);
+ /* If there's any resources left from the delayed
+ * message, it should be removed before it gets
+ * triggered.
+ */
+ if (directory_view->details->new_folder_name_timeout_id > 0) {
+ g_source_remove (directory_view->details->new_folder_name_timeout_id);
+ directory_view->details->new_folder_name_timeout_id = 0;
+ }
- g_free (parent_uri);
+ g_free (dialog_data);
+ g_object_unref (builder);
+ gtk_widget_destroy (GTK_WIDGET (dialog));
}
static NewFolderData *
diff --git a/src/nautilus.gresource.xml b/src/nautilus.gresource.xml
index c6dc3b872..e3d77f4f1 100644
--- a/src/nautilus.gresource.xml
+++ b/src/nautilus.gresource.xml
@@ -7,6 +7,7 @@
<file>nautilus-toolbar-ui.xml</file>
<file>nautilus-toolbar-view-menu.xml</file>
<file>nautilus-toolbar-action-menu.xml</file>
+ <file>nautilus-new-folder-dialog.ui</file>
<file>nautilus-view-context-menus.xml</file>
<file>nautilus-progress-info-widget.xml</file>
<file>nautilus-move-to-trash-shortcut-changed.ui</file>