summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorutkarshvg2401 <utkarshvg2401@gmail.com>2022-07-18 21:23:11 +0530
committerAntónio Fernandes <antoniof@gnome.org>2022-09-10 12:31:23 +0100
commitbe047e67dd7e2709b28e44fcaad2d24193eec16f (patch)
tree644bdf14a30c6f82384ad36d5471644f7e57893e
parent216d5c2dddda8c2a0182b6110a11214875d948ab (diff)
downloadnautilus-be047e67dd7e2709b28e44fcaad2d24193eec16f.tar.gz
templates-dialog: Changes for the dialog box method
The current implementation of the “New Documents” submenu is a GtkPopoverMenu. It uses the GMenuModel, which is added as submenus in the right-click popover menu. This implementation has many long-standing issues, as mentioned here https://gitlab.gnome.org/GNOME/nautilus/-/issues/2205, so we plan to move away from it by using a dialog box for creating new templates. The dialog box makes it possible to view the whole list of templates in a single view, gives users the ability to search for templates, and makes it easier for everyone to use the template creation feature!
-rw-r--r--src/nautilus-templates-dialog.c561
-rw-r--r--src/nautilus-templates-dialog.h3
-rw-r--r--src/resources/ui/nautilus-templates-dialog.ui33
3 files changed, 595 insertions, 2 deletions
diff --git a/src/nautilus-templates-dialog.c b/src/nautilus-templates-dialog.c
index 62ddea9bc..a830e8fcd 100644
--- a/src/nautilus-templates-dialog.c
+++ b/src/nautilus-templates-dialog.c
@@ -22,15 +22,42 @@
#include <glib/gi18n.h>
+#include "nautilus-directory.h"
#include "nautilus-file-operations.h"
+#include "nautilus-file.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-global-preferences.h"
+
+#define MAX_MENU_LEVELS 5
+#define TEMPLATE_LIMIT 30
struct _NautilusTemplatesDialog
{
GtkDialog parent_instance;
+
+ GtkWidget *templates_list_box;
+ GtkWidget *templates_search_entry;
+
+ GList *templates_directory_list;
+ GtkTreeListModel *templates_list_model;
+ GtkStack *templates_stack;
+ GtkFilterListModel *filter_model;
+ GtkCustomFilter *filter;
+
+ guint update_timeout_id;
+
+ gboolean show_hidden_files;
};
G_DEFINE_TYPE (NautilusTemplatesDialog, nautilus_templates_dialog, GTK_TYPE_DIALOG)
+/* Forward declarations */
+static void update_templates_menu (NautilusTemplatesDialog *self);
+
+static void schedule_update (NautilusTemplatesDialog *self);
+
+static void remove_update_timeout_callback (NautilusTemplatesDialog *self);
+
static void
on_import_response (GtkNativeDialog *native,
int response,
@@ -50,6 +77,8 @@ on_import_response (GtkNativeDialog *native,
templates_location,
GTK_WINDOW (self),
NULL, NULL, NULL);
+
+ schedule_update (self);
}
g_object_unref (native);
@@ -72,6 +101,505 @@ nautilus_templates_dialog_import_template_dialog (NautilusTemplatesDialog *self
gtk_native_dialog_show (GTK_NATIVE_DIALOG (native));
}
+static void
+templates_added_or_changed_callback (NautilusDirectory *directory,
+ GList *files,
+ gpointer callback_data)
+{
+ NautilusTemplatesDialog *self;
+
+ self = NAUTILUS_TEMPLATES_DIALOG (callback_data);
+
+ schedule_update (self);
+}
+
+static void
+add_directory_to_directory_list (NautilusTemplatesDialog *self,
+ NautilusDirectory *directory,
+ GList **directory_list,
+ GCallback changed_callback)
+{
+ NautilusFileAttributes attributes;
+
+ if (g_list_find (*directory_list, directory) == NULL)
+ {
+ nautilus_directory_ref (directory);
+
+ attributes =
+ NAUTILUS_FILE_ATTRIBUTES_FOR_ICON |
+ NAUTILUS_FILE_ATTRIBUTE_INFO |
+ NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT;
+
+ nautilus_directory_file_monitor_add (directory, directory_list,
+ FALSE, attributes,
+ (NautilusDirectoryCallback) changed_callback, self);
+
+ g_signal_connect_object (directory, "files-added",
+ G_CALLBACK (changed_callback), self, 0);
+ g_signal_connect_object (directory, "files-changed",
+ G_CALLBACK (changed_callback), self, 0);
+
+ *directory_list = g_list_append (*directory_list, directory);
+ }
+}
+
+static void
+remove_directory_from_directory_list (NautilusTemplatesDialog *self,
+ NautilusDirectory *directory,
+ GList **directory_list,
+ GCallback changed_callback)
+{
+ *directory_list = g_list_remove (*directory_list, directory);
+
+ g_signal_handlers_disconnect_by_func (directory,
+ G_CALLBACK (changed_callback),
+ self);
+
+ nautilus_directory_file_monitor_remove (directory, directory_list);
+
+ nautilus_directory_unref (directory);
+}
+
+static void
+add_directory_to_templates_directory_list (NautilusTemplatesDialog *self,
+ NautilusDirectory *directory)
+{
+ add_directory_to_directory_list (self, directory,
+ &self->templates_directory_list,
+ G_CALLBACK (templates_added_or_changed_callback));
+}
+
+static void
+remove_directory_from_templates_directory_list (NautilusTemplatesDialog *self,
+ NautilusDirectory *directory)
+{
+ remove_directory_from_directory_list (self, directory,
+ &self->templates_directory_list,
+ G_CALLBACK (templates_added_or_changed_callback));
+}
+
+static void
+nautilus_templates_dialog_dispose (GObject *object)
+{
+ NautilusTemplatesDialog *self = NAUTILUS_TEMPLATES_DIALOG (object);
+
+ /* Add other cleanup statements here*/
+ remove_update_timeout_callback (self);
+
+ g_clear_list (&self->templates_directory_list, g_object_unref);
+
+ g_clear_object (&self->templates_list_model);
+
+ G_OBJECT_CLASS (nautilus_templates_dialog_parent_class)->dispose (object);
+}
+
+static void
+remove_update_timeout_callback (NautilusTemplatesDialog *self)
+{
+ if (self->update_timeout_id != 0)
+ {
+ g_source_remove (self->update_timeout_id);
+ self->update_timeout_id = 0;
+ }
+}
+
+static gboolean
+update_timeout_callback (gpointer data)
+{
+ NautilusTemplatesDialog *self = NAUTILUS_TEMPLATES_DIALOG (data);
+
+ g_object_ref (G_OBJECT (self));
+
+ self->update_timeout_id = 0;
+ update_templates_menu (self);
+
+ g_object_unref (G_OBJECT (self));
+
+ return FALSE;
+}
+
+static gboolean
+directory_belongs_in_templates_menu (const char *templates_directory_uri,
+ const char *uri)
+{
+ int num_levels;
+ int i;
+
+ if (templates_directory_uri == NULL)
+ {
+ return FALSE;
+ }
+
+ if (!g_str_has_prefix (uri, templates_directory_uri))
+ {
+ return FALSE;
+ }
+
+ num_levels = 0;
+ for (i = strlen (templates_directory_uri); uri[i] != '\0'; i++)
+ {
+ if (uri[i] == '/')
+ {
+ num_levels++;
+ }
+ }
+
+ if (num_levels > MAX_MENU_LEVELS)
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+filter_templates_callback (NautilusFile *file,
+ gpointer callback_data)
+{
+ gboolean show_hidden = GPOINTER_TO_INT (callback_data);
+
+ if (nautilus_file_is_hidden_file (file))
+ {
+ if (!show_hidden)
+ {
+ return FALSE;
+ }
+
+ if (nautilus_file_is_directory (file))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static GList *
+filter_templates (GList *files,
+ gboolean show_hidden)
+{
+ GList *filtered_files;
+ GList *removed_files;
+
+ filtered_files = nautilus_file_list_filter (files,
+ &removed_files,
+ filter_templates_callback,
+ GINT_TO_POINTER (show_hidden));
+ nautilus_file_list_free (removed_files);
+
+ return filtered_files;
+}
+
+static GListStore *
+update_directory_in_templates_menu (NautilusTemplatesDialog *self,
+ NautilusDirectory *directory)
+{
+ GList *file_list, *filtered, *node;
+ GListStore *store;
+ gboolean any_templates;
+ NautilusFile *file;
+ int num;
+
+ g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL);
+
+ file_list = nautilus_directory_get_file_list (directory);
+
+ /*
+ * The nautilus_file_list_filter_hidden() function isn't used here, because
+ * we want to show hidden files, but not directories. This is a compromise
+ * to allow creating hidden files but to prevent content from .git directory
+ * for example. See https://gitlab.gnome.org/GNOME/nautilus/issues/1413.
+ */
+ filtered = filter_templates (file_list, self->show_hidden_files);
+ nautilus_file_list_free (file_list);
+ store = g_list_store_new (NAUTILUS_TYPE_FILE);
+
+ filtered = nautilus_file_list_sort_by_display_name (filtered);
+
+ num = 0;
+ any_templates = FALSE;
+ for (node = filtered; num < TEMPLATE_LIMIT && node != NULL; node = node->next, num++)
+ {
+ file = node->data;
+ g_list_store_append (store, file);
+ any_templates = TRUE;
+ }
+
+ nautilus_file_list_free (filtered);
+
+ if (!any_templates)
+ {
+ g_object_unref (store);
+ store = NULL;
+ }
+
+ return G_LIST_STORE (store);
+}
+
+static GListModel *
+templates_tree_list_model (GObject *item,
+ gpointer user_data)
+{
+ NautilusFile *file;
+ NautilusDirectory *dir;
+ char *uri;
+ char *templates_directory_uri;
+ NautilusTemplatesDialog *self = NAUTILUS_TEMPLATES_DIALOG (user_data);
+
+ file = NAUTILUS_FILE (item);
+
+ templates_directory_uri = nautilus_get_templates_directory_uri ();
+
+ if (nautilus_file_is_directory (file))
+ {
+ uri = nautilus_file_get_uri (file);
+ if (directory_belongs_in_templates_menu (templates_directory_uri, uri))
+ {
+ dir = nautilus_directory_get_by_uri (uri);
+ add_directory_to_templates_directory_list (self, dir);
+
+ return G_LIST_MODEL (update_directory_in_templates_menu (self, dir));
+ }
+ g_free (uri);
+ }
+
+ return NULL;
+}
+
+static void
+update_templates_menu (NautilusTemplatesDialog *self)
+{
+ g_autofree char *templates_directory_uri = NULL;
+ g_autolist (NautilusDirectory) sorted_copy = NULL;
+ g_autoptr (NautilusDirectory) directory = NULL;
+ GListStore *model;
+ GtkTreeListModel *treemodel;
+
+ if (!nautilus_should_use_templates_directory ())
+ {
+ return;
+ }
+
+ templates_directory_uri = nautilus_get_templates_directory_uri ();
+ sorted_copy = nautilus_directory_list_sort_by_uri
+ (nautilus_directory_list_copy (self->templates_directory_list));
+
+ for (GList *dir_l = sorted_copy; dir_l != NULL; dir_l = dir_l->next)
+ {
+ g_autofree char *uri = nautilus_directory_get_uri (dir_l->data);
+ if (!directory_belongs_in_templates_menu (templates_directory_uri, uri))
+ {
+ remove_directory_from_templates_directory_list (self, dir_l->data);
+ }
+ }
+
+ directory = nautilus_directory_get_by_uri (templates_directory_uri);
+
+ model = update_directory_in_templates_menu (self, directory);
+ treemodel = gtk_tree_list_model_new (G_LIST_MODEL (model),
+ FALSE, FALSE,
+ (GtkTreeListModelCreateModelFunc) templates_tree_list_model,
+ self, NULL);
+
+ self->templates_list_model = treemodel;
+ gtk_filter_list_model_set_model (self->filter_model, G_LIST_MODEL (self->templates_list_model));
+}
+
+static void
+row_activated (GtkListBox *self,
+ GtkListBoxRow *row,
+ gpointer user_data)
+{
+ GtkTreeExpander *expander;
+ NautilusFile *file;
+
+ expander = GTK_TREE_EXPANDER (gtk_list_box_row_get_child (row));
+ file = NAUTILUS_FILE (gtk_tree_expander_get_item (expander));
+
+ if (file == NULL)
+ {
+ return;
+ }
+
+ if (nautilus_file_is_directory (file))
+ {
+ if (!gtk_tree_list_row_get_expanded (gtk_tree_expander_get_list_row (expander)))
+ {
+ gtk_tree_list_row_set_expanded (gtk_tree_expander_get_list_row (expander), TRUE);
+ }
+ else
+ {
+ gtk_tree_list_row_set_expanded (gtk_tree_expander_get_list_row (expander), FALSE);
+ }
+ }
+
+ g_object_unref (file);
+}
+
+static GtkWidget *
+templates_list_box_widgets (GObject *item,
+ gpointer user_data)
+{
+ NautilusFile *file;
+ GtkWidget *expander;
+ GtkWidget *box;
+ GtkWidget *label;
+ GtkWidget *image;
+ GtkWidget *list_box_row;
+ NautilusFileIconFlags flags;
+
+ flags = 0;
+ file = NAUTILUS_FILE (gtk_tree_list_row_get_item (GTK_TREE_LIST_ROW (item)));
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL);
+
+ expander = gtk_tree_expander_new ();
+ gtk_tree_expander_set_list_row (GTK_TREE_EXPANDER (expander), GTK_TREE_LIST_ROW (item));
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+ label = gtk_label_new (nautilus_file_get_name (file));
+ image = gtk_image_new ();
+
+ gtk_image_set_from_gicon (GTK_IMAGE (image), nautilus_file_get_gicon (file, flags));
+ gtk_image_set_icon_size (GTK_IMAGE (image), GTK_ICON_SIZE_LARGE);
+ gtk_tree_expander_set_child (GTK_TREE_EXPANDER (expander), box);
+ gtk_box_append (GTK_BOX (box), image);
+ gtk_box_append (GTK_BOX (box), label);
+
+ gtk_widget_set_margin_top (expander, 12);
+ gtk_widget_set_margin_bottom (expander, 12);
+ gtk_widget_set_margin_start (expander, 12);
+ gtk_widget_set_margin_end (expander, 12);
+
+ list_box_row = gtk_list_box_row_new ();
+ gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (list_box_row), expander);
+
+ if (nautilus_file_is_directory (file))
+ {
+ gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (list_box_row), FALSE);
+ }
+
+ g_object_unref (file);
+
+ return list_box_row;
+}
+
+static void
+set_expanded_recursive (GtkTreeListRow *row,
+ gboolean expanded)
+{
+ guint i = 0;
+ GtkTreeListRow *child_row;
+
+ gtk_tree_list_row_set_expanded (row, expanded);
+
+ while ((child_row = gtk_tree_list_row_get_child_row (row, i)) != NULL)
+ {
+ set_expanded_recursive (child_row, expanded);
+ i += 1;
+ }
+}
+
+static gboolean
+match_function (GObject *item,
+ gpointer user_data)
+{
+ const gchar *text;
+ NautilusFile *file;
+ const gchar *file_name;
+
+ file = NAUTILUS_FILE (gtk_tree_list_row_get_item (GTK_TREE_LIST_ROW (item)));
+
+ if (file == NULL)
+ {
+ return FALSE;
+ }
+
+ file_name = nautilus_file_get_display_name (file);
+ text = gtk_editable_get_text (GTK_EDITABLE (user_data));
+
+ g_return_val_if_fail (text != NULL, TRUE);
+
+ return g_str_match_string (text, file_name, TRUE);
+}
+
+static void
+search_changed (GtkSearchEntry *entry,
+ gpointer user_data)
+{
+ NautilusTemplatesDialog *self = NAUTILUS_TEMPLATES_DIALOG (user_data);
+ guint i = 0;
+ GtkTreeListRow *child_row;
+ const gchar *text;
+ gboolean check;
+
+ text = gtk_editable_get_text (GTK_EDITABLE (entry));
+
+ g_return_if_fail (text != NULL);
+
+ if (g_strcmp0 ("", text) != 0)
+ {
+ check = TRUE;
+ }
+ else
+ {
+ check = FALSE;
+ }
+
+ while ((child_row = gtk_tree_list_model_get_child_row (self->templates_list_model, i)) != NULL)
+ {
+ if (check)
+ {
+ set_expanded_recursive (child_row, TRUE);
+ }
+ else
+ {
+ set_expanded_recursive (child_row, FALSE);
+ }
+
+ i += 1;
+ }
+
+ gtk_filter_changed (GTK_FILTER (self->filter), GTK_FILTER_CHANGE_DIFFERENT);
+}
+
+static void
+schedule_update (NautilusTemplatesDialog *self)
+{
+ if (self->update_timeout_id == 0)
+ {
+ self->update_timeout_id
+ = g_timeout_add (100, update_timeout_callback, self);
+ }
+}
+
+NautilusFile *
+nautilus_templates_dialog_get_selected_file (NautilusTemplatesDialog *self)
+{
+ GtkTreeExpander *expander;
+ GtkListBoxRow *row;
+ NautilusFile *file = NULL;
+
+ row = gtk_list_box_get_selected_row (GTK_LIST_BOX (self->templates_list_box));
+
+ if (row != NULL)
+ {
+ expander = GTK_TREE_EXPANDER (gtk_list_box_row_get_child (row));
+ file = NAUTILUS_FILE (gtk_tree_expander_get_item (expander));
+ }
+
+ return file;
+}
+
+static void
+row_selected (GtkListBox *box,
+ GtkListBoxRow *row,
+ gpointer user_data)
+{
+ NautilusTemplatesDialog *self = NAUTILUS_TEMPLATES_DIALOG (user_data);
+
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (self), GTK_RESPONSE_OK, TRUE);
+}
+
NautilusTemplatesDialog *
nautilus_templates_dialog_new (GtkWindow *parent_window)
{
@@ -84,13 +612,46 @@ static void
nautilus_templates_dialog_class_init (NautilusTemplatesDialogClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->dispose = nautilus_templates_dialog_dispose;
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-templates-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusTemplatesDialog, templates_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTemplatesDialog, templates_list_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusTemplatesDialog, templates_search_entry);
gtk_widget_class_bind_template_callback (widget_class, nautilus_templates_dialog_import_template_dialog);
+ gtk_widget_class_bind_template_callback (widget_class, row_activated);
+ gtk_widget_class_bind_template_callback (widget_class, row_selected);
+ gtk_widget_class_bind_template_callback (widget_class, search_changed);
}
static void
nautilus_templates_dialog_init (NautilusTemplatesDialog *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->filter = gtk_custom_filter_new ((GtkCustomFilterFunc) match_function, self->templates_search_entry, NULL);
+ self->filter_model = gtk_filter_list_model_new (NULL, GTK_FILTER (self->filter));
+
+ /* Make templates model */
+ update_templates_menu (self);
+
+ gtk_list_box_bind_model (GTK_LIST_BOX (self->templates_list_box),
+ G_LIST_MODEL (self->filter_model),
+ (GtkListBoxCreateWidgetFunc) templates_list_box_widgets,
+ NULL,
+ NULL);
+
+ if (g_list_model_get_n_items (G_LIST_MODEL (self->templates_list_model)) == 0)
+ {
+ gtk_stack_set_visible_child_name (self->templates_stack, "no-templates");
+ }
+ else
+ {
+ gtk_stack_set_visible_child_name (self->templates_stack, "templates-list");
+ }
+
+ self->show_hidden_files = g_settings_get_boolean (gtk_filechooser_preferences, NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES);
}
diff --git a/src/nautilus-templates-dialog.h b/src/nautilus-templates-dialog.h
index a95857ad4..a0c34ecc5 100644
--- a/src/nautilus-templates-dialog.h
+++ b/src/nautilus-templates-dialog.h
@@ -21,6 +21,7 @@
#pragma once
#include <adwaita.h>
+#include "nautilus-types.h"
G_BEGIN_DECLS
@@ -30,4 +31,6 @@ G_DECLARE_FINAL_TYPE (NautilusTemplatesDialog, nautilus_templates_dialog, NAUTIL
NautilusTemplatesDialog * nautilus_templates_dialog_new (GtkWindow *parent_window);
+NautilusFile * nautilus_templates_dialog_get_selected_file (NautilusTemplatesDialog *self);
+
G_END_DECLS
diff --git a/src/resources/ui/nautilus-templates-dialog.ui b/src/resources/ui/nautilus-templates-dialog.ui
index 13e960a87..d378ef7c0 100644
--- a/src/resources/ui/nautilus-templates-dialog.ui
+++ b/src/resources/ui/nautilus-templates-dialog.ui
@@ -9,11 +9,12 @@
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
- <object class="GtkStack">
+ <object class="GtkStack" id="templates_stack">
<child>
<object class="GtkStackPage">
<property name="child">
<object class="AdwStatusPage">
+ <property name="name">no-templates</property>
<property name="icon-name">folder-templates-symbolic</property>
<property name="title" translatable="yes">No Templates Found</property>
<property name="description" translatable="yes">Add more templates by importing them from files.</property>
@@ -33,13 +34,41 @@
</child>
<child>
<object class="GtkStackPage">
+ <property name="name">templates-list</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkSearchEntry" id="templates_search_entry">
+ <property name="halign">center</property>
+ <property name="margin-top">24</property>
+ <property name="margin-bottom">12</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <signal name="search-changed" handler="search_changed"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="templates_list_box">
+ <property name="show-separators">1</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="margin-start">48</property>
+ <property name="margin-end">48</property>
+ <signal name="row-activated" handler="row_activated"/>
+ <signal name="row-selected" handler="row_selected"/>
+ <style>
+ <class name="boxed-list"/>
+ </style>
+ </object>
+ </child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Import</property>
<property name="halign">center</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">24</property>
<signal name="clicked" handler="nautilus_templates_dialog_import_template_dialog" swapped="yes"/>
<style>
<class name="pill"/>
@@ -70,4 +99,4 @@
<action-widget response="cancel">cancel_button</action-widget>
</action-widgets>
</template>
-</interface>
+</interface> \ No newline at end of file