transmission/gtk/file-list.c

924 lines
30 KiB
C
Raw Normal View History

/*
* This file Copyright (C) Mnemosyne LLC
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2 (b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id$
*/
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <libtransmission/transmission.h>
#include <libtransmission/utils.h>
#include "file-list.h"
#include "hig.h"
#include "icons.h"
#include "tr-prefs.h"
#include "util.h"
#define TR_DOWNLOAD_KEY "tr-download-key"
#define TR_COLUMN_ID_KEY "tr-model-column-id-key"
#define TR_PRIORITY_KEY "tr-priority-key"
enum
{
/* these two fields could be any number at all so long as they're not
* TR_PRI_LOW, TR_PRI_NORMAL, TR_PRI_HIGH, TRUE, or FALSE */
NOT_SET = 1000,
MIXED = 1001
};
enum
{
FC_ICON,
FC_LABEL,
FC_LABEL_ESC,
FC_PROG,
FC_INDEX,
FC_SIZE,
FC_SIZE_STR,
FC_HAVE,
FC_PRIORITY,
FC_ENABLED,
N_FILE_COLS
};
typedef struct
{
TrCore * core;
GtkWidget * top;
GtkWidget * view;
GtkTreeModel * model; /* same object as store, but recast */
GtkTreeStore * store; /* same object as model, but recast */
int torrentId;
guint timeout_tag;
}
FileData;
static void
clearData (FileData * data)
{
data->torrentId = -1;
if (data->timeout_tag) {
g_source_remove (data->timeout_tag);
data->timeout_tag = 0;
}
}
static void
freeData (gpointer data)
{
clearData (data);
g_free (data);
}
/***
****
***/
struct RefreshData
{
int sort_column_id;
gboolean resort_needed;
tr_file_stat * refresh_file_stat;
tr_torrent * tor;
FileData * file_data;
};
static gboolean
refreshFilesForeach (GtkTreeModel * model,
GtkTreePath * path UNUSED,
GtkTreeIter * iter,
gpointer gdata)
{
struct RefreshData * refresh_data = gdata;
FileData * data = refresh_data->file_data;
unsigned int index;
uint64_t size;
uint64_t old_have;
int old_prog;
int old_priority;
int old_enabled;
const gboolean is_file = !gtk_tree_model_iter_has_child (model, iter);
gtk_tree_model_get (model, iter, FC_ENABLED, &old_enabled,
FC_PRIORITY, &old_priority,
FC_INDEX, &index,
FC_HAVE, &old_have,
FC_SIZE, &size,
FC_PROG, &old_prog,
-1);
if (is_file)
{
tr_torrent * tor = refresh_data->tor;
const tr_info * inf = tr_torrentInfo (tor);
const int enabled = !inf->files[index].dnd;
const int priority = inf->files[index].priority;
const uint64_t have = refresh_data->refresh_file_stat[index].bytesCompleted;
const int prog = size ? (int)((100.0*have)/size) : 1;
if ((priority!=old_priority) || (enabled!=old_enabled) || (have!=old_have) || (prog!=old_prog))
{
/* Changing a value in the sort column can trigger a resort
* which breaks this foreach () call. (See #3529)
* As a workaround: if that's about to happen, temporarily disable
* sorting until we finish walking the tree. */
if (!refresh_data->resort_needed)
{
if ((refresh_data->resort_needed =
((refresh_data->sort_column_id==FC_PRIORITY) && (priority!=old_priority)) ||
((refresh_data->sort_column_id==FC_ENABLED) && (enabled!=old_enabled))))
{
refresh_data->resort_needed = TRUE;
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (data->model),
GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID,
GTK_SORT_ASCENDING);
}
}
gtk_tree_store_set (data->store, iter, FC_PRIORITY, priority,
FC_ENABLED, enabled,
FC_HAVE, have,
FC_PROG, prog,
-1);
}
}
else
{
GtkTreeIter child;
uint64_t sub_size = 0;
uint64_t have = 0;
int prog;
int enabled = NOT_SET;
int priority = NOT_SET;
/* since gtk_tree_model_foreach () is depth-first, we can
* get the `sub' info by walking the immediate children */
if (gtk_tree_model_iter_children (model, &child, iter)) do
{
int child_enabled;
int child_priority;
int64_t child_have, child_size;
gtk_tree_model_get (model, &child, FC_SIZE, &child_size,
FC_HAVE, &child_have,
FC_PRIORITY, &child_priority,
FC_ENABLED, &child_enabled,
-1);
sub_size += child_size;
have += child_have;
if (enabled == NOT_SET)
enabled = child_enabled;
else if (enabled != child_enabled)
enabled = MIXED;
if (priority == NOT_SET)
priority = child_priority;
else if (priority != child_priority)
priority = MIXED;
}
while (gtk_tree_model_iter_next (model, &child));
prog = sub_size ? (int)((100.0*have)/sub_size) : 1;
if ((size!=sub_size) || (have!=old_have)
|| (priority!=old_priority)
|| (enabled!=old_enabled)
|| (prog!=old_prog))
{
char size_str[64];
tr_strlsize (size_str, sub_size, sizeof size_str);
gtk_tree_store_set (data->store, iter, FC_SIZE, sub_size,
FC_SIZE_STR, size_str,
FC_HAVE, have,
FC_PRIORITY, priority,
FC_ENABLED, enabled,
FC_PROG, prog,
-1);
}
}
return FALSE; /* keep walking */
}
static void
gtr_tree_model_foreach_postorder_subtree (GtkTreeModel * model,
GtkTreeIter * parent,
GtkTreeModelForeachFunc func,
gpointer data)
{
GtkTreeIter child;
if (gtk_tree_model_iter_children (model, &child, parent)) do
gtr_tree_model_foreach_postorder_subtree (model, &child, func, data);
while (gtk_tree_model_iter_next (model, &child));
if (parent)
func (model, NULL, parent, data);
}
static void
gtr_tree_model_foreach_postorder (GtkTreeModel * model,
GtkTreeModelForeachFunc func,
gpointer data)
{
GtkTreeIter iter;
if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 0)) do
gtr_tree_model_foreach_postorder_subtree (model, &iter, func, data);
while (gtk_tree_model_iter_next (model, &iter));
}
static void
refresh (FileData * data)
{
tr_torrent * tor = gtr_core_find_torrent (data->core, data->torrentId);
if (tor == NULL)
{
gtr_file_list_clear (data->top);
}
else
{
GtkSortType order;
int sort_column_id;
tr_file_index_t fileCount;
struct RefreshData refresh_data;
GtkTreeSortable * sortable = GTK_TREE_SORTABLE (data->model);
gtk_tree_sortable_get_sort_column_id (sortable, &sort_column_id, &order);
refresh_data.sort_column_id = sort_column_id;
refresh_data.resort_needed = FALSE;
refresh_data.refresh_file_stat = tr_torrentFiles (tor, &fileCount);
refresh_data.tor = tor;
refresh_data.file_data = data;
gtr_tree_model_foreach_postorder (data->model, refreshFilesForeach, &refresh_data);
if (refresh_data.resort_needed)
gtk_tree_sortable_set_sort_column_id (sortable, sort_column_id, order);
tr_torrentFilesFree (refresh_data.refresh_file_stat, fileCount);
}
}
static gboolean
refreshModel (gpointer file_data)
{
refresh (file_data);
return TRUE;
}
/***
****
***/
struct ActiveData
{
GtkTreeSelection * sel;
GArray * array;
};
static gboolean
getSelectedFilesForeach (GtkTreeModel * model,
GtkTreePath * path UNUSED,
GtkTreeIter * iter,
gpointer gdata)
{
const gboolean is_file = !gtk_tree_model_iter_has_child (model, iter);
if (is_file)
{
struct ActiveData * data = gdata;
/* active means: if it's selected or any ancestor is selected */
gboolean is_active = gtk_tree_selection_iter_is_selected (data->sel, iter);
if (!is_active)
{
GtkTreeIter walk = *iter;
GtkTreeIter parent;
while (!is_active && gtk_tree_model_iter_parent (model, &parent, &walk))
{
is_active = gtk_tree_selection_iter_is_selected (data->sel, &parent);
walk = parent;
}
}
if (is_active)
{
unsigned int i;
gtk_tree_model_get (model, iter, FC_INDEX, &i, -1);
g_array_append_val (data->array, i);
}
}
return FALSE; /* keep walking */
}
static GArray*
getSelectedFilesAndDescendants (GtkTreeView * view)
{
struct ActiveData data;
data.sel = gtk_tree_view_get_selection (view);
data.array = g_array_new (FALSE, FALSE, sizeof (tr_file_index_t));
gtk_tree_model_foreach (gtk_tree_view_get_model (view),
getSelectedFilesForeach, &data);
return data.array;
}
struct SubtreeForeachData
{
GArray * array;
GtkTreePath * path;
};
static gboolean
getSubtreeForeach (GtkTreeModel * model,
GtkTreePath * path,
GtkTreeIter * iter,
gpointer gdata)
{
const gboolean is_file = !gtk_tree_model_iter_has_child (model, iter);
if (is_file)
{
struct SubtreeForeachData * data = gdata;
if (!gtk_tree_path_compare (path, data->path) || gtk_tree_path_is_descendant (path, data->path))
{
unsigned int i;
gtk_tree_model_get (model, iter, FC_INDEX, &i, -1);
g_array_append_val (data->array, i);
}
}
return FALSE; /* keep walking */
}
static void
getSubtree (GtkTreeView * view, GtkTreePath * path, GArray * indices)
{
struct SubtreeForeachData tmp;
tmp.array = indices;
tmp.path = path;
gtk_tree_model_foreach (gtk_tree_view_get_model (view), getSubtreeForeach, &tmp);
}
/* if `path' is a selected row, all selected rows are returned.
* otherwise, only the row indicated by `path' is returned.
* this is for toggling all the selected rows' states in a batch.
*/
static GArray*
getActiveFilesForPath (GtkTreeView * view, GtkTreePath * path)
2008-02-15 18:20:56 +00:00
{
GArray * indices;
GtkTreeSelection * sel = gtk_tree_view_get_selection (view);
if (gtk_tree_selection_path_is_selected (sel, path))
{
/* clicked in a selected row... use the current selection */
indices = getSelectedFilesAndDescendants (view);
}
else
{
/* clicked OUTSIDE of the selected row... just use the clicked row */
indices = g_array_new (FALSE, FALSE, sizeof (tr_file_index_t));
getSubtree (view, path, indices);
}
return indices;
2008-02-15 18:20:56 +00:00
}
/***
****
***/
void
gtr_file_list_clear (GtkWidget * w)
{
gtr_file_list_set_torrent (w, -1);
}
struct build_data
{
GtkWidget * w;
tr_torrent * tor;
GtkTreeIter * iter;
GtkTreeStore * store;
};
struct row_struct
{
uint64_t length;
char * name;
int index;
};
static void
buildTree (GNode * node, gpointer gdata)
{
char size_str[64];
GtkTreeIter child_iter;
struct build_data * build = gdata;
struct row_struct *child_data = node->data;
const gboolean isLeaf = node->children == NULL;
const char * mime_type = isLeaf ? gtr_get_mime_type_from_filename (child_data->name) : DIRECTORY_MIME_TYPE;
GdkPixbuf * icon = gtr_get_mime_type_icon (mime_type, GTK_ICON_SIZE_MENU, build->w);
const tr_info * inf = tr_torrentInfo (build->tor);
const int priority = isLeaf ? inf->files[ child_data->index ].priority : 0;
const gboolean enabled = isLeaf ? !inf->files[ child_data->index ].dnd : TRUE;
char * name_esc = g_markup_escape_text (child_data->name, -1);
tr_strlsize (size_str, child_data->length, sizeof size_str);
gtk_tree_store_insert_with_values (build->store, &child_iter, build->iter, INT_MAX,
FC_INDEX, child_data->index,
FC_LABEL, child_data->name,
FC_LABEL_ESC, name_esc,
FC_SIZE, child_data->length,
FC_SIZE_STR, size_str,
FC_ICON, icon,
FC_PRIORITY, priority,
FC_ENABLED, enabled,
-1);
if (!isLeaf)
{
struct build_data b = *build;
b.iter = &child_iter;
g_node_children_foreach (node, G_TRAVERSE_ALL, buildTree, &b);
}
g_free (name_esc);
g_object_unref (icon);
2009-08-10 20:04:08 +00:00
/* we're done with this node */
g_free (child_data->name);
g_free (child_data);
}
static GNode*
find_child (GNode* parent, const char * name)
{
GNode * child = parent->children;
while (child) {
const struct row_struct * child_data = child->data;
if ((*child_data->name == *name) && !strcmp (child_data->name, name))
break;
child = child->next;
}
return child;
}
void
gtr_file_list_set_torrent (GtkWidget * w, int torrentId)
{
GtkTreeStore * store;
FileData * data = g_object_get_data (G_OBJECT (w), "file-data");
2008-02-15 18:20:56 +00:00
/* unset the old fields */
clearData (data);
2008-02-15 18:20:56 +00:00
/* instantiate the model */
store = gtk_tree_store_new (N_FILE_COLS,
GDK_TYPE_PIXBUF, /* icon */
G_TYPE_STRING, /* label */
G_TYPE_STRING, /* label esc */
G_TYPE_INT, /* prog [0..100] */
G_TYPE_UINT, /* index */
G_TYPE_UINT64, /* size */
G_TYPE_STRING, /* size str */
G_TYPE_UINT64, /* have */
G_TYPE_INT, /* priority */
G_TYPE_INT); /* dl enabled */
data->store = store;
data->model = GTK_TREE_MODEL (store);
data->torrentId = torrentId;
/* populate the model */
if (torrentId > 0)
{
tr_torrent * tor = gtr_core_find_torrent (data->core, torrentId);
if (tor != NULL)
{
tr_file_index_t i;
const tr_info * inf = tr_torrentInfo (tor);
struct row_struct * root_data;
GNode * root;
struct build_data build;
/* build a GNode tree of the files */
root_data = g_new0 (struct row_struct, 1);
root_data->name = g_strdup (tr_torrentName (tor));
root_data->index = -1;
root_data->length = 0;
root = g_node_new (root_data);
for (i=0; i<inf->fileCount; ++i) {
int j;
GNode * parent = root;
const tr_file * file = &inf->files[i];
char ** tokens = g_strsplit (file->name, G_DIR_SEPARATOR_S, 0);
for (j=0; tokens[j]; ++j) {
const gboolean isLeaf = tokens[j+1] == NULL;
const char * name = tokens[j];
GNode * node = find_child (parent, name);
if (node == NULL) {
struct row_struct * row = g_new (struct row_struct, 1);
row->name = g_strdup (name);
row->index = isLeaf ? (int)i : -1;
row->length = isLeaf ? file->length : 0;
node = g_node_new (row);
g_node_append (parent, node);
}
parent = node;
}
g_strfreev (tokens);
}
/* now, add them to the model */
build.w = w;
build.tor = tor;
build.store = data->store;
build.iter = NULL;
g_node_children_foreach (root, G_TRAVERSE_ALL, buildTree, &build);
/* cleanup */
g_node_destroy (root);
g_free (root_data->name);
g_free (root_data);
}
refresh (data);
data->timeout_tag = gdk_threads_add_timeout_seconds (SECONDARY_WINDOW_REFRESH_INTERVAL_SECONDS, refreshModel, data);
}
gtk_tree_view_set_model (GTK_TREE_VIEW (data->view), data->model);
gtk_tree_view_expand_all (GTK_TREE_VIEW (data->view));
g_object_unref (data->model);
}
/***
****
***/
static void
renderDownload (GtkTreeViewColumn * column UNUSED,
GtkCellRenderer * renderer,
GtkTreeModel * model,
GtkTreeIter * iter,
gpointer data UNUSED)
{
gboolean enabled;
gtk_tree_model_get (model, iter, FC_ENABLED, &enabled, -1);
g_object_set (renderer, "inconsistent", (enabled==MIXED),
"active", (enabled==TRUE),
NULL);
}
static void
renderPriority (GtkTreeViewColumn * column UNUSED,
GtkCellRenderer * renderer,
GtkTreeModel * model,
GtkTreeIter * iter,
gpointer data UNUSED)
{
int priority;
const char * text;
gtk_tree_model_get (model, iter, FC_PRIORITY, &priority, -1);
switch (priority) {
case TR_PRI_HIGH: text = _("High"); break;
case TR_PRI_NORMAL: text = _("Normal"); break;
case TR_PRI_LOW: text = _("Low"); break;
default: text = _("Mixed"); break;
}
g_object_set (renderer, "text", text, NULL);
2008-02-15 18:20:56 +00:00
}
/* build a filename from tr_torrentGetCurrentDir () + the model's FC_LABELs */
static char*
buildFilename (tr_torrent * tor, GtkTreeModel * model,
GtkTreePath * path, GtkTreeIter * iter)
{
char * ret;
GtkTreeIter child;
GtkTreeIter parent = *iter;
int n = gtk_tree_path_get_depth (path);
char ** tokens = g_new0 (char*, n + 2);
tokens[0] = g_strdup (tr_torrentGetCurrentDir (tor));
do {
child = parent;
gtk_tree_model_get (model, &child, FC_LABEL, &tokens[n--], -1);
} while (gtk_tree_model_iter_parent (model, &parent, &child));
ret = g_build_filenamev (tokens);
g_strfreev (tokens);
return ret;
}
static gboolean
onRowActivated (GtkTreeView * view, GtkTreePath * path,
GtkTreeViewColumn * col UNUSED, gpointer gdata)
{
gboolean handled = FALSE;
FileData * data = gdata;
tr_torrent * tor = gtr_core_find_torrent (data->core, data->torrentId);
if (tor != NULL)
{
GtkTreeIter iter;
GtkTreeModel * model = gtk_tree_view_get_model (view);
if (gtk_tree_model_get_iter (model, &iter, path))
{
int prog;
char * filename = buildFilename (tor, model, path, &iter);
gtk_tree_model_get (model, &iter, FC_PROG, &prog, -1);
/* if the file's not done, walk up the directory tree until we find
* an ancestor that exists, and open that instead */
if (filename && (prog<100 || !g_file_test (filename, G_FILE_TEST_EXISTS))) do
{
char * tmp = g_path_get_dirname (filename);
g_free (filename);
filename = tmp;
}
while (filename && *filename && !g_file_test (filename, G_FILE_TEST_EXISTS));
if ((handled = filename && *filename))
gtr_open_file (filename);
}
}
return handled;
}
static gboolean
onViewPathToggled (GtkTreeView * view,
GtkTreeViewColumn * col,
GtkTreePath * path,
FileData * data)
{
int cid;
tr_torrent * tor;
gboolean handled = FALSE;
if (!col || !path)
return FALSE;
cid = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (col), TR_COLUMN_ID_KEY));
tor = gtr_core_find_torrent (data->core, data->torrentId);
if ((tor != NULL) && ((cid == FC_PRIORITY) || (cid == FC_ENABLED)))
{
GtkTreeIter iter;
GArray * indices = getActiveFilesForPath (view, path);
GtkTreeModel * model = data->model;
gtk_tree_model_get_iter (model, &iter, path);
if (cid == FC_PRIORITY)
{
int priority;
gtk_tree_model_get (model, &iter, FC_PRIORITY, &priority, -1);
switch (priority) {
case TR_PRI_NORMAL: priority = TR_PRI_HIGH; break;
case TR_PRI_HIGH: priority = TR_PRI_LOW; break;
default: priority = TR_PRI_NORMAL; break;
}
tr_torrentSetFilePriorities (tor,
(tr_file_index_t *) indices->data,
(tr_file_index_t) indices->len,
priority);
}
else
{
int enabled;
gtk_tree_model_get (model, &iter, FC_ENABLED, &enabled, -1);
enabled = !enabled;
tr_torrentSetFileDLs (tor,
(tr_file_index_t *) indices->data,
(tr_file_index_t) indices->len,
enabled);
}
refresh (data);
g_array_free (indices, TRUE);
handled = TRUE;
}
return handled;
}
/**
* @note 'col' and 'path' are assumed not to be NULL.
*/
static gboolean
getAndSelectEventPath (GtkTreeView * treeview,
GdkEventButton * event,
GtkTreeViewColumn ** col,
GtkTreePath ** path)
{
GtkTreeSelection * sel;
if (gtk_tree_view_get_path_at_pos (treeview,
event->x, event->y,
path, col, NULL, NULL))
{
sel = gtk_tree_view_get_selection (treeview);
if (!gtk_tree_selection_path_is_selected (sel, *path))
{
gtk_tree_selection_unselect_all (sel);
gtk_tree_selection_select_path (sel, *path);
}
return TRUE;
}
2008-09-08 15:17:18 +00:00
return FALSE;
}
static gboolean
onViewButtonPressed (GtkWidget * w, GdkEventButton * event, gpointer gdata)
{
GtkTreeViewColumn * col;
GtkTreePath * path = NULL;
gboolean handled = FALSE;
GtkTreeView * treeview = GTK_TREE_VIEW (w);
FileData * data = gdata;
if ((event->type == GDK_BUTTON_PRESS)
&& (event->button == 1)
&& ! (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))
&& getAndSelectEventPath (treeview, event, &col, &path))
{
handled = onViewPathToggled (treeview, col, path, data);
if (path != NULL)
gtk_tree_path_free (path);
}
return handled;
}
GtkWidget *
gtr_file_list_new (TrCore * core, int torrentId)
{
int size;
int width;
GtkWidget * ret;
GtkWidget * view;
GtkWidget * scroll;
GtkCellRenderer * rend;
GtkTreeSelection * sel;
GtkTreeViewColumn * col;
GtkTreeView * tree_view;
const char * title;
PangoLayout * pango_layout;
PangoContext * pango_context;
PangoFontDescription * pango_font_description;
FileData * data = g_new0 (FileData, 1);
data->core = core;
/* create the view */
view = gtk_tree_view_new ();
tree_view = GTK_TREE_VIEW (view);
gtk_tree_view_set_rules_hint (tree_view, TRUE);
gtk_container_set_border_width (GTK_CONTAINER (view), GUI_PAD_BIG);
g_signal_connect (view, "button-press-event",
G_CALLBACK (onViewButtonPressed), data);
g_signal_connect (view, "row_activated",
G_CALLBACK (onRowActivated), data);
g_signal_connect (view, "button-release-event",
G_CALLBACK (on_tree_view_button_released), NULL);
pango_context = gtk_widget_create_pango_context (view);
pango_font_description = pango_font_description_copy (pango_context_get_font_description (pango_context));
size = pango_font_description_get_size (pango_font_description);
pango_font_description_set_size (pango_font_description, size * 0.8);
g_object_unref (G_OBJECT (pango_context));
/* set up view */
sel = gtk_tree_view_get_selection (tree_view);
gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE);
gtk_tree_view_expand_all (tree_view);
gtk_tree_view_set_search_column (tree_view, FC_LABEL);
/* add file column */
col = GTK_TREE_VIEW_COLUMN (g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
"expand", TRUE,
"title", _("Name"),
NULL));
gtk_tree_view_column_set_resizable (col, TRUE);
rend = gtk_cell_renderer_pixbuf_new ();
gtk_tree_view_column_pack_start (col, rend, FALSE);
gtk_tree_view_column_add_attribute (col, rend, "pixbuf", FC_ICON);
/* add text renderer */
rend = gtk_cell_renderer_text_new ();
g_object_set (rend, "ellipsize", PANGO_ELLIPSIZE_END, "font-desc", pango_font_description, NULL);
gtk_tree_view_column_pack_start (col, rend, TRUE);
gtk_tree_view_column_set_attributes (col, rend, "text", FC_LABEL, NULL);
gtk_tree_view_column_set_sort_column_id (col, FC_LABEL);
gtk_tree_view_append_column (tree_view, col);
/* add "size" column */
title = _("Size");
rend = gtk_cell_renderer_text_new ();
g_object_set (rend, "alignment", PANGO_ALIGN_RIGHT,
"font-desc", pango_font_description,
"xpad", GUI_PAD,
"xalign", 1.0f,
"yalign", 0.5f,
NULL);
col = gtk_tree_view_column_new_with_attributes (title, rend, NULL);
gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
gtk_tree_view_column_set_sort_column_id (col, FC_SIZE);
gtk_tree_view_column_set_attributes (col, rend, "text", FC_SIZE_STR, NULL);
gtk_tree_view_append_column (tree_view, col);
/* add "progress" column */
title = _("Have");
pango_layout = gtk_widget_create_pango_layout (view, title);
pango_layout_get_pixel_size (pango_layout, &width, NULL);
width += 30; /* room for the sort indicator */
g_object_unref (G_OBJECT (pango_layout));
rend = gtk_cell_renderer_progress_new ();
col = gtk_tree_view_column_new_with_attributes (title, rend, "value", FC_PROG, NULL);
gtk_tree_view_column_set_fixed_width (col, width);
gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_FIXED);
gtk_tree_view_column_set_sort_column_id (col, FC_PROG);
gtk_tree_view_append_column (tree_view, col);
/* add "enabled" column */
title = _("Download");
pango_layout = gtk_widget_create_pango_layout (view, title);
pango_layout_get_pixel_size (pango_layout, &width, NULL);
width += 30; /* room for the sort indicator */
g_object_unref (G_OBJECT (pango_layout));
rend = gtk_cell_renderer_toggle_new ();
col = gtk_tree_view_column_new_with_attributes (title, rend, NULL);
g_object_set_data (G_OBJECT (col), TR_COLUMN_ID_KEY,
GINT_TO_POINTER (FC_ENABLED));
gtk_tree_view_column_set_fixed_width (col, width);
gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_FIXED);
gtk_tree_view_column_set_cell_data_func (col, rend, renderDownload, NULL, NULL);
gtk_tree_view_column_set_sort_column_id (col, FC_ENABLED);
gtk_tree_view_append_column (tree_view, col);
/* add priority column */
title = _("Priority");
pango_layout = gtk_widget_create_pango_layout (view, title);
pango_layout_get_pixel_size (pango_layout, &width, NULL);
width += 30; /* room for the sort indicator */
g_object_unref (G_OBJECT (pango_layout));
rend = gtk_cell_renderer_text_new ();
g_object_set (rend, "xalign", (gfloat)0.5, "yalign", (gfloat)0.5, NULL);
col = gtk_tree_view_column_new_with_attributes (title, rend, NULL);
g_object_set_data (G_OBJECT (col), TR_COLUMN_ID_KEY,
GINT_TO_POINTER (FC_PRIORITY));
gtk_tree_view_column_set_fixed_width (col, width);
gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_FIXED);
gtk_tree_view_column_set_sort_column_id (col, FC_PRIORITY);
gtk_tree_view_column_set_cell_data_func (col, rend, renderPriority, NULL, NULL);
gtk_tree_view_append_column (tree_view, col);
/* add tooltip to tree */
gtk_tree_view_set_tooltip_column (tree_view, FC_LABEL_ESC);
/* create the scrolled window and stick the view in it */
scroll = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll),
GTK_SHADOW_IN);
gtk_container_add (GTK_CONTAINER (scroll), view);
gtk_widget_set_size_request (scroll, -1, 200);
ret = scroll;
data->view = view;
data->top = scroll;
g_object_set_data_full (G_OBJECT (ret), "file-data", data, freeData);
gtr_file_list_set_torrent (ret, torrentId);
pango_font_description_free (pango_font_description);
return ret;
}