[nautilus] view: show "New Folder" dialog



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]