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-activatable.c | 129 +++++ src/core/gtd-activatable.h | 52 ++ src/core/gtd-clock.c | 292 ++++++++++ src/core/gtd-clock.h | 33 ++ src/core/gtd-log.c | 103 ++++ src/core/gtd-log.h | 27 + src/core/gtd-manager-protected.h | 29 + src/core/gtd-manager.c | 933 ++++++++++++++++++++++++++++++ src/core/gtd-manager.h | 86 +++ src/core/gtd-notification.c | 441 +++++++++++++++ src/core/gtd-notification.h | 68 +++ src/core/gtd-object.c | 313 ++++++++++ src/core/gtd-object.h | 54 ++ src/core/gtd-plugin-manager.c | 323 +++++++++++ src/core/gtd-plugin-manager.h | 45 ++ src/core/gtd-provider.c | 681 ++++++++++++++++++++++ src/core/gtd-provider.h | 208 +++++++ src/core/gtd-task-list.c | 1161 ++++++++++++++++++++++++++++++++++++++ src/core/gtd-task-list.h | 118 ++++ src/core/gtd-task.c | 993 ++++++++++++++++++++++++++++++++ src/core/gtd-task.h | 122 ++++ 21 files changed, 6211 insertions(+) create mode 100644 src/core/gtd-activatable.c create mode 100644 src/core/gtd-activatable.h create mode 100644 src/core/gtd-clock.c create mode 100644 src/core/gtd-clock.h create mode 100644 src/core/gtd-log.c create mode 100644 src/core/gtd-log.h create mode 100644 src/core/gtd-manager-protected.h create mode 100644 src/core/gtd-manager.c create mode 100644 src/core/gtd-manager.h create mode 100644 src/core/gtd-notification.c create mode 100644 src/core/gtd-notification.h create mode 100644 src/core/gtd-object.c create mode 100644 src/core/gtd-object.h create mode 100644 src/core/gtd-plugin-manager.c create mode 100644 src/core/gtd-plugin-manager.h create mode 100644 src/core/gtd-provider.c create mode 100644 src/core/gtd-provider.h create mode 100644 src/core/gtd-task-list.c create mode 100644 src/core/gtd-task-list.h create mode 100644 src/core/gtd-task.c create mode 100644 src/core/gtd-task.h (limited to 'src/core') diff --git a/src/core/gtd-activatable.c b/src/core/gtd-activatable.c new file mode 100644 index 0000000..fcfd60e --- /dev/null +++ b/src/core/gtd-activatable.c @@ -0,0 +1,129 @@ +/* gtd-activatable.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 "GtdActivatable" + +#include "gtd-activatable.h" +#include "gtd-panel.h" +#include "gtd-provider.h" + +/** + * SECTION:gtd-activatable + * @short_description:entry point for plugins + * @title:GtdActivatable + * @stability:Unstable + * + * The #GtdActivatable interface is the interface plugins must + * implement in order to be seen by Endeavour. + * + * When plugins are loaded, the gtd_activatable_activate() vfunc + * is called. Use this vfunc to load anything that depends on + * Endeavour. + * + * When plugins are unloaded, the gtd_activatable_deactivate() vfunc + * if called. Ideally, the implementation should undo everything that + * was done on gtd_activatable_activate(). + * + * A plugin implementation may expose one or more #GtdProvider instances, + * which are the data sources of Endeavour. See the 'eds' plugin for + * a reference on how to expose one ('local') and multiple (Online Accounts) + * providers. + * + * Plugins may also expose one or more #GtdPanel implementations. + * + * Optionally, a plugin may expose a preferences panel. See gtd_activatable_get_preferences_panel(). + */ + +G_DEFINE_INTERFACE (GtdActivatable, gtd_activatable, G_TYPE_OBJECT) + +static void +gtd_activatable_default_init (GtdActivatableInterface *iface) +{ + /** + * GtdActivatable::preferences-panel: + * + * The preferences panel of the plugin, or %NULL. + */ + g_object_interface_install_property (iface, + g_param_spec_object ("preferences-panel", + "Preferences panel", + "The preferences panel of the plugins", + GTK_TYPE_WIDGET, + G_PARAM_READABLE)); +} + +/** + * gtd_activatable_activate: + * @activatable: a #GtdActivatable + * + * Activates the extension. This is the starting point where + * the implementation does everything it needs to do. Avoid + * doing it earlier than this call. + * + * This function is called after the extension is loaded and + * the signals are connected. If you want to do anything before + * that, the _init function should be used instead. + */ +void +gtd_activatable_activate (GtdActivatable *activatable) +{ + g_return_if_fail (GTD_IS_ACTIVATABLE (activatable)); + + if (GTD_ACTIVATABLE_GET_IFACE (activatable)->activate) + GTD_ACTIVATABLE_GET_IFACE (activatable)->activate (activatable); +} + +/** + * gtd_activatable_deactivate: + * @activatable: a #GtdActivatable + * + * Deactivates the extension. Here, the extension should remove + * all providers and panels it set. + * + * This function is called before the extension is removed. At + * this point, the plugin manager already removed all providers + * and widgets this extension exported. If you want to do anything + * after the extension is removed, use GObject::finalize instead. + */ +void +gtd_activatable_deactivate (GtdActivatable *activatable) +{ + g_return_if_fail (GTD_IS_ACTIVATABLE (activatable)); + + if (GTD_ACTIVATABLE_GET_IFACE (activatable)->deactivate) + GTD_ACTIVATABLE_GET_IFACE (activatable)->deactivate (activatable); +} + +/** + * gtd_activatable_get_preferences_panel: + * @activatable: a #GtdActivatable + * + * Retrieve the preferences panel of @activatable if any. + * + * Returns: (transfer none)(nullable): a #GtkWidget, or %NULL + */ +GtkWidget* +gtd_activatable_get_preferences_panel (GtdActivatable *activatable) +{ + g_return_val_if_fail (GTD_IS_ACTIVATABLE (activatable), NULL); + + if (GTD_ACTIVATABLE_GET_IFACE (activatable)->get_preferences_panel) + return GTD_ACTIVATABLE_GET_IFACE (activatable)->get_preferences_panel (activatable); + + return NULL; +} diff --git a/src/core/gtd-activatable.h b/src/core/gtd-activatable.h new file mode 100644 index 0000000..2e26375 --- /dev/null +++ b/src/core/gtd-activatable.h @@ -0,0 +1,52 @@ +/* gtd-activatable.h + * + * 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 . + */ + +#ifndef GTD_ACTIVATABLE_H +#define GTD_ACTIVATABLE_H + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define GTD_TYPE_ACTIVATABLE (gtd_activatable_get_type ()) + +G_DECLARE_INTERFACE (GtdActivatable, gtd_activatable, GTD, ACTIVATABLE, GObject) + +struct _GtdActivatableInterface +{ + GTypeInterface parent; + + void (*activate) (GtdActivatable *activatable); + + void (*deactivate) (GtdActivatable *activatable); + + GtkWidget* (*get_preferences_panel) (GtdActivatable *activatable); +}; + +void gtd_activatable_activate (GtdActivatable *activatable); + +void gtd_activatable_deactivate (GtdActivatable *activatable); + +GtkWidget* gtd_activatable_get_preferences_panel (GtdActivatable *activatable); + +G_END_DECLS + +#endif /* GTD_ACTIVATABLE_H */ diff --git a/src/core/gtd-clock.c b/src/core/gtd-clock.c new file mode 100644 index 0000000..68263c6 --- /dev/null +++ b/src/core/gtd-clock.c @@ -0,0 +1,292 @@ +/* gtd-clock.c + * + * Copyright (C) 2017-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 "GtdClock" + +#include "gtd-clock.h" +#include "gtd-debug.h" + +#include + +struct _GtdClock +{ + GtdObject parent; + + guint timeout_id; + + GDateTime *current; + + GDBusProxy *logind; + GCancellable *cancellable; +}; + +static gboolean timeout_cb (gpointer user_data); + +G_DEFINE_TYPE (GtdClock, gtd_clock, GTD_TYPE_OBJECT) + +enum +{ + DAY_CHANGED, + HOUR_CHANGED, + MINUTE_CHANGED, + N_SIGNALS +}; + +static guint signals[N_SIGNALS] = { 0, }; + +/* + * Auxiliary methods + */ + +static void +update_current_date (GtdClock *self) +{ + g_autoptr (GDateTime) now = NULL; + gboolean minute_changed; + gboolean hour_changed; + gboolean day_changed; + + GTD_ENTRY; + + now = g_date_time_new_now_local (); + + day_changed = g_date_time_get_year (now) != g_date_time_get_year (self->current) || + g_date_time_get_day_of_year (now) != g_date_time_get_day_of_year (self->current); + hour_changed = day_changed || g_date_time_get_hour (now) != g_date_time_get_hour (self->current); + minute_changed = hour_changed || g_date_time_get_minute (now) != g_date_time_get_minute (self->current); + + if (day_changed) + g_signal_emit (self, signals[DAY_CHANGED], 0); + + if (hour_changed) + g_signal_emit (self, signals[HOUR_CHANGED], 0); + + if (minute_changed) + g_signal_emit (self, signals[MINUTE_CHANGED], 0); + + GTD_TRACE_MSG ("Ticking clock"); + + g_clear_pointer (&self->current, g_date_time_unref); + self->current = g_date_time_ref (now); + + GTD_EXIT; +} + +static void +schedule_update (GtdClock *self) +{ + g_autoptr (GDateTime) now = NULL; + guint seconds_between; + + /* Remove the previous timeout if we came from resume */ + if (self->timeout_id > 0) + { + g_source_remove (self->timeout_id); + self->timeout_id = 0; + } + + now = g_date_time_new_now_local (); + + seconds_between = 60 - g_date_time_get_second (now); + + self->timeout_id = g_timeout_add_seconds (seconds_between, timeout_cb, self); +} + +/* + * Callbacks + */ + +static void +logind_signal_received_cb (GDBusProxy *logind, + const gchar *sender, + const gchar *signal, + GVariant *params, + GtdClock *self) +{ + GVariant *child; + gboolean resuming; + + if (!g_str_equal (signal, "PrepareForSleep")) + return; + + child = g_variant_get_child_value (params, 0); + resuming = !g_variant_get_boolean (child); + + /* Only emit :update when resuming */ + if (resuming) + { + /* Reschedule the daily timeout */ + update_current_date (self); + schedule_update (self); + } + + g_clear_pointer (&child, g_variant_unref); +} + +static void +login_proxy_acquired_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr (GError) error = NULL; + GtdClock *self; + + self = GTD_CLOCK (user_data); + + gtd_object_pop_loading (GTD_OBJECT (self)); + + self->logind = g_dbus_proxy_new_for_bus_finish (res, &error); + + if (error) + { + g_warning ("Error acquiring org.freedesktop.login1: %s", error->message); + return; + } + + g_signal_connect (self->logind, + "g-signal", + G_CALLBACK (logind_signal_received_cb), + self); +} + +static gboolean +timeout_cb (gpointer user_data) +{ + GtdClock *self = user_data; + + self->timeout_id = 0; + + update_current_date (self); + schedule_update (self); + + return G_SOURCE_REMOVE; +} + + +/* + * GObject overrides + */ +static void +gtd_clock_finalize (GObject *object) +{ + GtdClock *self = (GtdClock *)object; + + g_cancellable_cancel (self->cancellable); + + if (self->timeout_id > 0) + { + g_source_remove (self->timeout_id); + self->timeout_id = 0; + } + + g_clear_pointer (&self->current, g_date_time_unref); + + g_clear_object (&self->cancellable); + g_clear_object (&self->logind); + + G_OBJECT_CLASS (gtd_clock_parent_class)->finalize (object); +} + +static void +gtd_clock_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +} + +static void +gtd_clock_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +} + +static void +gtd_clock_class_init (GtdClockClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gtd_clock_finalize; + object_class->get_property = gtd_clock_get_property; + object_class->set_property = gtd_clock_set_property; + + /** + * GtdClock:day-changed: + * + * Emited when the day changes. + */ + signals[DAY_CHANGED] = g_signal_new ("day-changed", + GTD_TYPE_CLOCK, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, + 0); + /** + * GtdClock:hour-changed: + * + * Emited when the current hour changes. + */ + signals[HOUR_CHANGED] = g_signal_new ("hour-changed", + GTD_TYPE_CLOCK, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, + 0); + /** + * GtdClock:minute-changed: + * + * Emited when the current minute changes. + */ + signals[MINUTE_CHANGED] = g_signal_new ("minute-changed", + GTD_TYPE_CLOCK, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, + 0); +} + +static void +gtd_clock_init (GtdClock *self) +{ + gtd_object_push_loading (GTD_OBJECT (self)); + + self->current = g_date_time_new_now_local (); + self->cancellable = g_cancellable_new (); + + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + self->cancellable, + login_proxy_acquired_cb, + self); + + schedule_update (self); +} + +GtdClock* +gtd_clock_new (void) +{ + return g_object_new (GTD_TYPE_CLOCK, NULL); +} diff --git a/src/core/gtd-clock.h b/src/core/gtd-clock.h new file mode 100644 index 0000000..ab09b0b --- /dev/null +++ b/src/core/gtd-clock.h @@ -0,0 +1,33 @@ +/* gtd-clock.h + * + * Copyright (C) 2017 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 . + */ + +#pragma once + +#include "gtd-object.h" + +#include + +G_BEGIN_DECLS + +#define GTD_TYPE_CLOCK (gtd_clock_get_type()) + +G_DECLARE_FINAL_TYPE (GtdClock, gtd_clock, GTD, CLOCK, GtdObject) + +GtdClock* gtd_clock_new (void); + +G_END_DECLS diff --git a/src/core/gtd-log.c b/src/core/gtd-log.c new file mode 100644 index 0000000..fc2645d --- /dev/null +++ b/src/core/gtd-log.c @@ -0,0 +1,103 @@ +/* gtd-log.c + * + * Copyright (C) 2018 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 . + */ + +#include "gtd-debug.h" +#include "gtd-log.h" + +#include +#include + +G_LOCK_DEFINE_STATIC (channel_lock); + +GIOChannel *standard_channel = NULL; + +static const gchar* ignored_domains[] = +{ + "GdkPixbuf", + NULL +}; + +static const gchar * +log_level_str (GLogLevelFlags log_level) +{ + switch (((gulong)log_level & G_LOG_LEVEL_MASK)) + { + case G_LOG_LEVEL_ERROR: return " \033[1;31mERROR\033[0m"; + case G_LOG_LEVEL_CRITICAL: return "\033[1;35mCRITICAL\033[0m"; + case G_LOG_LEVEL_WARNING: return " \033[1;33mWARNING\033[0m"; + case G_LOG_LEVEL_MESSAGE: return " \033[1;34mMESSAGE\033[0m"; + case G_LOG_LEVEL_INFO: return " \033[1;32mINFO\033[0m"; + case G_LOG_LEVEL_DEBUG: return " \033[1;32mDEBUG\033[0m"; + case GTD_LOG_LEVEL_TRACE: return " \033[1;36mTRACE\033[0m"; + + default: + return " UNKNOWN"; + } +} + +static void +gtd_log_handler (const gchar *domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) +{ + g_autoptr (GDateTime) now = NULL; + g_autofree gchar *buffer = NULL; + g_autofree gchar *ftime = NULL; + const gchar *level; + gint microsecond; + + /* Skip ignored log domains */ + if (domain && g_strv_contains (ignored_domains, domain)) + return; + + level = log_level_str (log_level); + now = g_date_time_new_now_local (); + ftime = g_date_time_format (now, "%H:%M:%S"); + microsecond = g_date_time_get_microsecond (now); + buffer = g_strdup_printf ("%s.%4.4d %28s: %s: %s\n", + ftime, + microsecond, + domain, + level, + message); + + /* Safely write to the channel */ + G_LOCK (channel_lock); + + g_io_channel_write_chars (standard_channel, buffer, -1, NULL, NULL); + g_io_channel_flush (standard_channel, NULL); + + G_UNLOCK (channel_lock); +} + +void +gtd_log_init (void) +{ + static gsize initialized = FALSE; + + if (g_once_init_enter (&initialized)) + { + standard_channel = g_io_channel_unix_new (STDOUT_FILENO); + + g_log_set_default_handler (gtd_log_handler, NULL); + + g_once_init_leave (&initialized, TRUE); + } +} + diff --git a/src/core/gtd-log.h b/src/core/gtd-log.h new file mode 100644 index 0000000..0ad53a8 --- /dev/null +++ b/src/core/gtd-log.h @@ -0,0 +1,27 @@ +/* gtd-log.h + * + * Copyright (C) 2017 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 . + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +void gtd_log_init (void); + +G_END_DECLS diff --git a/src/core/gtd-manager-protected.h b/src/core/gtd-manager-protected.h new file mode 100644 index 0000000..142419f --- /dev/null +++ b/src/core/gtd-manager-protected.h @@ -0,0 +1,29 @@ +/* gtd-manager-protected.h + * + * 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 . + */ + +#pragma once + +#include "gtd-types.h" + +G_BEGIN_DECLS + +void gtd_manager_load_plugins (GtdManager *manager); + +GtdPluginManager* gtd_manager_get_plugin_manager (GtdManager *manager); + +G_END_DECLS 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; +} diff --git a/src/core/gtd-manager.h b/src/core/gtd-manager.h new file mode 100644 index 0000000..68d661b --- /dev/null +++ b/src/core/gtd-manager.h @@ -0,0 +1,86 @@ +/* gtd-manager.h + * + * 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 . + */ + +#pragma once + +#include + +#include "gtd-object.h" +#include "gtd-types.h" + +G_BEGIN_DECLS + +#define GTD_TYPE_MANAGER (gtd_manager_get_type()) + +G_DECLARE_FINAL_TYPE (GtdManager, gtd_manager, GTD, MANAGER, GtdObject) + +/** + * GtdErrorActionFunc: + */ +typedef void (*GtdErrorActionFunc) (GtdNotification *notification, + gpointer user_data); + + +GtdManager* gtd_manager_new (void); + +GtdManager* gtd_manager_get_default (void); + +GList* gtd_manager_get_providers (GtdManager *manager); + +void gtd_manager_add_provider (GtdManager *self, + GtdProvider *provider); + +void gtd_manager_remove_provider (GtdManager *self, + GtdProvider *provider); + +GtdProvider* gtd_manager_get_default_provider (GtdManager *manager); + +void gtd_manager_set_default_provider (GtdManager *manager, + GtdProvider *provider); + +GtdTaskList* gtd_manager_get_inbox (GtdManager *self); + +GSettings* gtd_manager_get_settings (GtdManager *manager); + +gboolean gtd_manager_get_is_first_run (GtdManager *manager); + +void gtd_manager_set_is_first_run (GtdManager *manager, + gboolean is_first_run); + +void gtd_manager_emit_error_message (GtdManager *self, + const gchar *title, + const gchar *description, + GtdErrorActionFunc function, + gpointer user_data); + +void gtd_manager_send_notification (GtdManager *self, + GtdNotification *notification); + +GtdClock* gtd_manager_get_clock (GtdManager *self); + +GListModel* gtd_manager_get_task_lists_model (GtdManager *self); + +GListModel* gtd_manager_get_all_tasks_model (GtdManager *self); + +GListModel* gtd_manager_get_tasks_model (GtdManager *self); + +GListModel* gtd_manager_get_inbox_model (GtdManager *self); + +GListModel* gtd_manager_get_providers_model (GtdManager *self); + +G_END_DECLS diff --git a/src/core/gtd-notification.c b/src/core/gtd-notification.c new file mode 100644 index 0000000..eee3e46 --- /dev/null +++ b/src/core/gtd-notification.c @@ -0,0 +1,441 @@ +/* gtd-notification.c + * + * Copyright (C) 2015 Georges Basile Stavracas Neto + * Copyright (C) 2022 Jamie Murphy + * + * 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 "GtdNotification" + +#include "gtd-notification.h" +#include "gtd-object.h" + +#include + +/** + * SECTION: gtd-notification + * @short_description: An auxiliary class around #AdwToast + * @title: GtdNotification + * @stability: Stable + * + * The #GtdNotification represents a notification shown at the top of + * the window. It is an auxiliary class around #AdwToast, and is used only + * to store toast data and callbacks. + * + * A notification may have a dismissal action that is called when the toast + * is dismissed, whether by user interaction or a timeout + * + * Optionally, the notification may have a secondary action + * (see gtd_notification_set_secondary_action()), shown as a button. + * + * A user should not create a UI for a #GtdNotification and should instead + * pass it to a #GtdManager, via gtd_manager_send_notification() + * + * Example: + * |[ + * GtdNotification *notification; + * + * notification = gtd_notification_new ("Something happened!"); + * + * gtd_notification_set_dismissal_action (notification, + * called_when_notification_is_dismissed, + * self); + * + * gtd_notification_set_secondary_action (notification, + * "Details", + * show_details, + * self); + * [...] + * ]| + */ + +typedef struct +{ + gchar *text; + + GtdNotificationActionFunc dismissal_action; + gboolean has_dismissal_action; + gpointer dismissal_action_data; + + GtdNotificationActionFunc secondary_action; + gboolean has_secondary_action; + gpointer secondary_action_data; + gchar *secondary_action_name; +} GtdNotificationPrivate; + +struct _GtdNotification +{ + GtdObject parent; + + /*< private >*/ + GtdNotificationPrivate *priv; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GtdNotification, gtd_notification, GTD_TYPE_OBJECT) + +enum +{ + PROP_0, + PROP_HAS_DISMISSAL_ACTION, + PROP_HAS_SECONDARY_ACTION, + PROP_SECONDARY_ACTION_NAME, + PROP_TEXT, + LAST_PROP +}; + +enum +{ + EXECUTED, + NUM_SIGNALS +}; + +static guint signals[NUM_SIGNALS] = { 0, }; + +static void +gtd_notification_finalize (GObject *object) +{ + GtdNotification *self = (GtdNotification *)object; + GtdNotificationPrivate *priv = gtd_notification_get_instance_private (self); + + g_clear_pointer (&priv->secondary_action_name, g_free); + g_clear_pointer (&priv->text, g_free); + + G_OBJECT_CLASS (gtd_notification_parent_class)->finalize (object); +} + +static void +gtd_notification_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtdNotification *self = GTD_NOTIFICATION (object); + + switch (prop_id) + { + case PROP_HAS_DISMISSAL_ACTION: + g_value_set_boolean (value, self->priv->has_dismissal_action); + break; + + case PROP_HAS_SECONDARY_ACTION: + g_value_set_boolean (value, self->priv->has_secondary_action); + break; + + case PROP_SECONDARY_ACTION_NAME: + g_value_set_string (value, self->priv->secondary_action_name ? self->priv->secondary_action_name : ""); + break; + + case PROP_TEXT: + g_value_set_string (value, gtd_notification_get_text (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtd_notification_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtdNotification *self = GTD_NOTIFICATION (object); + + switch (prop_id) + { + case PROP_SECONDARY_ACTION_NAME: + gtd_notification_set_secondary_action (self, + g_value_get_string (value), + self->priv->secondary_action, + self->priv->secondary_action_data); + break; + + case PROP_TEXT: + gtd_notification_set_text (self, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtd_notification_class_init (GtdNotificationClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gtd_notification_finalize; + object_class->get_property = gtd_notification_get_property; + object_class->set_property = gtd_notification_set_property; + + /** + * GtdNotification::has-dismissal-action: + * + * @TRUE if the notification has a dismissal action or @FALSE otherwise. + */ + g_object_class_install_property ( + object_class, + PROP_HAS_DISMISSAL_ACTION, + g_param_spec_boolean ("has-dismissal-action", + "Whether the notification has a dismissal action", + "Whether the notification has the dismissal action.", + FALSE, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY)); + + /** + * GtdNotification::has-secondary-action: + * + * @TRUE if the notification has a secondary action or @FALSE otherwise. The + * secondary action is triggered only by user explicit input. + */ + g_object_class_install_property ( + object_class, + PROP_HAS_SECONDARY_ACTION, + g_param_spec_boolean ("has-secondary-action", + "Whether the notification has a secondary action", + "Whether the notification has the secondary action, activated by the user", + FALSE, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY)); + + /** + * GtdNotification::secondary-action-name: + * + * The main text of the notification, usually a markuped text. + */ + g_object_class_install_property ( + object_class, + PROP_SECONDARY_ACTION_NAME, + g_param_spec_string ("secondary-action-name", + "Text of the secondary action button", + "The text of the secondary action button", + "", + G_PARAM_READWRITE)); + + /** + * GtdNotification::text: + * + * The main text of the notification, usually a markuped text. + */ + g_object_class_install_property ( + object_class, + PROP_TEXT, + g_param_spec_string ("text", + "Notification message", + "The main message of the notification", + "", + G_PARAM_READWRITE)); + + /** + * GtdNotification::executed: + * + * The ::executed signal is emitted after the primary or secondary + * #GtdNotification action is executed. + */ + signals[EXECUTED] = g_signal_new ("executed", + GTD_TYPE_NOTIFICATION, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); +} + +static void +gtd_notification_init (GtdNotification *self) +{ + self->priv = gtd_notification_get_instance_private (self); + self->priv->secondary_action_name = NULL; + self->priv->text = NULL; +} + +/** + * gtd_notification_new: + * @text: (nullable): text of the notification + * + * Creates a new notification with @text. + * + * Returns: (transfer full): a new #GtdNotification + */ +GtdNotification* +gtd_notification_new (const gchar *text) +{ + return g_object_new (GTD_TYPE_NOTIFICATION, + "text", text, + NULL); +} + +/** + * gtd_notification_set_dismissal_action: + * @notification: a #GtdNotification + * @func: (closure user_data) (scope call) (nullable): the dismissal action function + * @user_data: data passed to @func + * + * Sets the dismissal action of @notification + */ +void +gtd_notification_set_dismissal_action (GtdNotification *notification, + GtdNotificationActionFunc func, + gpointer user_data) +{ + GtdNotificationPrivate *priv; + gboolean has_action; + + g_return_if_fail (GTD_IS_NOTIFICATION (notification)); + + priv = notification->priv; + has_action = (func != NULL); + + if (has_action != priv->has_dismissal_action) + { + priv->has_dismissal_action = has_action; + + priv->dismissal_action = has_action ? func : NULL; + priv->dismissal_action_data = has_action ? user_data : NULL; + + g_object_notify (G_OBJECT (notification), "has-dismissal-action"); + } +} + +/** + * gtd_notification_set_secondary_action: + * @notification: a #GtdNotification + * @name: the name of the secondary action + * @func: (closure user_data) (scope call) (nullable): the secondary action function + * @user_data: data passed to @func + * + * Sets the secondary action of @notification, which is triggered + * only on user explicit input. + */ +void +gtd_notification_set_secondary_action (GtdNotification *notification, + const gchar *name, + GtdNotificationActionFunc func, + gpointer user_data) +{ + GtdNotificationPrivate *priv; + gboolean has_action; + + g_return_if_fail (GTD_IS_NOTIFICATION (notification)); + + priv = notification->priv; + has_action = (func != NULL); + + if (has_action != priv->has_secondary_action) + { + priv->has_secondary_action = has_action; + + priv->secondary_action = has_action ? func : NULL; + priv->secondary_action_data = has_action ? user_data : NULL; + + if (priv->secondary_action_name != name) + { + g_clear_pointer (&priv->secondary_action_name, g_free); + priv->secondary_action_name = g_strdup (name); + + g_object_notify (G_OBJECT (notification), "secondary-action-name"); + } + + g_object_notify (G_OBJECT (notification), "has-secondary-action"); + } +} + +/** + * gtd_notification_get_text: + * @notification: a #GtdNotification + * + * Gets the text of @notification. + * + * Returns: (transfer none): the text of @notification. + */ +const gchar* +gtd_notification_get_text (GtdNotification *notification) +{ + g_return_val_if_fail (GTD_IS_NOTIFICATION (notification), NULL); + + return notification->priv->text ? notification->priv->text : ""; +} + +/** + * gtd_notification_set_text: + * @notification: a #GtdNotification + * @text: the user-visible text of @notification + * + * Sets the text of @notification to @text. + */ +void +gtd_notification_set_text (GtdNotification *notification, + const gchar *text) +{ + GtdNotificationPrivate *priv; + + g_return_if_fail (GTD_IS_NOTIFICATION (notification)); + + priv = notification->priv; + + if (g_strcmp0 (priv->text, text) != 0) + { + g_clear_pointer (&priv->text, g_free); + priv->text = g_strdup (text); + + g_object_notify (G_OBJECT (notification), "text"); + } +} + +/** + * gtd_notification_execute_dismissal_action: + * @notification: a #GtdNotification + * + * Executes the dismissal action of @notification if any. + */ +void +gtd_notification_execute_dismissal_action (GtdNotification *notification) +{ + GtdNotificationPrivate *priv; + + g_return_if_fail (GTD_IS_NOTIFICATION (notification)); + + priv = notification->priv; + + if (priv->dismissal_action) + priv->dismissal_action (notification, priv->dismissal_action_data); + + g_signal_emit (notification, signals[EXECUTED], 0); +} + +/** + * gtd_notification_execute_secondary_action: + * @notification: a #GtdNotification + * + * Executes the secondary action of @notification if any. + */ +void +gtd_notification_execute_secondary_action (GtdNotification *notification) +{ + GtdNotificationPrivate *priv; + + g_return_if_fail (GTD_IS_NOTIFICATION (notification)); + + priv = notification->priv; + + if (priv->secondary_action) + { + priv->secondary_action (notification, priv->secondary_action_data); + + g_signal_emit (notification, signals[EXECUTED], 0); + } +} diff --git a/src/core/gtd-notification.h b/src/core/gtd-notification.h new file mode 100644 index 0000000..fd420ad --- /dev/null +++ b/src/core/gtd-notification.h @@ -0,0 +1,68 @@ +/* gtd-notification.h + * + * Copyright (C) 2015 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 . + */ + +#ifndef GTD_NOTIFICATION_H +#define GTD_NOTIFICATION_H + +#include "gtd-object.h" +#include "gtd-types.h" + +#include +#include + +G_BEGIN_DECLS + +#define GTD_TYPE_NOTIFICATION (gtd_notification_get_type()) + +G_DECLARE_FINAL_TYPE (GtdNotification, gtd_notification, GTD, NOTIFICATION, GtdObject) + +/** + * GtdNotificationActionFunc: + * @notification: the #GtdNotification running the function + * @user_data: (closure): user data + * + * Will be called when the dismissal or secondary action of @notification + * is executed. + */ +typedef void (*GtdNotificationActionFunc) (GtdNotification *notification, + gpointer user_data); + + +GtdNotification* gtd_notification_new (const gchar *text); + +void gtd_notification_execute_dismissal_action (GtdNotification *notification); + +void gtd_notification_execute_secondary_action (GtdNotification *notification); + +void gtd_notification_set_dismissal_action (GtdNotification *notification, + GtdNotificationActionFunc func, + gpointer user_data); + +void gtd_notification_set_secondary_action (GtdNotification *notification, + const gchar *name, + GtdNotificationActionFunc func, + gpointer user_data); + +const gchar* gtd_notification_get_text (GtdNotification *notification); + +void gtd_notification_set_text (GtdNotification *notification, + const gchar *text); + +G_END_DECLS + +#endif /* GTD_NOTIFICATION_H */ diff --git a/src/core/gtd-object.c b/src/core/gtd-object.c new file mode 100644 index 0000000..c1157fc --- /dev/null +++ b/src/core/gtd-object.c @@ -0,0 +1,313 @@ +/* gtd-object.c + * + * Copyright © 2018 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 "GtdObject" + +#include "gtd-object.h" + +#include + +/** + * SECTION:gtd-object + * @Short_description: base class for loadable and uniquely identifiable objects + * @Title: GtdObject + * + * #GtdObject is the base class of many object in Endeavour, and it useful for + * when a given object is loadable and/or uniquely identifiable. Some examples of + * it are #GtdTask, #GtdTaskList and #GtdNotification. + * + */ + +typedef struct +{ + guint64 loading; + gchar *uid; +} GtdObjectPrivate; + + +G_DEFINE_TYPE_WITH_PRIVATE (GtdObject, gtd_object, G_TYPE_OBJECT) + + +enum +{ + PROP_0, + PROP_LOADING, + PROP_UID, + N_PROPS +}; + + +static GParamSpec *properties[N_PROPS] = { NULL, }; + + +static const gchar* +gtd_object_real_get_uid (GtdObject *object) +{ + GtdObjectPrivate *priv; + + g_return_val_if_fail (GTD_IS_OBJECT (object), NULL); + + priv = gtd_object_get_instance_private (object); + + return priv->uid; +} + +static void +gtd_object_real_set_uid (GtdObject *object, + const gchar *uid) +{ + GtdObjectPrivate *priv; + + g_assert (GTD_IS_OBJECT (object)); + + priv = gtd_object_get_instance_private (object); + + if (g_strcmp0 (priv->uid, uid) == 0) + return; + + g_clear_pointer (&priv->uid, g_free); + priv->uid = g_strdup (uid); + + g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_UID]); +} + + +/* + * GObject overrides + */ + +static void +gtd_object_finalize (GObject *object) +{ + GtdObject *self = GTD_OBJECT (object); + GtdObjectPrivate *priv = gtd_object_get_instance_private (self); + + g_clear_pointer (&priv->uid, g_free); + + G_OBJECT_CLASS (gtd_object_parent_class)->finalize (object); +} + +static void +gtd_object_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtdObject *self = GTD_OBJECT (object); + GtdObjectPrivate *priv = gtd_object_get_instance_private (self); + + switch (prop_id) + { + case PROP_LOADING: + g_value_set_boolean (value, priv->loading > 0); + break; + + case PROP_UID: + g_value_set_string (value, GTD_OBJECT_GET_CLASS (self)->get_uid (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtd_object_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtdObject *self = GTD_OBJECT (object); + + switch (prop_id) + { + case PROP_UID: + GTD_OBJECT_GET_CLASS (self)->set_uid (self, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtd_object_class_init (GtdObjectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + klass->get_uid = gtd_object_real_get_uid; + klass->set_uid = gtd_object_real_set_uid; + + object_class->finalize = gtd_object_finalize; + object_class->get_property = gtd_object_get_property; + object_class->set_property = gtd_object_set_property; + + /** + * GtdObject::uid: + * + * The unique identified of the object, set by the backend. + */ + properties[PROP_UID] = g_param_spec_string ("uid", + "Unique identifier of the object", + "The unique identifier of the object, defined by the backend", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * GtdObject::loading: + * + * Whether the object is loading or not. + */ + properties[PROP_LOADING] = g_param_spec_boolean ("loading", + "Loading state of the object", + "Whether the object is loading or not", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +gtd_object_init (GtdObject *self) +{ +} + +/** + * gtd_object_new: + * @uid: unique identifier of the object + * + * Creates a new #GtdObject object. + * + * Returns: (transfer full): a new #GtdObject + */ +GtdObject* +gtd_object_new (const gchar *uid) +{ + return g_object_new (GTD_TYPE_OBJECT, "uid", uid, NULL); +} + +/** + * gtd_object_get_uid: + * @object: a #GtdObject + * + * Retrieves the internal unique identifier of @object. + * + * Returns: (transfer none): the unique identifier of @object. Do + * not free after usage. + */ +const gchar* +gtd_object_get_uid (GtdObject *object) +{ + GtdObjectClass *class; + + g_return_val_if_fail (GTD_IS_OBJECT (object), NULL); + + class = GTD_OBJECT_GET_CLASS (object); + + g_assert (class); + return class->get_uid (object); +} + +/** + * gtd_object_set_uid: + * @object: a #GtdObject + * @uid: the unique identifier of @object + * + * Sets the unique identifier of @object to @uid. Only + * a #GtdBackend should do it. + */ +void +gtd_object_set_uid (GtdObject *object, + const gchar *uid) +{ + GtdObjectClass *class; + + g_return_if_fail (GTD_IS_OBJECT (object)); + + class = GTD_OBJECT_GET_CLASS (object); + + g_assert (class); + class->set_uid (object, uid); +} + +/** + * gtd_object_get_loading: + * @object: a #GtdObject + * + * Whether @object is loading or not. + * + * Returns: %TRUE if @object is loading, %FALSE otherwise. + */ +gboolean +gtd_object_get_loading (GtdObject *object) +{ + GtdObjectPrivate *priv; + + g_return_val_if_fail (GTD_IS_OBJECT (object), FALSE); + + priv = gtd_object_get_instance_private (object); + + return priv->loading > 0; +} + +/** + * gtd_object_push_loading: + * @object: a #GtdObject + * + * Increases the loading counter of @object by one. The object is marked + * as loading while the loading counter is greater than zero. + */ +void +gtd_object_push_loading (GtdObject *object) +{ + GtdObjectPrivate *priv; + + g_return_if_fail (GTD_IS_OBJECT (object)); + + priv = gtd_object_get_instance_private (object); + + priv->loading++; + + if (priv->loading == 1) + g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_LOADING]); +} + +/** + * gtd_object_pop_loading: + * @object: a #GtdObject + * + * Decreases the loading counter of @object by one. The object is marked + * as loading while the loading counter is greater than zero. + * + * It is a programming error to pop more times then push the loading the + * counter. + */ +void +gtd_object_pop_loading (GtdObject *object) +{ + GtdObjectPrivate *priv; + + g_return_if_fail (GTD_IS_OBJECT (object)); + + priv = gtd_object_get_instance_private (object); + + priv->loading--; + + if (priv->loading == 0) + g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_LOADING]); +} diff --git a/src/core/gtd-object.h b/src/core/gtd-object.h new file mode 100644 index 0000000..c0033d3 --- /dev/null +++ b/src/core/gtd-object.h @@ -0,0 +1,54 @@ +/* gtd-object.h + * + * Copyright (C) 2015 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 . + */ + +#pragma once + +#include "gtd-types.h" + +#include + +G_BEGIN_DECLS + +#define GTD_TYPE_OBJECT (gtd_object_get_type()) + +G_DECLARE_DERIVABLE_TYPE (GtdObject, gtd_object, GTD, OBJECT, GObject) + +struct _GtdObjectClass +{ + GObjectClass parent; + + /* public */ + const gchar* (* get_uid) (GtdObject *object); + void (* set_uid) (GtdObject *object, + const gchar *uid); +}; + +GtdObject* gtd_object_new (const gchar *uid); + +const gchar* gtd_object_get_uid (GtdObject *object); + +void gtd_object_set_uid (GtdObject *object, + const gchar *uid); + +gboolean gtd_object_get_loading (GtdObject *object); + +void gtd_object_push_loading (GtdObject *object); + +void gtd_object_pop_loading (GtdObject *object); + +G_END_DECLS diff --git a/src/core/gtd-plugin-manager.c b/src/core/gtd-plugin-manager.c new file mode 100644 index 0000000..e159916 --- /dev/null +++ b/src/core/gtd-plugin-manager.c @@ -0,0 +1,323 @@ +/* gtd-plugin-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 "GtdPluginManager" + +#include "gtd-activatable.h" +#include "gtd-manager.h" +#include "gtd-panel.h" +#include "gtd-provider.h" +#include "gtd-plugin-manager.h" + +#include + +struct _GtdPluginManager +{ + GtdObject parent; + + GHashTable *info_to_extension; +}; + +G_DEFINE_TYPE (GtdPluginManager, gtd_plugin_manager, GTD_TYPE_OBJECT) + +enum +{ + PLUGIN_LOADED, + PLUGIN_UNLOADED, + NUM_SIGNALS +}; + +static guint signals[NUM_SIGNALS] = { 0, }; + +static const gchar * const default_plugins[] = { + "all-tasks-panel", + "eds", + "task-lists-workspace", + "today-panel", + "inbox-panel", + "next-week-panel", + "peace", +}; + +static gboolean +gtd_str_equal0 (gconstpointer a, + gconstpointer b) +{ + if (a == b) + return TRUE; + else if (!a || !b) + return FALSE; + else + return g_str_equal (a, b); +} + +static gchar** +get_loaded_extensions (const gchar **extensions) +{ + g_autoptr (GPtrArray) loaded_plugins = NULL; + gsize i; + + loaded_plugins = g_ptr_array_new (); + + for (i = 0; extensions && extensions[i]; i++) + g_ptr_array_add (loaded_plugins, g_strdup (extensions[i])); + + for (i = 0; i < G_N_ELEMENTS (default_plugins); i++) + { + if (g_ptr_array_find_with_equal_func (loaded_plugins, + default_plugins[i], + gtd_str_equal0, + NULL)) + { + continue; + } + + g_ptr_array_add (loaded_plugins, g_strdup (default_plugins[i])); + } + + g_ptr_array_add (loaded_plugins, NULL); + + return (gchar**) g_ptr_array_free (g_steal_pointer (&loaded_plugins), FALSE); +} + +static gboolean +from_gsetting_to_property_func (GValue *value, + GVariant *variant, + gpointer user_data) +{ + g_autofree const gchar **extensions = NULL; + g_autofree gchar **loaded_extensions = NULL; + + extensions = g_variant_get_strv (variant, NULL); + loaded_extensions = get_loaded_extensions (extensions); + + g_value_take_boxed (value, g_steal_pointer (&loaded_extensions)); + + return TRUE; +} + +static GVariant* +from_property_to_gsetting_func (const GValue *value, + const GVariantType *expected_type, + gpointer user_data) +{ + g_autofree gchar **loaded_extensions = NULL; + const gchar **extensions = NULL; + + extensions = g_value_get_boxed (value); + loaded_extensions = get_loaded_extensions (extensions); + + return g_variant_new_strv ((const gchar * const *)loaded_extensions, -1); +} + +static void +on_plugin_unloaded_cb (PeasEngine *engine, + PeasPluginInfo *info, + GtdPluginManager *self) +{ + GtdActivatable *activatable; + + activatable = g_hash_table_lookup (self->info_to_extension, info); + + if (!activatable) + return; + + /* Deactivates the extension */ + gtd_activatable_deactivate (activatable); + + /* Emit the signal */ + g_signal_emit (self, signals[PLUGIN_UNLOADED], 0, info, activatable); + + g_hash_table_remove (self->info_to_extension, info); + + /* Destroy the extension */ + g_clear_object (&activatable); +} + +static void +on_plugin_loaded_cb (PeasEngine *engine, + PeasPluginInfo *info, + GtdPluginManager *self) +{ + if (peas_engine_provides_extension (engine, info, GTD_TYPE_ACTIVATABLE)) + { + GtdActivatable *activatable; + PeasExtension *extension; + + /* + * Actually create the plugin object, + * which should load all the providers. + */ + extension = peas_engine_create_extension (engine, + info, + GTD_TYPE_ACTIVATABLE, + NULL); + + /* All extensions shall be GtdActivatable impls */ + activatable = GTD_ACTIVATABLE (extension); + + g_hash_table_insert (self->info_to_extension, + info, + extension); + + /* Activate extension */ + gtd_activatable_activate (activatable); + + /* Emit the signal */ + g_signal_emit (self, signals[PLUGIN_LOADED], 0, info, extension); + } +} + +static void +setup_engine (GtdPluginManager *self) +{ + PeasEngine *engine; + gchar *plugin_dir; + + engine = peas_engine_get_default (); + + /* Enable Python3 plugins */ + peas_engine_enable_loader (engine, "python3"); + + /* Let Peas search for plugins in the specified directory */ + plugin_dir = g_build_filename (PACKAGE_LIB_DIR, + "plugins", + NULL); + + peas_engine_add_search_path (engine, + plugin_dir, + NULL); + + g_free (plugin_dir); + + /* User-installed plugins shall be detected too */ + plugin_dir = g_build_filename (g_get_home_dir (), + ".local", + "lib", + "endeavour", + "plugins", + NULL); + + peas_engine_add_search_path (engine, plugin_dir, NULL); + peas_engine_prepend_search_path (engine, + "resource:///org/gnome/todo/plugins", + "resource:///org/gnome/todo/plugins"); + + g_free (plugin_dir); + + /* Hear about loaded plugins */ + g_signal_connect_after (engine, "load-plugin", G_CALLBACK (on_plugin_loaded_cb), self); + g_signal_connect (engine, "unload-plugin",G_CALLBACK (on_plugin_unloaded_cb), self); +} + +static void +gtd_plugin_manager_finalize (GObject *object) +{ + GtdPluginManager *self = (GtdPluginManager *)object; + + g_clear_pointer (&self->info_to_extension, g_hash_table_destroy); + + G_OBJECT_CLASS (gtd_plugin_manager_parent_class)->finalize (object); +} + +static void +gtd_plugin_manager_class_init (GtdPluginManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gtd_plugin_manager_finalize; + + signals[PLUGIN_LOADED] = g_signal_new ("plugin-loaded", + GTD_TYPE_PLUGIN_MANAGER, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + PEAS_TYPE_PLUGIN_INFO, + GTD_TYPE_ACTIVATABLE); + + signals[PLUGIN_UNLOADED] = g_signal_new ("plugin-unloaded", + GTD_TYPE_PLUGIN_MANAGER, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + PEAS_TYPE_PLUGIN_INFO, + GTD_TYPE_ACTIVATABLE); +} + +static void +gtd_plugin_manager_init (GtdPluginManager *self) +{ + self->info_to_extension = g_hash_table_new (g_direct_hash, g_direct_equal); + + gtd_object_push_loading (GTD_OBJECT (self)); + + setup_engine (self); + + gtd_object_pop_loading (GTD_OBJECT (self)); +} + +GtdPluginManager* +gtd_plugin_manager_new (void) +{ + return g_object_new (GTD_TYPE_PLUGIN_MANAGER, NULL); +} + +void +gtd_plugin_manager_load_plugins (GtdPluginManager *self) +{ + PeasEngine *engine; + GSettings *settings; + + engine = peas_engine_get_default (); + settings = gtd_manager_get_settings (gtd_manager_get_default ()); + + g_settings_bind_with_mapping (settings, + "active-extensions", + engine, + "loaded-plugins", + G_SETTINGS_BIND_DEFAULT, + from_gsetting_to_property_func, + from_property_to_gsetting_func, + self, + NULL); +} + +GtdActivatable* +gtd_plugin_manager_get_plugin (GtdPluginManager *self, + PeasPluginInfo *info) +{ + g_return_val_if_fail (GTD_IS_PLUGIN_MANAGER (self), NULL); + + return g_hash_table_lookup (self->info_to_extension, info); +} + +GList* +gtd_plugin_manager_get_loaded_plugins (GtdPluginManager *self) +{ + g_return_val_if_fail (GTD_IS_PLUGIN_MANAGER (self), NULL); + + return g_hash_table_get_values (self->info_to_extension); +} diff --git a/src/core/gtd-plugin-manager.h b/src/core/gtd-plugin-manager.h new file mode 100644 index 0000000..7bef2c6 --- /dev/null +++ b/src/core/gtd-plugin-manager.h @@ -0,0 +1,45 @@ +/* gtd-plugin-manager.h + * + * Copyright (C) 2015 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 . + */ + +#ifndef GTD_PLUGIN_MANAGER_H +#define GTD_PLUGIN_MANAGER_H + +#include +#include + +#include "gtd-object.h" +#include "gtd-types.h" + +G_BEGIN_DECLS + +#define GTD_TYPE_PLUGIN_MANAGER (gtd_plugin_manager_get_type()) + +G_DECLARE_FINAL_TYPE (GtdPluginManager, gtd_plugin_manager, GTD, PLUGIN_MANAGER, GtdObject) + +GtdPluginManager* gtd_plugin_manager_new (void); + +void gtd_plugin_manager_load_plugins (GtdPluginManager *self); + +GtdActivatable* gtd_plugin_manager_get_plugin (GtdPluginManager *self, + PeasPluginInfo *info); + +GList* gtd_plugin_manager_get_loaded_plugins (GtdPluginManager *self); + +G_END_DECLS + +#endif /* GTD_PLUGIN_MANAGER_H */ diff --git a/src/core/gtd-provider.c b/src/core/gtd-provider.c new file mode 100644 index 0000000..1d69a80 --- /dev/null +++ b/src/core/gtd-provider.c @@ -0,0 +1,681 @@ +/* gtd-provider.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 "GtdProvider" + +#include "gtd-provider.h" +#include "gtd-task.h" +#include "gtd-task-list.h" +#include "gtd-utils.h" + +/** + * SECTION:gtd-provider + * @short_description:data sources for Endeavour + * @title: GtdProvider + * @stability:Unstable + * + * The #GtdProvider is the interface that Endeavour uses to + * connect to data sources. It must provide ways to create, update + * and remove tasks and tasklists. + * + * A provider implementation must also expose which is the default + * tasklist among the tasklists it manages. + */ + +G_DEFINE_INTERFACE (GtdProvider, gtd_provider, GTD_TYPE_OBJECT) + +enum +{ + LIST_ADDED, + LIST_CHANGED, + LIST_REMOVED, + NUM_SIGNALS +}; + +static guint signals[NUM_SIGNALS] = { 0, }; + + +static void +gtd_provider_default_init (GtdProviderInterface *iface) +{ + /** + * GtdProvider::enabled: + * + * Whether the #GtdProvider is enabled. + */ + g_object_interface_install_property (iface, + g_param_spec_boolean ("enabled", + "Identifier of the provider", + "The identifier of the provider", + FALSE, + G_PARAM_READABLE)); + + /** + * GtdProvider::icon: + * + * The icon of the #GtdProvider, e.g. the account icon + * of a GNOME Online Accounts' account. + */ + g_object_interface_install_property (iface, + g_param_spec_object ("icon", + "Icon of the provider", + "The icon of the provider", + G_TYPE_ICON, + G_PARAM_READABLE)); + + /** + * GtdProvider::id: + * + * The unique identifier of the #GtdProvider. + */ + g_object_interface_install_property (iface, + g_param_spec_string ("id", + "Identifier of the provider", + "The identifier of the provider", + NULL, + G_PARAM_READABLE)); + + /** + * GtdProvider::name: + * + * The user-visible name of the #GtdProvider. + */ + g_object_interface_install_property (iface, + g_param_spec_string ("name", + "Name of the provider", + "The user-visible name of the provider", + NULL, + G_PARAM_READABLE)); + + /** + * GtdProvider::provider-type: + * + * The type of the #GtdProvider. + */ + g_object_interface_install_property (iface, + g_param_spec_string ("provider-type", + "Type of the provider", + "The type of the provider", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * GtdProvider::description: + * + * The description of the #GtdProvider, e.g. the account user + * of a GNOME Online Accounts' account. + */ + g_object_interface_install_property (iface, + g_param_spec_string ("description", + "Description of the provider", + "The description of the provider", + NULL, + G_PARAM_READABLE)); + + /** + * GtdProvider::list-added: + * @provider: a #GtdProvider + * @list: a #GtdTaskList + * + * The ::list-added signal is emmited after a #GtdTaskList + * is connected. + */ + signals[LIST_ADDED] = g_signal_new ("list-added", + GTD_TYPE_PROVIDER, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + GTD_TYPE_TASK_LIST); + + /** + * GtdProvider::list-changed: + * @provider: a #GtdProvider + * @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_PROVIDER, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + GTD_TYPE_TASK_LIST); + + /** + * GtdProvider::list-removed: + * @provider: a #GtdProvider + * @list: a #GtdTaskList + * + * The ::list-removed signal is emmited after a #GtdTaskList + * is disconnected. + */ + signals[LIST_REMOVED] = g_signal_new ("list-removed", + GTD_TYPE_PROVIDER, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + GTD_TYPE_TASK_LIST); +} + +/** + * gtd_provider_get_id: + * @provider: a #GtdProvider + * + * Retrieves the identifier of @provider. + * + * Returns: (transfer none): the id of @provider + */ +const gchar* +gtd_provider_get_id (GtdProvider *provider) +{ + g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL); + g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_id, NULL); + + return GTD_PROVIDER_GET_IFACE (provider)->get_id (provider); +} + +/** + * gtd_provider_get_name: + * @provider: a #GtdProvider + * + * Retrieves the user-visible name of @provider. + * + * Returns: (transfer none): the name of @provider + */ +const gchar* +gtd_provider_get_name (GtdProvider *provider) +{ + g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL); + g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_name, NULL); + + return GTD_PROVIDER_GET_IFACE (provider)->get_name (provider); +} + +/** + * gtd_provider_get_provider_type: + * @provider: a #GtdProvider + * + * Retrieves the type of the @provider. This should return the + * same value, regardless of the account name. + * + * For example: "todoist", "todo-txt" or "google" + * + * Returns: (transfer none): the type of the @provider + */ +const gchar* +gtd_provider_get_provider_type (GtdProvider *provider) +{ + g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL); + g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_name, NULL); + + return GTD_PROVIDER_GET_IFACE (provider)->get_provider_type (provider); +} + +/** + * gtd_provider_get_description: + * @provider: a #GtdProvider + * + * Retrieves the description of @provider. + * + * Returns: (transfer none): the description of @provider + */ +const gchar* +gtd_provider_get_description (GtdProvider *provider) +{ + g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL); + g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_description, NULL); + + return GTD_PROVIDER_GET_IFACE (provider)->get_description (provider); +} + +/** + * gtd_provider_get_enabled: + * @provider: a #GtdProvider + * + * Retrieves whether @provider is enabled or not. A disabled + * provider cannot be selected to be default nor be selected + * to add tasks to it. + * + * Returns: %TRUE if provider is enabled, %FALSE otherwise. + */ +gboolean +gtd_provider_get_enabled (GtdProvider *provider) +{ + g_return_val_if_fail (GTD_IS_PROVIDER (provider), FALSE); + g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_enabled, FALSE); + + return GTD_PROVIDER_GET_IFACE (provider)->get_enabled (provider); +} + +/** + * gtd_provider_refresh: + * @provider: a #GtdProvider + * + * Asks the provider to refresh. Online providers may want to + * synchronize tasks and tasklists, credentials, etc, when this + * is called. + * + * This is an optional feature. Providers that do not implement + * the "refresh" vfunc will be ignored. + */ +void +gtd_provider_refresh (GtdProvider *provider) +{ + g_return_if_fail (GTD_IS_PROVIDER (provider)); + + if (GTD_PROVIDER_GET_IFACE (provider)->refresh) + GTD_PROVIDER_GET_IFACE (provider)->refresh (provider); +} + +/** + * gtd_provider_get_icon: + * @provider: a #GtdProvider + * + * The icon of @provider. + * + * Returns: (transfer none): a #GIcon + */ +GIcon* +gtd_provider_get_icon (GtdProvider *provider) +{ + g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL); + g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_icon, NULL); + + return GTD_PROVIDER_GET_IFACE (provider)->get_icon (provider); +} + +/** + * gtd_provider_create_task: + * @provider: a #GtdProvider + * @list: a #GtdTaskLast + * @title: The task title + * @due_date: (nullable): a #GDateTime + * @cancellable: (nullable): a #GCancellable + * @callback: (scope async): a callback + * @user_data: (closure): user data for @callback + * + * Creates the given task in @provider. + */ +void +gtd_provider_create_task (GtdProvider *provider, + GtdTaskList *list, + const gchar *title, + GDateTime *due_date, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (GTD_IS_PROVIDER (provider)); + g_return_if_fail (GTD_PROVIDER_GET_IFACE (provider)->create_task); + + GTD_PROVIDER_GET_IFACE (provider)->create_task (provider, + list, + title, + due_date, + cancellable, + callback, + user_data); +} + +/** + * gtd_provider_create_task_finish: + * @self: a #GtdProvider + * @result: a #GAsyncResult + * @error: (out)(nullable): return location for a #GError + * + * Finishes creating the task. + * + * Returns: (transfer none)(nullable): a #GtdTask + */ +GtdTask* +gtd_provider_create_task_finish (GtdProvider *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (GTD_IS_PROVIDER (self), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (self)->create_task_finish, FALSE); + + return GTD_PROVIDER_GET_IFACE (self)->create_task_finish (self, result, error); +} + +/** + * gtd_provider_update_task: + * @provider: a #GtdProvider + * @task: a #GtdTask + * @cancellable: (nullable): a #GCancellable + * @callback: (scope async): a callback + * @user_data: (closure): user data for @callback + * + * Updates the given task in @provider. + */ +void +gtd_provider_update_task (GtdProvider *provider, + GtdTask *task, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (GTD_IS_PROVIDER (provider)); + g_return_if_fail (GTD_PROVIDER_GET_IFACE (provider)->update_task); + + GTD_PROVIDER_GET_IFACE (provider)->update_task (provider, + task, + cancellable, + callback, + user_data); +} + +/** + * gtd_provider_update_task_finish: + * @self: a #GtdProvider + * @result: a #GAsyncResult + * @error: (out)(nullable): return location for a #GError + * + * Finishes updating the task list. + * + * Returns: %TRUE if task list was successfully updated, %FALSE otherwise + */ +gboolean +gtd_provider_update_task_finish (GtdProvider *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (GTD_IS_PROVIDER (self), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (self)->update_task_finish, FALSE); + + return GTD_PROVIDER_GET_IFACE (self)->update_task_finish (self, result, error); +} + +/** + * gtd_provider_remove_task: + * @provider: a #GtdProvider + * @task: a #GtdTask + * @cancellable: (nullable): a #GCancellable + * @callback: (scope async): a callback + * @user_data: (closure): user data for @callback + * + * Removes the given task from @provider. + */ +void +gtd_provider_remove_task (GtdProvider *provider, + GtdTask *task, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (GTD_IS_PROVIDER (provider)); + g_return_if_fail (GTD_PROVIDER_GET_IFACE (provider)->remove_task); + + GTD_PROVIDER_GET_IFACE (provider)->remove_task (provider, + task, + cancellable, + callback, + user_data); +} + +/** + * gtd_provider_remove_task_finish: + * @self: a #GtdProvider + * @result: a #GAsyncResult + * @error: (out)(nullable): return location for a #GError + * + * Finishes removing the task. + * + * Returns: %TRUE if task was successfully removed, %FALSE otherwise + */ +gboolean +gtd_provider_remove_task_finish (GtdProvider *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (GTD_IS_PROVIDER (self), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (self)->remove_task_finish, FALSE); + + return GTD_PROVIDER_GET_IFACE (self)->remove_task_finish (self, result, error); +} + +/** + * gtd_provider_create_task_list: + * @provider: a #GtdProvider + * @name: (nullable): the name of the new task list + * @cancellable: (nullable): a #GCancellable + * @callback: (scope async): a callback + * @user_data: (closure): user data for @callback + * + * Creates the given list in @provider. + */ +void +gtd_provider_create_task_list (GtdProvider *provider, + const gchar *name, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (GTD_IS_PROVIDER (provider)); + g_return_if_fail (GTD_PROVIDER_GET_IFACE (provider)->create_task_list); + + GTD_PROVIDER_GET_IFACE (provider)->create_task_list (provider, + name, + cancellable, + callback, + user_data); +} + +/** + * gtd_provider_create_task_list_finish: + * @self: a #GtdProvider + * @result: a #GAsyncResult + * @error: (out)(nullable): return location for a #GError + * + * Finishes creating the task list. The provider will emit the + * GtdProvider:list-added signal after creating the task list. + * + * Returns: %TRUE if task list was successfully created, %FALSE otherwise + */ +gboolean +gtd_provider_create_task_list_finish (GtdProvider *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (GTD_IS_PROVIDER (self), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (self)->create_task_list_finish, FALSE); + + return GTD_PROVIDER_GET_IFACE (self)->create_task_list_finish (self, result, error); +} + +/** + * gtd_provider_update_task_list: + * @provider: a #GtdProvider + * @list: a #GtdTaskList + * @cancellable: (nullable): a #GCancellable + * @callback: (scope async): a callback + * @user_data: (closure): user data for @callback + * + * Updates the given list in @provider. + */ +void +gtd_provider_update_task_list (GtdProvider *provider, + GtdTaskList *list, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (GTD_IS_PROVIDER (provider)); + g_return_if_fail (GTD_PROVIDER_GET_IFACE (provider)->update_task_list); + + GTD_PROVIDER_GET_IFACE (provider)->update_task_list (provider, + list, + cancellable, + callback, + user_data); +} + +/** + * gtd_provider_update_task_list_finish: + * @self: a #GtdProvider + * @result: a #GAsyncResult + * @error: (out)(nullable): return location for a #GError + * + * Finishes updating the task list. The provider will emit the + * GtdProvider:list-updated signal after updating the task list. + * + * Returns: %TRUE if task list was successfully updated, %FALSE otherwise + */ +gboolean +gtd_provider_update_task_list_finish (GtdProvider *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (GTD_IS_PROVIDER (self), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (self)->update_task_list_finish, FALSE); + + return GTD_PROVIDER_GET_IFACE (self)->update_task_list_finish (self, result, error); +} + +/** + * gtd_provider_remove_task_list: + * @provider: a #GtdProvider + * @list: a #GtdTaskList + * @cancellable: (nullable): a #GCancellable + * @callback: (scope async): a callback + * @user_data: (closure): user data for @callback + * + * Removes the given list from @provider. + */ +void +gtd_provider_remove_task_list (GtdProvider *provider, + GtdTaskList *list, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (GTD_IS_PROVIDER (provider)); + g_return_if_fail (GTD_PROVIDER_GET_IFACE (provider)->remove_task_list); + + GTD_PROVIDER_GET_IFACE (provider)->remove_task_list (provider, + list, + cancellable, + callback, + user_data); +} + +/** + * gtd_provider_remove_task_list_finish: + * @self: a #GtdProvider + * @result: a #GAsyncResult + * @error: (out)(nullable): return location for a #GError + * + * Finishes removing the task list. The provider will emit the + * GtdProvider:list-removed signal after removing the task list. + * + * Returns: %TRUE if task list was successfully removed, %FALSE otherwise + */ +gboolean +gtd_provider_remove_task_list_finish (GtdProvider *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (GTD_IS_PROVIDER (self), FALSE); + g_return_val_if_fail (!error || !*error, FALSE); + g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (self)->remove_task_list_finish, FALSE); + + return GTD_PROVIDER_GET_IFACE (self)->remove_task_list_finish (self, result, error); +} + +/** + * gtd_provider_get_task_lists: + * @provider: a #GtdProvider + * + * Retrieves the tasklists that this provider contains. + * + * Returns: (transfer container) (element-type Gtd.TaskList): the list of tasks, or %NULL + */ +GList* +gtd_provider_get_task_lists (GtdProvider *provider) +{ + g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL); + g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_task_lists, NULL); + + return GTD_PROVIDER_GET_IFACE (provider)->get_task_lists (provider); +} + +/** + * gtd_provider_get_inbox: + * @provider: a #GtdProvider + * + * Retrieves the inbox of @provider. + * + * Returns: (transfer none)(nullable): a #GtdTaskList + */ +GtdTaskList* +gtd_provider_get_inbox (GtdProvider *provider) +{ + g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL); + g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_inbox, NULL); + + return GTD_PROVIDER_GET_IFACE (provider)->get_inbox (provider); +} + +/** + * gtd_provider_compare: + * @a: a #GtdProvider + * @b: a #GtdProvider + * + * Compares @a and @b. The sorting criteria is internal and + * may change. + * + * Returns: -1 if @a comes before @b, 1 for the oposite, and + * 0 if they're equal + */ +gint +gtd_provider_compare (GtdProvider *a, + GtdProvider *b) +{ + gint result; + + g_return_val_if_fail (GTD_IS_PROVIDER (a), 0); + g_return_val_if_fail (GTD_IS_PROVIDER (b), 0); + + if (a == b) + return 0; + + result = gtd_collate_compare_strings (gtd_provider_get_name (a), gtd_provider_get_name (b)); + + if (result != 0) + return result; + + return gtd_collate_compare_strings (gtd_provider_get_description (a), gtd_provider_get_description (b)); +} diff --git a/src/core/gtd-provider.h b/src/core/gtd-provider.h new file mode 100644 index 0000000..0b8e097 --- /dev/null +++ b/src/core/gtd-provider.h @@ -0,0 +1,208 @@ +/* gtd-storage.h + * + * 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 . + */ + +#ifndef GTD_PROVIDER_H +#define GTD_PROVIDER_H + +#include "gtd-object.h" +#include "gtd-types.h" + +#include +#include + +G_BEGIN_DECLS + +#define GTD_TYPE_PROVIDER (gtd_provider_get_type ()) + +G_DECLARE_INTERFACE (GtdProvider, gtd_provider, GTD, PROVIDER, GtdObject) + +struct _GtdProviderInterface +{ + GTypeInterface parent; + + /* Information */ + const gchar* (*get_id) (GtdProvider *provider); + + const gchar* (*get_name) (GtdProvider *provider); + + const gchar* (*get_provider_type) (GtdProvider *provider); + + const gchar* (*get_description) (GtdProvider *provider); + + gboolean (*get_enabled) (GtdProvider *provider); + + void (*refresh) (GtdProvider *provider); + + /* Customs */ + GIcon* (*get_icon) (GtdProvider *provider); + + /* Tasks */ + void (*create_task) (GtdProvider *provider, + GtdTaskList *list, + const gchar *title, + GDateTime *due_date, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + + GtdTask* (*create_task_finish) (GtdProvider *self, + GAsyncResult *result, + GError **error); + + void (*update_task) (GtdProvider *provider, + GtdTask *task, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + + gboolean (*update_task_finish) (GtdProvider *self, + GAsyncResult *result, + GError **error); + + void (*remove_task) (GtdProvider *provider, + GtdTask *task, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + + gboolean (*remove_task_finish) (GtdProvider *self, + GAsyncResult *result, + GError **error); + + /* Task lists */ + void (*create_task_list) (GtdProvider *provider, + const gchar *name, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + + gboolean (*create_task_list_finish) (GtdProvider *self, + GAsyncResult *result, + GError **error); + + void (*update_task_list) (GtdProvider *provider, + GtdTaskList *list, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + + gboolean (*update_task_list_finish) (GtdProvider *self, + GAsyncResult *result, + GError **error); + + void (*remove_task_list) (GtdProvider *provider, + GtdTaskList *list, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + + gboolean (*remove_task_list_finish) (GtdProvider *self, + GAsyncResult *result, + GError **error); + + GList* (*get_task_lists) (GtdProvider *provider); + + GtdTaskList* (*get_inbox) (GtdProvider *provider); +}; + +const gchar* gtd_provider_get_id (GtdProvider *provider); + +const gchar* gtd_provider_get_name (GtdProvider *provider); + +const gchar* gtd_provider_get_provider_type (GtdProvider *provider); + +const gchar* gtd_provider_get_description (GtdProvider *provider); + +gboolean gtd_provider_get_enabled (GtdProvider *provider); + +void gtd_provider_refresh (GtdProvider *provider); + +GIcon* gtd_provider_get_icon (GtdProvider *provider); + +void gtd_provider_create_task (GtdProvider *provider, + GtdTaskList *list, + const gchar *title, + GDateTime *due_date, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +GtdTask* gtd_provider_create_task_finish (GtdProvider *self, + GAsyncResult *result, + GError **error); + +void gtd_provider_update_task (GtdProvider *provider, + GtdTask *task, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean gtd_provider_update_task_finish (GtdProvider *self, + GAsyncResult *result, + GError **error); + +void gtd_provider_remove_task (GtdProvider *provider, + GtdTask *task, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean gtd_provider_remove_task_finish (GtdProvider *self, + GAsyncResult *result, + GError **error); + +void gtd_provider_create_task_list (GtdProvider *provider, + const gchar *name, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean gtd_provider_create_task_list_finish (GtdProvider *self, + GAsyncResult *result, + GError **error); + +void gtd_provider_update_task_list (GtdProvider *provider, + GtdTaskList *list, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean gtd_provider_update_task_list_finish (GtdProvider *self, + GAsyncResult *result, + GError **error); + +void gtd_provider_remove_task_list (GtdProvider *provider, + GtdTaskList *list, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean gtd_provider_remove_task_list_finish (GtdProvider *self, + GAsyncResult *result, + GError **error); + +GList* gtd_provider_get_task_lists (GtdProvider *provider); + +GtdTaskList* gtd_provider_get_inbox (GtdProvider *provider); + +gint gtd_provider_compare (GtdProvider *a, + GtdProvider *b); + +G_END_DECLS + +#endif /* GTD_PROVIDER_H */ diff --git a/src/core/gtd-task-list.c b/src/core/gtd-task-list.c new file mode 100644 index 0000000..2e71a9a --- /dev/null +++ b/src/core/gtd-task-list.c @@ -0,0 +1,1161 @@ +/* gtd-task-list.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 "GtdTaskList" + +#include "gtd-debug.h" +#include "gtd-provider.h" +#include "gtd-task.h" +#include "gtd-task-list.h" + +#include + +/** + * SECTION:gtd-task-list + * @short_description:a list of tasks + * @title:GtdTaskList + * @stability:Unstable + * @see_also:#GtdTask + * + * A #GtdTaskList represents a task list, and contains a list of tasks, a + * color, a name and the provider who generated it. + * + * Only a #GtdProvider can create a #GtdTaskList. Equally, a #GtdTaskList + * is only valid when associated with a #GtdProvider. + * + * It implements #GListModel, and can be used as the model for #GtkListBox. + */ + +typedef struct +{ + GtdTask *task; + GTask *gtask; +} ImportingTaskData; + +typedef struct +{ + GtdProvider *provider; + GdkRGBA *color; + + GHashTable *task_to_uid; + GHashTable *tasks; + GSequence *sorted_tasks; + guint n_tasks; + + guint freeze_counter; + + gchar *name; + gboolean removable; + gboolean archived; +} GtdTaskListPrivate; + + +static gint compare_tasks_cb (gconstpointer a, + gconstpointer b, + gpointer user_data); + +static void task_changed_cb (GtdTask *task, + GParamSpec *pspec, + GtdTaskList *self); + +static void g_list_model_iface_init (GListModelInterface *iface); + + +G_DEFINE_TYPE_WITH_CODE (GtdTaskList, gtd_task_list, GTD_TYPE_OBJECT, + G_ADD_PRIVATE (GtdTaskList) + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, g_list_model_iface_init)) + +enum +{ + TASK_ADDED, + TASK_REMOVED, + TASK_UPDATED, + NUM_SIGNALS +}; + +enum +{ + PROP_0, + PROP_ARCHIVED, + PROP_COLOR, + PROP_IS_REMOVABLE, + PROP_NAME, + PROP_PROVIDER, + N_PROPS +}; + +static guint signals[NUM_SIGNALS] = { 0, }; +static GParamSpec *properties[N_PROPS] = { NULL, }; + + +/* + * Auxiliary functions + */ + +static void +update_task_uid (GtdTaskList *self, + GtdTask *task) +{ + GtdTaskListPrivate *priv; + GSequenceIter *iter; + const gchar *old_uid; + gchar *new_uid; + + priv = gtd_task_list_get_instance_private (self); + + g_debug ("Updating uid of task '%s'", gtd_task_get_title (task)); + + new_uid = g_strdup (gtd_object_get_uid (GTD_OBJECT (task))); + + old_uid = g_hash_table_lookup (priv->task_to_uid, task); + iter = g_hash_table_lookup (priv->tasks, old_uid); + + g_assert (g_sequence_get (iter) == task); + + g_hash_table_remove (priv->tasks, old_uid); + + g_hash_table_insert (priv->task_to_uid, task, new_uid); + g_hash_table_insert (priv->tasks, new_uid, iter); +} + +static guint +add_task (GtdTaskList *self, + GtdTask *task) +{ + GtdTaskListPrivate *priv; + GSequenceIter *iter; + gchar *uid; + + priv = gtd_task_list_get_instance_private (self); + + uid = g_strdup (gtd_object_get_uid (GTD_OBJECT (task))); + iter = g_sequence_insert_sorted (priv->sorted_tasks, + g_object_ref (task), + compare_tasks_cb, + NULL); + + g_hash_table_insert (priv->task_to_uid, task, uid); + g_hash_table_insert (priv->tasks, uid, iter); + + g_signal_connect (task, "notify", G_CALLBACK (task_changed_cb), self); + + priv->n_tasks++; + + g_signal_emit (self, signals[TASK_ADDED], 0, task); + + return g_sequence_iter_get_position (iter); +} + +static guint +remove_task (GtdTaskList *self, + GtdTask *task) +{ + GtdTaskListPrivate *priv; + GSequenceIter *iter; + const gchar *uid; + guint position; + + priv = gtd_task_list_get_instance_private (self); + + g_signal_handlers_disconnect_by_func (task, task_changed_cb, self); + + uid = gtd_object_get_uid (GTD_OBJECT (task)); + iter = g_hash_table_lookup (priv->tasks, uid); + position = g_sequence_iter_get_position (iter); + + g_hash_table_remove (priv->task_to_uid, task); + g_hash_table_remove (priv->tasks, uid); + + g_sequence_remove (iter); + + priv->n_tasks--; + + g_signal_emit (self, signals[TASK_REMOVED], 0, task); + + return position; +} + + +/* + * Callbacks + */ + +static void +on_import_old_task_removed_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GtdTask *new_task; + GTask *gtask; + g_autoptr (GError) error = NULL; + + gtask = user_data; + + gtd_provider_remove_task_finish (GTD_PROVIDER (object), result, &error); + + if (error) + { + g_warning ("Error removing task: %s", error->message); + return; + } + + new_task = g_task_get_task_data (gtask); + g_task_return_pointer (gtask, g_object_ref (new_task), g_object_unref); +} + +static void +on_task_updated_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr (GError) error = NULL; + + gtd_provider_update_task_finish (GTD_PROVIDER (object), result, &error); + + if (error) + { + g_warning ("Error updating task: %s", error->message); + return; + } +} + +static void +on_import_new_task_added_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr (GtdTask) new_task = NULL; + g_autoptr (GError) error = NULL; + GTask *gtask; + GtdProvider *provider; + GtdTask *importing_task; + GtdTaskList *list; + ImportingTaskData *data; + + GTD_ENTRY; + + data = user_data; + gtask = data->gtask; + importing_task = data->task; + + new_task = gtd_provider_create_task_finish (GTD_PROVIDER (object), result, &error); + g_task_set_task_data (gtask, g_object_ref (new_task), g_object_unref); + list = gtd_task_get_list (new_task); + + if (error) + { + g_warning ("Error creating task: %s", error->message); + GTD_RETURN (); + } + + gtd_task_set_complete (new_task, gtd_task_get_complete (importing_task)); + gtd_task_set_description (new_task, gtd_task_get_description (importing_task)); + gtd_task_set_due_date (new_task, gtd_task_get_due_date (importing_task)); + gtd_task_set_important (new_task, gtd_task_get_important (importing_task)); + gtd_task_set_title (new_task, gtd_task_get_title (importing_task)); + + gtd_task_list_update_task (list, new_task); + + provider = gtd_task_get_provider (importing_task); + + gtd_provider_remove_task (provider, + importing_task, + NULL, + on_import_old_task_removed_cb, + gtask); + + g_free (data); + + GTD_EXIT; +} + +static gint +compare_tasks_cb (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + return gtd_task_compare ((GtdTask*) a, (GtdTask*) b); +} + +static void +task_changed_cb (GtdTask *task, + GParamSpec *pspec, + GtdTaskList *self) +{ + GtdTaskListPrivate *priv; + GSequenceIter *iter; + guint old_position; + guint new_position; + + GTD_ENTRY; + + priv = gtd_task_list_get_instance_private (self); + + if (g_strcmp0 (g_param_spec_get_name (pspec), "loading") == 0) + GTD_RETURN (); + + if (g_strcmp0 (g_param_spec_get_name (pspec), "uid") == 0) + { + update_task_uid (self, task); + GTD_RETURN (); + } + + /* Don't update when the list is frozen */ + if (priv->freeze_counter > 0) + GTD_RETURN (); + + iter = g_hash_table_lookup (priv->tasks, gtd_object_get_uid (GTD_OBJECT (task))); + + old_position = g_sequence_iter_get_position (iter); + g_sequence_sort_changed (iter, compare_tasks_cb, NULL); + new_position = g_sequence_iter_get_position (iter); + + if (old_position != new_position) + { + GTD_TRACE_MSG ("Old position: %u, New position: %u", old_position, new_position); + + g_list_model_items_changed (G_LIST_MODEL (self), old_position, 1, 0); + g_list_model_items_changed (G_LIST_MODEL (self), new_position, 0, 1); + } + + GTD_EXIT; +} + + +/* + * GListModel iface + */ + +static GType +gtd_list_model_get_type (GListModel *model) +{ + return GTD_TYPE_TASK; +} + +static guint +gtd_list_model_get_n_items (GListModel *model) +{ + GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (GTD_TASK_LIST (model)); + return priv->n_tasks; +} + +static gpointer +gtd_list_model_get_item (GListModel *model, + guint i) +{ + GtdTaskListPrivate *priv; + GSequenceIter *iter; + GtdTask *task; + + priv = gtd_task_list_get_instance_private (GTD_TASK_LIST (model)); + iter = g_sequence_get_iter_at_pos (priv->sorted_tasks, i); + task = g_sequence_get (iter); + + return g_object_ref (task); +} + +static void +g_list_model_iface_init (GListModelInterface *iface) +{ + iface->get_item_type = gtd_list_model_get_type; + iface->get_n_items = gtd_list_model_get_n_items; + iface->get_item = gtd_list_model_get_item; +} + + +/* + * GtdTaskList overrides + */ + +static gboolean +gtd_task_list_real_get_archived (GtdTaskList *self) +{ + GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self); + + return priv->archived; +} + +static void +gtd_task_list_real_set_archived (GtdTaskList *self, + gboolean archived) +{ + GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self); + + priv->archived = archived; +} + + +/* + * GObject overrides + */ + +static void +gtd_task_list_finalize (GObject *object) +{ + GtdTaskList *self = (GtdTaskList*) object; + GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self); + + g_clear_object (&priv->provider); + + g_clear_pointer (&priv->color, gdk_rgba_free); + g_clear_pointer (&priv->name, g_free); + g_clear_pointer (&priv->sorted_tasks, g_sequence_free); + g_clear_pointer (&priv->tasks, g_hash_table_destroy); + g_clear_pointer (&priv->task_to_uid, g_hash_table_destroy); + + G_OBJECT_CLASS (gtd_task_list_parent_class)->finalize (object); +} + +static void +gtd_task_list_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtdTaskList *self = GTD_TASK_LIST (object); + GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self); + + switch (prop_id) + { + case PROP_ARCHIVED: + g_value_set_boolean (value, gtd_task_list_get_archived (self)); + break; + + case PROP_COLOR: + { + GdkRGBA *color = gtd_task_list_get_color (self); + g_value_set_boxed (value, color); + gdk_rgba_free (color); + break; + } + + case PROP_IS_REMOVABLE: + g_value_set_boolean (value, gtd_task_list_is_removable (self)); + break; + + case PROP_NAME: + g_value_set_string (value, priv->name); + break; + + case PROP_PROVIDER: + g_value_set_object (value, priv->provider); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtd_task_list_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtdTaskList *self = GTD_TASK_LIST (object); + + switch (prop_id) + { + case PROP_ARCHIVED: + gtd_task_list_set_archived (self, g_value_get_boolean (value)); + break; + + case PROP_COLOR: + gtd_task_list_set_color (self, g_value_get_boxed (value)); + break; + + case PROP_IS_REMOVABLE: + gtd_task_list_set_is_removable (self, g_value_get_boolean (value)); + break; + + case PROP_NAME: + gtd_task_list_set_name (self, g_value_get_string (value)); + break; + + case PROP_PROVIDER: + gtd_task_list_set_provider (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtd_task_list_class_init (GtdTaskListClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gtd_task_list_finalize; + object_class->get_property = gtd_task_list_get_property; + object_class->set_property = gtd_task_list_set_property; + + klass->get_archived = gtd_task_list_real_get_archived; + klass->set_archived = gtd_task_list_real_set_archived; + + /** + * GtdTaskList::archived: + * + * Whether the task list is archived or not. + */ + properties[PROP_ARCHIVED] = g_param_spec_boolean ("archived", + "Whether the list is archived", + "Whether the list is archived or not", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtdTaskList::color: + * + * The color of the list. + */ + properties[PROP_COLOR] = g_param_spec_boxed ("color", + "Color of the list", + "The color of the list", + GDK_TYPE_RGBA, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtdTaskList::is-removable: + * + * Whether the task list can be removed from the system. + */ + properties[PROP_IS_REMOVABLE] = g_param_spec_boolean ("is-removable", + "Whether the task list is removable", + "Whether the task list can be removed from the system", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtdTaskList::name: + * + * The display name of the list. + */ + properties[PROP_NAME] = g_param_spec_string ("name", + "Name of the list", + "The name of the list", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtdTaskList::provider: + * + * The data provider of the list. + */ + properties[PROP_PROVIDER] = g_param_spec_object ("provider", + "Provider of the list", + "The provider that handles the list", + GTD_TYPE_PROVIDER, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + /** + * GtdTaskList::task-added: + * @list: a #GtdTaskList + * @task: a #GtdTask + * + * The ::task-added signal is emitted after a #GtdTask + * is added to the list. + */ + signals[TASK_ADDED] = g_signal_new ("task-added", + GTD_TYPE_TASK_LIST, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtdTaskListClass, task_added), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + GTD_TYPE_TASK); + + /** + * GtdTaskList::task-removed: + * @list: a #GtdTaskList + * @task: a #GtdTask + * + * The ::task-removed signal is emitted after a #GtdTask + * is removed from the list. + */ + signals[TASK_REMOVED] = g_signal_new ("task-removed", + GTD_TYPE_TASK_LIST, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtdTaskListClass, task_removed), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + GTD_TYPE_TASK); + + /** + * GtdTaskList::task-updated: + * @list: a #GtdTaskList + * @task: a #GtdTask + * + * The ::task-updated signal is emitted after a #GtdTask + * in the list is updated. + */ + signals[TASK_UPDATED] = g_signal_new ("task-updated", + GTD_TYPE_TASK_LIST, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtdTaskListClass, task_updated), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 1, + GTD_TYPE_TASK); +} + +static void +gtd_task_list_init (GtdTaskList *self) +{ + GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self); + + priv->task_to_uid = g_hash_table_new (g_str_hash, g_str_equal); + priv->tasks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + priv->sorted_tasks = g_sequence_new (g_object_unref); +} + +/** + * gtd_task_list_new: + * @provider: (nullable): a #GtdProvider + * + * Creates a new list. + * + * Returns: (transfer full): the new #GtdTaskList + */ +GtdTaskList * +gtd_task_list_new (GtdProvider *provider) +{ + return g_object_new (GTD_TYPE_TASK_LIST, + "provider", provider, + NULL); +} + +/** + * gtd_task_list_get_color: + * @list: a #GtdTaskList + * + * Retrieves the color of %list. It is guarantee that it always returns a + * color, given a valid #GtdTaskList. + * + * Returns: (transfer full): the color of %list. Free with %gdk_rgba_free after use. + */ +GdkRGBA* +gtd_task_list_get_color (GtdTaskList *list) +{ + GtdTaskListPrivate *priv; + GdkRGBA rgba; + + g_return_val_if_fail (GTD_IS_TASK_LIST (list), NULL); + + priv = gtd_task_list_get_instance_private (list); + + if (!priv->color) + { + gdk_rgba_parse (&rgba, "#ffffff"); + priv->color = gdk_rgba_copy (&rgba); + } + + return gdk_rgba_copy (priv->color); +} + +/** + * gtd_task_list_set_color: + * @list: a #GtdTaskList + * #color: a #GdkRGBA + * + * sets the color of @list. + */ +void +gtd_task_list_set_color (GtdTaskList *list, + const GdkRGBA *color) +{ + GtdTaskListPrivate *priv; + GdkRGBA *current_color; + + g_return_if_fail (GTD_IS_TASK_LIST (list)); + + priv = gtd_task_list_get_instance_private (list); + current_color = gtd_task_list_get_color (list); + + if (!gdk_rgba_equal (current_color, color)) + { + g_clear_pointer (&priv->color, gdk_rgba_free); + priv->color = gdk_rgba_copy (color); + + g_object_notify (G_OBJECT (list), "color"); + } + + gdk_rgba_free (current_color); +} + +/** + * gtd_task_list_get_name: + * @list: a #GtdTaskList + * + * Retrieves the user-visible name of @list, or %NULL. + * + * Returns: (transfer none): the internal name of @list. Do not free + * after use. + */ +const gchar* +gtd_task_list_get_name (GtdTaskList *list) +{ + GtdTaskListPrivate *priv; + + g_return_val_if_fail (GTD_IS_TASK_LIST (list), NULL); + + priv = gtd_task_list_get_instance_private (list); + + return priv->name; +} + +/** + * gtd_task_list_set_name: + * @list: a #GtdTaskList + * @name: (nullable): the name of @list + * + * Sets the @list name to @name. + */ +void +gtd_task_list_set_name (GtdTaskList *list, + const gchar *name) +{ + GtdTaskListPrivate *priv; + + g_assert (GTD_IS_TASK_LIST (list)); + + priv = gtd_task_list_get_instance_private (list); + + if (g_strcmp0 (priv->name, name) != 0) + { + g_free (priv->name); + priv->name = g_strdup (name); + + g_object_notify (G_OBJECT (list), "name"); + } +} + +/** + * gtd_task_list_get_provider: + * @list: a #GtdTaskList + * + * Retrieves the #GtdProvider who owns this list. + * + * Returns: (transfer none): a #GtdProvider + */ +GtdProvider* +gtd_task_list_get_provider (GtdTaskList *list) +{ + GtdTaskListPrivate *priv; + + g_return_val_if_fail (GTD_IS_TASK_LIST (list), NULL); + + priv = gtd_task_list_get_instance_private (list); + + return priv->provider; +} + +/** + * gtd_task_list_set_provider: + * @self: a #GtdTaskList + * @provider: (nullable): a #GtdProvider, or %NULL + * + * Sets the provider of this tasklist. + */ +void +gtd_task_list_set_provider (GtdTaskList *self, + GtdProvider *provider) +{ + GtdTaskListPrivate *priv; + + g_assert (GTD_IS_TASK_LIST (self)); + + priv = gtd_task_list_get_instance_private (self); + + if (g_set_object (&priv->provider, provider)) + g_object_notify (G_OBJECT (self), "provider"); +} + +/** + * gtd_task_list_add_task: + * @list: a #GtdTaskList + * @task: a #GtdTask + * + * Adds @task to @list. + */ +void +gtd_task_list_add_task (GtdTaskList *self, + GtdTask *task) +{ + guint position; + + g_assert (GTD_IS_TASK_LIST (self)); + g_assert (GTD_IS_TASK (task)); + g_assert (!gtd_task_list_contains (self, task)); + + position = add_task (self, task); + g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1); +} + +/** + * gtd_task_list_update_task: + * @list: a #GtdTaskList + * @task: a #GtdTask + * + * Updates @task at @list. + */ +void +gtd_task_list_update_task (GtdTaskList *self, + GtdTask *task) +{ + GtdTaskListPrivate *priv; + GSequenceIter *iter; + + g_return_if_fail (GTD_IS_TASK_LIST (self)); + g_return_if_fail (GTD_IS_TASK (task)); + + priv = gtd_task_list_get_instance_private (self); + + g_return_if_fail (gtd_task_list_contains (self, task)); + + iter = g_hash_table_lookup (priv->tasks, gtd_object_get_uid (GTD_OBJECT (task))); + + g_list_model_items_changed (G_LIST_MODEL (self), + g_sequence_iter_get_position (iter), + 1, + 1); + + g_signal_emit (self, signals[TASK_UPDATED], 0, task); +} + +/** + * gtd_task_list_remove_task: + * @list: a #GtdTaskList + * @task: a #GtdTask + * + * Removes @task from @list if it's inside the list. + */ +void +gtd_task_list_remove_task (GtdTaskList *list, + GtdTask *task) +{ + guint position; + + g_assert (GTD_IS_TASK_LIST (list)); + g_assert (GTD_IS_TASK (task)); + g_assert (gtd_task_list_contains (list, task)); + + position = remove_task (list, task); + g_list_model_items_changed (G_LIST_MODEL (list), position, 1, 0); +} + +/** + * gtd_task_list_contains: + * @list: a #GtdTaskList + * @task: a #GtdTask + * + * Checks if @task is inside @list. + * + * Returns: %TRUE if @list contains @task, %FALSE otherwise + */ +gboolean +gtd_task_list_contains (GtdTaskList *list, + GtdTask *task) +{ + GtdTaskListPrivate *priv; + + g_assert (GTD_IS_TASK_LIST (list)); + g_assert (GTD_IS_TASK (task)); + + priv = gtd_task_list_get_instance_private (list); + + return g_hash_table_contains (priv->tasks, gtd_object_get_uid (GTD_OBJECT (task))); +} + +/** + * gtd_task_list_get_is_removable: + * @list: a #GtdTaskList + * + * Retrieves whether @list can be removed or not. + * + * Returns: %TRUE if the @list can be removed, %FALSE otherwise + */ +gboolean +gtd_task_list_is_removable (GtdTaskList *list) +{ + GtdTaskListPrivate *priv; + + g_return_val_if_fail (GTD_IS_TASK_LIST (list), FALSE); + + priv = gtd_task_list_get_instance_private (list); + + return priv->removable; +} + +/** + * gtd_task_list_set_is_removable: + * @list: a #GtdTaskList + * @is_removable: %TRUE if @list can be deleted, %FALSE otherwise + * + * Sets whether @list can be deleted or not. + */ +void +gtd_task_list_set_is_removable (GtdTaskList *list, + gboolean is_removable) +{ + GtdTaskListPrivate *priv; + + g_return_if_fail (GTD_IS_TASK_LIST (list)); + + priv = gtd_task_list_get_instance_private (list); + + if (priv->removable != is_removable) + { + priv->removable = is_removable; + + g_object_notify (G_OBJECT (list), "is-removable"); + } +} + +/** + * gtd_task_list_get_task_by_id: + * @self: a #GtdTaskList + * @id: the id of the task + * + * Retrieves a task from @self with the given @id. + * + * Returns: (transfer none)(nullable): a #GtdTask, or %NULL + */ +GtdTask* +gtd_task_list_get_task_by_id (GtdTaskList *self, + const gchar *id) +{ + GtdTaskListPrivate *priv; + GSequenceIter *iter; + + g_return_val_if_fail (GTD_IS_TASK_LIST (self), NULL); + + priv = gtd_task_list_get_instance_private (self); + iter = g_hash_table_lookup (priv->tasks, id); + + if (!iter) + return NULL; + + return g_sequence_get (iter); +} + +/** + * gtd_task_list_move_task_to_position: + * @self: a #GtdTaskList + * @task: a #GtdTask + * @new_position: the new position of @task inside @self + * + * Moves @task to @new_position, and repositions the elements + * in between as well. + * + * @task must belong to @self. + */ +void +gtd_task_list_move_task_to_position (GtdTaskList *self, + GtdTask *task, + guint new_position) +{ + + GtdTaskListPrivate *priv = gtd_task_list_get_instance_private (self); + GSequenceIter *new_position_iter; + GSequenceIter *iter; + guint old_position; + guint length; + guint start; + guint i; + + + g_return_if_fail (GTD_IS_TASK_LIST (self)); + g_return_if_fail (GTD_IS_TASK (task)); + g_return_if_fail (gtd_task_list_contains (self, task)); + g_return_if_fail (g_list_model_get_n_items (G_LIST_MODEL (self)) >= new_position); + + iter = g_hash_table_lookup (priv->tasks, gtd_object_get_uid (GTD_OBJECT (task))); + old_position = g_sequence_iter_get_position (iter); + + if (old_position == new_position) + return; + + GTD_TRACE_MSG ("Moving task '%s' (%s) from %u to %u", + gtd_task_get_title (task), + gtd_object_get_uid (GTD_OBJECT (task)), + old_position, + new_position); + + /* Update the GSequence */ + new_position_iter = new_position < old_position ? + g_sequence_get_iter_at_pos (priv->sorted_tasks, new_position) : + g_sequence_get_iter_at_pos (priv->sorted_tasks, new_position + 1); + g_sequence_move (iter, new_position_iter); + + /* Update the 'position' property of all tasks in between */ + priv->freeze_counter++; + + length = ABS ((gint) new_position - (gint64) old_position) + 1; + start = MIN (old_position, new_position); + iter = g_sequence_get_iter_at_pos (priv->sorted_tasks, start); + + for (i = 0; i < length; i++) + { + GtdTask *aux = g_sequence_get (iter); + + g_signal_handlers_block_by_func (aux, task_changed_cb, self); + gtd_task_set_position (aux, start + i); + g_signal_handlers_unblock_by_func (aux, task_changed_cb, self); + + gtd_provider_update_task (priv->provider, aux, NULL, on_task_updated_cb, self); + + iter = g_sequence_iter_next (iter); + } + + g_list_model_items_changed (G_LIST_MODEL (self), old_position, 1, 0); + g_list_model_items_changed (G_LIST_MODEL (self), new_position, 0, 1); + + priv->freeze_counter--; +} + +/** + * gtd_task_list_get_archived: + * @self: a #GtdTaskList + * + * Retrieves whether @self is archived or not. Archived task lists + * are hidden by default, and new tasks cannot be added. + * + * Returns: %TRUE if @self is archived, %FALSE otherwise. + */ +gboolean +gtd_task_list_get_archived (GtdTaskList *self) +{ + g_return_val_if_fail (GTD_IS_TASK_LIST (self), FALSE); + + return GTD_TASK_LIST_GET_CLASS (self)->get_archived (self); +} + +/** + * gtd_task_list_set_archived: + * @self: a #GtdTaskList + * @archived: whether @self is archived or not + * + * Sets the "archive" property of @self to @archived. + */ +void +gtd_task_list_set_archived (GtdTaskList *self, + gboolean archived) +{ + gboolean was_archived; + + g_return_if_fail (GTD_IS_TASK_LIST (self)); + + was_archived = gtd_task_list_get_archived (self); + + if (archived == was_archived) + return; + + GTD_TASK_LIST_GET_CLASS (self)->set_archived (self, archived); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ARCHIVED]); +} + +/** + * gtd_task_list_import_task: + * @self: a #GtdTaskList + * @task: a #GtdTask + * + * Imports task into @self + */ +void +gtd_task_list_import_task (GtdTaskList *self, + GtdTask *task, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr (GTask) gtask = NULL; + GtdProvider *provider; + ImportingTaskData *data; + + g_return_if_fail (GTD_IS_TASK_LIST (self)); + + gtask = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (gtask, gtd_task_list_import_task); + + provider = gtd_task_get_provider (task); + + data = g_new0 (ImportingTaskData, 1); + data->gtask = g_object_ref (gtask); + data->task = g_object_ref (task); + + gtd_provider_create_task (provider, + self, + gtd_task_get_title (task), + gtd_task_get_due_date (task), + NULL, + on_import_new_task_added_cb, + data); +} + +/** + * gtd_task_list_import_task_finish: + * @self: a #GtdTaskList + * @result: a #GAsyncResult + * @error: a #GError + * + * Imports task into @self + * + * Returns: (transfer full): a #GTask + */ +GTask * +gtd_task_list_import_task_finish (GtdTaskList *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, self), NULL); + g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gtd_task_list_import_task, NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + + +/** + * gtd_task_list_is_inbox: + * @self: a #GtdTaskList + * + * Retrieves whether @self is the inbox task list of its provider. + * + * Returns: %TRUE if @self is the inbox of it's provider, %FALSE otherwise. + */ +gboolean +gtd_task_list_is_inbox (GtdTaskList *self) +{ + GtdTaskListPrivate *priv; + + g_return_val_if_fail (GTD_IS_TASK_LIST (self), FALSE); + + priv = gtd_task_list_get_instance_private (self); + + return self == gtd_provider_get_inbox (priv->provider); +} diff --git a/src/core/gtd-task-list.h b/src/core/gtd-task-list.h new file mode 100644 index 0000000..6f06490 --- /dev/null +++ b/src/core/gtd-task-list.h @@ -0,0 +1,118 @@ +/* gtd-task-list.h + * + * 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 . + */ + +#ifndef GTD_TASK_LIST_H +#define GTD_TASK_LIST_H + +#include +#include +#include + +#include "gtd-object.h" +#include "gtd-types.h" + +G_BEGIN_DECLS + +#define GTD_TYPE_TASK_LIST (gtd_task_list_get_type()) + +G_DECLARE_DERIVABLE_TYPE (GtdTaskList, gtd_task_list, GTD, TASK_LIST, GtdObject) + +struct _GtdTaskListClass +{ + GtdObjectClass parent; + + /* Vfuncs */ + gboolean (*get_archived) (GtdTaskList *self); + + void (*set_archived) (GtdTaskList *self, + gboolean archived); + + /* Signal methods */ + void (*task_added) (GtdTaskList *list, + GtdTask *task); + + void (*task_updated) (GtdTaskList *list, + GtdTask *task); + + void (*task_removed) (GtdTaskList *list, + GtdTask *task); + + gpointer padding[10]; +}; + +GtdTaskList* gtd_task_list_new (GtdProvider *provider); + +GdkRGBA* gtd_task_list_get_color (GtdTaskList *list); + +void gtd_task_list_set_color (GtdTaskList *list, + const GdkRGBA *color); + +gboolean gtd_task_list_is_removable (GtdTaskList *list); + +void gtd_task_list_set_is_removable (GtdTaskList *list, + gboolean is_removable); + +const gchar* gtd_task_list_get_name (GtdTaskList *list); + +void gtd_task_list_set_name (GtdTaskList *list, + const gchar *name); + +GtdProvider* gtd_task_list_get_provider (GtdTaskList *list); + +void gtd_task_list_set_provider (GtdTaskList *self, + GtdProvider *provider); + +void gtd_task_list_add_task (GtdTaskList *list, + GtdTask *task); + +void gtd_task_list_update_task (GtdTaskList *list, + GtdTask *task); + +void gtd_task_list_remove_task (GtdTaskList *list, + GtdTask *task); + +gboolean gtd_task_list_contains (GtdTaskList *list, + GtdTask *task); + +GtdTask* gtd_task_list_get_task_by_id (GtdTaskList *self, + const gchar *id); + +void gtd_task_list_move_task_to_position (GtdTaskList *self, + GtdTask *task, + guint new_position); + +gboolean gtd_task_list_get_archived (GtdTaskList *self); + +void gtd_task_list_set_archived (GtdTaskList *self, + gboolean archived); + +void gtd_task_list_import_task (GtdTaskList *self, + GtdTask *task, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +GTask * gtd_task_list_import_task_finish (GtdTaskList *self, + GAsyncResult *result, + GError **error); + +gboolean gtd_task_list_is_inbox (GtdTaskList *self); + +G_END_DECLS + +#endif /* GTD_TASK_LIST_H */ diff --git a/src/core/gtd-task.c b/src/core/gtd-task.c new file mode 100644 index 0000000..6951dec --- /dev/null +++ b/src/core/gtd-task.c @@ -0,0 +1,993 @@ +/* gtd-task.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 "GtdTask" + +#include "gtd-debug.h" +#include "gtd-task.h" +#include "gtd-task-list.h" + +#include + +/** + * SECTION:gtd-task + * @short_description: a task + * @title:GtdTask + * @stability:Unstable + * @see_also:#GtdTaskList + * + * A #GtdTask is an object that represents a task. All #GtdTasks + * must be inside a #GtdTaskList. + */ + +typedef struct +{ + gchar *description; + GtdTaskList *list; + + GDateTime *creation_date; + GDateTime *completion_date; + GDateTime *due_date; + + gchar *title; + + gint32 priority; + gint64 position; + gboolean complete; + gboolean important; +} GtdTaskPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GtdTask, gtd_task, GTD_TYPE_OBJECT) + +enum +{ + PROP_0, + PROP_COMPLETE, + PROP_DESCRIPTION, + PROP_CREATION_DATE, + PROP_DUE_DATE, + PROP_IMPORTANT, + PROP_LIST, + PROP_POSITION, + PROP_TITLE, + LAST_PROP +}; + +static void +task_list_weak_notified (gpointer data, + GObject *where_the_object_was) +{ + GtdTask *task = GTD_TASK (data); + GtdTaskPrivate *priv = gtd_task_get_instance_private (task); + priv->list = NULL; +} + +/* + * GtdTask default implementations + */ + +static GDateTime* +gtd_task_real_get_completion_date (GtdTask *self) +{ + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + + return priv->completion_date ? g_date_time_ref (priv->completion_date) : NULL; +} + +static void +gtd_task_real_set_completion_date (GtdTask *self, + GDateTime *dt) +{ + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + + g_clear_pointer (&priv->completion_date, g_date_time_unref); + priv->completion_date = dt ? g_date_time_ref (dt) : NULL; +} + +static gboolean +gtd_task_real_get_complete (GtdTask *self) +{ + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + + return priv->complete; +} + +static void +gtd_task_real_set_complete (GtdTask *self, + gboolean complete) +{ + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + GDateTime *dt; + + dt = complete ? g_date_time_new_now_local () : NULL; + gtd_task_real_set_completion_date (self, dt); + + priv->complete = complete; +} + +static GDateTime* +gtd_task_real_get_creation_date (GtdTask *self) +{ + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + + return priv->creation_date ? g_date_time_ref (priv->creation_date) : NULL; +} + +static void +gtd_task_real_set_creation_date (GtdTask *self, + GDateTime *dt) +{ + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + + g_clear_pointer (&priv->creation_date, g_date_time_unref); + priv->creation_date = dt ? g_date_time_ref (dt) : NULL; +} + +static const gchar* +gtd_task_real_get_description (GtdTask *self) +{ + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + + return priv->description ? priv->description : ""; +} + +static void +gtd_task_real_set_description (GtdTask *self, + const gchar *description) +{ + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + + g_clear_pointer (&priv->description, g_free); + priv->description = g_strdup (description); +} + +static GDateTime* +gtd_task_real_get_due_date (GtdTask *self) +{ + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + + return priv->due_date ? g_date_time_ref (priv->due_date) : NULL; +} + +static void +gtd_task_real_set_due_date (GtdTask *self, + GDateTime *due_date) +{ + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + + g_clear_pointer (&priv->due_date, g_date_time_unref); + + if (due_date) + priv->due_date = g_date_time_ref (due_date); +} + +static gboolean +gtd_task_real_get_important (GtdTask *self) +{ + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + + return priv->important; +} + +static void +gtd_task_real_set_important (GtdTask *self, + gboolean important) +{ + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + + if (priv->important == important) + return; + + priv->important = important; +} + +static gint64 +gtd_task_real_get_position (GtdTask *self) +{ + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + + return priv->position; +} + +static void +gtd_task_real_set_position (GtdTask *self, + gint64 position) +{ + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + + priv->position = position; +} + +static const gchar* +gtd_task_real_get_title (GtdTask *self) +{ + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + + return priv->title; +} + +static void +gtd_task_real_set_title (GtdTask *self, + const gchar *title) +{ + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + + g_clear_pointer (&priv->title, g_free); + priv->title = title ? g_strdup (title) : NULL; +} + + +/* + * GObject overrides + */ + +static void +gtd_task_finalize (GObject *object) +{ + GtdTask *self = (GtdTask*) object; + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + + if (priv->list) + g_object_weak_unref (G_OBJECT (priv->list), task_list_weak_notified, self); + + priv->list = NULL; + g_free (priv->description); + + G_OBJECT_CLASS (gtd_task_parent_class)->finalize (object); +} + +static void +gtd_task_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtdTask *self = GTD_TASK (object); + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + GDateTime *date; + + switch (prop_id) + { + case PROP_COMPLETE: + g_value_set_boolean (value, gtd_task_get_complete (self)); + break; + + case PROP_CREATION_DATE: + g_value_set_boxed (value, gtd_task_get_creation_date (self)); + break; + + case PROP_DESCRIPTION: + g_value_set_string (value, gtd_task_get_description (self)); + break; + + case PROP_DUE_DATE: + date = gtd_task_get_due_date (self); + g_value_set_boxed (value, date); + g_clear_pointer (&date, g_date_time_unref); + break; + + case PROP_IMPORTANT: + g_value_set_boolean (value, gtd_task_get_important (self)); + break; + + case PROP_LIST: + g_value_set_object (value, priv->list); + break; + + case PROP_POSITION: + g_value_set_int64 (value, gtd_task_get_position (self)); + break; + + case PROP_TITLE: + g_value_set_string (value, gtd_task_get_title (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtd_task_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtdTask *self = GTD_TASK (object); + + switch (prop_id) + { + case PROP_COMPLETE: + gtd_task_set_complete (self, g_value_get_boolean (value)); + break; + + case PROP_CREATION_DATE: + gtd_task_set_creation_date (self, g_value_get_boxed (value)); + break; + + case PROP_DESCRIPTION: + gtd_task_set_description (self, g_value_get_string (value)); + break; + + case PROP_DUE_DATE: + gtd_task_set_due_date (self, g_value_get_boxed (value)); + break; + + case PROP_IMPORTANT: + gtd_task_set_important (self, g_value_get_boolean (value)); + break; + + case PROP_LIST: + gtd_task_set_list (self, g_value_get_object (value)); + break; + + case PROP_POSITION: + gtd_task_set_position (self, g_value_get_int64 (value)); + break; + + case PROP_TITLE: + gtd_task_set_title (self, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtd_task_class_init (GtdTaskClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + klass->get_complete = gtd_task_real_get_complete; + klass->set_complete = gtd_task_real_set_complete; + klass->get_creation_date = gtd_task_real_get_creation_date; + klass->set_creation_date = gtd_task_real_set_creation_date; + klass->get_completion_date = gtd_task_real_get_completion_date; + klass->set_completion_date = gtd_task_real_set_completion_date; + klass->get_description = gtd_task_real_get_description; + klass->set_description = gtd_task_real_set_description; + klass->get_due_date = gtd_task_real_get_due_date; + klass->set_due_date = gtd_task_real_set_due_date; + klass->get_important = gtd_task_real_get_important; + klass->set_important = gtd_task_real_set_important; + klass->get_position = gtd_task_real_get_position; + klass->set_position = gtd_task_real_set_position; + klass->get_title = gtd_task_real_get_title; + klass->set_title = gtd_task_real_set_title; + + object_class->finalize = gtd_task_finalize; + object_class->get_property = gtd_task_get_property; + object_class->set_property = gtd_task_set_property; + + /** + * GtdTask::complete: + * + * @TRUE if the task is marked as complete or @FALSE otherwise. Usually + * represented by a checkbox at user interfaces. + */ + g_object_class_install_property ( + object_class, + PROP_COMPLETE, + g_param_spec_boolean ("complete", + "Whether the task is completed or not", + "Whether the task is marked as completed by the user", + FALSE, + G_PARAM_READWRITE)); + + /** + * GtdTask::creation-date: + * + * The @GDateTime that represents the time in which the task was created. + */ + g_object_class_install_property ( + object_class, + PROP_CREATION_DATE, + g_param_spec_boxed ("creation-date", + "Creation date of the task", + "The day the task was created.", + G_TYPE_DATE_TIME, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GtdTask::description: + * + * Description of the task. + */ + g_object_class_install_property ( + object_class, + PROP_DESCRIPTION, + g_param_spec_string ("description", + "Description of the task", + "Optional string describing the task", + NULL, + G_PARAM_READWRITE)); + + /** + * GtdTask::due-date: + * + * The @GDateTime that represents the time in which the task should + * be completed before. + */ + g_object_class_install_property ( + object_class, + PROP_DUE_DATE, + g_param_spec_boxed ("due-date", + "End date of the task", + "The day the task is supposed to be completed", + G_TYPE_DATE_TIME, + G_PARAM_READWRITE)); + + /** + * GtdTask::important: + * + * @TRUE if the task is important, @FALSE otherwise. + */ + g_object_class_install_property ( + object_class, + PROP_IMPORTANT, + g_param_spec_boolean ("important", + "Whether the task is important or not", + "Whether the task is important or not", + FALSE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + /** + * GtdTask::list: + * + * The @GtdTaskList that contains this task. + */ + g_object_class_install_property ( + object_class, + PROP_LIST, + g_param_spec_object ("list", + "List of the task", + "The list that owns this task", + GTD_TYPE_TASK_LIST, + G_PARAM_READWRITE)); + + /** + * GtdTask::position: + * + * Position of the task, -1 if not set. + */ + g_object_class_install_property ( + object_class, + PROP_POSITION, + g_param_spec_int64 ("position", + "Position of the task", + "The position of the task. -1 means no position, and tasks will be sorted alphabetically.", + -1, + G_MAXINT64, + 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GtdTask::title: + * + * The title of the task, usually the task name. + */ + g_object_class_install_property ( + object_class, + PROP_TITLE, + g_param_spec_string ("title", + "Title of the task", + "The title of the task", + NULL, + G_PARAM_READWRITE)); +} + +static void +gtd_task_init (GtdTask *self) +{ + GtdTaskPrivate *priv = gtd_task_get_instance_private (self); + + priv->position = -1; +} + +/** + * gtd_task_new: + * + * Creates a new #GtdTask + * + * Returns: (transfer full): a #GtdTask + */ +GtdTask * +gtd_task_new (void) +{ + return g_object_new (GTD_TYPE_TASK, NULL); +} + +/** + * gtd_task_get_complete: + * @self: a #GtdTask + * + * Retrieves whether the task is complete or not. + * + * Returns: %TRUE if the task is complete, %FALSE otherwise + */ +gboolean +gtd_task_get_complete (GtdTask *self) +{ + g_return_val_if_fail (GTD_IS_TASK (self), FALSE); + + return GTD_TASK_CLASS (G_OBJECT_GET_CLASS (self))->get_complete (self); +} + +/** + * gtd_task_set_complete: + * @self: a #GtdTask + * @complete: the new value + * + * Updates the complete state of @task. + */ +void +gtd_task_set_complete (GtdTask *task, + gboolean complete) +{ + g_return_if_fail (GTD_IS_TASK (task)); + + if (gtd_task_get_complete (task) == complete) + return; + + GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->set_complete (task, complete); + + g_object_notify (G_OBJECT (task), "complete"); +} + +/** + * gtd_task_get_creation_date: + * @self: a #GtdTask + * + * Returns the #GDateTime that represents the task's creation date. + * The value is referenced for thread safety. Returns %NULL if + * no date is set. + * + * Returns: (transfer full): the internal #GDateTime referenced + * for thread safety, or %NULL. Unreference it after use. + */ +GDateTime* +gtd_task_get_creation_date (GtdTask *task) +{ + g_return_val_if_fail (GTD_IS_TASK (task), NULL); + + return GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->get_creation_date (task); +} + +/** + * gtd_task_set_creation_date: + * @self: a #GtdTask + * + * Sets the creation date of @task. + */ +void +gtd_task_set_creation_date (GtdTask *task, + GDateTime *dt) +{ + g_return_if_fail (GTD_IS_TASK (task)); + + if (gtd_task_get_creation_date (task) == dt) + return; + + GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->set_creation_date (task, dt); + + g_object_notify (G_OBJECT (task), "complete"); +} + +/** + * gtd_task_get_completion_date: + * @self: a #GtdTask + * + * Returns the #GDateTime that represents the task's completion date. + * Returns %NULL if no date is set. + * + * Returns: (transfer full)(nullable): the internal #GDateTime or %NULL. + * Unreference it after use. + */ +GDateTime* +gtd_task_get_completion_date (GtdTask *task) +{ + g_return_val_if_fail (GTD_IS_TASK (task), NULL); + + return GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->get_completion_date (task); +} + +/** + * gtd_task_get_description: + * @self: a #GtdTask + * + * Retrieves the description of the task. + * + * Returns: (transfer none): the description of @task + */ +const gchar* +gtd_task_get_description (GtdTask *task) +{ + g_return_val_if_fail (GTD_IS_TASK (task), NULL); + + return GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->get_description (task); +} + +/** + * gtd_task_set_description: + * @self: a #GtdTask + * @description: (nullable): the new description, or %NULL + * + * Updates the description of @task. The string is not stripped off of + * spaces to preserve user data. + */ +void +gtd_task_set_description (GtdTask *task, + const gchar *description) +{ + GtdTaskPrivate *priv; + + g_assert (GTD_IS_TASK (task)); + g_assert (g_utf8_validate (description, -1, NULL)); + + priv = gtd_task_get_instance_private (task); + + if (g_strcmp0 (priv->description, description) == 0) + return; + + GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->set_description (task, description); + + g_object_notify (G_OBJECT (task), "description"); +} + +/** + * gtd_task_get_due_date: + * @self: a #GtdTask + * + * Returns the #GDateTime that represents the task's due date. + * The value is referenced for thread safety. Returns %NULL if + * no date is set. + * + * Returns: (transfer full) (nullable): the internal #GDateTime referenced + * for thread safety, or %NULL. Unreference it after use. + */ +GDateTime* +gtd_task_get_due_date (GtdTask *task) +{ + g_return_val_if_fail (GTD_IS_TASK (task), NULL); + + return GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->get_due_date (task); +} + +/** + * gtd_task_set_due_date: + * @self: a #GtdTask + * @dt: (nullable): a #GDateTime + * + * Updates the internal @GtdTask::due-date property. + */ +void +gtd_task_set_due_date (GtdTask *task, + GDateTime *dt) +{ + g_autoptr (GDateTime) current_dt = NULL; + + g_assert (GTD_IS_TASK (task)); + + current_dt = gtd_task_get_due_date (task); + + /* Don't do anything if the date is equal */ + if (current_dt == dt || (current_dt && dt && g_date_time_equal (current_dt, dt))) + return; + + GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->set_due_date (task, dt); + + g_object_notify (G_OBJECT (task), "due-date"); +} + +/** + * gtd_task_get_important: + * @self: a #GtdTask + * + * Retrieves whether @self is @important or not. + * + * Returns: %TRUE if @self is important, %FALSE otherwise + */ +gboolean +gtd_task_get_important (GtdTask *self) +{ + g_return_val_if_fail (GTD_IS_TASK (self), FALSE); + + return GTD_TASK_GET_CLASS (self)->get_important (self); +} + +/** + * gtd_task_set_important: + * @self: a #GtdTask + * @important: whether @self is important or not + * + * Sets whether @self is @important or not. + */ +void +gtd_task_set_important (GtdTask *self, + gboolean important) +{ + g_return_if_fail (GTD_IS_TASK (self)); + + important = !!important; + + GTD_TASK_GET_CLASS (self)->set_important (self, important); + g_object_notify (G_OBJECT (self), "important"); +} + +/** + * gtd_task_get_list: + * + * Returns a weak reference to the #GtdTaskList that + * owns the given @task. + * + * Returns: (transfer none): a weak reference to the + * #GtdTaskList that owns @task. Do not free after + * usage. + */ +GtdTaskList* +gtd_task_get_list (GtdTask *task) +{ + GtdTaskPrivate *priv; + + g_return_val_if_fail (GTD_IS_TASK (task), NULL); + + priv = gtd_task_get_instance_private (task); + + return priv->list; +} + +/** + * gtd_task_set_list: + * @self: a #GtdTask + * @list: (nullable): a #GtdTaskList + * + * Sets the parent #GtdTaskList of @task. + */ +void +gtd_task_set_list (GtdTask *task, + GtdTaskList *list) +{ + GtdTaskPrivate *priv; + + g_assert (GTD_IS_TASK (task)); + g_assert (GTD_IS_TASK_LIST (list)); + + priv = gtd_task_get_instance_private (task); + + if (priv->list == list) + return; + + if (priv->list) + g_object_weak_unref (G_OBJECT (priv->list), task_list_weak_notified, task); + + priv->list = list; + g_object_weak_ref (G_OBJECT (priv->list), task_list_weak_notified, task); + g_object_notify (G_OBJECT (task), "list"); +} + +/** + * gtd_task_get_position: + * @self: a #GtdTask + * + * Returns the position of @task inside the parent #GtdTaskList, + * or -1 if not set. + * + * Returns: the position of the task, or -1 + */ +gint64 +gtd_task_get_position (GtdTask *self) +{ + g_return_val_if_fail (GTD_IS_TASK (self), -1); + + return GTD_TASK_CLASS (G_OBJECT_GET_CLASS (self))->get_position (self); +} + +/** + * gtd_task_set_position: + * @self: a #GtdTask + * @position: the priority of @task, or -1 + * + * Sets the @task position inside the parent #GtdTaskList. It + * is up to the interface to handle two or more #GtdTask with + * the same position value. + */ +void +gtd_task_set_position (GtdTask *self, + gint64 position) +{ + g_return_if_fail (GTD_IS_TASK (self)); + + if (gtd_task_get_position (self) == position) + return; + + GTD_TASK_CLASS (G_OBJECT_GET_CLASS (self))->set_position (self, position); + + g_object_notify (G_OBJECT (self), "position"); +} + +/** + * gtd_task_get_title: + * @self: a #GtdTask + * + * Retrieves the title of the task, or %NULL. + * + * Returns: (transfer none): the title of @task, or %NULL + */ +const gchar* +gtd_task_get_title (GtdTask *task) +{ + const gchar *title; + + g_return_val_if_fail (GTD_IS_TASK (task), NULL); + + title = GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->get_title (task); + + return title ? title : ""; +} + +/** + * gtd_task_set_title: + * @self: a #GtdTask + * @title: (nullable): the new title, or %NULL + * + * Updates the title of @task. The string is stripped off of + * leading spaces. + */ +void +gtd_task_set_title (GtdTask *task, + const gchar *title) +{ + const gchar *current_title; + + g_return_if_fail (GTD_IS_TASK (task)); + g_return_if_fail (g_utf8_validate (title, -1, NULL)); + + current_title = gtd_task_get_title (task); + + if (g_strcmp0 (current_title, title) == 0) + return; + + GTD_TASK_CLASS (G_OBJECT_GET_CLASS (task))->set_title (task, title); + + g_object_notify (G_OBJECT (task), "title"); +} + +/** + * gtd_task_compare: + * @t1: (nullable): a #GtdTask + * @t2: (nullable): a #GtdTask + * + * Compare @t1 and @t2. + * + * Returns: %-1 if @t1 comes before @t2, %1 for the opposite, %0 if they're equal + */ +gint +gtd_task_compare (GtdTask *t1, + GtdTask *t2) +{ + GDateTime *dt1; + GDateTime *dt2; + gchar *txt1; + gchar *txt2; + gint retval; + + if (!t1 && !t2) + return 0; + if (!t1) + return 1; + if (!t2) + return -1; + + /* + * The custom position overrides any comparison we can make. To keep compatibility, + * for now, we only compare by position if both tasks have a custom position set. + */ + if (gtd_task_get_position (t1) != -1 && gtd_task_get_position (t2) != -1) + { + retval = gtd_task_get_position (t1) - gtd_task_get_position (t2); + + if (retval != 0) + return retval; + } + + /* Compare by due date */ + dt1 = gtd_task_get_due_date (t1); + dt2 = gtd_task_get_due_date (t2); + + if (!dt1 && !dt2) + retval = 0; + else if (!dt1) + retval = 1; + else if (!dt2) + retval = -1; + else + retval = g_date_time_compare (dt1, dt2); + + if (dt1) + g_date_time_unref (dt1); + if (dt2) + g_date_time_unref (dt2); + + if (retval != 0) + return retval; + + /* Compare by creation date */ + dt1 = gtd_task_get_creation_date (t1); + dt2 = gtd_task_get_creation_date (t2); + + if (!dt1 && !dt2) + retval = 0; + else if (!dt1) + retval = 1; + else if (!dt2) + retval = -1; + else + retval = g_date_time_compare (dt1, dt2); + + g_clear_pointer (&dt1, g_date_time_unref); + g_clear_pointer (&dt2, g_date_time_unref); + + if (retval != 0) + return retval; + + /* If they're equal up to now, compare by title */ + txt1 = txt2 = NULL; + + txt1 = g_utf8_casefold (gtd_task_get_title (t1), -1); + txt2 = g_utf8_casefold (gtd_task_get_title (t2), -1); + + retval = g_strcmp0 (txt1, txt2); + + g_free (txt1); + g_free (txt2); + + return retval; +} + +/** + * gtd_task_get_provider: + * @self: a #GtdTaskList + * + * Utility function to retrieve the data provider that backs this + * task. Notice that this is exactly the same as writing: + * + * |[ + * GtdTaskList *list; + * GtdProvider *provider; + * + * list = gtd_task_get_list (task); + * provider = gtd_task_list_get_provider (list); + * ]| + * + * Returns: (transfer none)(nullable): the #GtdProvider of this task's list. + */ +GtdProvider* +gtd_task_get_provider (GtdTask *self) +{ + GtdTaskPrivate *priv; + + g_return_val_if_fail (GTD_IS_TASK (self), NULL); + + priv = gtd_task_get_instance_private (self); + + if (priv->list) + return gtd_task_list_get_provider (priv->list); + + return NULL; +} diff --git a/src/core/gtd-task.h b/src/core/gtd-task.h new file mode 100644 index 0000000..0c2b5aa --- /dev/null +++ b/src/core/gtd-task.h @@ -0,0 +1,122 @@ +/* gtd-task.h + * + * 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 . + */ + +#ifndef GTD_TASK_H +#define GTD_TASK_H + +#include "gtd-object.h" + +#include + +G_BEGIN_DECLS + +#define GTD_TYPE_TASK (gtd_task_get_type()) + +G_DECLARE_DERIVABLE_TYPE (GtdTask, gtd_task, GTD, TASK, GtdObject) + +struct _GtdTaskClass +{ + GtdObjectClass parent; + + gboolean (*get_complete) (GtdTask *self); + void (*set_complete) (GtdTask *self, + gboolean complete); + + GDateTime* (*get_creation_date) (GtdTask *self); + void (*set_creation_date) (GtdTask *self, + GDateTime *dt); + + GDateTime* (*get_completion_date) (GtdTask *self); + void (*set_completion_date) (GtdTask *self, + GDateTime *dt); + + const gchar* (*get_description) (GtdTask *self); + void (*set_description) (GtdTask *self, + const gchar *description); + + GDateTime* (*get_due_date) (GtdTask *self); + void (*set_due_date) (GtdTask *self, + GDateTime *dt); + + gboolean (*get_important) (GtdTask *self); + void (*set_important) (GtdTask *self, + gboolean important); + + gint64 (*get_position) (GtdTask *self); + void (*set_position) (GtdTask *self, + gint64 position); + + const gchar* (*get_title) (GtdTask *self); + void (*set_title) (GtdTask *self, + const gchar *title); + + gpointer padding[8]; +}; + +GtdTask* gtd_task_new (void); + +gboolean gtd_task_get_complete (GtdTask *self); + +void gtd_task_set_complete (GtdTask *self, + gboolean complete); + +GDateTime* gtd_task_get_creation_date (GtdTask *self); + +void gtd_task_set_creation_date (GtdTask *self, + GDateTime *dt); + +GDateTime* gtd_task_get_completion_date (GtdTask *self); + +const gchar* gtd_task_get_description (GtdTask *self); + +void gtd_task_set_description (GtdTask *self, + const gchar *description); + +GDateTime* gtd_task_get_due_date (GtdTask *self); + +void gtd_task_set_due_date (GtdTask *self, + GDateTime *dt); + +gboolean gtd_task_get_important (GtdTask *self); + +void gtd_task_set_important (GtdTask *self, + gboolean important); + +GtdTaskList* gtd_task_get_list (GtdTask *self); + +void gtd_task_set_list (GtdTask *self, + GtdTaskList *list); + +gint64 gtd_task_get_position (GtdTask *self); + +void gtd_task_set_position (GtdTask *self, + gint64 position); + +const gchar* gtd_task_get_title (GtdTask *self); + +void gtd_task_set_title (GtdTask *self, + const gchar *title); + +gint gtd_task_compare (GtdTask *t1, + GtdTask *t2); + +GtdProvider* gtd_task_get_provider (GtdTask *self); + +G_END_DECLS + +#endif /* GTD_TASK_H */ -- cgit v1.2.3