From 5d8e439bc597159e3c9f0a8b65c0ae869dead3a8 Mon Sep 17 00:00:00 2001 From: Matthew Fennell Date: Sat, 27 Dec 2025 12:40:20 +0000 Subject: Import Upstream version 43.0 --- src/core/gtd-manager.c | 933 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 933 insertions(+) create mode 100644 src/core/gtd-manager.c (limited to 'src/core/gtd-manager.c') diff --git a/src/core/gtd-manager.c b/src/core/gtd-manager.c new file mode 100644 index 0000000..b499b76 --- /dev/null +++ b/src/core/gtd-manager.c @@ -0,0 +1,933 @@ +/* gtd-manager.c + * + * Copyright (C) 2015-2020 Georges Basile Stavracas Neto + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define G_LOG_DOMAIN "GtdManager" + +#include "models/gtd-list-store.h" +#include "models/gtd-task-model-private.h" +#include "gtd-clock.h" +#include "gtd-debug.h" +#include "gtd-manager.h" +#include "gtd-manager-protected.h" +#include "gtd-notification.h" +#include "gtd-panel.h" +#include "gtd-plugin-manager.h" +#include "gtd-provider.h" +#include "gtd-task.h" +#include "gtd-task-list.h" +#include "gtd-utils.h" +#include "gtd-workspace.h" + +#include + +/** + * SECTION:gtd-manager + * @short_description:bridge between plugins and Endeavour + * @title:GtdManager + * @stability:Unstable + * @see_also:#GtdNotification,#GtdActivatable + * + * The #GtdManager object is a singleton object that exposes all the data + * inside the plugin to Endeavour, and vice-versa. From here, plugins have + * access to all the tasklists, tasks and panels of the other plugins. + * + * Objects can use gtd_manager_emit_error_message() to send errors to + * Endeavour. This will create a #GtdNotification internally. + */ + +struct _GtdManager +{ + GtdObject parent; + + GSettings *settings; + GtdPluginManager *plugin_manager; + + GListModel *inbox_model; + GListModel *lists_model; + GListModel *providers_model; + GListModel *tasks_model; + GListModel *unarchived_tasks_model; + + GList *providers; + GtdProvider *default_provider; + GtdClock *clock; + + GCancellable *cancellable; +}; + +G_DEFINE_TYPE (GtdManager, gtd_manager, GTD_TYPE_OBJECT) + +/* Singleton instance */ +static GtdManager *gtd_manager_instance = NULL; + +enum +{ + LIST_ADDED, + LIST_CHANGED, + LIST_REMOVED, + SHOW_ERROR_MESSAGE, + SHOW_NOTIFICATION, + PROVIDER_ADDED, + PROVIDER_REMOVED, + NUM_SIGNALS +}; + +enum +{ + PROP_0, + PROP_DEFAULT_PROVIDER, + PROP_CLOCK, + PROP_PLUGIN_MANAGER, + LAST_PROP +}; + +static guint signals[NUM_SIGNALS] = { 0, }; + + +/* + * Auxiliary methods + */ + +static void +check_provider_is_default (GtdManager *self, + GtdProvider *provider) +{ + g_autofree gchar *default_provider = NULL; + + default_provider = g_settings_get_string (self->settings, "default-provider"); + + if (g_strcmp0 (default_provider, gtd_provider_get_id (provider)) == 0) + gtd_manager_set_default_provider (self, provider); +} + + +/* + * Callbacks + */ + +static gboolean +filter_archived_lists_func (gpointer item, + gpointer user_data) +{ + GtdTaskList *list; + GtdTask *task; + + task = (GtdTask*) item; + list = gtd_task_get_list (task); + + return !gtd_task_list_get_archived (list); +} + +static gboolean +filter_inbox_cb (gpointer item, + gpointer user_data) +{ + GtdProvider *provider = gtd_task_list_get_provider (item); + + return gtd_provider_get_inbox (provider) == item; +} + +static gint +compare_lists_cb (GtdTaskList *list_a, + GtdTaskList *list_b, + gpointer user_data) +{ + gint result; + + /* First, compare by their providers */ + result = gtd_provider_compare (gtd_task_list_get_provider (list_a), gtd_task_list_get_provider (list_b)); + + if (result != 0) + return result; + + return gtd_collate_compare_strings (gtd_task_list_get_name (list_a), gtd_task_list_get_name (list_b)); +} + +static void +on_task_list_modified_cb (GtdTaskList *list, + GtdTask *task, + GtdManager *self) +{ + GTD_ENTRY; + g_signal_emit (self, signals[LIST_CHANGED], 0, list); + GTD_EXIT; +} + +static void +on_list_added_cb (GtdProvider *provider, + GtdTaskList *list, + GtdManager *self) +{ + GTD_ENTRY; + + gtd_list_store_insert_sorted (GTD_LIST_STORE (self->lists_model), + list, + (GCompareDataFunc) compare_lists_cb, + self); + + g_signal_connect (list, + "task-added", + G_CALLBACK (on_task_list_modified_cb), + self); + + g_signal_connect (list, + "task-updated", + G_CALLBACK (on_task_list_modified_cb), + self); + + g_signal_connect (list, + "task-removed", + G_CALLBACK (on_task_list_modified_cb), + self); + + g_signal_emit (self, signals[LIST_ADDED], 0, list); + + GTD_EXIT; +} + +static void +on_list_changed_cb (GtdProvider *provider, + GtdTaskList *list, + GtdManager *self) +{ + GtkFilter *filter; + + GTD_ENTRY; + + gtd_list_store_sort (GTD_LIST_STORE (self->lists_model), + (GCompareDataFunc) compare_lists_cb, + self); + + filter = gtk_filter_list_model_get_filter (GTK_FILTER_LIST_MODEL (self->unarchived_tasks_model)); + gtk_filter_changed (filter, GTK_FILTER_CHANGE_DIFFERENT); + + g_signal_emit (self, signals[LIST_CHANGED], 0, list); + + GTD_EXIT; +} + +static void +on_list_removed_cb (GtdProvider *provider, + GtdTaskList *list, + GtdManager *self) +{ + GTD_ENTRY; + + if (!list) + GTD_RETURN (); + + gtd_list_store_remove (GTD_LIST_STORE (self->lists_model), list); + + g_signal_handlers_disconnect_by_func (list, + on_task_list_modified_cb, + self); + + g_signal_emit (self, signals[LIST_REMOVED], 0, list); + + GTD_EXIT; +} + + +/* + * GObject overrides + */ + +static void +gtd_manager_finalize (GObject *object) +{ + GtdManager *self = (GtdManager *)object; + + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + g_clear_object (&self->plugin_manager); + g_clear_object (&self->settings); + g_clear_object (&self->clock); + g_clear_object (&self->unarchived_tasks_model); + g_clear_object (&self->lists_model); + g_clear_object (&self->inbox_model); + + G_OBJECT_CLASS (gtd_manager_parent_class)->finalize (object); +} + +static void +gtd_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtdManager *self = (GtdManager *) object; + + switch (prop_id) + { + case PROP_DEFAULT_PROVIDER: + g_value_set_object (value, self->default_provider); + break; + + case PROP_CLOCK: + g_value_set_object (value, self->clock); + break; + + case PROP_PLUGIN_MANAGER: + g_value_set_object (value, self->plugin_manager); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtd_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtdManager *self = (GtdManager *) object; + + switch (prop_id) + { + case PROP_DEFAULT_PROVIDER: + if (g_set_object (&self->default_provider, g_value_get_object (value))) + g_object_notify (object, "default-provider"); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtd_manager_class_init (GtdManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gtd_manager_finalize; + object_class->get_property = gtd_manager_get_property; + object_class->set_property = gtd_manager_set_property; + + /** + * GtdManager::default-provider: + * + * The default provider. + */ + g_object_class_install_property ( + object_class, + PROP_DEFAULT_PROVIDER, + g_param_spec_object ("default-provider", + "The default provider of the application", + "The default provider of the application", + GTD_TYPE_PROVIDER, + G_PARAM_READWRITE)); + + /** + * GtdManager::clock: + * + * The underlying clock of Endeavour. + */ + g_object_class_install_property ( + object_class, + PROP_CLOCK, + g_param_spec_object ("clock", + "The clock", + "The clock of the application", + GTD_TYPE_CLOCK, + G_PARAM_READABLE)); + + /** + * GtdManager::plugin-manager: + * + * The plugin manager. + */ + g_object_class_install_property ( + object_class, + PROP_PLUGIN_MANAGER, + g_param_spec_object ("plugin-manager", + "The plugin manager", + "The plugin manager of the application", + GTD_TYPE_PLUGIN_MANAGER, + G_PARAM_READABLE)); + + /** + * GtdManager::list-added: + * @manager: a #GtdManager + * @list: a #GtdTaskList + * + * The ::list-added signal is emmited after a #GtdTaskList + * is connected. + */ + signals[LIST_ADDED] = g_signal_new ("list-added", + GTD_TYPE_MANAGER, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + GTD_TYPE_TASK_LIST); + + /** + * GtdManager::list-changed: + * @manager: a #GtdManager + * @list: a #GtdTaskList + * + * The ::list-changed signal is emmited after a #GtdTaskList + * has any of it's properties changed. + */ + signals[LIST_CHANGED] = g_signal_new ("list-changed", + GTD_TYPE_MANAGER, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + GTD_TYPE_TASK_LIST); + + /** + * GtdManager::list-removed: + * @manager: a #GtdManager + * @list: a #GtdTaskList + * + * The ::list-removed signal is emmited after a #GtdTaskList + * is disconnected. + */ + signals[LIST_REMOVED] = g_signal_new ("list-removed", + GTD_TYPE_MANAGER, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + GTD_TYPE_TASK_LIST); + + /** + * GtdManager::show-error-message: + * @manager: a #GtdManager + * @primary_text: the primary message + * @secondary_text: the detailed explanation of the error or the text to the notification button. + * @action : optionally action of type GtdNotificationActionFunc ignored if it's null. + * @user_data : user data passed to the action. + * + * Notifies about errors, and sends the error message for widgets + * to display. + */ + signals[SHOW_ERROR_MESSAGE] = g_signal_new ("show-error-message", + GTD_TYPE_MANAGER, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 4, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_POINTER, + G_TYPE_POINTER); + + /** + * GtdManager::show-notification: + * @manager: a #GtdManager + * @notification: the #GtdNotification + * + * Sends a notification. + */ + signals[SHOW_NOTIFICATION] = g_signal_new ("show-notification", + GTD_TYPE_MANAGER, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + GTD_TYPE_NOTIFICATION); + + /** + * GtdManager::provider-added: + * @manager: a #GtdManager + * @provider: a #GtdProvider + * + * The ::provider-added signal is emmited after a #GtdProvider + * is added. + */ + signals[PROVIDER_ADDED] = g_signal_new ("provider-added", + GTD_TYPE_MANAGER, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + GTD_TYPE_PROVIDER); + + /** + * GtdManager::provider-removed: + * @manager: a #GtdManager + * @provider: a #GtdProvider + * + * The ::provider-removed signal is emmited after a #GtdProvider + * is removed from the list. + */ + signals[PROVIDER_REMOVED] = g_signal_new ("provider-removed", + GTD_TYPE_MANAGER, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + GTD_TYPE_PROVIDER); +} + + +static void +gtd_manager_init (GtdManager *self) +{ + GtkCustomFilter *archived_lists_filter; + GtkCustomFilter *inbox_filter; + + inbox_filter = gtk_custom_filter_new (filter_inbox_cb, self, NULL); + archived_lists_filter = gtk_custom_filter_new (filter_archived_lists_func, self, NULL); + + self->settings = g_settings_new ("org.gnome.todo"); + self->plugin_manager = gtd_plugin_manager_new (); + self->clock = gtd_clock_new (); + self->cancellable = g_cancellable_new (); + self->lists_model = (GListModel*) gtd_list_store_new (GTD_TYPE_TASK_LIST); + self->inbox_model = (GListModel*) gtk_filter_list_model_new (self->lists_model, + GTK_FILTER (inbox_filter)); + self->tasks_model = (GListModel*) _gtd_task_model_new (self); + self->unarchived_tasks_model = (GListModel*) gtk_filter_list_model_new (self->tasks_model, + GTK_FILTER (archived_lists_filter)); + self->providers_model = (GListModel*) gtd_list_store_new (GTD_TYPE_PROVIDER); +} + +/** + * gtd_manager_get_default: + * + * Retrieves the singleton #GtdManager instance. You should always + * use this function instead of @gtd_manager_new. + * + * Returns: (transfer none): the singleton #GtdManager instance. + */ +GtdManager* +gtd_manager_get_default (void) +{ + if (!gtd_manager_instance) + gtd_manager_instance = gtd_manager_new (); + + return gtd_manager_instance; +} + +GtdManager* +gtd_manager_new (void) +{ + return g_object_new (GTD_TYPE_MANAGER, NULL); +} + +/** + * gtd_manager_get_providers: + * @manager: a #GtdManager + * + * Retrieves the list of available #GtdProvider. + * + * Returns: (transfer container) (element-type Gtd.Provider): a newly allocated #GList of + * #GtdStorage. Free with @g_list_free after use. + */ +GList* +gtd_manager_get_providers (GtdManager *self) +{ + g_return_val_if_fail (GTD_IS_MANAGER (self), NULL); + + return g_list_copy (self->providers); +} + +/** + * gtd_manager_add_provider: + * @self: a #GtdManager + * @provider: a #GtdProvider + * + * Adds @provider to the list of providers. + */ +void +gtd_manager_add_provider (GtdManager *self, + GtdProvider *provider) +{ + g_autoptr (GList) lists = NULL; + GList *l; + + g_return_if_fail (GTD_IS_MANAGER (self)); + g_return_if_fail (GTD_IS_PROVIDER (provider)); + + GTD_ENTRY; + + self->providers = g_list_prepend (self->providers, provider); + gtd_list_store_append (GTD_LIST_STORE (self->providers_model), provider); + + /* Add lists */ + lists = gtd_provider_get_task_lists (provider); + + for (l = lists; l != NULL; l = l->next) + on_list_added_cb (provider, l->data, self); + + g_object_connect (provider, + "signal::list-added", G_CALLBACK (on_list_added_cb), self, + "signal::list-changed", G_CALLBACK (on_list_changed_cb), self, + "signal::list-removed", G_CALLBACK (on_list_removed_cb), self, + NULL); + + /* If we just added the default provider, update the property */ + check_provider_is_default (self, provider); + + g_signal_emit (self, signals[PROVIDER_ADDED], 0, provider); + + GTD_EXIT; +} + +/** + * gtd_manager_remove_provider: + * @self: a #GtdManager + * @provider: a #GtdProvider + * + * Removes @provider from the list of providers. + */ +void +gtd_manager_remove_provider (GtdManager *self, + GtdProvider *provider) +{ + g_autoptr (GList) lists = NULL; + GList *l; + + g_return_if_fail (GTD_IS_MANAGER (self)); + g_return_if_fail (GTD_IS_PROVIDER (provider)); + + GTD_ENTRY; + + self->providers = g_list_remove (self->providers, provider); + gtd_list_store_remove (GTD_LIST_STORE (self->providers_model), provider); + + /* Remove lists */ + lists = gtd_provider_get_task_lists (provider); + + for (l = lists; l != NULL; l = l->next) + on_list_removed_cb (provider, l->data, self); + + g_signal_handlers_disconnect_by_func (provider, on_list_added_cb, self); + g_signal_handlers_disconnect_by_func (provider, on_list_changed_cb, self); + g_signal_handlers_disconnect_by_func (provider, on_list_removed_cb, self); + + g_signal_emit (self, signals[PROVIDER_REMOVED], 0, provider); + + GTD_EXIT; +} + + +/** + * gtd_manager_get_default_provider: + * @manager: a #GtdManager + * + * Retrieves the default provider location. Default is "local". + * + * Returns: (transfer none): the default provider. + */ +GtdProvider* +gtd_manager_get_default_provider (GtdManager *self) +{ + g_return_val_if_fail (GTD_IS_MANAGER (self), NULL); + + return self->default_provider; +} + +/** + * gtd_manager_set_default_provider: + * @manager: a #GtdManager + * @provider: (nullable): the default provider. + * + * Sets the provider. + */ +void +gtd_manager_set_default_provider (GtdManager *self, + GtdProvider *provider) +{ + g_return_if_fail (GTD_IS_MANAGER (self)); + + if (!g_set_object (&self->default_provider, provider)) + return; + + g_settings_set_string (self->settings, + "default-provider", + provider ? gtd_provider_get_id (provider) : "local"); + + g_object_notify (G_OBJECT (self), "default-provider"); +} + +/** + * gtd_manager_get_inbox: + * @self: a #GtdManager + * + * Retrieves the local inbox. + * + * Returns: (transfer none)(nullable): a #GtdTaskList + */ +GtdTaskList* +gtd_manager_get_inbox (GtdManager *self) +{ + GList *l; + + g_return_val_if_fail (GTD_IS_MANAGER (self), NULL); + + for (l = self->providers; l; l = l->next) + { + if (g_str_equal (gtd_provider_get_id (l->data), "local")) + return gtd_provider_get_inbox (l->data); + } + + return NULL; +} + +/** + * gtd_manager_get_settings: + * @manager: a #GtdManager + * + * Retrieves the internal #GSettings from @manager. + * + * Returns: (transfer none): the internal #GSettings of @manager + */ +GSettings* +gtd_manager_get_settings (GtdManager *self) +{ + g_return_val_if_fail (GTD_IS_MANAGER (self), NULL); + + return self->settings; +} + +/** + * gtd_manager_get_is_first_run: + * @manager: a #GtdManager + * + * Retrieves the 'first-run' setting. + * + * Returns: %TRUE if Endeavour was never run before, %FALSE otherwise. + */ +gboolean +gtd_manager_get_is_first_run (GtdManager *self) +{ + g_return_val_if_fail (GTD_IS_MANAGER (self), FALSE); + + return g_settings_get_boolean (self->settings, "first-run"); +} + +/** + * gtd_manager_set_is_first_run: + * @manager: a #GtdManager + * @is_first_run: %TRUE to make it first run, %FALSE otherwise. + * + * Sets the 'first-run' setting. + */ +void +gtd_manager_set_is_first_run (GtdManager *self, + gboolean is_first_run) +{ + g_return_if_fail (GTD_IS_MANAGER (self)); + + g_settings_set_boolean (self->settings, "first-run", is_first_run); +} + +/** + * gtd_manager_emit_error_message: + * @self: a #GtdManager + * @title: (nullable): the title of the error + * @description: (nullable): detailed description of the error + * @function: (scope call)(nullable): function to be called when the notification is dismissed + * @user_data: user data + * + * Reports an error. + */ +void +gtd_manager_emit_error_message (GtdManager *self, + const gchar *title, + const gchar *description, + GtdErrorActionFunc function, + gpointer user_data) +{ + g_return_if_fail (GTD_IS_MANAGER (self)); + + g_signal_emit (self, + signals[SHOW_ERROR_MESSAGE], + 0, + title, + description, + function, + user_data); +} + +/** + * gtd_manager_send_notification: + * @self: a #GtdManager + * @notification: a #GtdNotification + * + * Sends a notification to the notification system. + */ +void +gtd_manager_send_notification (GtdManager *self, + GtdNotification *notification) +{ + g_return_if_fail (GTD_IS_MANAGER (self)); + + g_signal_emit (self, signals[SHOW_NOTIFICATION], 0, notification); +} + +/** + * gtd_manager_get_clock: + * @self: a #GtdManager + * + * Retrieves the #GtdClock from @self. You can use the + * clock to know when your code should be updated. + * + * Returns: (transfer none): a #GtdClock + */ +GtdClock* +gtd_manager_get_clock (GtdManager *self) +{ + g_return_val_if_fail (GTD_IS_MANAGER (self), NULL); + + return self->clock; +} + +/** + * gtd_manager_get_task_lists_model: + * @self: a #GtdManager + * + * Retrieves the #GListModel containing #GtdTaskLists from + * @self. You can use the this model to bind to GtkListBox + * or other widgets. + * + * The model is sorted. + * + * Returns: (transfer none): a #GListModel + */ +GListModel* +gtd_manager_get_task_lists_model (GtdManager *self) +{ + g_return_val_if_fail (GTD_IS_MANAGER (self), NULL); + + return self->lists_model; +} + + +/** + * gtd_manager_get_all_tasks_model: + * @self: a #GtdManager + * + * Retrieves the #GListModel containing #GtdTasks from + * @self. You can use the this model to bind to GtkListBox + * or other widgets. + * + * The model is sorted. + * + * Returns: (transfer none): a #GListModel + */ +GListModel* +gtd_manager_get_all_tasks_model (GtdManager *self) +{ + g_return_val_if_fail (GTD_IS_MANAGER (self), NULL); + + return self->tasks_model; +} + +/** + * gtd_manager_get_tasks_model: + * @self: a #GtdManager + * + * Retrieves the #GListModel containing #GtdTasks from + * @self. You can use the this model to bind to GtkListBox + * or other widgets. + * + * The model returned by this function is filtered to only + * contain tasks from unarchived lists. If you need all tasks, + * see gtd_manager_get_all_tasks_model(). + * + * The model is sorted. + * + * Returns: (transfer none): a #GListModel + */ +GListModel* +gtd_manager_get_tasks_model (GtdManager *self) +{ + g_return_val_if_fail (GTD_IS_MANAGER (self), NULL); + + return self->unarchived_tasks_model; +} + +/** + * gtd_manager_get_inbox_model: + * @self: a #GtdManager + * + * Retrieves the #GListModel containing #GtdTaskLists that are + * inbox. + * + * Returns: (transfer none): a #GListModel + */ +GListModel* +gtd_manager_get_inbox_model (GtdManager *self) +{ + g_return_val_if_fail (GTD_IS_MANAGER (self), NULL); + + return self->inbox_model; +} + +/** + * gtd_manager_get_providers_model: + * @self: a #GtdManager + * + * Retrieves the #GListModel containing #GtdProviders. + * + * Returns: (transfer none): a #GListModel + */ +GListModel* +gtd_manager_get_providers_model (GtdManager *self) +{ + g_return_val_if_fail (GTD_IS_MANAGER (self), NULL); + + return self->providers_model; +} + +void +gtd_manager_load_plugins (GtdManager *self) +{ + GTD_ENTRY; + + gtd_plugin_manager_load_plugins (self->plugin_manager); + + GTD_EXIT; +} + +GtdPluginManager* +gtd_manager_get_plugin_manager (GtdManager *self) +{ + g_return_val_if_fail (GTD_IS_MANAGER (self), NULL); + + return self->plugin_manager; +} -- cgit v1.2.3