diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index 247e166eb8..968080eb21 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -75,6 +75,7 @@ + diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index bc70b8bce1..fbd8ca4d78 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -1402,6 +1402,21 @@ GTK_DIRECTORY_LIST_GET_CLASS gtk_directory_list_get_type +
+gtkbookmarklist +GtkBookmarkList +GtkBookmarkList +gtk_bookmark_list_new +gtk_bookmark_list_get_filename +gtk_bookmark_list_set_attributes +gtk_bookmark_list_get_attributes +gtk_bookmark_list_set_io_priority +gtk_bookmark_list_get_io_priority +gtk_bookmark_list_is_loading + +GTK_TYPE_BOOKMARK_LIST +
+
gtkfilter GtkFilter diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index 472ec2846b..118d2bc42d 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -18,6 +18,7 @@ gtk_aspect_frame_get_type gtk_assistant_get_type gtk_assistant_page_get_type gtk_bin_layout_get_type +gtk_bookmark_list_get_type gtk_box_get_type gtk_box_layout_get_type gtk_buildable_get_type diff --git a/gtk/gtk.h b/gtk/gtk.h index 055957d5e9..ed9ee638cd 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkbookmarklist.c b/gtk/gtkbookmarklist.c new file mode 100644 index 0000000000..d342bf1f07 --- /dev/null +++ b/gtk/gtkbookmarklist.c @@ -0,0 +1,572 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Matthias Clasen + */ + +#include "config.h" + +#include "gtkbookmarklist.h" + +#include "gtksettings.h" +#include "gtkintl.h" +#include "gtkprivate.h" + +/** + * SECTION:gtkbookmarklist + * @title: GtkBookmarkList + * @short_description: A list model for recently used files + * @see_also: #GListModel, #GBookmarkFile + * + * #GtkBookmarkList is a list model that wraps GBookmarkFile. + * It presents a #GListModel and fills it asynchronously with the #GFileInfos + * returned from that function. + */ + +enum { + PROP_0, + PROP_FILENAME, + PROP_ATTRIBUTES, + PROP_IO_PRIORITY, + PROP_LOADING, + NUM_PROPERTIES +}; + +struct _GtkBookmarkList +{ + GObject parent_instance; + + char *attributes; + char *filename; + int io_priority; + int loading; + + GCancellable *cancellable; + GFileMonitor *monitor; + GBookmarkFile *file; + + GSequence *items; +}; + +struct _GtkBookmarkListClass +{ + GObjectClass parent_class; +}; + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +static GType +gtk_bookmark_list_get_item_type (GListModel *list) +{ + return G_TYPE_FILE_INFO; +} + +static guint +gtk_bookmark_list_get_n_items (GListModel *list) +{ + GtkBookmarkList *self = GTK_BOOKMARK_LIST (list); + + return g_sequence_get_length (self->items); +} + +static gpointer +gtk_bookmark_list_get_item (GListModel *list, + guint position) +{ + GtkBookmarkList *self = GTK_BOOKMARK_LIST (list); + GSequenceIter *iter; + + iter = g_sequence_get_iter_at_pos (self->items, position); + + if (g_sequence_iter_is_end (iter)) + return NULL; + else + return g_object_ref (g_sequence_get (iter)); +} + +static void +gtk_bookmark_list_model_init (GListModelInterface *iface) +{ + iface->get_item_type = gtk_bookmark_list_get_item_type; + iface->get_n_items = gtk_bookmark_list_get_n_items; + iface->get_item = gtk_bookmark_list_get_item; +} + +static void gtk_bookmark_list_start_loading (GtkBookmarkList *self); +static gboolean gtk_bookmark_list_stop_loading (GtkBookmarkList *self); +static void bookmark_file_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event, + gpointer data); +static void gtk_bookmark_list_set_filename (GtkBookmarkList *self, + const char *filename); + +G_DEFINE_TYPE_WITH_CODE (GtkBookmarkList, gtk_bookmark_list, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_bookmark_list_model_init)) + +static void +gtk_bookmark_list_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkBookmarkList *self = GTK_BOOKMARK_LIST (object); + + switch (prop_id) + { + case PROP_ATTRIBUTES: + gtk_bookmark_list_set_attributes (self, g_value_get_string (value)); + break; + + case PROP_IO_PRIORITY: + gtk_bookmark_list_set_io_priority (self, g_value_get_int (value)); + break; + + case PROP_FILENAME: + gtk_bookmark_list_set_filename (self, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_bookmark_list_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkBookmarkList *self = GTK_BOOKMARK_LIST (object); + + switch (prop_id) + { + case PROP_ATTRIBUTES: + g_value_set_string (value, self->attributes); + break; + + case PROP_IO_PRIORITY: + g_value_set_int (value, self->io_priority); + break; + + case PROP_FILENAME: + g_value_set_string (value, self->filename); + break; + + case PROP_LOADING: + g_value_set_boolean (value, gtk_bookmark_list_is_loading (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_bookmark_list_dispose (GObject *object) +{ + GtkBookmarkList *self = GTK_BOOKMARK_LIST (object); + + gtk_bookmark_list_stop_loading (self); + + g_clear_pointer (&self->attributes, g_free); + g_clear_pointer (&self->filename, g_free); + g_clear_pointer (&self->items, g_sequence_free); + g_clear_pointer (&self->file, g_bookmark_file_free); + + g_signal_handlers_disconnect_by_func (self->monitor, G_CALLBACK (bookmark_file_changed), self); + g_clear_object (&self->monitor); + + G_OBJECT_CLASS (gtk_bookmark_list_parent_class)->dispose (object); +} + +static void +gtk_bookmark_list_class_init (GtkBookmarkListClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->set_property = gtk_bookmark_list_set_property; + gobject_class->get_property = gtk_bookmark_list_get_property; + gobject_class->dispose = gtk_bookmark_list_dispose; + + properties[PROP_FILENAME] = + g_param_spec_string ("filename", + P_("Filename"), + P_("Bookmark file to load"), + NULL, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY); + /** + * GtkBookmarkList:attributes: + * + * The attributes to query + */ + properties[PROP_ATTRIBUTES] = + g_param_spec_string ("attributes", + P_("Attributes"), + P_("Attributes to query"), + NULL, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkBookmarkList:io-priority: + * + * Priority used when loading + */ + properties[PROP_IO_PRIORITY] = + g_param_spec_int ("io-priority", + P_("IO priority"), + P_("Priority used when loading"), + -G_MAXINT, G_MAXINT, G_PRIORITY_DEFAULT, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkBookmarkList:loading: + * + * %TRUE if files are being loaded + */ + properties[PROP_LOADING] = + g_param_spec_boolean ("loading", + P_("loading"), + P_("TRUE if files are being loaded"), + FALSE, + GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties); +} + +static void +gtk_bookmark_list_init (GtkBookmarkList *self) +{ + self->items = g_sequence_new (g_object_unref); + self->io_priority = G_PRIORITY_DEFAULT; + self->file = g_bookmark_file_new (); +} + +static gboolean +gtk_bookmark_list_stop_loading (GtkBookmarkList *self) +{ + if (self->cancellable == NULL) + return FALSE; + + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + + self->loading = 0; + + return TRUE; +} + +static void +got_file_info (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + GtkBookmarkList *self = user_data; + GFile *file = G_FILE (source); + GFileInfo *info; + + info = g_file_query_info_finish (file, res, NULL); + if (info) + { + char *uri; + gboolean is_private; + char **apps; + + uri = g_file_get_uri (file); + is_private = g_bookmark_file_get_is_private (self->file, uri, NULL); + apps = g_bookmark_file_get_applications (self->file, uri, NULL, NULL); + + g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file)); + g_file_info_set_attribute_boolean (info, "recent::private", is_private); + g_file_info_set_attribute_stringv (info, "recent::applications", apps); + + g_strfreev (apps); + + g_sequence_append (self->items, info); + g_list_model_items_changed (G_LIST_MODEL (self), g_sequence_get_length (self->items) - 1, 0, 1); + + g_free (uri); + } + + self->loading--; + + if (self->loading == 0) + { + g_clear_object (&self->cancellable); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]); + } +} + +static void +gtk_bookmark_list_clear_items (GtkBookmarkList *self) +{ + guint n_items; + + n_items = g_sequence_get_length (self->items); + if (n_items > 0) + { + g_sequence_remove_range (g_sequence_get_begin_iter (self->items), + g_sequence_get_end_iter (self->items)); + + g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, 0); + } +} + +static void +gtk_bookmark_list_start_loading (GtkBookmarkList *self) +{ + gboolean was_loading; + GError *error = NULL; + + was_loading = gtk_bookmark_list_stop_loading (self); + gtk_bookmark_list_clear_items (self); + + if (g_bookmark_file_load_from_file (self->file, self->filename, &error)) + { + char **uris; + gsize len; + int i; + + uris = g_bookmark_file_get_uris (self->file, &len); + if (len > 0) + { + self->cancellable = g_cancellable_new (); + self->loading = len; + } + + for (i = 0; i < len; i++) + { + const gchar *uri = uris[i]; + GFile *file; + + /* add this item */ + file = g_file_new_for_uri (uri); + g_file_query_info_async (file, + self->attributes, + 0, + self->io_priority, + self->cancellable, + got_file_info, + self); + g_object_unref (file); + } + } + else + { + if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) + g_warning ("Failed to load %s: %s", self->filename, error->message); + g_clear_error (&error); + } + + if (was_loading != (self->cancellable != NULL)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]); +} + +static void +bookmark_file_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer data) +{ + GtkBookmarkList *self = data; + + switch (event_type) + { + case G_FILE_MONITOR_EVENT_CHANGED: + case G_FILE_MONITOR_EVENT_CREATED: + case G_FILE_MONITOR_EVENT_DELETED: + gtk_bookmark_list_start_loading (self); + break; + + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: + case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: + case G_FILE_MONITOR_EVENT_UNMOUNTED: + case G_FILE_MONITOR_EVENT_MOVED: + case G_FILE_MONITOR_EVENT_RENAMED: + case G_FILE_MONITOR_EVENT_MOVED_IN: + case G_FILE_MONITOR_EVENT_MOVED_OUT: + + default: + break; + } +} + +static void +gtk_bookmark_list_set_filename (GtkBookmarkList *self, + const char *filename) +{ + GFile *file; + + if (filename) + self->filename = g_strdup (filename); + else + self->filename = g_build_filename (g_get_user_data_dir (), "recently-used.xbel", NULL); + + file = g_file_new_for_path (self->filename); + self->monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL); + g_signal_connect (self->monitor, "changed", + G_CALLBACK (bookmark_file_changed), self); + g_object_unref (file); + + gtk_bookmark_list_start_loading (self); +} + +/** + * gtk_bookmark_list_get_filename: + * @self: a #GtkBookmarkList + * + * Returns the filename of the bookmark file that + * this list is loading. + * + * Returns: the filename of the .xbel file + */ +const char * +gtk_bookmark_list_get_filename (GtkBookmarkList *self) +{ + g_return_val_if_fail (GTK_IS_BOOKMARK_LIST (self), NULL); + + return self->filename; +} + +/** + * gtk_bookmark_list_new: + * @filename: (allow-none): The bookmark file to load + * @attributes: (allow-none): The attributes to query + * + * Creates a new #GtkBookmarkList with the given @attributes. + * + * Returns: a new #GtkBookmarkList + **/ +GtkBookmarkList * +gtk_bookmark_list_new (const char *filename, + const char *attributes) +{ + return g_object_new (GTK_TYPE_BOOKMARK_LIST, + "filename", filename, + "attributes", attributes, + NULL); +} + +/** + * gtk_bookmark_list_set_attributes: + * @self: a #GtkBookmarkList + * @attributes: (allow-none): the attributes to enumerate + * + * Sets the @attributes to be enumerated and starts the enumeration. + * + * If @attributes is %NULL, no attributes will be queried, but a list + * of #GFileInfos will still be created. + */ +void +gtk_bookmark_list_set_attributes (GtkBookmarkList *self, + const char *attributes) +{ + g_return_if_fail (GTK_IS_BOOKMARK_LIST (self)); + + if (g_strcmp0 (self->attributes, attributes) == 0) + return; + + g_object_freeze_notify (G_OBJECT (self)); + + g_free (self->attributes); + self->attributes = g_strdup (attributes); + + gtk_bookmark_list_start_loading (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ATTRIBUTES]); + + g_object_thaw_notify (G_OBJECT (self)); +} + +/** + * gtk_bookmark_list_get_attributes: + * @self: a #GtkBookmarkList + * + * Gets the attributes queried on the children. + * + * Returns: (nullable) (transfer none): The queried attributes + */ +const char * +gtk_bookmark_list_get_attributes (GtkBookmarkList *self) +{ + g_return_val_if_fail (GTK_IS_BOOKMARK_LIST (self), NULL); + + return self->attributes; +} + +/** + * gtk_bookmark_list_set_io_priority: + * @self: a #GtkBookmarkList + * @io_priority: IO priority to use + * + * Sets the IO priority to use while loading files. + * + * The default IO priority is %G_PRIORITY_DEFAULT. + */ +void +gtk_bookmark_list_set_io_priority (GtkBookmarkList *self, + int io_priority) +{ + g_return_if_fail (GTK_IS_BOOKMARK_LIST (self)); + + if (self->io_priority == io_priority) + return; + + self->io_priority = io_priority; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_IO_PRIORITY]); +} + +/** + * gtk_bookmark_list_get_io_priority: + * @self: a #GtkBookmarkList + * + * Gets the IO priority set via gtk_bookmark_list_set_io_priority(). + * + * Returns: The IO priority. + */ +int +gtk_bookmark_list_get_io_priority (GtkBookmarkList *self) +{ + g_return_val_if_fail (GTK_IS_BOOKMARK_LIST (self), G_PRIORITY_DEFAULT); + + return self->io_priority; +} + +/** + * gtk_bookmark_list_is_loading: + * @self: a #GtkBookmarkList + * + * Returns %TRUE if the files are currently being loaded. + * + * Files will be added to @self from time to time while loading is + * going on. The order in which are added is undefined and may change + * inbetween runs. + * + * Returns: %TRUE if @self is loading + */ +gboolean +gtk_bookmark_list_is_loading (GtkBookmarkList *self) +{ + g_return_val_if_fail (GTK_IS_BOOKMARK_LIST (self), FALSE); + + return self->cancellable != NULL; +} diff --git a/gtk/gtkbookmarklist.h b/gtk/gtkbookmarklist.h new file mode 100644 index 0000000000..78c48a5f69 --- /dev/null +++ b/gtk/gtkbookmarklist.h @@ -0,0 +1,64 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Matthias Clasen + */ + +#ifndef __GTK_BOOKMARK_LIST_H__ +#define __GTK_BOOKMARK_LIST_H__ + + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +/* for GDK_AVAILABLE_IN_ALL */ +#include + + +G_BEGIN_DECLS + +#define GTK_TYPE_BOOKMARK_LIST (gtk_bookmark_list_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkBookmarkList, gtk_bookmark_list, GTK, BOOKMARK_LIST, GObject) + +GDK_AVAILABLE_IN_ALL +GtkBookmarkList * gtk_bookmark_list_new (const char *filename, + const char *attributes); + +GDK_AVAILABLE_IN_ALL +const char * gtk_bookmark_list_get_filename (GtkBookmarkList *self); + +GDK_AVAILABLE_IN_ALL +void gtk_bookmark_list_set_attributes (GtkBookmarkList *self, + const char *attributes); +GDK_AVAILABLE_IN_ALL +const char * gtk_bookmark_list_get_attributes (GtkBookmarkList *self); + +GDK_AVAILABLE_IN_ALL +void gtk_bookmark_list_set_io_priority (GtkBookmarkList *self, + int io_priority); +GDK_AVAILABLE_IN_ALL +int gtk_bookmark_list_get_io_priority (GtkBookmarkList *self); + +GDK_AVAILABLE_IN_ALL +gboolean gtk_bookmark_list_is_loading (GtkBookmarkList *self); + +G_END_DECLS + +#endif /* __GTK_BOOKMARK_LIST_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index 4f02dbc4c7..3bf6baef7e 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -164,6 +164,7 @@ gtk_public_sources = files([ 'gtkaspectframe.c', 'gtkassistant.c', 'gtkbinlayout.c', + 'gtkbookmarklist.c', 'gtkborder.c', 'gtkboxlayout.c', 'gtkbox.c', @@ -448,6 +449,7 @@ gtk_public_headers = files([ 'gtkaspectframe.h', 'gtkassistant.h', 'gtkbinlayout.h', + 'gtkbookmarklist.h', 'gtkborder.h', 'gtkbox.h', 'gtkboxlayout.h', diff --git a/testsuite/gtk/defaultvalue.c b/testsuite/gtk/defaultvalue.c index 79fa0050e4..ee59c32851 100644 --- a/testsuite/gtk/defaultvalue.c +++ b/testsuite/gtk/defaultvalue.c @@ -420,6 +420,11 @@ G_GNUC_END_IGNORE_DEPRECATIONS strcmp (pspec->name, "factory") == 0) continue; + if (g_type_is_a (type, GTK_TYPE_BOOKMARK_LIST) && + (strcmp (pspec->name, "filename") == 0 || + strcmp (pspec->name, "loading") == 0)) + continue; + /* All the icontheme properties depend on the environment */ if (g_type_is_a (type, GTK_TYPE_ICON_THEME)) continue;