/*
 * transmission-remote-gtk - A GTK RPC client to Transmission
 * Copyright (C) 2011-2013  Alan Fitton

 * 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "config.h"

#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <json-glib/json-glib.h>
#include <stdlib.h>

#include "trg-cell-renderer-epoch.h"
#include "trg-cell-renderer-eta.h"
#include "trg-cell-renderer-file-icon.h"
#include "trg-cell-renderer-numgteqthan.h"
#include "trg-cell-renderer-priority.h"
#include "trg-cell-renderer-ratio.h"
#include "trg-cell-renderer-size.h"
#include "trg-cell-renderer-speed.h"
#include "trg-cell-renderer-wanted.h"
#include "trg-prefs.h"
#include "trg-tree-view.h"

/* A subclass of GtkTreeView which allows the user to change column visibility
 * by right clicking on any column for a menu to hide the clicked column, or
 * insert any hidden column after.
 *
 * This class persists these choices to TrgPrefs, and restores them when it is
 * initialised. Column widths are also saved/restored.
 *
 * All the columns must be preregistered so it knows what model column,
 * renderers etc to use if it should be created, and what columns are available.
 */

enum {
    PROP_0,
    PROP_PREFS,
    PROP_CONFIGID
};

enum {
    SIGNAL_COLUMN_ADDED,
    SIGNAL_COUNT
};

static guint signals[SIGNAL_COUNT] = { 0 };

typedef struct {
    GList *columns;
    TrgPrefs *prefs;
    gchar *configId;
} TrgTreeViewPrivate;

G_DEFINE_TYPE_WITH_PRIVATE(TrgTreeView, trg_tree_view, GTK_TYPE_TREE_VIEW)

#define GDATA_KEY_COLUMN_DESC "column-desc"

gboolean trg_tree_view_is_column_showing(TrgTreeView *tv, gint index)
{
    TrgTreeViewPrivate *priv = trg_tree_view_get_instance_private(tv);

    GList *li;
    for (li = priv->columns; li; li = g_list_next(li)) {
        trg_column_description *cd = (trg_column_description *)li->data;
        if (cd->model_column == index) {
            if (cd->flags & TRG_COLUMN_SHOWING)
                return TRUE;
            else
                break;
        }
    }

    return FALSE;
}

static void trg_column_description_free(trg_column_description *desc)
{
    if (desc) {
        g_free(desc->header);
        g_free(desc->id);
        g_free(desc);
    }
}

static void trg_tree_view_get_property(GObject *object, guint property_id, GValue *value,
                                       GParamSpec *pspec)
{
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
}

static void trg_tree_view_set_property(GObject *object, guint property_id, const GValue *value,
                                       GParamSpec *pspec)
{
    TrgTreeViewPrivate *priv = trg_tree_view_get_instance_private(TRG_TREE_VIEW(object));
    switch (property_id) {
    case PROP_PREFS:
        priv->prefs = g_value_get_object(value);
        break;
    case PROP_CONFIGID:
        g_free(priv->configId);
        priv->configId = g_value_dup_string(value);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
        break;
    }
}

static GObject *trg_tree_view_constructor(GType type, guint n_construct_properties,
                                          GObjectConstructParam *construct_params)
{
    GObject *obj = G_OBJECT_CLASS(trg_tree_view_parent_class)
                       ->constructor(type, n_construct_properties, construct_params);

    return obj;
}

static JsonObject *trg_prefs_get_tree_view_props(TrgTreeView *tv)
{
    TrgTreeViewPrivate *priv = trg_tree_view_get_instance_private(tv);
    JsonObject *root = trg_prefs_get_root(priv->prefs);
    const gchar *className
        = priv->configId && strlen(priv->configId) > 0 ? priv->configId : G_OBJECT_TYPE_NAME(tv);
    JsonObject *obj;
    JsonObject *tvProps = NULL;

    if (!json_object_has_member(root, TRG_PREFS_KEY_TREE_VIEWS)) {
        obj = json_object_new();
        json_object_set_object_member(root, TRG_PREFS_KEY_TREE_VIEWS, obj);
    } else {
        obj = json_object_get_object_member(root, TRG_PREFS_KEY_TREE_VIEWS);
    }

    if (!json_object_has_member(obj, className)) {
        tvProps = json_object_new();
        json_object_set_object_member(obj, className, tvProps);
    } else {
        tvProps = json_object_get_object_member(obj, className);
    }

    return tvProps;
}

static void trg_tree_view_add_column_after(TrgTreeView *tv, trg_column_description *desc,
                                           gint64 width, GtkTreeViewColumn *after_col);

trg_column_description *trg_tree_view_reg_column(TrgTreeView *tv, gint type, gint model_column,
                                                 const gchar *header, const gchar *id, guint flags)
{
    TrgTreeViewPrivate *priv = trg_tree_view_get_instance_private(tv);
    trg_column_description *desc = g_new0(trg_column_description, 1);

    desc->type = type;
    desc->model_column = model_column;
    desc->header = g_strdup(header);
    desc->id = g_strdup(id);
    desc->flags = flags;

    priv->columns = g_list_append(priv->columns, desc);

    return desc;
}

static trg_column_description *trg_tree_view_find_column(TrgTreeView *tv, const gchar *id)
{
    TrgTreeViewPrivate *priv = trg_tree_view_get_instance_private(tv);
    GList *li;
    trg_column_description *desc;

    for (li = priv->columns; li; li = g_list_next(li)) {
        desc = (trg_column_description *)li->data;
        if (!g_strcmp0(desc->id, id))
            return desc;
    }

    return NULL;
}

static void trg_tree_view_hide_column(GtkWidget *w, GtkTreeViewColumn *col)
{
    trg_column_description *desc = g_object_get_data(G_OBJECT(col), GDATA_KEY_COLUMN_DESC);
    GtkWidget *tv = gtk_tree_view_column_get_tree_view(col);
    desc->flags &= ~TRG_COLUMN_SHOWING;
    gtk_tree_view_remove_column(GTK_TREE_VIEW(tv), col);
}

static void trg_tree_view_add_column(TrgTreeView *tv, trg_column_description *desc, gint64 width)
{
    trg_tree_view_add_column_after(tv, desc, width, NULL);
}

static void trg_tree_view_user_add_column_cb(GtkWidget *w, trg_column_description *desc)
{
    GtkTreeViewColumn *col = g_object_get_data(G_OBJECT(w), "parent-col");
    TrgTreeView *tv = TRG_TREE_VIEW(gtk_tree_view_column_get_tree_view(col));

    trg_tree_view_add_column_after(tv, desc, -1, col);

    g_signal_emit(tv, signals[SIGNAL_COLUMN_ADDED], 0, desc->id);
}

static void trg_tree_view_sort_menu_item_toggled(GtkCheckMenuItem *w, gpointer data)
{
    GtkTreeSortable *model = GTK_TREE_SORTABLE(data);
    trg_column_description *desc
        = (trg_column_description *)g_object_get_data(G_OBJECT(w), GDATA_KEY_COLUMN_DESC);

    if (gtk_check_menu_item_get_active(w)) {
        GtkSortType sortType;
        gtk_tree_sortable_get_sort_column_id(model, NULL, &sortType);
        gtk_tree_sortable_set_sort_column_id(model, desc->model_column, sortType);
    }
}

static void trg_tree_view_sort_menu_type_toggled(GtkCheckMenuItem *w, gpointer data)
{
    GtkTreeSortable *model = GTK_TREE_SORTABLE(data);

    if (gtk_check_menu_item_get_active(w)) {
        gint sortColumn;
        gint sortType = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w), "sort-type"));
        gtk_tree_sortable_get_sort_column_id(model, &sortColumn, NULL);
        gtk_tree_sortable_set_sort_column_id(model, sortColumn, sortType);
    }
}

GtkWidget *trg_tree_view_sort_menu(TrgTreeView *tv, const gchar *label)
{
    TrgTreeViewPrivate *priv = trg_tree_view_get_instance_private(tv);
    GtkWidget *item = gtk_menu_item_new_with_mnemonic(label);
    GtkTreeModel *treeViewModel = gtk_tree_view_get_model(GTK_TREE_VIEW(tv));
    GtkTreeSortable *sortableModel
        = GTK_TREE_SORTABLE(gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(treeViewModel)));
    GtkWidget *menu = gtk_menu_new();
    GtkWidget *b;
    GList *li;
    gint sort;
    GtkSortType sortType;
    GSList *group = NULL;

    gtk_tree_sortable_get_sort_column_id(sortableModel, &sort, &sortType);

    b = gtk_radio_menu_item_new_with_label(group, _("Ascending"));
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(b), sortType == GTK_SORT_ASCENDING);
    g_object_set_data(G_OBJECT(b), "sort-type", GINT_TO_POINTER(GTK_SORT_ASCENDING));
    g_signal_connect(b, "toggled", G_CALLBACK(trg_tree_view_sort_menu_type_toggled), sortableModel);
    gtk_menu_shell_append(GTK_MENU_SHELL(menu), b);
    group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(b));
    b = gtk_radio_menu_item_new_with_label(group, _("Descending"));
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(b), sortType == GTK_SORT_DESCENDING);
    g_object_set_data(G_OBJECT(b), "sort-type", GINT_TO_POINTER(GTK_SORT_DESCENDING));
    g_signal_connect(b, "toggled", G_CALLBACK(trg_tree_view_sort_menu_type_toggled), sortableModel);
    gtk_menu_shell_append(GTK_MENU_SHELL(menu), b);

    group = NULL;

    gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());

    for (li = priv->columns; li; li = g_list_next(li)) {
        trg_column_description *desc = (trg_column_description *)li->data;
        if (!(desc->flags & TRG_COLUMN_HIDE_FROM_TOP_MENU)) {
            b = gtk_radio_menu_item_new_with_label(group, desc->header);
            group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(b));

            if (desc->model_column == sort)
                gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(b), TRUE);

            g_object_set_data(G_OBJECT(b), GDATA_KEY_COLUMN_DESC, desc);
            g_signal_connect(b, "toggled", G_CALLBACK(trg_tree_view_sort_menu_item_toggled),
                             sortableModel);

            gtk_menu_shell_append(GTK_MENU_SHELL(menu), b);
        }
    }

    gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu);

    return item;
}

static void view_popup_menu(GtkButton *button, GdkEventButton *event, GtkTreeViewColumn *column)
{
    GtkWidget *tv = gtk_tree_view_column_get_tree_view(column);
    TrgTreeViewPrivate *priv = trg_tree_view_get_instance_private(TRG_TREE_VIEW(tv));
    GtkWidget *menu, *menuitem;
    trg_column_description *desc;
    guint n_showing = 0;
    GList *li;

    menu = gtk_menu_new();

    desc = g_object_get_data(G_OBJECT(column), GDATA_KEY_COLUMN_DESC);

    for (li = priv->columns; li; li = g_list_next(li)) {
        trg_column_description *desc = (trg_column_description *)li->data;
        if (!(desc->flags & TRG_COLUMN_SHOWING)) {
            menuitem = gtk_check_menu_item_new_with_label(desc->header);
            g_object_set_data(G_OBJECT(menuitem), "parent-col", column);
            g_signal_connect(menuitem, "activate", G_CALLBACK(trg_tree_view_user_add_column_cb),
                             desc);
            gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
        } else {
            n_showing++;
        }
    }

    menuitem = gtk_check_menu_item_new_with_label(desc->header);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
    g_signal_connect(menuitem, "activate", G_CALLBACK(trg_tree_view_hide_column), column);
    gtk_widget_set_sensitive(menuitem, !(desc->flags & TRG_COLUMN_UNREMOVABLE) && n_showing > 1);
    gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);

    gtk_widget_show_all(menu);

    gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
}

/* This used to get the column as an argument binded when the signal was
 * created, but it seems GTK now destroys and recreates them in some
 * circumstances. So bind the column description instead and search for the
 * column by its title.
 */
static gboolean col_onButtonPressed(GtkButton *button, GdkEventButton *event,
                                    trg_column_description *desc)
{
    if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
        GtkTreeView *gtv = GTK_TREE_VIEW(gtk_widget_get_parent(GTK_WIDGET(button)));
        GList *cols = gtk_tree_view_get_columns(gtv);
        GList *li;
        for (li = cols; li; li = g_list_next(li)) {
            GtkTreeViewColumn *col = GTK_TREE_VIEW_COLUMN(li->data);
            if (!g_strcmp0(desc->header, gtk_tree_view_column_get_title(col))) {
                view_popup_menu(button, event, col);
                break;
            }
        }
        g_list_free(cols);

        return TRUE;
    }

    return FALSE;
}

static GtkTreeViewColumn *trg_tree_view_icontext_column_new(trg_column_description *desc,
                                                            gchar *renderer_property)
{
    GtkTreeViewColumn *column = gtk_tree_view_column_new();
    GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new();

    gtk_tree_view_column_set_title(column, desc->header);
    gtk_tree_view_column_pack_start(column, renderer, FALSE);
    gtk_tree_view_column_set_attributes(column, renderer, renderer_property,
                                        desc->model_column_extra, NULL);

    renderer = gtk_cell_renderer_text_new();
    gtk_tree_view_column_pack_start(column, renderer, TRUE);
    gtk_tree_view_column_set_attributes(column, renderer, "text", desc->model_column, NULL);

    return column;
}

static GtkTreeViewColumn *trg_tree_view_fileicontext_column_new(trg_column_description *desc)
{
    GtkTreeViewColumn *column = gtk_tree_view_column_new();
    GtkCellRenderer *renderer = trg_cell_renderer_file_icon_new();

    gtk_tree_view_column_set_title(column, desc->header);
    gtk_tree_view_column_pack_start(column, renderer, FALSE);
    gtk_tree_view_column_set_attributes(column, renderer, "file-id", desc->model_column_extra,
                                        "file-name", desc->model_column, NULL);

    renderer = gtk_cell_renderer_text_new();
    gtk_tree_view_column_pack_start(column, renderer, TRUE);
    gtk_tree_view_column_set_attributes(column, renderer, "text", desc->model_column, NULL);

    return column;
}

static void trg_tree_view_add_column_after(TrgTreeView *tv, trg_column_description *desc,
                                           gint64 width, GtkTreeViewColumn *after_col)
{
    GtkCellRenderer *renderer;
    GtkTreeViewColumn *column = NULL;
    GtkButton *column_button = NULL;

    switch (desc->type) {
    case TRG_COLTYPE_TEXT:
        renderer = desc->customRenderer ? desc->customRenderer : gtk_cell_renderer_text_new();
        column = gtk_tree_view_column_new_with_attributes(desc->header, renderer, "text",
                                                          desc->model_column, NULL);

        break;
    case TRG_COLTYPE_SPEED:
        renderer = trg_cell_renderer_speed_new();
        column = gtk_tree_view_column_new_with_attributes(desc->header, renderer, "speed-value",
                                                          desc->model_column, NULL);
        break;
    case TRG_COLTYPE_EPOCH:
        renderer = trg_cell_renderer_epoch_new();
        column = gtk_tree_view_column_new_with_attributes(desc->header, renderer, "epoch-value",
                                                          desc->model_column, NULL);
        break;
    case TRG_COLTYPE_ETA:
        renderer = trg_cell_renderer_eta_new();
        column = gtk_tree_view_column_new_with_attributes(desc->header, renderer, "eta-value",
                                                          desc->model_column, NULL);
        break;
    case TRG_COLTYPE_SIZE:
        renderer = trg_cell_renderer_size_new();
        column = gtk_tree_view_column_new_with_attributes(desc->header, renderer, "size-value",
                                                          desc->model_column, NULL);
        break;
    case TRG_COLTYPE_PROG:
        renderer = gtk_cell_renderer_progress_new();
        column = gtk_tree_view_column_new_with_attributes(desc->header, renderer, "value",
                                                          desc->model_column, NULL);
        break;
    case TRG_COLTYPE_RATIO:
        renderer = trg_cell_renderer_ratio_new();
        column = gtk_tree_view_column_new_with_attributes(desc->header, renderer, "ratio-value",
                                                          desc->model_column, NULL);
        break;
    case TRG_COLTYPE_WANTED:
        column = gtk_tree_view_column_new();
        renderer = trg_cell_renderer_wanted_new();
        /*gtk_cell_renderer_set_alignment(GTK_CELL_RENDERER(renderer), 0.5f,
           0.0); */
        gtk_tree_view_column_set_title(column, desc->header);
        gtk_tree_view_column_pack_start(column, renderer, TRUE);
        gtk_tree_view_column_set_attributes(column, renderer, "wanted-value", desc->model_column,
                                            NULL);
        break;
    case TRG_COLTYPE_ICONTEXT:
        column = trg_tree_view_icontext_column_new(desc, "icon-name");
        break;
    case TRG_COLTYPE_FILEICONTEXT:
        column = trg_tree_view_fileicontext_column_new(desc);
        break;
    case TRG_COLTYPE_PRIO:
        renderer = trg_cell_renderer_priority_new();
        column = gtk_tree_view_column_new_with_attributes(desc->header, renderer, "priority-value",
                                                          desc->model_column, NULL);
        break;
    case TRG_COLTYPE_NUMGTZERO:
        renderer = trg_cell_renderer_numgteqthan_new(1);
        column = gtk_tree_view_column_new_with_attributes(desc->header, renderer, "value",
                                                          desc->model_column, NULL);
        break;
    case TRG_COLTYPE_NUMGTEQZERO:
        renderer = trg_cell_renderer_numgteqthan_new(0);
        column = gtk_tree_view_column_new_with_attributes(desc->header, renderer, "value",
                                                          desc->model_column, NULL);
        break;
    default:
        g_critical("unknown TrgTreeView column");
        return;
    }

    // gtk_tree_view_column_set_min_width(column, 0);
    gtk_tree_view_column_set_resizable(column, TRUE);
    gtk_tree_view_column_set_reorderable(column, TRUE);
    gtk_tree_view_column_set_sort_column_id(column, desc->model_column);
    gtk_tree_view_column_set_expand(column, TRUE);

    /*gtk_tree_view_column_set_sizing(column,
                                    GTK_TREE_VIEW_COLUMN_FIXED);*/

    if (width > 0) {
        // gtk_tree_view_column_set_fixed_width(column, width);
    }

    g_object_set_data(G_OBJECT(column), GDATA_KEY_COLUMN_DESC, desc);

    gtk_tree_view_append_column(GTK_TREE_VIEW(tv), column);

    if (after_col)
        gtk_tree_view_move_column_after(GTK_TREE_VIEW(tv), column, after_col);

    column_button = GTK_BUTTON(gtk_tree_view_column_get_button(column));

    g_signal_connect(column_button, "button-press-event", G_CALLBACK(col_onButtonPressed), desc);

    if (desc->out)
        *(desc->out) = column;

    desc->flags |= TRG_COLUMN_SHOWING;
}

void trg_tree_view_remove_all_columns(TrgTreeView *tv)
{
    GtkTreeView *gtv = GTK_TREE_VIEW(tv);
    GList *cols = gtk_tree_view_get_columns(gtv);
    GList *li;
    for (li = cols; li; li = g_list_next(li)) {
        gtk_tree_view_remove_column(gtv, GTK_TREE_VIEW_COLUMN(li->data));
    }
    g_list_free(cols);
}

void trg_tree_view_persist(TrgTreeView *tv, guint flags)
{
    JsonObject *props = trg_prefs_get_tree_view_props(tv);
    GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(tv));
    GList *cols, *li;
    JsonArray *widths, *columns;
    gint sort_column_id;
    GtkSortType sort_type;

    if (flags & TRG_TREE_VIEW_PERSIST_SORT) {
        gtk_tree_sortable_get_sort_column_id(
            GTK_TREE_SORTABLE((flags & TRG_TREE_VIEW_SORTABLE_PARENT)
                                  ? gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model))
                                  : model),
            &sort_column_id, &sort_type);

        if (json_object_has_member(props, TRG_PREFS_KEY_TV_SORT_COL))
            json_object_remove_member(props, TRG_PREFS_KEY_TV_SORT_COL);

        if (json_object_has_member(props, TRG_PREFS_KEY_TV_SORT_TYPE))
            json_object_remove_member(props, TRG_PREFS_KEY_TV_SORT_TYPE);

        json_object_set_int_member(props, TRG_PREFS_KEY_TV_SORT_COL, (gint64)sort_column_id);
        json_object_set_int_member(props, TRG_PREFS_KEY_TV_SORT_TYPE, (gint64)sort_type);
    }

    if (flags & TRG_TREE_VIEW_PERSIST_LAYOUT) {
        cols = gtk_tree_view_get_columns(GTK_TREE_VIEW(tv));

        if (json_object_has_member(props, TRG_PREFS_KEY_TV_WIDTHS))
            json_object_remove_member(props, TRG_PREFS_KEY_TV_WIDTHS);

        widths = json_array_new();
        json_object_set_array_member(props, TRG_PREFS_KEY_TV_WIDTHS, widths);

        if (json_object_has_member(props, TRG_PREFS_KEY_TV_COLUMNS))
            json_object_remove_member(props, TRG_PREFS_KEY_TV_COLUMNS);

        columns = json_array_new();
        json_object_set_array_member(props, TRG_PREFS_KEY_TV_COLUMNS, columns);

        for (li = cols; li; li = g_list_next(li)) {
            GtkTreeViewColumn *col = (GtkTreeViewColumn *)li->data;
            trg_column_description *desc
                = g_object_get_data(G_OBJECT(li->data), GDATA_KEY_COLUMN_DESC);

            json_array_add_string_element(columns, desc->id);
            json_array_add_int_element(widths, gtk_tree_view_column_get_width(col));
        }

        g_list_free(cols);
    }
}

void trg_tree_view_restore_sort(TrgTreeView *tv, guint flags)
{
    JsonObject *props = trg_prefs_get_tree_view_props(tv);
    GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(tv));

    if (json_object_has_member(props, TRG_PREFS_KEY_TV_SORT_COL)
        && json_object_has_member(props, TRG_PREFS_KEY_TV_SORT_TYPE)) {
        gint64 sort_col = json_object_get_int_member(props, TRG_PREFS_KEY_TV_SORT_COL);
        gint64 sort_type = json_object_get_int_member(props, TRG_PREFS_KEY_TV_SORT_TYPE);
        gtk_tree_sortable_set_sort_column_id(
            GTK_TREE_SORTABLE((flags & TRG_TREE_VIEW_SORTABLE_PARENT)
                                  ? gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(model))
                                  : model),
            sort_col, (GtkSortType)sort_type);
    }
}

void trg_tree_view_set_prefs(TrgTreeView *tv, TrgPrefs *prefs)
{
    TrgTreeViewPrivate *priv = trg_tree_view_get_instance_private(tv);
    priv->prefs = prefs;
}

void trg_tree_view_setup_columns(TrgTreeView *tv)
{
    TrgTreeViewPrivate *priv = trg_tree_view_get_instance_private(tv);
    JsonObject *props = trg_prefs_get_tree_view_props(tv);
    GList *columns, *widths, *cli, *wli;

    if (!json_object_has_member(props, TRG_PREFS_KEY_TV_COLUMNS)
        || !json_object_has_member(props, TRG_PREFS_KEY_TV_WIDTHS)) {
        GList *li;
        for (li = priv->columns; li; li = g_list_next(li)) {
            trg_column_description *desc = (trg_column_description *)li->data;
            if (desc && !(desc->flags & TRG_COLUMN_EXTRA))
                trg_tree_view_add_column(tv, desc, -1);
        }
        return;
    }

    columns
        = json_array_get_elements(json_object_get_array_member(props, TRG_PREFS_KEY_TV_COLUMNS));
    widths = json_array_get_elements(json_object_get_array_member(props, TRG_PREFS_KEY_TV_WIDTHS));

    for (cli = columns, wli = widths; cli && wli; cli = g_list_next(cli), wli = g_list_next(wli)) {
        trg_column_description *desc
            = trg_tree_view_find_column(tv, json_node_get_string((JsonNode *)cli->data));
        if (desc) {
            gint64 width = json_node_get_int((JsonNode *)wli->data);
            trg_tree_view_add_column(tv, desc, width);
        }
    }

    g_list_free(columns);
    g_list_free(widths);
}

GList *trg_tree_view_get_selected_refs_list(GtkTreeView *tv)
{
    GtkTreeModel *model = gtk_tree_view_get_model(tv);
    GtkTreeSelection *selection = gtk_tree_view_get_selection(tv);
    GList *li, *selectionList;
    GList *refList = NULL;

    selectionList = gtk_tree_selection_get_selected_rows(selection, NULL);
    for (li = selectionList; li != NULL; li = g_list_next(li)) {
        GtkTreePath *path = (GtkTreePath *)li->data;
        GtkTreeRowReference *ref = gtk_tree_row_reference_new(model, path);
        gtk_tree_path_free(path);
        refList = g_list_append(refList, ref);
    }
    g_list_free(selectionList);

    return refList;
}

static void trg_tree_view_dispose(GObject *object)
{
    G_OBJECT_CLASS(trg_tree_view_parent_class)->dispose(object);
}

static void trg_tree_view_finalize(GObject *object)
{
    TrgTreeViewPrivate *priv = trg_tree_view_get_instance_private(TRG_TREE_VIEW(object));

    g_list_free_full(priv->columns, (GDestroyNotify)trg_column_description_free);
    g_free(priv->configId);

    G_OBJECT_CLASS(trg_tree_view_parent_class)->finalize(object);
}

static void trg_tree_view_class_init(TrgTreeViewClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS(klass);

    object_class->get_property = trg_tree_view_get_property;
    object_class->set_property = trg_tree_view_set_property;
    object_class->constructor = trg_tree_view_constructor;
    object_class->dispose = trg_tree_view_dispose;
    object_class->finalize = trg_tree_view_finalize;

    g_object_class_install_property(
        object_class, PROP_PREFS,
        g_param_spec_object("prefs", "Trg Prefs", "Trg Prefs", TRG_TYPE_PREFS,
                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME
                                | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));

    g_object_class_install_property(
        object_class, PROP_CONFIGID,
        g_param_spec_string("config-id", "config-id", "config-id", NULL,
                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME
                                | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));

    signals[SIGNAL_COLUMN_ADDED] = g_signal_new(
        "column-added", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, 0,
        NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING);
}

static void trg_tree_view_init(TrgTreeView *tv)
{
    gtk_tree_view_set_rubber_banding(GTK_TREE_VIEW(tv), TRUE);
    gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(tv), TRUE);
    gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(tv)),
                                GTK_SELECTION_MULTIPLE);

    gtk_widget_set_sensitive(GTK_WIDGET(tv), FALSE);
}

GtkWidget *trg_tree_view_new(void)
{
    return GTK_WIDGET(g_object_new(TRG_TYPE_TREE_VIEW, NULL));
}
