static void
gnc_sx_instance_model_dispose(GObject *object)
{
    GncSxInstanceModel *model;
    g_return_if_fail(object != NULL);
    model = GNC_SX_INSTANCE_MODEL(object);

    g_return_if_fail(!model->disposed);
    model->disposed = TRUE;

    qof_event_unregister_handler(model->qof_event_handler_id);

    G_OBJECT_CLASS(parent_class)->dispose(object);
}
static void
gnc_sx_instance_model_finalize (GObject *object)
{
    GncSxInstanceModel *model;
    GList *sx_list_iter;

    g_return_if_fail(object != NULL);

    model = GNC_SX_INSTANCE_MODEL(object);
    for (sx_list_iter = model->sx_instance_list; sx_list_iter != NULL; sx_list_iter = sx_list_iter->next)
    {
        GncSxInstances *instances = (GncSxInstances*)sx_list_iter->data;
        gnc_sx_instances_free(instances);
    }
    g_list_free(model->sx_instance_list);
    model->sx_instance_list = NULL;

    G_OBJECT_CLASS(parent_class)->finalize(object);
}
static GtkWidget *
gnc_plugin_page_sx_list_create_widget (GncPluginPage *plugin_page)
{
    GncPluginPageSxList *page;
    GncPluginPageSxListPrivate *priv;
    GtkWidget *widget;
    GtkWidget *vbox;
    GtkWidget *label;
    GtkWidget *swin;
    char *markup;
    char *text;

    page = GNC_PLUGIN_PAGE_SX_LIST(plugin_page);
    priv = GNC_PLUGIN_PAGE_SX_LIST_GET_PRIVATE(page);
    if (priv->widget != NULL)
        return priv->widget;

    /* Create Vpaned widget for top level */
    widget = gtk_vpaned_new();
    priv->widget = widget;
    gtk_widget_show (priv->widget);

    /* Add vbox and label */
    vbox = gtk_vbox_new(FALSE, 0);
    gtk_paned_pack1( GTK_PANED(widget), vbox, TRUE, FALSE);

    label = gtk_label_new(NULL);
    text = g_strdup_printf(_("Transactions"));
    markup = g_markup_printf_escaped ("<b> %s</b>", text);
    gtk_label_set_markup (GTK_LABEL (label), markup);
    g_free (markup);
    g_free (text);
    gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0);
    gtk_widget_show (label);
    gtk_box_pack_start ( GTK_BOX(vbox), label, FALSE, FALSE, 0);
    gtk_widget_show (vbox);

    /* Create scrolled window for top area */
    swin = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swin),
                                    GTK_POLICY_AUTOMATIC,
                                    GTK_POLICY_AUTOMATIC);
    gtk_box_pack_start ( GTK_BOX(vbox), swin, TRUE, TRUE, 5);
    gtk_widget_show (swin);

    {
        // gint half_way;
        // half_way = plugin_page->notebook_page->allocation.height * 0.5;
        // fixme; get a real value:
        gtk_paned_set_position(GTK_PANED(priv->widget), 160);
    }

    {
        GDate end;
        g_date_clear(&end, 1);
        gnc_gdate_set_today (&end);
        g_date_add_years(&end, 1);
        priv->instances = GNC_SX_INSTANCE_MODEL(gnc_sx_get_instances(&end, TRUE));
    }

    {
        GtkAction *edit_action, *delete_action;
        edit_action = gnc_plugin_page_get_action(GNC_PLUGIN_PAGE(page), "SxListEditAction");
        delete_action = gnc_plugin_page_get_action(GNC_PLUGIN_PAGE(page), "SxListDeleteAction");
        gtk_action_set_sensitive(edit_action, FALSE);
        gtk_action_set_sensitive(delete_action, FALSE);
    }

    {
        GtkTreeSelection *selection;

        priv->tree_view = GTK_TREE_VIEW(gnc_tree_view_sx_list_new(priv->instances));
        g_object_set(G_OBJECT(priv->tree_view),
                     "gconf-section", GCONF_SECTION,
                     "show-column-menu", TRUE,
                     NULL);
        gtk_container_add(GTK_CONTAINER( swin ), GTK_WIDGET(priv->tree_view));

        selection = gtk_tree_view_get_selection(priv->tree_view);
        gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
        g_signal_connect(G_OBJECT(selection), "changed", (GCallback)gppsl_selection_changed_cb, (gpointer)page);
        g_signal_connect(G_OBJECT(priv->tree_view), "row-activated", (GCallback)gppsl_row_activated_cb, (gpointer)page);
    }

    /* Add vbox and label */
    vbox = gtk_vbox_new(FALSE, 0);
    gtk_paned_pack2( GTK_PANED(widget), vbox, TRUE, FALSE);

    label = gtk_label_new(NULL);
    text = g_strdup_printf(_("Upcoming Transactions"));
    markup = g_markup_printf_escaped ("<b> %s</b>", text);
    gtk_label_set_markup (GTK_LABEL (label), markup);
    g_free (markup);
    g_free (text);
    gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0);
    gtk_widget_show (label);

    gtk_box_pack_start ( GTK_BOX(vbox), label, FALSE, FALSE, 0);
    gtk_widget_show (vbox);

    /* Create scrolled window for bottom area */
    swin = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swin),
                                    GTK_POLICY_AUTOMATIC,
                                    GTK_POLICY_AUTOMATIC);
    gtk_box_pack_start ( GTK_BOX(vbox), swin, TRUE, TRUE, 5);
    gtk_widget_show (swin);

    {
        priv->dense_cal_model = gnc_sx_instance_dense_cal_adapter_new(GNC_SX_INSTANCE_MODEL(priv->instances));
        priv->gdcal = GNC_DENSE_CAL(gnc_dense_cal_new_with_model(GNC_DENSE_CAL_MODEL(priv->dense_cal_model)));
        g_object_ref_sink(priv->gdcal);

        gnc_dense_cal_set_months_per_col(priv->gdcal, 4);
        gnc_dense_cal_set_num_months(priv->gdcal, 12);

        gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(swin), GTK_WIDGET(priv->gdcal));
    }

    priv->gnc_component_id = gnc_register_gui_component("plugin-page-sx-list",
                             gnc_plugin_page_sx_list_refresh_cb,
                             gnc_plugin_page_sx_list_close_cb,
                             page);

    return priv->widget;
}
static void
_gnc_sx_instance_event_handler(QofInstance *ent, QofEventId event_type, gpointer user_data, gpointer evt_data)
{
    GncSxInstanceModel *instances = GNC_SX_INSTANCE_MODEL(user_data);

    /* selection rules {
    //   (gnc_collection_get_schedxaction_list(book), GNC_EVENT_ITEM_ADDED)
    //   (gnc_collection_get_schedxaction_list(book), GNC_EVENT_ITEM_REMOVED)
    //   (GNC_IS_SX(ent), QOF_EVENT_MODIFIED)
    // } */
    if (!(GNC_IS_SX(ent) || GNC_IS_SXES(ent)))
        return;

    if (GNC_IS_SX(ent))
    {
        SchedXaction *sx;
        gboolean sx_is_in_model = FALSE;

        sx = GNC_SX(ent);
        // only send `updated` if it's actually in the model
        sx_is_in_model = (g_list_find_custom(instances->sx_instance_list, sx, (GCompareFunc)_gnc_sx_instance_find_by_sx) != NULL);
        if (event_type & QOF_EVENT_MODIFY)
        {
            if (sx_is_in_model)
            {
                if (instances->include_disabled || xaccSchedXactionGetEnabled(sx))
                {
                    g_signal_emit_by_name(instances, "updated", (gpointer)sx);
                }
                else
                {
                    /* the sx was enabled but is now disabled */
                    g_signal_emit_by_name(instances, "removing", (gpointer)sx);
                }
            }
            else
            {
                /* determine if this is a legitimate SX or just a "one-off" / being created */
                GList *all_sxes = gnc_book_get_schedxactions(gnc_get_current_book())->sx_list;
                if (g_list_find(all_sxes, sx) && (!instances->include_disabled && xaccSchedXactionGetEnabled(sx)))
                {
                    /* it's moved from disabled to enabled, add the instances */
                    instances->sx_instance_list
                    = g_list_append(instances->sx_instance_list,
                                    _gnc_sx_gen_instances((gpointer)sx, (gpointer) & instances->range_end));
                    g_signal_emit_by_name(instances, "added", (gpointer)sx);
                }
            }
        }
        /* else { unsupported event type; ignore } */
    }
    else if (GNC_IS_SXES(ent))
    {
        SchedXactions *sxes = GNC_SXES(ent);
        SchedXaction *sx = GNC_SX(evt_data);

        sxes = NULL;
        if (event_type & GNC_EVENT_ITEM_REMOVED)
        {
            GList *instances_link;
            instances_link = g_list_find_custom(instances->sx_instance_list, sx, (GCompareFunc)_gnc_sx_instance_find_by_sx);
            if (instances_link != NULL)
            {
                g_signal_emit_by_name(instances, "removing", (gpointer)sx);
            }
            else if (instances->include_disabled)
            {
                g_warning("could not remove instances that do not exist in the model");
            }
        }
        else if (event_type & GNC_EVENT_ITEM_ADDED)
        {
            if (instances->include_disabled || xaccSchedXactionGetEnabled(sx))
            {
                /* generate instances, add to instance list, emit update. */
                instances->sx_instance_list
                = g_list_append(instances->sx_instance_list,
                                _gnc_sx_gen_instances((gpointer)sx, (gpointer) & instances->range_end));
                g_signal_emit_by_name(instances, "added", (gpointer)sx);
            }
        }
        /* else { g_critical("unsupported event type [%d]\n", event_type); } */
    }
}
static GncSxInstanceModel*
gnc_sx_instance_model_new(void)
{
    return GNC_SX_INSTANCE_MODEL(g_object_new(GNC_TYPE_SX_INSTANCE_MODEL, NULL));
}