Files
federated-calendar/src/gcal-subscriber-view.c
T
Georges Basile Stavracas Neto f3da1b2cec subscriber-view: iter the single day table
When we modify an event, the start date may change
and there is no way to retrieve it back. Since we're
relying on the start date to get the cell number and,
remove the widget, it leads to a crash because the
widget is not removed.

Fix that by brute-forcely search the widget in the
correct table.
2016-05-21 18:53:37 -03:00

496 lines
18 KiB
C

/* -*- mode: c; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
* gcal-subscriber-view.c
*
* Copyright (C) 2014 - Erick Pérez Castellanos
*
* 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 2 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/>.
*/
#include "gcal-subscriber-view.h"
#include "gcal-subscriber-view-private.h"
#include "gcal-event.h"
#include "gcal-event-widget.h"
#include "gcal-view.h"
enum
{
EVENT_ACTIVATED,
NUM_SIGNALS
};
static guint signals[NUM_SIGNALS] = { 0, };
static void event_activated (GcalEventWidget *widget,
gpointer user_data);
static void event_visibility_changed (GtkWidget *widget,
gpointer user_data);
static void gcal_data_model_subscriber_interface_init (ECalDataModelSubscriberInterface *iface);
static void gcal_subscriber_view_finalize (GObject *object);
static void gcal_subscriber_view_add (GtkContainer *container,
GtkWidget *widget);
static void gcal_subscriber_view_remove (GtkContainer *container,
GtkWidget *widget);
static void gcal_subscriber_view_forall (GtkContainer *container,
gboolean include_internals,
GtkCallback callback,
gpointer callback_data);
static guint gcal_subscriber_view_get_child_cell (GcalSubscriberView *subscriber,
GcalEventWidget *child);
static void gcal_subscriber_view_clear_state (GcalSubscriberView *subscriber_view);
static void gcal_subscriber_view_component_added (ECalDataModelSubscriber *subscriber,
ECalClient *client,
ECalComponent *comp);
static void gcal_subscriber_view_component_modified (ECalDataModelSubscriber *subscriber,
ECalClient *client,
ECalComponent *comp);
static void gcal_subscriber_view_component_removed (ECalDataModelSubscriber *subscriber,
ECalClient *client,
const gchar *uid,
const gchar *rid);
static void gcal_subscriber_view_freeze (ECalDataModelSubscriber *subscriber);
static void gcal_subscriber_view_thaw (ECalDataModelSubscriber *subscriber);
G_DEFINE_TYPE_WITH_CODE (GcalSubscriberView, gcal_subscriber_view, GTK_TYPE_CONTAINER,
G_ADD_PRIVATE (GcalSubscriberView)
G_IMPLEMENT_INTERFACE (E_TYPE_CAL_DATA_MODEL_SUBSCRIBER,
gcal_data_model_subscriber_interface_init));
static void
event_activated (GcalEventWidget *widget,
gpointer user_data)
{
/* FIXME: implement clear_state vfunc in descendants */
gcal_subscriber_view_clear_state (GCAL_SUBSCRIBER_VIEW (user_data));
g_signal_emit (GCAL_SUBSCRIBER_VIEW (user_data), signals[EVENT_ACTIVATED], 0, widget);
}
static void
event_visibility_changed(GtkWidget *widget,
gpointer user_data)
{
GcalSubscriberViewPrivate *priv = GCAL_SUBSCRIBER_VIEW (user_data)->priv;
priv->children_changed = TRUE;
}
static void
gcal_subscriber_view_class_init (GcalSubscriberViewClass *klass)
{
GtkContainerClass *container_class;
klass->get_child_cell = gcal_subscriber_view_get_child_cell;
G_OBJECT_CLASS (klass)->finalize = gcal_subscriber_view_finalize;
container_class = GTK_CONTAINER_CLASS (klass);
container_class->add = gcal_subscriber_view_add;
container_class->remove = gcal_subscriber_view_remove;
container_class->forall = gcal_subscriber_view_forall;
signals[EVENT_ACTIVATED] = g_signal_new ("event-activated", GCAL_TYPE_SUBSCRIBER_VIEW, G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GcalSubscriberViewClass, event_activated),
NULL, NULL, NULL,
G_TYPE_NONE, 1, GCAL_TYPE_EVENT_WIDGET);
}
static void
gcal_subscriber_view_init (GcalSubscriberView *self)
{
GcalSubscriberViewPrivate *priv;
priv = gcal_subscriber_view_get_instance_private (self);
self->priv = priv;
priv->children = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_list_free);
priv->single_cell_children = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) g_list_free);
priv->overflow_cells = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) g_list_free);
priv->hidden_as_overflow = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
}
static void
gcal_data_model_subscriber_interface_init (ECalDataModelSubscriberInterface *iface)
{
iface->component_added = gcal_subscriber_view_component_added;
iface->component_modified = gcal_subscriber_view_component_modified;
iface->component_removed = gcal_subscriber_view_component_removed;
iface->freeze = gcal_subscriber_view_freeze;
iface->thaw = gcal_subscriber_view_thaw;
}
static void
gcal_subscriber_view_finalize (GObject *object)
{
GcalSubscriberViewPrivate *priv;
priv = gcal_subscriber_view_get_instance_private (GCAL_SUBSCRIBER_VIEW (object));
g_hash_table_destroy (priv->children);
g_hash_table_destroy (priv->single_cell_children);
g_hash_table_destroy (priv->overflow_cells);
g_hash_table_destroy (priv->hidden_as_overflow);
if (priv->multi_cell_children != NULL)
g_list_free (priv->multi_cell_children);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (gcal_subscriber_view_parent_class)->finalize (object);
}
static void
gcal_subscriber_view_add (GtkContainer *container,
GtkWidget *widget)
{
GcalSubscriberViewPrivate *priv;
GcalEvent *event;
const gchar *uuid;
GList *l = NULL;
g_return_if_fail (GCAL_IS_EVENT_WIDGET (widget));
g_return_if_fail (gtk_widget_get_parent (widget) == NULL);
priv = gcal_subscriber_view_get_instance_private (GCAL_SUBSCRIBER_VIEW (container));
event = gcal_event_widget_get_event (GCAL_EVENT_WIDGET (widget));
uuid = gcal_event_get_uid (event);
/* inserting in all children hash */
if (g_hash_table_lookup (priv->children, uuid) != NULL)
{
g_warning ("Event with uuid: %s already added", uuid);
gtk_widget_destroy (widget);
return;
}
priv->children_changed = TRUE;
l = g_list_append (l, widget);
g_hash_table_insert (priv->children, g_strdup (uuid), l);
if (gcal_event_is_multiday (event))
{
priv->multi_cell_children = g_list_insert_sorted (priv->multi_cell_children, widget,
(GCompareFunc) gcal_event_widget_compare_by_length);
}
else
{
guint cell_idx = gcal_subscriber_view_get_child_cell (GCAL_SUBSCRIBER_VIEW (container), GCAL_EVENT_WIDGET (widget));
l = g_hash_table_lookup (priv->single_cell_children, GINT_TO_POINTER (cell_idx));
l = g_list_insert_sorted (l, widget, (GCompareFunc) gcal_event_widget_compare_by_start_date);
if (g_list_length (l) != 1)
{
g_hash_table_steal (priv->single_cell_children, GINT_TO_POINTER (cell_idx));
}
g_hash_table_insert (priv->single_cell_children, GINT_TO_POINTER (cell_idx), l);
}
_gcal_subscriber_view_setup_child (GCAL_SUBSCRIBER_VIEW (container), widget);
}
static void
gcal_subscriber_view_remove (GtkContainer *container,
GtkWidget *widget)
{
GcalSubscriberViewPrivate *priv;
GcalEvent *event;
const gchar *uuid;
GList *l, *aux;
GtkWidget *master_widget;
g_return_if_fail (gtk_widget_get_parent (widget) == GTK_WIDGET (container));
priv = gcal_subscriber_view_get_instance_private (GCAL_SUBSCRIBER_VIEW (container));
event = gcal_event_widget_get_event (GCAL_EVENT_WIDGET (widget));
uuid = gcal_event_get_uid (event);
l = g_hash_table_lookup (priv->children, uuid);
if (l != NULL)
{
priv->children_changed = TRUE;
gtk_widget_unparent (widget);
master_widget = (GtkWidget*) l->data;
if (widget == master_widget)
{
if (g_list_find (priv->multi_cell_children, widget) != NULL)
{
priv->multi_cell_children = g_list_remove (priv->multi_cell_children, widget);
aux = g_list_next (l);
if (aux != NULL)
{
l->next = NULL;
aux->prev = NULL;
g_list_foreach (aux, (GFunc) gtk_widget_unparent, NULL);
g_list_free (aux);
}
}
else
{
GHashTableIter iter;
gpointer key, value;
/*
* When an event is changed, we can't rely on it's old date
* to remove the corresponding widget. Because of that, we have
* to iter through all the widgets to see which one matches
*/
g_hash_table_iter_init (&iter, priv->single_cell_children);
while (g_hash_table_iter_next (&iter, &key, &value))
{
GList *aux;
gboolean should_break;
should_break = FALSE;
for (aux = value; aux != NULL; aux = aux->next)
{
if (aux->data == widget)
{
aux = g_list_remove (g_list_copy (value), widget);
/*
* If we removed the event and there's no event left for
* the day, remove the key from the table. If there are
* events for that day, replace the list.
*/
if (aux == NULL)
g_hash_table_remove (priv->single_cell_children, key);
else
g_hash_table_replace (priv->single_cell_children, key, aux);
should_break = TRUE;
break;
}
}
if (should_break)
break;
}
}
}
l = g_list_remove (g_list_copy (l), widget);
if (l == NULL)
g_hash_table_remove (priv->children, uuid);
else
g_hash_table_replace (priv->children, g_strdup (uuid), l);
g_hash_table_remove (priv->hidden_as_overflow, uuid);
gtk_widget_queue_resize (GTK_WIDGET (container));
}
}
static void
gcal_subscriber_view_forall (GtkContainer *container,
gboolean include_internals,
GtkCallback callback,
gpointer callback_data)
{
GcalSubscriberViewPrivate *priv;
GList *l, *l2, *aux = NULL;
priv = gcal_subscriber_view_get_instance_private (GCAL_SUBSCRIBER_VIEW (container));
l2 = g_hash_table_get_values (priv->children);
for (l = l2; l != NULL; l = g_list_next (l))
aux = g_list_concat (aux, g_list_reverse (g_list_copy (l->data)));
g_list_free (l2);
l = aux;
while (aux != NULL)
{
GtkWidget *widget = (GtkWidget*) aux->data;
aux = aux->next;
(*callback) (widget, callback_data);
}
g_list_free (l);
}
static guint
gcal_subscriber_view_get_child_cell (GcalSubscriberView *subscriber,
GcalEventWidget *child)
{
GcalSubscriberViewClass *klass;
g_return_val_if_fail (GCAL_IS_SUBSCRIBER_VIEW (subscriber), 0);
g_return_val_if_fail (GCAL_IS_EVENT_WIDGET (child), 0);
klass = GCAL_SUBSCRIBER_VIEW_GET_CLASS (subscriber);
if (klass->get_child_cell)
return klass->get_child_cell (subscriber, child);
return 0;
}
static void
gcal_subscriber_view_clear_state (GcalSubscriberView *subscriber_view)
{
GcalSubscriberViewClass *klass;
g_return_if_fail (GCAL_IS_SUBSCRIBER_VIEW (subscriber_view));
klass = GCAL_SUBSCRIBER_VIEW_GET_CLASS (subscriber_view);
if (klass->clear_state)
klass->clear_state (subscriber_view);
}
/* ECalDataModelSubscriber interface API */
static void
gcal_subscriber_view_component_added (ECalDataModelSubscriber *subscriber,
ECalClient *client,
ECalComponent *comp)
{
GtkWidget *event_widget;
GcalEvent *event;
event = gcal_event_new (e_client_get_source (E_CLIENT (client)), comp);
event_widget = gcal_event_widget_new (event);
gcal_event_widget_set_read_only (GCAL_EVENT_WIDGET (event_widget), e_client_is_readonly (E_CLIENT (client)));
gtk_widget_show (event_widget);
gtk_container_add (GTK_CONTAINER (subscriber), event_widget);
g_clear_object (&event);
}
static void
gcal_subscriber_view_component_modified (ECalDataModelSubscriber *subscriber,
ECalClient *client,
ECalComponent *comp)
{
GcalSubscriberViewPrivate *priv;
GList *l;
GtkWidget *new_widget;
GcalEvent *event;
priv = gcal_subscriber_view_get_instance_private (GCAL_SUBSCRIBER_VIEW (subscriber));
event = gcal_event_new (e_client_get_source (E_CLIENT (client)), comp);
new_widget = gcal_event_widget_new (event);
l = g_hash_table_lookup (priv->children, gcal_event_get_uid (event));
if (l != NULL)
{
gtk_widget_destroy (l->data);
gtk_widget_show (new_widget);
gtk_container_add (GTK_CONTAINER (subscriber), new_widget);
}
else
{
g_warning ("%s: Widget with uuid: %s not found in view: %s",
G_STRFUNC, gcal_event_get_uid (event),
gtk_widget_get_name (GTK_WIDGET (subscriber)));
gtk_widget_destroy (new_widget);
}
g_clear_object (&event);
}
static void
gcal_subscriber_view_component_removed (ECalDataModelSubscriber *subscriber,
ECalClient *client,
const gchar *uid,
const gchar *rid)
{
GcalSubscriberViewPrivate *priv;
const gchar *sid;
gchar *uuid;
GList *l;
priv = gcal_subscriber_view_get_instance_private (GCAL_SUBSCRIBER_VIEW (subscriber));
sid = e_source_get_uid (e_client_get_source (E_CLIENT (client)));
if (rid != NULL)
uuid = g_strdup_printf ("%s:%s:%s", sid, uid, rid);
else
uuid = g_strdup_printf ("%s:%s", sid, uid);
l = g_hash_table_lookup (priv->children, uuid);
if (l != NULL)
{
gtk_widget_destroy (l->data);
}
else
{
g_warning ("%s: Widget with uuid: %s not found in view: %s",
G_STRFUNC, uuid, gtk_widget_get_name (GTK_WIDGET (subscriber)));
}
g_free (uuid);
}
static void
gcal_subscriber_view_freeze (ECalDataModelSubscriber *subscriber)
{
}
static void
gcal_subscriber_view_thaw (ECalDataModelSubscriber *subscriber)
{
}
/* Public API */
GtkWidget*
gcal_subscriber_view_get_child_by_uuid (GcalSubscriberView *subscriber_view,
const gchar *uuid)
{
GcalSubscriberViewClass *klass;
GcalSubscriberViewPrivate *priv;
GList *l;
g_return_val_if_fail (GCAL_IS_SUBSCRIBER_VIEW (subscriber_view), NULL);
/* Allow for descendants to hook in here */
klass = GCAL_SUBSCRIBER_VIEW_GET_CLASS (subscriber_view);
if (klass->get_child_by_uuid)
return klass->get_child_by_uuid (subscriber_view, uuid);
priv = gcal_subscriber_view_get_instance_private (subscriber_view);
l = g_hash_table_lookup (priv->children, uuid);
if (l != NULL)
return l->data;
return 0;
}
void
_gcal_subscriber_view_setup_child (GcalSubscriberView *subscriber_view,
GtkWidget *child_widget)
{
if (gtk_widget_get_parent (child_widget) == NULL)
gtk_widget_set_parent (child_widget, GTK_WIDGET (subscriber_view));
g_signal_connect (child_widget, "activate", G_CALLBACK (event_activated), subscriber_view);
g_signal_connect (child_widget, "hide", G_CALLBACK (event_visibility_changed), subscriber_view);
g_signal_connect (child_widget, "show", G_CALLBACK (event_visibility_changed), subscriber_view);
}