mirror of
https://github.com/transmission/transmission
synced 2024-12-27 18:18:10 +00:00
964 lines
32 KiB
C
964 lines
32 KiB
C
/******************************************************************************
|
|
* $Id$
|
|
*
|
|
* Copyright (c) 2005-2007 Transmission authors and contributors
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*****************************************************************************/
|
|
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
#include <glib/gi18n.h>
|
|
|
|
#include "conf.h"
|
|
#include "dialogs.h"
|
|
#include "tr_cell_renderer_progress.h"
|
|
#include "tr_core.h"
|
|
#include "tr_icon.h"
|
|
#include "tr_prefs.h"
|
|
#include "util.h"
|
|
|
|
#include "transmission.h"
|
|
|
|
#define UPDATE_INTERVAL 1000
|
|
#define PREFNAME "transmission-dialog-pref-name"
|
|
#define FILESWIND_EXTRA_INDENT 4
|
|
|
|
#define STRIPROOT( path ) \
|
|
( g_path_is_absolute( (path) ) ? g_path_skip_root( (path) ) : (path) )
|
|
|
|
struct addcb
|
|
{
|
|
GtkWidget * widget;
|
|
TrCore * core;
|
|
gboolean autostart;
|
|
gboolean usingaltdir;
|
|
GtkFileChooser *altdir;
|
|
GtkButtonBox *altbox;
|
|
};
|
|
|
|
struct dirdata
|
|
{
|
|
GtkWidget * widget;
|
|
TrCore * core;
|
|
GList * files;
|
|
uint8_t * data;
|
|
size_t size;
|
|
enum tr_torrent_action action;
|
|
gboolean paused;
|
|
};
|
|
|
|
struct quitdata
|
|
{
|
|
callbackfunc_t func;
|
|
void * cbdata;
|
|
};
|
|
|
|
struct infowind
|
|
{
|
|
GtkWidget * widget;
|
|
TrTorrent * tor;
|
|
int64_t size;
|
|
GtkTreeModel * model;
|
|
GtkTreeRowReference * row;
|
|
GtkTreeModel * filesmodel;
|
|
guint timer;
|
|
struct
|
|
{
|
|
tr_tracker_info_t * track;
|
|
GtkLabel * trackwid;
|
|
GtkLabel * annwid;
|
|
GtkLabel * scrwid;
|
|
int seed;
|
|
GtkLabel * seedwid;
|
|
int leech;
|
|
GtkLabel * leechwid;
|
|
int done;
|
|
GtkLabel * donewid;
|
|
uint64_t up;
|
|
GtkLabel * upwid;
|
|
uint64_t down;
|
|
GtkLabel * downwid;
|
|
uint64_t left;
|
|
GtkLabel * leftwid;
|
|
} inf;
|
|
};
|
|
|
|
static void
|
|
addwindnocore( gpointer gdata, GObject * core );
|
|
static void
|
|
autoclick(GtkWidget *widget, gpointer gdata);
|
|
static void
|
|
dirclick(GtkWidget *widget, gpointer gdata);
|
|
static void
|
|
addresp(GtkWidget *widget, gint resp, gpointer gdata);
|
|
static GtkWidget *
|
|
makeinfotab( TrTorrent * tor, struct infowind * iw );
|
|
static void
|
|
infoupdate( struct infowind * iw, int force );
|
|
static void
|
|
fmtpeercount( GtkLabel * label, int count );
|
|
static void
|
|
promptdirnocore( gpointer gdata, GObject * core );
|
|
static void
|
|
promptresp( GtkWidget * widget, gint resp, gpointer data );
|
|
static void
|
|
quitresp( GtkWidget * widget, gint resp, gpointer data );
|
|
static GtkWidget *
|
|
makefilestab( TrTorrent * tor, GtkTreeModel ** modelret );
|
|
static void
|
|
stylekludge( GObject * obj, GParamSpec * spec, gpointer data );
|
|
static void
|
|
infowinddead( GtkWidget * widget, gpointer data );
|
|
static void
|
|
infotorclosed( gpointer data, GObject * tor );
|
|
static void
|
|
parsepath( GtkTreeStore * store, GtkTreeIter * ret,
|
|
const char * path, int index, uint64_t size );
|
|
static uint64_t
|
|
getdirtotals( GtkTreeStore * store, GtkTreeIter * parent );
|
|
static gboolean
|
|
infowindupdate( gpointer data );
|
|
static float
|
|
updateprogress( GtkTreeModel * model, GtkTreeIter * parent,
|
|
uint64_t total, float * progress );
|
|
|
|
void
|
|
makeaddwind( GtkWindow * parent, TrCore * core )
|
|
{
|
|
GtkWidget *wind = gtk_file_chooser_dialog_new(_("Add a Torrent"), parent,
|
|
GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
|
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
|
|
struct addcb *data = g_new(struct addcb, 1);
|
|
GtkWidget *vbox = gtk_vbox_new(FALSE, 3);
|
|
GtkWidget *bbox = gtk_hbutton_box_new();
|
|
GtkWidget *autocheck = gtk_check_button_new_with_mnemonic(
|
|
_("Automatically _start torrent"));
|
|
GtkWidget *dircheck = gtk_check_button_new_with_mnemonic(
|
|
_("Use alternate _download directory"));
|
|
GtkFileFilter *filter = gtk_file_filter_new();
|
|
GtkFileFilter *unfilter = gtk_file_filter_new();
|
|
GtkWidget *getdir = gtk_file_chooser_button_new(
|
|
_("Choose a download directory"), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
|
|
const char * pref;
|
|
|
|
data->widget = wind;
|
|
data->core = core;
|
|
data->autostart = TRUE;
|
|
data->usingaltdir = FALSE;
|
|
data->altdir = GTK_FILE_CHOOSER(getdir);
|
|
data->altbox = GTK_BUTTON_BOX(bbox);
|
|
|
|
g_object_weak_ref( G_OBJECT( core ), addwindnocore, data );
|
|
|
|
gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_EDGE);
|
|
gtk_box_pack_start_defaults(GTK_BOX(bbox), dircheck);
|
|
gtk_box_pack_start_defaults(GTK_BOX(bbox), getdir);
|
|
|
|
gtk_box_pack_start_defaults(GTK_BOX(vbox), autocheck);
|
|
gtk_box_pack_start_defaults(GTK_BOX(vbox), bbox);
|
|
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(autocheck), TRUE);
|
|
pref = tr_prefs_get( PREF_ID_DIR );
|
|
if( NULL != pref )
|
|
{
|
|
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( wind ), pref );
|
|
}
|
|
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dircheck), FALSE);
|
|
gtk_widget_set_sensitive(getdir, FALSE);
|
|
|
|
gtk_file_filter_set_name(filter, _("Torrent files"));
|
|
gtk_file_filter_add_pattern(filter, "*.torrent");
|
|
gtk_file_filter_set_name(unfilter, _("All files"));
|
|
gtk_file_filter_add_pattern(unfilter, "*");
|
|
|
|
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(wind), filter);
|
|
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(wind), unfilter);
|
|
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(wind), TRUE);
|
|
gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(wind), vbox);
|
|
|
|
g_signal_connect(G_OBJECT(autocheck), "clicked", G_CALLBACK(autoclick),data);
|
|
g_signal_connect(G_OBJECT(dircheck), "clicked", G_CALLBACK(dirclick), data);
|
|
g_signal_connect(G_OBJECT(wind), "response", G_CALLBACK(addresp), data);
|
|
|
|
gtk_widget_show_all(wind);
|
|
}
|
|
|
|
void
|
|
addwindnocore( gpointer gdata, GObject * core SHUTUP )
|
|
{
|
|
struct addcb * data = gdata;
|
|
|
|
/* prevent the response callback from trying to remove the weak
|
|
reference which no longer exists */
|
|
data->core = NULL;
|
|
|
|
gtk_dialog_response( GTK_DIALOG( data->widget ), GTK_RESPONSE_NONE );
|
|
}
|
|
|
|
static void
|
|
autoclick(GtkWidget *widget, gpointer gdata) {
|
|
struct addcb *data = gdata;
|
|
|
|
data->autostart = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
|
|
}
|
|
|
|
static void
|
|
dirclick(GtkWidget *widget, gpointer gdata) {
|
|
struct addcb *data = gdata;
|
|
|
|
data->usingaltdir = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
|
|
gtk_widget_set_sensitive(GTK_WIDGET(data->altdir), data->usingaltdir);
|
|
}
|
|
|
|
static void
|
|
addresp(GtkWidget *widget, gint resp, gpointer gdata) {
|
|
struct addcb *data = gdata;
|
|
GSList *files, *ii;
|
|
GList *stupidgtk;
|
|
char *dir;
|
|
enum tr_torrent_action action;
|
|
|
|
if(GTK_RESPONSE_ACCEPT == resp) {
|
|
dir = NULL;
|
|
if(data->usingaltdir)
|
|
dir = gtk_file_chooser_get_filename(data->altdir);
|
|
files = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(widget));
|
|
action = toraddaction( tr_prefs_get( PREF_ID_ADDSTD ) );
|
|
if( NULL == dir )
|
|
{
|
|
stupidgtk = NULL;
|
|
for( ii = files; NULL != ii; ii = ii->next )
|
|
{
|
|
stupidgtk = g_list_append( stupidgtk, ii->data );
|
|
}
|
|
tr_core_add_list( data->core, stupidgtk, action, !data->autostart );
|
|
freestrlist(stupidgtk);
|
|
}
|
|
else
|
|
{
|
|
for( ii = files; NULL != ii; ii = ii->next )
|
|
{
|
|
tr_core_add_dir( data->core, ii->data, dir,
|
|
action, !data->autostart );
|
|
g_free( ii->data );
|
|
}
|
|
g_free( dir );
|
|
}
|
|
tr_core_torrents_added( data->core );
|
|
g_slist_free(files);
|
|
}
|
|
|
|
if( NULL != data->core )
|
|
{
|
|
g_object_weak_unref( G_OBJECT( data->core ), addwindnocore, data );
|
|
}
|
|
|
|
g_free( data );
|
|
gtk_widget_destroy(widget);
|
|
}
|
|
|
|
void
|
|
makeinfowind( GtkWindow * parent, GtkTreeModel * model, GtkTreePath * path,
|
|
TrTorrent * tor )
|
|
{
|
|
struct infowind * iw;
|
|
GtkWidget * wind, * box, * label, * sep, * tabs, * page;
|
|
tr_info_t * inf;
|
|
char * name, * size;
|
|
|
|
iw = g_new0( struct infowind, 1 );
|
|
inf = tr_torrent_info( tor );
|
|
name = g_strdup_printf( _("%s - Properties for %s"),
|
|
g_get_application_name(), inf->name );
|
|
wind = gtk_dialog_new_with_buttons( name, parent,
|
|
GTK_DIALOG_DESTROY_WITH_PARENT |
|
|
GTK_DIALOG_NO_SEPARATOR,
|
|
GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
|
|
NULL );
|
|
g_free( name );
|
|
gtk_dialog_set_default_response( GTK_DIALOG( wind ), GTK_RESPONSE_ACCEPT );
|
|
gtk_window_set_resizable( GTK_WINDOW( wind ), TRUE );
|
|
gtk_window_set_role( GTK_WINDOW( wind ), "tr-info" );
|
|
gtk_widget_set_name( wind, "TransmissionDialog" );
|
|
box = GTK_DIALOG( wind )->vbox;
|
|
|
|
/* add label with file name and size */
|
|
label = gtk_label_new( NULL );
|
|
size = readablesize( inf->totalSize );
|
|
name = g_markup_printf_escaped( "<big>%s (%s)</big>", inf->name, size );
|
|
free( size );
|
|
gtk_label_set_markup( GTK_LABEL( label ), name );
|
|
g_free( name );
|
|
gtk_label_set_selectable( GTK_LABEL( label ), TRUE );
|
|
gtk_widget_show( label );
|
|
gtk_box_pack_start( GTK_BOX( box ), label, FALSE, FALSE, 6 );
|
|
|
|
/* add separator */
|
|
sep = gtk_hseparator_new();
|
|
gtk_widget_show( sep );
|
|
gtk_box_pack_start( GTK_BOX( box ), sep, FALSE, FALSE, 6 );
|
|
|
|
/* add tab bar */
|
|
tabs = gtk_notebook_new();
|
|
gtk_widget_show( tabs );
|
|
gtk_box_pack_start( GTK_BOX( box ), tabs, TRUE, TRUE, 6 );
|
|
|
|
/* add general tab */
|
|
label = gtk_label_new( _("General") );
|
|
gtk_widget_show( label );
|
|
page = makeinfotab( tor, iw );
|
|
gtk_notebook_append_page( GTK_NOTEBOOK( tabs ), page, label );
|
|
|
|
/* add files tab */
|
|
label = gtk_label_new( _("Files") );
|
|
gtk_widget_show( label );
|
|
/* XXX should use sizingmagic() here */
|
|
page = makefilestab( tor, &iw->filesmodel );
|
|
gtk_notebook_append_page( GTK_NOTEBOOK( tabs ), page, label );
|
|
|
|
/* set up the callback data */
|
|
iw->widget = wind;
|
|
iw->tor = tor;
|
|
iw->size = inf->totalSize;
|
|
iw->model = model;
|
|
iw->row = gtk_tree_row_reference_new( model, path );
|
|
iw->timer = g_timeout_add( UPDATE_INTERVAL, infowindupdate, iw );
|
|
|
|
g_object_ref( model );
|
|
g_object_weak_ref( G_OBJECT( tor ), infotorclosed, iw );
|
|
g_signal_connect( wind, "destroy", G_CALLBACK( infowinddead ), iw );
|
|
g_signal_connect( wind, "response", G_CALLBACK( gtk_widget_destroy ), 0 );
|
|
infoupdate( iw, 1 );
|
|
infowindupdate( iw );
|
|
|
|
gtk_widget_show( wind );
|
|
}
|
|
|
|
#define INFOLINE( tab, ii, nam, val ) \
|
|
do \
|
|
{ \
|
|
char * txt = g_markup_printf_escaped( "<b>%s</b>", (nam) ); \
|
|
GtkWidget * wid = gtk_label_new( NULL ); \
|
|
gtk_misc_set_alignment( GTK_MISC( wid ), 0, .5 ); \
|
|
gtk_label_set_markup( GTK_LABEL( wid ), txt ); \
|
|
gtk_table_attach( GTK_TABLE( (tab) ), wid, 0, 1, (ii), (ii) + 1, \
|
|
GTK_FILL, GTK_FILL, 0, 0 ); \
|
|
gtk_label_set_selectable( GTK_LABEL( (val) ), TRUE ); \
|
|
gtk_misc_set_alignment( GTK_MISC( (val) ), 0, .5 ); \
|
|
gtk_table_attach( GTK_TABLE( (tab) ), (val), 1, 2, (ii), (ii) + 1, \
|
|
GTK_FILL, GTK_FILL, 0, 0); \
|
|
(ii)++; \
|
|
g_free( txt ); \
|
|
} while( 0 )
|
|
|
|
#define INFOLINEF( tab, ii, fmt, nam, val ) \
|
|
do \
|
|
{ \
|
|
char * buf = g_strdup_printf( fmt, val ); \
|
|
GtkWidget * lwid = gtk_label_new( buf ); \
|
|
g_free( buf ); \
|
|
INFOLINE( tab, ii, nam, lwid ); \
|
|
} while( 0 )
|
|
|
|
#define INFOLINEW( tab, ii, nam, val ) \
|
|
do \
|
|
{ \
|
|
GtkWidget * lwid = gtk_label_new( (val) ); \
|
|
INFOLINE( (tab), (ii), (nam), lwid ); \
|
|
} while( 0 )
|
|
|
|
#define INFOLINEA( tab, ii, nam, val ) \
|
|
do \
|
|
{ \
|
|
GtkWidget * lwid = gtk_label_new( (val) ); \
|
|
g_free( val ); \
|
|
INFOLINE( (tab), (ii), (nam), lwid ); \
|
|
} while( 0 )
|
|
|
|
#define INFOLINEU( tab, ii, nam, stor ) \
|
|
do \
|
|
{ \
|
|
GtkWidget * lwid = gtk_label_new( NULL ); \
|
|
(stor) = GTK_LABEL( lwid ); \
|
|
INFOLINE( (tab), (ii), (nam), lwid); \
|
|
} while( 0 )
|
|
|
|
#define INFOSEP( tab, ii ) \
|
|
do \
|
|
{ \
|
|
GtkWidget * wid = gtk_hseparator_new(); \
|
|
gtk_table_attach( GTK_TABLE( (tab) ), wid, 0, 2, (ii), (ii) + 1, \
|
|
GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0 ); \
|
|
(ii)++; \
|
|
} while( 0 )
|
|
|
|
GtkWidget *
|
|
makeinfotab( TrTorrent * tor, struct infowind * iw )
|
|
{
|
|
const int rowcount = 17;
|
|
tr_info_t * inf;
|
|
int ii;
|
|
GtkWidget * table;
|
|
|
|
inf = tr_torrent_info( tor );
|
|
table = gtk_table_new( rowcount, 2, FALSE );
|
|
gtk_table_set_col_spacings( GTK_TABLE( table ), 12 );
|
|
gtk_table_set_row_spacings( GTK_TABLE( table ), 12 );
|
|
gtk_container_set_border_width( GTK_CONTAINER( table ), 6 );
|
|
|
|
ii = 0;
|
|
|
|
INFOLINEU( table, ii, _("Tracker:"), iw->inf.trackwid );
|
|
INFOLINEU( table, ii, _("Announce:"), iw->inf.annwid );
|
|
INFOLINEU( table, ii, _("Scrape:"), iw->inf.scrwid );
|
|
INFOSEP( table, ii );
|
|
INFOLINEW( table, ii, _("Info Hash:"), inf->hashString );
|
|
INFOLINEA( table, ii, _("Piece Size:"), readablesize( inf->pieceSize ) );
|
|
INFOLINEF( table, ii, "%i", _("Pieces:"), inf->pieceCount );
|
|
INFOLINEA( table, ii, _("Total Size:"), readablesize( inf->totalSize ) );
|
|
INFOSEP( table, ii );
|
|
INFOLINEU( table, ii, _("Seeders:"), iw->inf.seedwid );
|
|
INFOLINEU( table, ii, _("Leechers:"), iw->inf.leechwid );
|
|
INFOLINEU( table, ii, _("Completed:"), iw->inf.donewid );
|
|
INFOSEP( table, ii );
|
|
INFOLINEW( table, ii, _("Directory:"),
|
|
tr_torrentGetFolder( tr_torrent_handle( tor ) ) );
|
|
INFOLINEU( table, ii, _("Downloaded:"), iw->inf.downwid );
|
|
INFOLINEU( table, ii, _("Uploaded:"), iw->inf.upwid );
|
|
INFOLINEU( table, ii, _("Remaining:"), iw->inf.leftwid );
|
|
|
|
g_assert( rowcount == ii );
|
|
|
|
gtk_widget_show_all( table );
|
|
|
|
return table;
|
|
}
|
|
|
|
void
|
|
infoupdate( struct infowind * iw, int force )
|
|
{
|
|
int seed, leech, done;
|
|
uint64_t up, down, left;
|
|
tr_tracker_info_t * track;
|
|
GtkTreePath * path;
|
|
GtkTreeIter iter;
|
|
char * str;
|
|
|
|
path = gtk_tree_row_reference_get_path( iw->row );
|
|
if( NULL == path || !gtk_tree_model_get_iter( iw->model, &iter, path ) )
|
|
{
|
|
g_free( path );
|
|
return;
|
|
}
|
|
g_free( path );
|
|
gtk_tree_model_get( iw->model, &iter, MC_TRACKER, &track,
|
|
MC_SEED, &seed, MC_LEECH, &leech, MC_DONE, &done,
|
|
MC_DOWN, &down, MC_UP, &up, MC_LEFT, &left, -1 );
|
|
|
|
if( track != iw->inf.track || force )
|
|
{
|
|
if( 80 == track->port )
|
|
{
|
|
str = g_strdup_printf( "http://%s", track->address );
|
|
}
|
|
else
|
|
{
|
|
str = g_strdup_printf( "http://%s:%i",
|
|
track->address, track->port );
|
|
}
|
|
gtk_label_set_text( iw->inf.trackwid, str );
|
|
g_free( str );
|
|
gtk_label_set_text( iw->inf.annwid, track->announce );
|
|
gtk_label_set_text( iw->inf.scrwid, track->scrape );
|
|
}
|
|
if( seed != iw->inf.seed || force )
|
|
{
|
|
fmtpeercount( iw->inf.seedwid, seed );
|
|
iw->inf.seed = seed;
|
|
}
|
|
if( leech != iw->inf.leech || force )
|
|
{
|
|
fmtpeercount( iw->inf.leechwid, leech );
|
|
iw->inf.leech = leech;
|
|
}
|
|
if( done != iw->inf.done || force )
|
|
{
|
|
fmtpeercount( iw->inf.donewid, done );
|
|
iw->inf.done = done;
|
|
}
|
|
if( down != iw->inf.down || force )
|
|
{
|
|
str = readablesize( down );
|
|
gtk_label_set_text( iw->inf.downwid, str );
|
|
g_free( str );
|
|
iw->inf.down = down;
|
|
}
|
|
if( up != iw->inf.up || force )
|
|
{
|
|
str = readablesize( up );
|
|
gtk_label_set_text( iw->inf.upwid, str );
|
|
g_free( str );
|
|
iw->inf.up = up;
|
|
}
|
|
if( left != iw->inf.left || force )
|
|
{
|
|
str = readablesize( left );
|
|
gtk_label_set_text( iw->inf.leftwid, str );
|
|
g_free( str );
|
|
iw->inf.left = left;
|
|
}
|
|
}
|
|
|
|
void
|
|
fmtpeercount( GtkLabel * label, int count )
|
|
{
|
|
char str[16];
|
|
|
|
if( 0 > count )
|
|
{
|
|
gtk_label_set_text( label, _("?") );
|
|
}
|
|
else
|
|
{
|
|
snprintf( str, sizeof str, "%i", count );
|
|
gtk_label_set_text( label, str );
|
|
}
|
|
}
|
|
|
|
void
|
|
promptfordir( GtkWindow * parent, TrCore * core, GList * files, uint8_t * data,
|
|
size_t size, enum tr_torrent_action act, gboolean paused )
|
|
{
|
|
struct dirdata * stuff;
|
|
GtkWidget * wind;
|
|
|
|
stuff = g_new0( struct dirdata, 1 );
|
|
stuff->core = core;
|
|
if( NULL != files )
|
|
{
|
|
stuff->files = dupstrlist( files );
|
|
}
|
|
if( NULL != data )
|
|
{
|
|
stuff->data = g_new( uint8_t, size );
|
|
memcpy( stuff->data, data, size );
|
|
stuff->size = size;
|
|
}
|
|
stuff->action = act;
|
|
stuff->paused = paused;
|
|
|
|
g_object_weak_ref( G_OBJECT( core ), promptdirnocore, stuff );
|
|
|
|
wind = gtk_file_chooser_dialog_new( _("Choose a directory"), parent,
|
|
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
|
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
|
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
|
|
NULL );
|
|
gtk_file_chooser_set_local_only( GTK_FILE_CHOOSER( wind ), TRUE );
|
|
gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER( wind ), FALSE );
|
|
gtk_file_chooser_set_filename( GTK_FILE_CHOOSER( wind ),
|
|
getdownloaddir() );
|
|
|
|
stuff->widget = wind;
|
|
|
|
g_signal_connect( G_OBJECT( wind ), "response",
|
|
G_CALLBACK( promptresp ), stuff );
|
|
|
|
gtk_widget_show_all(wind);
|
|
}
|
|
|
|
void
|
|
promptdirnocore( gpointer gdata, GObject * core SHUTUP )
|
|
{
|
|
struct dirdata * stuff = gdata;
|
|
|
|
/* prevent the response callback from trying to remove the weak
|
|
reference which no longer exists */
|
|
stuff->core = NULL;
|
|
|
|
gtk_dialog_response( GTK_DIALOG( stuff->widget ), GTK_RESPONSE_NONE );
|
|
}
|
|
|
|
static void
|
|
promptresp( GtkWidget * widget, gint resp, gpointer data )
|
|
{
|
|
struct dirdata * stuff;
|
|
char * dir;
|
|
GList * ii;
|
|
|
|
stuff = data;
|
|
|
|
if( GTK_RESPONSE_ACCEPT == resp )
|
|
{
|
|
dir = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( widget ) );
|
|
/* it seems that we will always get a directory */
|
|
g_assert( NULL != dir );
|
|
for( ii = g_list_first( stuff->files ); NULL != ii; ii = ii->next )
|
|
{
|
|
tr_core_add_dir( stuff->core, ii->data, dir,
|
|
stuff->action, stuff->paused );
|
|
}
|
|
if( NULL != stuff->data )
|
|
{
|
|
tr_core_add_data_dir( stuff->core, stuff->data, stuff->size, dir,
|
|
stuff->paused );
|
|
}
|
|
tr_core_torrents_added( stuff->core );
|
|
g_free( dir );
|
|
}
|
|
|
|
if( NULL != stuff->core )
|
|
{
|
|
g_object_weak_unref( G_OBJECT( stuff->core ), promptdirnocore, stuff );
|
|
}
|
|
|
|
freestrlist( stuff->files );
|
|
g_free( stuff->data );
|
|
g_free( stuff );
|
|
gtk_widget_destroy( widget );
|
|
}
|
|
|
|
void
|
|
askquit( GtkWindow * parent, callbackfunc_t func, void * cbdata )
|
|
{
|
|
struct quitdata * stuff;
|
|
GtkWidget * wind;
|
|
|
|
if( !tr_prefs_get_bool_with_default( PREF_ID_ASKQUIT ) )
|
|
{
|
|
func( cbdata );
|
|
return;
|
|
}
|
|
|
|
stuff = g_new( struct quitdata, 1 );
|
|
stuff->func = func;
|
|
stuff->cbdata = cbdata;
|
|
|
|
wind = gtk_message_dialog_new( parent, GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
|
|
_("Are you sure you want to quit %s?"),
|
|
g_get_application_name() );
|
|
gtk_dialog_set_default_response( GTK_DIALOG( wind ), GTK_RESPONSE_YES );
|
|
g_signal_connect( G_OBJECT( wind ), "response",
|
|
G_CALLBACK( quitresp ), stuff );
|
|
|
|
gtk_widget_show_all( wind );
|
|
}
|
|
|
|
static void
|
|
quitresp( GtkWidget * widget, gint resp, gpointer data )
|
|
{
|
|
struct quitdata * stuff;
|
|
|
|
stuff = data;
|
|
|
|
if( GTK_RESPONSE_YES == resp )
|
|
{
|
|
stuff->func( stuff->cbdata );
|
|
}
|
|
|
|
g_free( stuff );
|
|
gtk_widget_destroy( widget );
|
|
}
|
|
|
|
enum filescols
|
|
{
|
|
FC_STOCK = 0, FC_LABEL, FC_PROG, FC_KEY, FC_INDEX, FC_SIZE, FC__MAX
|
|
};
|
|
|
|
GtkWidget *
|
|
makefilestab( TrTorrent * tor, GtkTreeModel ** modelret )
|
|
{
|
|
GType cols[] =
|
|
{
|
|
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_FLOAT,
|
|
G_TYPE_STRING, G_TYPE_INT, G_TYPE_UINT64
|
|
};
|
|
tr_info_t * inf;
|
|
GtkTreeStore * store;
|
|
int ii;
|
|
GtkWidget * view, * scroll, * frame;
|
|
GtkCellRenderer * rend;
|
|
GtkTreeViewColumn * col;
|
|
GtkTreeSelection * sel;
|
|
char * label;
|
|
|
|
g_assert( ALEN( cols ) == FC__MAX );
|
|
|
|
/* set up the model */
|
|
inf = tr_torrent_info( tor );
|
|
store = gtk_tree_store_newv( FC__MAX, cols );
|
|
*modelret = GTK_TREE_MODEL( store );
|
|
for( ii = 0; ii < inf->fileCount; ii++ )
|
|
{
|
|
parsepath( store, NULL, STRIPROOT( inf->files[ii].name ),
|
|
ii, inf->files[ii].length );
|
|
}
|
|
getdirtotals( store, NULL );
|
|
gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( store ),
|
|
FC_KEY, GTK_SORT_ASCENDING );
|
|
|
|
/* create the view */
|
|
view = gtk_tree_view_new_with_model( GTK_TREE_MODEL( store ) );
|
|
/* add file column */
|
|
col = gtk_tree_view_column_new();
|
|
gtk_tree_view_column_set_expand( col, TRUE );
|
|
gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_AUTOSIZE );
|
|
gtk_tree_view_column_set_title( col, _("File") );
|
|
/* add icon renderer */
|
|
rend = gtk_cell_renderer_pixbuf_new();
|
|
gtk_tree_view_column_pack_start( col, rend, FALSE );
|
|
gtk_tree_view_column_add_attribute( col, rend, "stock-id", FC_STOCK );
|
|
/* add text renderer */
|
|
rend = gtk_cell_renderer_text_new();
|
|
g_object_set( rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL );
|
|
gtk_tree_view_column_pack_start( col, rend, TRUE );
|
|
gtk_tree_view_column_add_attribute( col, rend, "markup", FC_LABEL );
|
|
gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
|
|
/* add progress column */
|
|
col = gtk_tree_view_column_new();
|
|
gtk_tree_view_column_set_title( col, _("Progress") );
|
|
rend = tr_cell_renderer_progress_new();
|
|
/* this string is only used to determine the size of the progress bar */
|
|
label = g_markup_printf_escaped( "<small>%s</small>", _(" fnord fnord ") );
|
|
g_object_set( rend, "show-text", FALSE, "bar-sizing", label, NULL );
|
|
g_free( label );
|
|
gtk_tree_view_column_pack_start( col, rend, FALSE );
|
|
gtk_tree_view_column_add_attribute( col, rend, "progress", FC_PROG );
|
|
gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
|
|
/* XXX this shouldn't be necessary */
|
|
g_signal_connect( view, "notify::style", G_CALLBACK( stylekludge ), rend );
|
|
/* set up view */
|
|
sel = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
|
|
gtk_tree_selection_set_mode( sel, GTK_SELECTION_NONE );
|
|
gtk_tree_view_expand_all( GTK_TREE_VIEW( view ) );
|
|
gtk_tree_view_set_search_column( GTK_TREE_VIEW( view ), FC_LABEL );
|
|
gtk_widget_show( view );
|
|
|
|
/* 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_NEVER, GTK_POLICY_AUTOMATIC );
|
|
gtk_container_add( GTK_CONTAINER( scroll ), view );
|
|
gtk_widget_show( scroll );
|
|
|
|
/* create a frame around the scroll to make it look a little better */
|
|
frame = gtk_frame_new( NULL );
|
|
gtk_frame_set_shadow_type( GTK_FRAME( frame ), GTK_SHADOW_IN );
|
|
gtk_container_add( GTK_CONTAINER( frame ), scroll );
|
|
gtk_widget_show( frame );
|
|
|
|
return frame;
|
|
}
|
|
|
|
/* kludge to have the progress bars notice theme changes */
|
|
static void
|
|
stylekludge( GObject * obj, GParamSpec * spec, gpointer data )
|
|
{
|
|
TrCellRendererProgress * rend = TR_CELL_RENDERER_PROGRESS( data );
|
|
|
|
if( 0 == strcmp( "style", spec->name ) )
|
|
{
|
|
tr_cell_renderer_progress_reset_style( rend );
|
|
gtk_widget_queue_draw( GTK_WIDGET( obj ) );
|
|
}
|
|
}
|
|
|
|
static void
|
|
infowinddead( GtkWidget * widget SHUTUP, gpointer data )
|
|
{
|
|
struct infowind * iw = data;
|
|
|
|
if( NULL != iw->widget )
|
|
{
|
|
g_object_weak_unref( G_OBJECT( iw->tor ), infotorclosed, iw );
|
|
infotorclosed( iw, G_OBJECT( iw->tor ) );
|
|
}
|
|
}
|
|
|
|
static void
|
|
infotorclosed( gpointer data, GObject * tor SHUTUP )
|
|
{
|
|
struct infowind * iw = data;
|
|
GtkWidget * widget;
|
|
|
|
/* neuter the widget's destroy callback */
|
|
widget = iw->widget;
|
|
iw->widget = NULL;
|
|
|
|
g_source_remove( iw->timer );
|
|
g_object_unref( iw->filesmodel );
|
|
g_object_unref( iw->model );
|
|
gtk_tree_row_reference_free( iw->row );
|
|
gtk_widget_destroy( widget );
|
|
g_free( iw );
|
|
}
|
|
|
|
static void
|
|
parsepath( GtkTreeStore * store, GtkTreeIter * ret,
|
|
const char * path, int index, uint64_t size )
|
|
{
|
|
GtkTreeModel * model;
|
|
GtkTreeIter * parent, start, iter;
|
|
char * file, * dir, * lower, * mykey, * modelkey;
|
|
const char * stock;
|
|
|
|
model = GTK_TREE_MODEL( store );
|
|
parent = NULL;
|
|
file = g_path_get_basename( path );
|
|
if( 0 != strcmp( file, path ) )
|
|
{
|
|
dir = g_path_get_dirname( path );
|
|
parsepath( store, &start, dir, index, size );
|
|
g_free( dir );
|
|
parent = &start;
|
|
}
|
|
|
|
lower = g_utf8_casefold( file, -1 );
|
|
mykey = g_utf8_collate_key( lower, -1 );
|
|
if( gtk_tree_model_iter_children( model, &iter, parent ) )
|
|
{
|
|
do
|
|
{
|
|
gtk_tree_model_get( model, &iter, FC_KEY, &modelkey, -1 );
|
|
if( NULL != modelkey && 0 == strcmp( mykey, modelkey ) )
|
|
{
|
|
goto done;
|
|
}
|
|
}
|
|
while( gtk_tree_model_iter_next( model, &iter ) );
|
|
}
|
|
|
|
gtk_tree_store_append( store, &iter, parent );
|
|
if( NULL == ret )
|
|
{
|
|
stock = GTK_STOCK_FILE;
|
|
}
|
|
else
|
|
{
|
|
stock = GTK_STOCK_DIRECTORY;
|
|
size = 0;
|
|
index = -1;
|
|
}
|
|
gtk_tree_store_set( store, &iter, FC_INDEX, index, FC_LABEL, file,
|
|
FC_KEY, mykey, FC_STOCK, stock, FC_SIZE, size, -1 );
|
|
done:
|
|
g_free( mykey );
|
|
g_free( lower );
|
|
g_free( file );
|
|
if( NULL != ret )
|
|
{
|
|
memcpy( ret, &iter, sizeof( iter ) );
|
|
}
|
|
}
|
|
|
|
static uint64_t
|
|
getdirtotals( GtkTreeStore * store, GtkTreeIter * parent )
|
|
{
|
|
GtkTreeModel * model;
|
|
GtkTreeIter iter;
|
|
uint64_t mysize, subsize;
|
|
char * sizestr, * name, * label;
|
|
|
|
model = GTK_TREE_MODEL( store );
|
|
mysize = 0;
|
|
if( gtk_tree_model_iter_children( model, &iter, parent ) )
|
|
{
|
|
do
|
|
{
|
|
if( gtk_tree_model_iter_has_child( model, &iter ) )
|
|
{
|
|
subsize = getdirtotals( store, &iter );
|
|
gtk_tree_store_set( store, &iter, FC_SIZE, subsize, -1 );
|
|
}
|
|
else
|
|
{
|
|
gtk_tree_model_get( model, &iter, FC_SIZE, &subsize, -1 );
|
|
}
|
|
gtk_tree_model_get( model, &iter, FC_LABEL, &name, -1 );
|
|
sizestr = readablesize( subsize );
|
|
label = g_markup_printf_escaped( "<small>%s (%s)</small>",
|
|
name, sizestr );
|
|
g_free( sizestr );
|
|
g_free( name );
|
|
gtk_tree_store_set( store, &iter, FC_LABEL, label, -1 );
|
|
g_free( label );
|
|
mysize += subsize;
|
|
}
|
|
while( gtk_tree_model_iter_next( model, &iter ) );
|
|
}
|
|
|
|
return mysize;
|
|
}
|
|
|
|
static gboolean
|
|
infowindupdate( gpointer data )
|
|
{
|
|
struct infowind * iw;
|
|
float * progress;
|
|
|
|
iw = data;
|
|
progress = tr_torrentCompletion( tr_torrent_handle( iw->tor ) );
|
|
updateprogress( iw->filesmodel, NULL, iw->size, progress );
|
|
free( progress );
|
|
infoupdate( iw, 0 );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static float
|
|
updateprogress( GtkTreeModel * model, GtkTreeIter * parent,
|
|
uint64_t total, float * progress )
|
|
{
|
|
GtkTreeIter iter;
|
|
float myprog, subprog;
|
|
int index;
|
|
uint64_t size;
|
|
|
|
myprog = 0.0;
|
|
if( gtk_tree_model_iter_children( model, &iter, parent ) )
|
|
{
|
|
do
|
|
{
|
|
if( gtk_tree_model_iter_has_child( model, &iter ) )
|
|
{
|
|
gtk_tree_model_get( model, &iter, FC_SIZE, &size, -1 );
|
|
subprog = updateprogress( model, &iter, size, progress );
|
|
}
|
|
else
|
|
{
|
|
gtk_tree_model_get( model, &iter,
|
|
FC_SIZE, &size, FC_INDEX, &index, -1 );
|
|
g_assert( 0 <= index );
|
|
subprog = progress[index];
|
|
}
|
|
gtk_tree_store_set( GTK_TREE_STORE( model ), &iter,
|
|
FC_PROG, subprog, -1 );
|
|
myprog += subprog * ( total ? ( float )size / ( float )total : 1 );
|
|
}
|
|
while( gtk_tree_model_iter_next( model, &iter ) );
|
|
}
|
|
|
|
return myprog;
|
|
}
|