file chooser: Allow renaming files

This has often been requested as a useful feature in save mode.

Based on a patch by John Beard,
https://bugzilla.gnome.org/show_bug.cgi?id=325150
This commit is contained in:
Matthias Clasen 2015-07-04 21:16:55 -04:00
parent 530d295a2e
commit 15617a69aa
3 changed files with 253 additions and 27 deletions

View File

@ -234,6 +234,7 @@ struct _GtkFileChooserWidgetPrivate {
GtkWidget *copy_file_location_item;
GtkWidget *visit_file_item;
GtkWidget *open_folder_item;
GtkWidget *rename_file_item;
GtkWidget *sort_directories_item;
GtkWidget *show_time_item;
GtkWidget *browse_new_folder_button;
@ -243,6 +244,12 @@ struct _GtkFileChooserWidgetPrivate {
GtkWidget *new_folder_create_button;
GtkWidget *new_folder_error_label;
GtkWidget *new_folder_popover;
GtkWidget *rename_file_name_entry;
GtkWidget *rename_file_rename_button;
GtkWidget *rename_file_error_label;
GtkWidget *rename_file_popover;
GtkWidget *file_error_label;
GFile *rename_file_source_file;
GtkFileSystemModel *browse_files_model;
char *browse_files_last_selected_name;
@ -377,7 +384,7 @@ static guint signals[LAST_SIGNAL] = { 0 };
#define MODEL_ATTRIBUTES "standard::name,standard::type,standard::display-name," \
"standard::is-hidden,standard::is-backup,standard::size," \
"standard::content-type,time::modified,time::access," \
"standard::target-uri"
"standard::target-uri,access::can-rename"
enum {
/* the first 3 must be these due to settings caching sort column */
MODEL_COL_NAME,
@ -955,6 +962,8 @@ struct FileExistsData
gboolean file_exists_and_is_not_folder;
GFile *parent_file;
GFile *file;
GtkWidget *error_label;
GtkWidget *button;
};
static void
@ -984,12 +993,12 @@ name_exists_get_info_cb (GCancellable *cancellable,
else
msg = _("A file with that name already exists");
gtk_widget_set_sensitive (priv->new_folder_create_button, FALSE);
gtk_label_set_text (GTK_LABEL (priv->new_folder_error_label), msg);
gtk_widget_set_sensitive (data->button, FALSE);
gtk_label_set_text (GTK_LABEL (data->error_label), msg);
}
else
{
gtk_widget_set_sensitive (priv->new_folder_create_button, TRUE);
gtk_widget_set_sensitive (data->button, TRUE);
/* Don't clear the label here, it may contain a warning */
}
@ -1002,35 +1011,42 @@ out:
}
static void
check_valid_folder_name (GtkFileChooserWidget *impl,
const gchar *name)
check_valid_file_or_folder_name (GtkFileChooserWidget *impl,
const gchar *name,
GFile *parent,
gboolean folder,
GtkWidget *error_label,
GtkWidget *button)
{
GtkFileChooserWidgetPrivate *priv = impl->priv;
gtk_widget_set_sensitive (priv->new_folder_create_button, FALSE);
gtk_widget_set_sensitive (button, FALSE);
if (name[0] == '\0')
gtk_label_set_text (GTK_LABEL (priv->new_folder_error_label), "");
gtk_label_set_text (GTK_LABEL (error_label), "");
else if (strcmp (name, ".") == 0)
gtk_label_set_text (GTK_LABEL (priv->new_folder_error_label),
_("A folder cannot be called “.”"));
gtk_label_set_text (GTK_LABEL (error_label),
folder ? _("A folder cannot be called “.”")
: _("A file cannot be called “.”"));
else if (strcmp (name, "..") == 0)
gtk_label_set_text (GTK_LABEL (priv->new_folder_error_label),
_("A folder cannot be called “..”"));
gtk_label_set_text (GTK_LABEL (error_label),
folder ? _("A folder cannot be called “..”")
: _("A file cannot be called “..”"));
else if (strchr (name, '/') != NULL)
gtk_label_set_text (GTK_LABEL (priv->new_folder_error_label),
_("Folder names cannot contain “/”"));
gtk_label_set_text (GTK_LABEL (error_label),
folder ? _("Folder names cannot contain “/”")
: _("File names cannot contain “/”"));
else
{
GFile *file;
GError *error = NULL;
gtk_label_set_text (GTK_LABEL (priv->new_folder_error_label), "");
gtk_label_set_text (GTK_LABEL (error_label), "");
file = g_file_get_child_for_display_name (priv->current_folder, name, &error);
file = g_file_get_child_for_display_name (parent, name, &error);
if (file == NULL)
{
gtk_label_set_text (GTK_LABEL (priv->new_folder_error_label), error->message);
gtk_label_set_text (GTK_LABEL (error_label), error->message);
g_error_free (error);
}
else
@ -1039,20 +1055,25 @@ check_valid_folder_name (GtkFileChooserWidget *impl,
/* Warn the user about questionable names that are technically valid */
if (g_ascii_isspace (name[0]))
gtk_label_set_text (GTK_LABEL (priv->new_folder_error_label),
_("Folder names should not begin with a space"));
gtk_label_set_text (GTK_LABEL (error_label),
folder ? _("Folder names should not begin with a space")
: _("File names should not begin with a space"));
else if (g_ascii_isspace (name[strlen (name) - 1]))
gtk_label_set_text (GTK_LABEL (priv->new_folder_error_label),
_("Folder names should not end with a space"));
gtk_label_set_text (GTK_LABEL (error_label),
folder ? _("Folder names should not end with a space")
: _("File names should not end with a space"));
else if (name[0] == '.')
gtk_label_set_text (GTK_LABEL (priv->new_folder_error_label),
_("Folder names starting with a “.” are hidden"));
gtk_label_set_text (GTK_LABEL (error_label),
folder ? _("Folder names starting with a “.” are hidden")
: _("File names starting with a “.” are hidden"));
data = g_new0 (struct FileExistsData, 1);
data->impl = g_object_ref (impl);
data->parent_file = g_object_ref (priv->current_folder);
data->parent_file = g_object_ref (parent);
data->file = g_object_ref (file);
data->error_label = error_label;
data->button = button;
if (priv->file_exists_get_info_cancellable)
g_cancellable_cancel (priv->file_exists_get_info_cancellable);
@ -1073,7 +1094,14 @@ static void
new_folder_name_changed (GtkEntry *entry,
GtkFileChooserWidget *impl)
{
check_valid_folder_name (impl, gtk_entry_get_text (entry));
GtkFileChooserWidgetPrivate *priv = impl->priv;
check_valid_file_or_folder_name (impl,
gtk_entry_get_text (entry),
priv->current_folder,
FALSE,
priv->new_folder_error_label,
priv->new_folder_create_button);
}
static void
@ -1360,6 +1388,7 @@ popup_menu_detach_cb (GtkWidget *attach_widget,
priv->size_column_item = NULL;
priv->copy_file_location_item = NULL;
priv->visit_file_item = NULL;
priv->rename_file_item = NULL;
priv->open_folder_item = NULL;
priv->sort_directories_item = NULL;
priv->show_time_item = NULL;
@ -1402,6 +1431,106 @@ add_to_shortcuts_cb (GtkMenuItem *item,
impl);
}
static void
rename_file_name_changed (GtkEntry *entry,
GtkFileChooserWidget *impl)
{
GtkFileChooserWidgetPrivate *priv = impl->priv;
GFileType file_type;
GFile *parent;
file_type = g_file_query_file_type (priv->rename_file_source_file,
G_FILE_QUERY_INFO_NONE, NULL);
parent = g_file_get_parent (priv->rename_file_source_file);
check_valid_file_or_folder_name (impl,
gtk_entry_get_text (entry),
parent,
file_type == G_FILE_TYPE_DIRECTORY,
priv->rename_file_error_label,
priv->rename_file_rename_button);
g_object_unref (parent);
}
static void
rename_file_end (GtkPopover *popover,
GtkFileChooserWidget *impl)
{
g_object_unref (impl->priv->rename_file_source_file);
}
static void
rename_file_rename_clicked (GtkButton *button,
GtkFileChooserWidget *impl)
{
GtkFileChooserWidgetPrivate *priv = impl->priv;
GFile *dest;
const gchar* new_name;
gtk_widget_hide (priv->rename_file_popover);
new_name = gtk_entry_get_text (GTK_ENTRY (priv->rename_file_name_entry));
dest = g_file_get_parent (priv->rename_file_source_file);
if (dest)
{
GFile *child;
GError *error = NULL;
child = g_file_get_child (dest, new_name);
if (child)
{
if (!g_file_move (priv->rename_file_source_file, child, G_FILE_COPY_NONE,
NULL, NULL, NULL, &error))
error_dialog (impl, _("The file could not be renamed"), child, error);
g_object_unref (child);
}
g_object_unref (dest);
}
}
static void
rename_file_cb (GtkMenuItem *item,
GtkFileChooserWidget *impl)
{
GtkFileChooserWidgetPrivate *priv = impl->priv;
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter iter;
GtkTreePath *path;
GdkRectangle rect;
gchar *filename;
/* insensitive until we change the name */
gtk_widget_set_sensitive (priv->rename_file_rename_button, FALSE);
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
if (gtk_tree_selection_get_selected (selection, &model, &iter))
{
gtk_tree_model_get (model, &iter,
MODEL_COL_FILE, &priv->rename_file_source_file,
-1);
path = gtk_tree_model_get_path (model, &iter);
gtk_tree_view_get_cell_area (GTK_TREE_VIEW (priv->browse_files_tree_view),
path, priv->list_name_column, &rect);
gtk_tree_view_convert_tree_to_widget_coords (GTK_TREE_VIEW (priv->browse_files_tree_view),
rect.x, rect.y, &rect.x, &rect.y);
filename = g_file_get_basename (priv->rename_file_source_file);
gtk_entry_set_text (GTK_ENTRY(priv->rename_file_name_entry), filename);
g_free (filename);
gtk_popover_set_pointing_to (GTK_POPOVER (priv->rename_file_popover), &rect);
gtk_widget_show (priv->rename_file_popover);
gtk_widget_grab_focus (priv->rename_file_popover);
}
}
/* callback used to set data to clipboard */
static void
copy_file_get_cb (GtkClipboard *clipboard,
@ -1849,9 +1978,26 @@ check_file_list_menu_sensitivity (GtkFileChooserWidget *impl)
gtk_widget_set_sensitive (priv->add_shortcut_item, active && all_folders);
if (priv->visit_file_item)
gtk_widget_set_sensitive (priv->visit_file_item, active);
if (priv->open_folder_item)
gtk_widget_set_visible (priv->open_folder_item, (num_selected == 1) && all_folders);
if (priv->rename_file_item)
{
if (num_selected == 1)
{
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter iter;
GFileInfo *info;
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->browse_files_tree_view));
gtk_tree_selection_get_selected (selection, &model, &iter);
info = _gtk_file_system_model_get_info (GTK_FILE_SYSTEM_MODEL (model), &iter);
gtk_widget_set_sensitive (priv->rename_file_item,
g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME));
}
else
gtk_widget_set_sensitive (priv->rename_file_item, FALSE);
}
}
static GtkWidget *
@ -1913,6 +2059,9 @@ file_list_build_popup_menu (GtkFileChooserWidget *impl)
priv->add_shortcut_item
= file_list_add_menu_item (impl, _("_Add to Bookmarks"), G_CALLBACK (add_to_shortcuts_cb));
priv->rename_file_item
= file_list_add_menu_item (impl, _("_Rename"), G_CALLBACK (rename_file_cb));
item = gtk_separator_menu_item_new ();
gtk_widget_show (item);
gtk_menu_shell_append (GTK_MENU_SHELL (priv->browse_files_popup_menu), item);
@ -1944,6 +2093,8 @@ file_list_update_popup_menu (GtkFileChooserWidget *impl)
* bookmarks_check_add_sensitivity()
*/
gtk_widget_set_visible (priv->rename_file_item, (priv->operation_mode == OPERATION_MODE_BROWSE));
/* 'Visit this file' */
gtk_widget_set_visible (priv->visit_file_item, (priv->operation_mode != OPERATION_MODE_BROWSE));
@ -7924,6 +8075,10 @@ gtk_file_chooser_widget_class_init (GtkFileChooserWidgetClass *class)
gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, new_folder_create_button);
gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, new_folder_error_label);
gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, new_folder_popover);
gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, rename_file_name_entry);
gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, rename_file_rename_button);
gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, rename_file_error_label);
gtk_widget_class_bind_template_child_private (widget_class, GtkFileChooserWidget, rename_file_popover);
/* And a *lot* of callbacks to bind ... */
gtk_widget_class_bind_template_callback (widget_class, browse_files_key_press_event_cb);
@ -7947,6 +8102,9 @@ gtk_file_chooser_widget_class_init (GtkFileChooserWidgetClass *class)
gtk_widget_class_bind_template_callback (widget_class, new_folder_popover_active);
gtk_widget_class_bind_template_callback (widget_class, new_folder_name_changed);
gtk_widget_class_bind_template_callback (widget_class, new_folder_create_clicked);
gtk_widget_class_bind_template_callback (widget_class, rename_file_name_changed);
gtk_widget_class_bind_template_callback (widget_class, rename_file_rename_clicked);
gtk_widget_class_bind_template_callback (widget_class, rename_file_end);
}
static void
@ -8011,6 +8169,7 @@ post_process_ui (GtkFileChooserWidget *impl)
set_icon_cell_renderer_fixed_size (impl);
gtk_popover_set_default_widget (GTK_POPOVER (impl->priv->new_folder_popover), impl->priv->new_folder_create_button);
gtk_popover_set_default_widget (GTK_POPOVER (impl->priv->rename_file_popover), impl->priv->rename_file_rename_button);
}
void

View File

@ -220,7 +220,8 @@ visit_directory (GFile *dir, SearchThreadData *data)
G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
G_FILE_ATTRIBUTE_STANDARD_TARGET_URI ","
G_FILE_ATTRIBUTE_TIME_MODIFIED ","
G_FILE_ATTRIBUTE_TIME_ACCESS,
G_FILE_ATTRIBUTE_TIME_ACCESS ","
G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
data->cancellable, NULL);
if (enumerator == NULL)

View File

@ -452,4 +452,70 @@
</object>
</child>
</object>
<object class="GtkPopover" id="rename_file_popover">
<property name="relative-to">browse_files_tree_view</property>
<property name="position">bottom</property>
<signal name="closed" handler="rename_file_end"/>
<child>
<object class="GtkGrid">
<property name="visible">True</property>
<property name="margin">10</property>
<property name="row-spacing">6</property>
<property name="column-spacing">6</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="label" translatable="yes">Name</property>
<property name="halign">start</property>
<property name="mnemonic_widget">rename_file_name_entry</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="rename_file_name_entry">
<property name="visible">True</property>
<property name="activates-default">True</property>
<signal name="changed" handler="rename_file_name_changed"/>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="rename_file_rename_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="label" translatable="yes">_Rename</property>
<property name="use_underline">True</property>
<property name="can_default">True</property>
<signal name="clicked" handler="rename_file_rename_clicked"/>
<style>
<class name="suggested-action"/>
</style>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="rename_file_error_label">
<property name="visible">True</property>
<property name="halign">start</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
<property name="width">2</property>
</packing>
</child>
</object>
</child>
</object>
</interface>