diff options
author | utkarshvg2401 <utkarshvg2401@gmail.com> | 2022-07-18 21:23:11 +0530 |
---|---|---|
committer | António Fernandes <antoniof@gnome.org> | 2022-09-10 12:31:23 +0100 |
commit | be047e67dd7e2709b28e44fcaad2d24193eec16f (patch) | |
tree | 644bdf14a30c6f82384ad36d5471644f7e57893e | |
parent | 216d5c2dddda8c2a0182b6110a11214875d948ab (diff) | |
download | nautilus-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.c | 561 | ||||
-rw-r--r-- | src/nautilus-templates-dialog.h | 3 | ||||
-rw-r--r-- | src/resources/ui/nautilus-templates-dialog.ui | 33 |
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 |