1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-26 01:27:28 +00:00
transmission/gtk/file-list.c
Jordan Lee 879a2afcbd Update the copyright year in the source code comments.
The Berne Convention says that the copyright year is moot, so instead of adding another year to each file as in previous years, I've removed the year altogether from the source code comments in libtransmission, gtk, qt, utils, daemon, and cli.

Juliusz's copyright notice in tr-dht and Johannes' copyright notice in tr-lpd have been left alone; it didn't seem appropriate to modify them.
2011-01-19 13:48:47 +00:00

1044 lines
35 KiB
C

/*
* 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 <stdlib.h>
#include <string.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <libtransmission/transmission.h>
#include "file-list.h"
#include "hig.h"
#include "icons.h"
#include "tr-prefs.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_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_get_iter_first( model, &iter ) ) 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 = NULL;
tr_session * session = tr_core_session( data->core );
if( session != NULL )
tor = tr_torrentFindFromId( session, 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 = tr_torrentFindFromId( session, data->torrentId );
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 )
{
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;
}
/***
****
***/
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;
tr_strlsize( size_str, child_data->length, sizeof size_str );
#if GTK_CHECK_VERSION(2,10,0)
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_SIZE, child_data->length,
FC_SIZE_STR, size_str,
FC_ICON, icon,
FC_PRIORITY, priority,
FC_ENABLED, enabled,
-1 );
#else
gtk_tree_store_append( build->store, &child_iter, build->iter );
gtk_tree_store_set( build->store, &child_iter,
FC_INDEX, child_data->index,
FC_LABEL, child_data->name,
FC_SIZE, child_data->length,
FC_SIZE_STR, size_str,
FC_ICON, icon,
FC_PRIORITY, priority,
FC_ENABLED, enabled,
-1 );
#endif
if( !isLeaf )
{
struct build_data b = *build;
b.iter = &child_iter;
g_node_children_foreach( node, G_TRAVERSE_ALL, buildTree, &b );
}
g_object_unref( icon );
/* 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" );
/* unset the old fields */
clearData( data );
/* instantiate the model */
store = gtk_tree_store_new ( N_FILE_COLS,
GDK_TYPE_PIXBUF, /* icon */
G_TYPE_STRING, /* label */
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_session * session = tr_core_session( data->core );
tr_torrent * tor = tr_torrentFindFromId( session, 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( inf->name );
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 = gtr_timeout_add_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 ) );
}
/***
****
***/
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 );
}
/* 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 = tr_torrentFindFromId( tr_core_session( 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 void
fileMenuSetDownload( GtkWidget * item, gpointer gdata )
{
tr_torrent * tor;
FileData * data = gdata;
if(( tor = tr_torrentFindFromId( tr_core_session( data->core ), data->torrentId )))
{
const tr_bool download_flag = g_object_get_data( G_OBJECT( item ), TR_DOWNLOAD_KEY ) != NULL;
GArray * indices = getSelectedFilesAndDescendants( GTK_TREE_VIEW( data->view ) );
tr_torrentSetFileDLs( tor,
(tr_file_index_t *) indices->data,
(tr_file_index_t) indices->len,
download_flag );
refresh( data );
g_array_free( indices, TRUE );
}
}
static void
fileMenuSetPriority( GtkWidget * item, gpointer gdata )
{
tr_torrent * tor;
FileData * data = gdata;
if(( tor = tr_torrentFindFromId( tr_core_session( data->core ), data->torrentId )))
{
const int priority = GPOINTER_TO_INT( g_object_get_data( G_OBJECT( item ), TR_PRIORITY_KEY ) );
GArray * indices = getSelectedFilesAndDescendants( GTK_TREE_VIEW( data->view ) );
tr_torrentSetFilePriorities( tor,
(tr_file_index_t *) indices->data,
(tr_file_index_t) indices->len,
priority );
refresh( data );
g_array_free( indices, TRUE );
}
}
static void
fileMenuPopup( GtkWidget * w, GdkEventButton * event, gpointer filedata )
{
GtkWidget * item;
GtkWidget * menu;
menu = gtk_menu_new( );
item = gtk_menu_item_new_with_label( _( "Set Priority High" ) );
g_object_set_data( G_OBJECT( item ), TR_PRIORITY_KEY,
GINT_TO_POINTER( TR_PRI_HIGH ) );
g_signal_connect( item, "activate",
G_CALLBACK( fileMenuSetPriority ), filedata );
gtk_menu_shell_append( GTK_MENU_SHELL( menu ), item );
item = gtk_menu_item_new_with_label( _( "Set Priority Normal" ) );
g_object_set_data( G_OBJECT( item ), TR_PRIORITY_KEY,
GINT_TO_POINTER( TR_PRI_NORMAL ) );
g_signal_connect( item, "activate",
G_CALLBACK( fileMenuSetPriority ), filedata );
gtk_menu_shell_append( GTK_MENU_SHELL( menu ), item );
item = gtk_menu_item_new_with_label( _( "Set Priority Low" ) );
g_object_set_data( G_OBJECT( item ), TR_PRIORITY_KEY,
GINT_TO_POINTER( TR_PRI_LOW ) );
g_signal_connect( item, "activate",
G_CALLBACK( fileMenuSetPriority ), filedata );
gtk_menu_shell_append( GTK_MENU_SHELL( menu ), item );
item = gtk_separator_menu_item_new( );
gtk_menu_shell_append( GTK_MENU_SHELL( menu ), item );
item = gtk_menu_item_new_with_label( _( "Download" ) );
g_object_set_data( G_OBJECT( item ), TR_DOWNLOAD_KEY,
GINT_TO_POINTER( TRUE ) );
g_signal_connect( item, "activate",
G_CALLBACK( fileMenuSetDownload ), filedata );
gtk_menu_shell_append( GTK_MENU_SHELL( menu ), item );
item = gtk_menu_item_new_with_label( _( "Do Not Download" ) );
g_object_set_data( G_OBJECT( item ), TR_DOWNLOAD_KEY,
GINT_TO_POINTER( FALSE ) );
g_signal_connect( item, "activate",
G_CALLBACK( fileMenuSetDownload ), filedata );
gtk_menu_shell_append( GTK_MENU_SHELL( menu ), item );
gtk_widget_show_all( menu );
gtk_menu_attach_to_widget( GTK_MENU( menu ), w, NULL );
gtk_menu_popup( GTK_MENU( menu ), NULL, NULL, NULL, NULL,
event ? event->button : 0,
event ? event->time : gtk_get_current_event_time( ) );
}
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 = tr_torrentFindFromId( tr_core_session( 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;
}
return FALSE;
}
static gboolean
onViewButtonPressed( GtkWidget * w, GdkEventButton * event, gpointer gdata )
{
tr_torrent * tor;
GtkTreeViewColumn * col = NULL;
GtkTreePath * path = NULL;
FileData * data = gdata;
gboolean handled = FALSE;
GtkTreeView * treeview = GTK_TREE_VIEW( w );
tor = tr_torrentFindFromId( tr_core_session( data->core ),
data->torrentId );
if( tor == NULL )
return FALSE;
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 );
}
else if( event->type == GDK_BUTTON_PRESS && event->button == 3
&& getAndSelectEventPath( treeview, event, &col, &path ) )
{
GtkTreeSelection * sel = gtk_tree_view_get_selection( treeview );
if( gtk_tree_selection_count_selected_rows( sel ) > 0 )
{
fileMenuPopup( w, event, data );
handled = TRUE;
}
}
gtk_tree_path_free( path );
return handled;
}
static gboolean
onViewPopupMenu( GtkWidget * w, gpointer gdata )
{
fileMenuPopup( w, NULL, gdata );
return TRUE;
}
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, "popup-menu",
G_CALLBACK( onViewPopupMenu ), 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 );
/* 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 );
return ret;
}