gem-graph-client/libide/core/ide-notifications.c

517 lines
16 KiB
C
Raw Permalink Normal View History

/* ide-notifications.c
*
* Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "ide-notifications"
#include "config.h"
#include "ide-macros.h"
#include "ide-notifications.h"
struct _IdeNotifications
{
IdeObject parent_instance;
};
typedef struct
{
gdouble progress;
guint total;
guint imprecise;
} Progress;
typedef struct
{
const gchar *id;
IdeNotification *notif;
} Find;
static void list_model_iface_init (GListModelInterface *iface);
G_DEFINE_FINAL_TYPE_WITH_CODE (IdeNotifications, ide_notifications, IDE_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
enum {
PROP_0,
PROP_HAS_PROGRESS,
PROP_PROGRESS,
PROP_PROGRESS_IS_IMPRECISE,
N_PROPS
};
static GParamSpec *properties [N_PROPS];
static void
ide_notifications_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
IdeNotifications *self = IDE_NOTIFICATIONS (object);
switch (prop_id)
{
case PROP_HAS_PROGRESS:
g_value_set_boolean (value, ide_notifications_get_has_progress (self));
break;
case PROP_PROGRESS:
g_value_set_double (value, ide_notifications_get_progress (self));
break;
case PROP_PROGRESS_IS_IMPRECISE:
g_value_set_boolean (value, ide_notifications_get_progress_is_imprecise (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_notifications_child_notify_progress_cb (IdeNotifications *self,
GParamSpec *pspec,
IdeNotification *child)
{
g_assert (IDE_IS_NOTIFICATIONS (self));
g_assert (IDE_IS_NOTIFICATION (child));
ide_object_notify_by_pspec (self, properties [PROP_PROGRESS]);
}
static void
ide_notifications_add (IdeObject *object,
IdeObject *sibling,
IdeObject *child,
IdeObjectLocation location)
{
IdeNotifications *self = (IdeNotifications *)object;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_NOTIFICATIONS (object));
g_assert (IDE_IS_OBJECT (child));
if (!IDE_IS_NOTIFICATION (child))
{
g_warning ("Attempt to add something other than an IdeNotification is not allowed");
return;
}
g_signal_connect_object (child,
"notify::progress",
G_CALLBACK (ide_notifications_child_notify_progress_cb),
self,
G_CONNECT_SWAPPED);
IDE_OBJECT_CLASS (ide_notifications_parent_class)->add (object, sibling, child, location);
g_list_model_items_changed (G_LIST_MODEL (object), ide_object_get_position (child), 0, 1);
ide_object_notify_by_pspec (self, properties [PROP_HAS_PROGRESS]);
ide_object_notify_by_pspec (self, properties [PROP_PROGRESS_IS_IMPRECISE]);
ide_object_notify_by_pspec (self, properties [PROP_PROGRESS]);
}
static void
ide_notifications_remove (IdeObject *object,
IdeObject *child)
{
IdeNotifications *self = (IdeNotifications *)object;
guint position;
g_assert (IDE_IS_NOTIFICATIONS (self));
g_assert (IDE_IS_OBJECT (child));
g_signal_handlers_disconnect_by_func (child,
G_CALLBACK (ide_notifications_child_notify_progress_cb),
self);
position = ide_object_get_position (child);
IDE_OBJECT_CLASS (ide_notifications_parent_class)->remove (object, child);
g_list_model_items_changed (G_LIST_MODEL (object), position, 1, 0);
ide_object_notify_by_pspec (self, properties [PROP_HAS_PROGRESS]);
ide_object_notify_by_pspec (self, properties [PROP_PROGRESS_IS_IMPRECISE]);
ide_object_notify_by_pspec (self, properties [PROP_PROGRESS]);
}
static void
ide_notifications_class_init (IdeNotificationsClass *klass)
{
GObjectClass *g_object_class = G_OBJECT_CLASS (klass);
IdeObjectClass *object_class = IDE_OBJECT_CLASS (klass);
g_object_class->get_property = ide_notifications_get_property;
object_class->add = ide_notifications_add;
object_class->remove = ide_notifications_remove;
/**
* IdeNotifications:has-progress:
*
* The "has-progress" property denotes if any of the notifications
* have progress supported.
*
* Since: 3.32
*/
properties [PROP_HAS_PROGRESS] =
g_param_spec_boolean ("has-progress",
"Has Progress",
"If any of the notifications have progress",
FALSE,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
/**
* IdeNotifications:progress:
*
* The "progress" property is the combination of all of the notifications
* currently monitored. It is updated when child notifications progress
* changes.
*
* Since: 3.32
*/
properties [PROP_PROGRESS] =
g_param_spec_double ("progress",
"Progress",
"The combined process of all child notifications",
0.0, 1.0, 0.0,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
/**
* IdeNotifications:progress-is-imprecise:
*
* The "progress-is-imprecise" property indicates that all progress-bearing
* notifications are imprecise.
*
* Since: 3.32
*/
properties [PROP_PROGRESS_IS_IMPRECISE] =
g_param_spec_boolean ("progress-is-imprecise",
"Progress is Imprecise",
"If all of the notifications have imprecise progress",
FALSE,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (g_object_class, N_PROPS, properties);
}
static void
ide_notifications_init (IdeNotifications *self)
{
#if 0
g_autoptr(IdeNotification) notif = NULL;
g_autoptr(IdeNotification) notif2 = NULL;
g_autoptr(IdeNotification) notif3 = NULL;
g_autoptr(IdeNotification) notif4 = NULL;
g_autoptr(IdeNotification) notif5 = NULL;
g_autoptr(IdeNotification) notif6 = NULL;
g_autoptr(IdeNotification) notif7 = NULL;
g_autoptr(GIcon) icon1 = NULL;
g_autoptr(GIcon) icon2 = NULL;
g_autoptr(GIcon) icon4 = NULL;
g_autoptr(GIcon) icon5 = NULL;
g_autoptr(GIcon) icon6 = NULL;
g_autoptr(GIcon) icon7 = NULL;
notif = ide_notification_new ();
ide_notification_set_title (notif, "Builder ready.");
ide_notification_set_has_progress (notif, FALSE);
ide_notification_add_button (notif, "Foo", (icon1 = g_icon_new_for_string ("media-playback-pause-symbolic", NULL)), "debugger.pause");
ide_notifications_add_notification (self, notif);
notif2 = ide_notification_new ();
ide_notification_set_title (notif2, "Downloading libdazzle…");
ide_notification_set_has_progress (notif2, TRUE);
ide_notification_set_progress (notif2, .75);
ide_notification_set_default_action (notif2, "win.close");
ide_notification_add_button (notif2, "Foo", (icon2 = g_icon_new_for_string ("process-stop-symbolic", NULL)), "build-manager.stop");
ide_notifications_add_notification (self, notif2);
notif3 = ide_notification_new ();
ide_notification_set_title (notif3, "SDK Not Installed");
ide_notification_set_body (notif3, "The org.gnome.Calculator.json build profile requires the org.gnome.Platform runtime. Install it to allow this project to be built.");
ide_notification_set_has_progress (notif3, FALSE);
ide_notification_set_progress (notif3, 0);
ide_notification_add_button (notif3, "Download and Install", NULL, "win.close");
ide_notification_set_default_action (notif3, "win.close");
ide_notification_set_urgent (notif3, TRUE);
ide_notifications_add_notification (self, notif3);
notif4 = ide_notification_new ();
ide_notification_set_title (notif4, "Code Analytics Unavailable");
ide_notification_set_body (notif4, "Code highlighting, error detection, and macros are not fully available, due to this project not being built recently. Rebuild to fully enable these features.");
ide_notification_set_has_progress (notif4, FALSE);
ide_notification_set_progress (notif4, 0);
ide_notification_set_default_action (notif4, "win.close");
ide_notifications_add_notification (self, notif4);
notif5 = ide_notification_new ();
ide_notification_set_title (notif5, "Running Partial Build");
ide_notification_set_body (notif5, "Diagnostics and autocompletion may be limited until complete.");
ide_notification_set_has_progress (notif5, TRUE);
ide_notification_set_progress_is_imprecise (notif5, TRUE);
ide_notification_set_progress (notif5, 0);
ide_notification_add_button (notif5, NULL, (icon5 = g_icon_new_for_string ("process-stop-symbolic", NULL)), "win.close");
ide_notifications_add_notification (self, notif5);
notif6 = ide_notification_new ();
ide_notification_set_title (notif6, "Indexing Source Code");
ide_notification_set_body (notif6, "Search, diagnostics, and autocompletion may be limited until complete.");
ide_notification_set_has_progress (notif6, TRUE);
ide_notification_set_progress (notif6, 0);
ide_notification_set_progress_is_imprecise (notif6, TRUE);
ide_notification_add_button (notif6, NULL, (icon6 = g_icon_new_for_string ("media-playback-pause-symbolic", NULL)), "win.close");
ide_notifications_add_notification (self, notif6);
notif7 = ide_notification_new ();
ide_notification_set_title (notif7, "Downloading org.gnome.Platform");
ide_notification_set_body (notif7, "3 minutes remaining");
ide_notification_set_has_progress (notif7, TRUE);
ide_notification_set_progress (notif7, 0);
ide_notification_add_button (notif7, NULL, (icon7 = g_icon_new_for_string ("process-stop-symbolic", NULL)), "win.close");
ide_notifications_add_notification (self, notif7);
ide_notification_withdraw_in_seconds (notif, 10);
ide_notification_withdraw_in_seconds (notif2, 12);
ide_notification_withdraw_in_seconds (notif3, 14);
ide_notification_withdraw_in_seconds (notif4, 16);
ide_notification_withdraw_in_seconds (notif5, 18);
#endif
}
/**
* ide_notifications_new:
*
* Create a new #IdeNotifications.
*
* Usually, creating this is not necessary, as the #IdeContext root
* #IdeObject will create it automatically.
*
* Returns: (transfer full): a newly created #IdeNotifications
*
* Since: 3.32
*/
IdeNotifications *
ide_notifications_new (void)
{
return g_object_new (IDE_TYPE_NOTIFICATIONS, NULL);
}
/**
* ide_notifications_add_notification:
* @self: an #IdeNotifications
* @notification: an #IdeNotification
*
* Adds @notification as a child of @self, sorting it by priority
* and urgency.
*
* Since: 3.32
*/
void
ide_notifications_add_notification (IdeNotifications *self,
IdeNotification *notification)
{
g_return_if_fail (IDE_IS_MAIN_THREAD ());
g_return_if_fail (IDE_IS_NOTIFICATIONS (self));
g_return_if_fail (IDE_IS_NOTIFICATION (notification));
ide_object_insert_sorted (IDE_OBJECT (self),
IDE_OBJECT (notification),
(GCompareDataFunc)ide_notification_compare,
NULL);
}
static GType
ide_notifications_get_item_type (GListModel *model)
{
return IDE_TYPE_NOTIFICATION;
}
static guint
ide_notifications_get_n_items (GListModel *model)
{
return ide_object_get_n_children (IDE_OBJECT (model));
}
static gpointer
ide_notifications_get_item (GListModel *model,
guint position)
{
return ide_object_get_nth_child (IDE_OBJECT (model), position);
}
static void
list_model_iface_init (GListModelInterface *iface)
{
iface->get_item_type = ide_notifications_get_item_type;
iface->get_n_items = ide_notifications_get_n_items;
iface->get_item = ide_notifications_get_item;
}
static void
collect_progress_cb (gpointer item,
gpointer user_data)
{
IdeNotification *notif = item;
Progress *prog = user_data;
g_assert (IDE_IS_NOTIFICATION (notif));
g_assert (prog != NULL);
if (ide_notification_get_has_progress (notif))
{
if (ide_notification_get_progress_is_imprecise (notif))
prog->imprecise++;
else
prog->progress += ide_notification_get_progress (notif);
prog->total++;
}
}
/**
* ide_notifications_get_progress:
* @self: a #IdeNotifications
*
* Gets the combined progress of the notifications contained in this
* #IdeNotifications object.
*
* Returns: A double between 0.0 and 1.0
*
* Since: 3.32
*/
gdouble
ide_notifications_get_progress (IdeNotifications *self)
{
Progress prog = {0};
g_return_val_if_fail (IDE_IS_NOTIFICATIONS (self), 0.0);
ide_object_lock (IDE_OBJECT (self));
ide_object_foreach (IDE_OBJECT (self), collect_progress_cb, &prog);
ide_object_unlock (IDE_OBJECT (self));
if (prog.total > 0)
{
if (prog.imprecise != prog.total)
return prog.progress / (gdouble)(prog.total - prog.imprecise);
else
return prog.progress / (gdouble)prog.total;
}
return 0.0;
}
/**
* ide_notifications_get_has_progress:
* @self: a #IdeNotifications
*
* Gets if any of the notification support progress updates.
*
* Returns: %TRUE if any notification has progress
*
* Since: 3.32
*/
gboolean
ide_notifications_get_has_progress (IdeNotifications *self)
{
Progress prog = {0};
g_return_val_if_fail (IDE_IS_NOTIFICATIONS (self), 0.0);
ide_object_lock (IDE_OBJECT (self));
ide_object_foreach (IDE_OBJECT (self), collect_progress_cb, &prog);
ide_object_unlock (IDE_OBJECT (self));
return prog.total > 0;
}
/**
* ide_notifications_get_progress_is_imprecise:
* @self: a #IdeNotifications
*
* Checks if all of the notifications with progress are imprecise.
*
* Returns: %TRUE if all progress-supporting notifications are imprecise.
*
* Since: 3.32
*/
gboolean
ide_notifications_get_progress_is_imprecise (IdeNotifications *self)
{
Progress prog = {0};
g_return_val_if_fail (IDE_IS_NOTIFICATIONS (self), 0.0);
ide_object_lock (IDE_OBJECT (self));
ide_object_foreach (IDE_OBJECT (self), collect_progress_cb, &prog);
ide_object_unlock (IDE_OBJECT (self));
if (prog.total > 0)
return prog.imprecise == prog.total;
return FALSE;
}
static void
find_by_id (gpointer item,
gpointer user_data)
{
IdeNotification *notif = item;
Find *find = user_data;
g_autofree gchar *id = NULL;
if (find->notif)
return;
id = ide_notification_dup_id (notif);
if (ide_str_equal0 (find->id, id))
find->notif = g_object_ref (notif);
}
/**
* ide_notifications_find_by_id:
* @self: a #IdeNotifications
* @id: the id of the notification
*
* Finds the first #IdeNotification registered with @self with
* #IdeNotification:id of @id.
*
* Returns: (transfer full) (nullable): an #IdeNotification or %NULL
*
* Since: 3.32
*/
IdeNotification *
ide_notifications_find_by_id (IdeNotifications *self,
const gchar *id)
{
Find find = { id, NULL };
g_return_val_if_fail (IDE_IS_NOTIFICATIONS (self), NULL);
g_return_val_if_fail (id != NULL, NULL);
ide_object_lock (IDE_OBJECT (self));
ide_object_foreach (IDE_OBJECT (self), find_by_id, &find);
ide_object_unlock (IDE_OBJECT (self));
return g_steal_pointer (&find.notif);
}