[nautilus] view: show "New Folder" dialog
- From: Georges Basile Stavracas Neto <gbsneto src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [nautilus] view: show "New Folder" dialog
- Date: Mon, 20 Apr 2015 13:44:43 +0000 (UTC)
commit 6ae3384aa4a0cba83ff803418fb08054980027d8
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date: Sat Apr 18 22:04:42 2015 -0300
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
libnautilus-private/nautilus-file-operations.c | 2 +
libnautilus-private/nautilus-file-operations.h | 1 +
.../nautilus-file-undo-operations.c | 5 +-
src/nautilus-new-folder-dialog.ui | 86 ++++++++
src/nautilus-view.c | 221 ++++++++++++++++++--
src/nautilus.gresource.xml | 1 +
6 files changed, 297 insertions(+), 19 deletions(-)
---
diff --git a/libnautilus-private/nautilus-file-operations.c b/libnautilus-private/nautilus-file-operations.c
index 0315a46..acb4c7c 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 87611b6..e65ba7d 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 33a1426..25a49f2 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 0000000..c47dbb3
--- /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 1e28f16..365aab1 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 c6dc3b8..e3d77f4 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>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]