1397 lines
44 KiB
C
1397 lines
44 KiB
C
/*
|
|
* This file Copyright (C) 2007-2008 Charles Kerr <charles@rebelbase.com>
|
|
*
|
|
* 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 <errno.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <glib/gi18n.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#include <libtransmission/transmission.h>
|
|
#include <libtransmission/utils.h> /* tr_httpIsValidURL */
|
|
|
|
#include "actions.h"
|
|
#include "details.h"
|
|
#include "file-list.h"
|
|
#include "tr-torrent.h"
|
|
#include "tracker-list.h"
|
|
#include "hig.h"
|
|
#include "util.h"
|
|
|
|
#define UPDATE_INTERVAL_MSEC 2000
|
|
|
|
/****
|
|
***** PIECES VIEW
|
|
****/
|
|
|
|
/* define SHOW_PIECES */
|
|
|
|
#ifdef SHOW_PIECES
|
|
static int
|
|
getGridSize (int pieceCount, int * n_rows, int * n_cols)
|
|
{
|
|
const int MAX_ACROSS = 16;
|
|
if (pieceCount >= (MAX_ACROSS * MAX_ACROSS)) {
|
|
*n_rows = *n_cols = MAX_ACROSS;
|
|
return MAX_ACROSS * MAX_ACROSS;
|
|
}
|
|
else {
|
|
int i;
|
|
for (i=0; (i*i) < pieceCount; ++i);
|
|
*n_rows = *n_cols = i;
|
|
return pieceCount;
|
|
}
|
|
}
|
|
|
|
#define TO16(a) ((guint16)((a<<8)|(a)))
|
|
#define RGB_2_GDK(R,G,B) { 0, TO16(R), TO16(G), TO16(B) }
|
|
|
|
enum { DRAW_AVAIL, DRAW_PROG };
|
|
|
|
static void
|
|
release_gobject_array (gpointer data)
|
|
{
|
|
int i;
|
|
GObject **objects = (GObject**) data;
|
|
for (i=0; objects[i]!=NULL; ++i)
|
|
g_object_unref (G_OBJECT(objects[i]));
|
|
g_free (objects);
|
|
}
|
|
|
|
static gboolean
|
|
refresh_pieces( GtkWidget * da, GdkEventExpose * event UNUSED, gpointer gtor )
|
|
{
|
|
tr_torrent * tor = tr_torrent_handle( TR_TORRENT(gtor) );
|
|
const tr_info * info = tr_torrent_info( TR_TORRENT(gtor) );
|
|
int mode = GPOINTER_TO_INT (g_object_get_data (G_OBJECT(da), "draw-mode"));
|
|
|
|
GdkColormap * colormap = gtk_widget_get_colormap (da);
|
|
const int widget_w = da->allocation.width;
|
|
const int widget_h = da->allocation.height;
|
|
int n_rows, n_cols;
|
|
const int n_cells = getGridSize (info->pieceCount, &n_rows, &n_cols);
|
|
const GdkRectangle grid_bounds = { 0, 0, widget_w, widget_h };
|
|
const double piece_w = grid_bounds.width / (double)n_cols;
|
|
const double piece_h = grid_bounds.height / (double)n_rows;
|
|
const int piece_w_int = (int) (piece_w + 1); /* pad for roundoff */
|
|
const int piece_h_int = (int) (piece_h + 1); /* pad for roundoff */
|
|
const gboolean rtl = gtk_widget_get_direction( da ) == GTK_TEXT_DIR_RTL;
|
|
|
|
guint8 * prev_color = NULL;
|
|
gboolean first_time = FALSE;
|
|
|
|
int i, x, y;
|
|
int8_t * pieces = NULL;
|
|
float * completeness = NULL;
|
|
|
|
/**
|
|
*** Get the Graphics Contexts...
|
|
**/
|
|
|
|
enum { ALL, LOTS, SOME, FEW, NONE,
|
|
BLACK, GRAY, BLINK,
|
|
N_COLORS };
|
|
GdkGC **gcs = (GdkGC**) g_object_get_data (G_OBJECT(da), "graphics-contexts");
|
|
if (gcs == NULL)
|
|
{
|
|
const GdkColor colors [N_COLORS] = {
|
|
RGB_2_GDK ( 114, 159, 207 ), /* all */
|
|
RGB_2_GDK ( 52, 101, 164 ), /* lots */
|
|
RGB_2_GDK ( 32, 74, 135 ), /* some */
|
|
RGB_2_GDK ( 85, 87, 83 ), /* few */
|
|
RGB_2_GDK ( 238, 238, 236 ), /* none - tango aluminum highlight */
|
|
RGB_2_GDK ( 46, 52, 54 ), /* black - tango slate shadow */
|
|
RGB_2_GDK ( 186, 189, 182 ), /* gray - tango aluminum shadow */
|
|
RGB_2_GDK ( 252, 233, 79 ), /* blink - tango butter highlight */
|
|
};
|
|
|
|
gcs = g_new (GdkGC*, N_COLORS+1);
|
|
|
|
for (i=0; i<N_COLORS; ++i) {
|
|
gcs[i] = gdk_gc_new (da->window);
|
|
gdk_gc_set_colormap (gcs[i], colormap);
|
|
gdk_gc_set_rgb_fg_color (gcs[i], &colors[i]);
|
|
gdk_gc_set_rgb_bg_color (gcs[i], &colors[i]);
|
|
};
|
|
|
|
gcs[N_COLORS] = NULL; /* a sentinel in the release function */
|
|
g_object_set_data_full (G_OBJECT(da), "graphics-contexts",
|
|
gcs, release_gobject_array);
|
|
}
|
|
|
|
/**
|
|
*** Get the cells' previous colors...
|
|
*** (this is used for blinking when the color changes)
|
|
**/
|
|
|
|
prev_color = (guint8*) g_object_get_data (G_OBJECT(da), "prev-color");
|
|
if (prev_color == NULL)
|
|
{
|
|
first_time = TRUE;
|
|
prev_color = g_new0 (guint8, n_cells);
|
|
g_object_set_data_full (G_OBJECT(da), "prev-color", prev_color, g_free);
|
|
}
|
|
|
|
/**
|
|
*** Get the piece data values...
|
|
**/
|
|
|
|
switch (mode) {
|
|
case DRAW_AVAIL:
|
|
pieces = g_new (int8_t, n_cells);
|
|
tr_torrentAvailability ( tor, pieces, n_cells );
|
|
break;
|
|
case DRAW_PROG:
|
|
completeness = g_new (float, n_cells);
|
|
tr_torrentAmountFinished ( tor, completeness, n_cells );
|
|
break;
|
|
default: g_error("no mode defined!");
|
|
}
|
|
|
|
/**
|
|
*** Draw...
|
|
**/
|
|
|
|
i = 0;
|
|
for (y=0; y<n_rows; ++y) {
|
|
for (x=0; x<n_cols; ++x) {
|
|
int draw_x = grid_bounds.x + (int)(x * piece_w);
|
|
int draw_y = grid_bounds.y + (int)(y * piece_h);
|
|
int color = BLACK;
|
|
int border = BLACK;
|
|
|
|
if( rtl )
|
|
draw_x = grid_bounds.x + grid_bounds.width - (int)((x+1) * piece_w);
|
|
else
|
|
draw_x = grid_bounds.x + (int)(x * piece_w);
|
|
draw_y = grid_bounds.y + (int)(y * piece_h);
|
|
|
|
if (i < n_cells)
|
|
{
|
|
border = GRAY;
|
|
|
|
if (mode == DRAW_AVAIL) {
|
|
const int8_t val = pieces[i];
|
|
if (val < 0) color = ALL;
|
|
else if (val == 0) color = NONE;
|
|
else if (val <= 4) color = FEW;
|
|
else if (val <= 8) color = SOME;
|
|
else color = LOTS;
|
|
} else { /* completeness */
|
|
const float val = completeness[i];
|
|
if (val >= 1.00) color = ALL;
|
|
else if (val >= 0.66) color = LOTS;
|
|
else if (val >= 0.33) color = SOME;
|
|
else if (val >= 0.01) color = FEW;
|
|
else color = NONE;
|
|
}
|
|
|
|
/* draw a "blink" for one interval when a piece changes */
|
|
if (first_time)
|
|
prev_color[i] = color;
|
|
else if (color != prev_color[i]) {
|
|
prev_color[i] = color;
|
|
color = border = BLINK;
|
|
}
|
|
}
|
|
|
|
gdk_draw_rectangle (da->window, gcs[color], TRUE,
|
|
draw_x, draw_y,
|
|
piece_w_int, piece_h_int);
|
|
|
|
if (i < n_cells)
|
|
gdk_draw_rectangle (da->window, gcs[border], FALSE,
|
|
draw_x, draw_y,
|
|
piece_w_int, piece_h_int);
|
|
|
|
++i;
|
|
}
|
|
}
|
|
|
|
gdk_draw_rectangle (da->window, gcs[GRAY], FALSE,
|
|
grid_bounds.x, grid_bounds.y,
|
|
grid_bounds.width-1, grid_bounds.height-1);
|
|
|
|
g_free (pieces);
|
|
g_free (completeness);
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
/****
|
|
***** PEERS TAB
|
|
****/
|
|
|
|
enum
|
|
{
|
|
WEBSEED_COL_URL,
|
|
WEBSEED_COL_DOWNLOAD_RATE,
|
|
N_WEBSEED_COLS
|
|
};
|
|
|
|
static const char * webseed_column_names[N_WEBSEED_COLS] =
|
|
{
|
|
N_( "Web Seeds" ),
|
|
/* 'download speed' column header. terse to keep the column narrow. */
|
|
N_( "Down" )
|
|
};
|
|
|
|
static GtkTreeModel*
|
|
webseed_model_new( const tr_torrent * tor )
|
|
{
|
|
int i;
|
|
const tr_info * inf = tr_torrentInfo( tor );
|
|
float * speeds = tr_torrentWebSpeeds( tor );
|
|
GtkListStore * store = gtk_list_store_new( N_WEBSEED_COLS,
|
|
G_TYPE_STRING,
|
|
G_TYPE_FLOAT );
|
|
for( i=0; i<inf->webseedCount; ++i )
|
|
{
|
|
GtkTreeIter iter;
|
|
gtk_list_store_append( store, &iter );
|
|
gtk_list_store_set( store, &iter, WEBSEED_COL_URL, inf->webseeds[i],
|
|
WEBSEED_COL_DOWNLOAD_RATE, speeds[i],
|
|
-1 );
|
|
}
|
|
|
|
tr_free( speeds );
|
|
return GTK_TREE_MODEL( store );
|
|
}
|
|
|
|
enum
|
|
{
|
|
PEER_COL_ADDRESS,
|
|
PEER_COL_DOWNLOAD_RATE,
|
|
PEER_COL_UPLOAD_RATE,
|
|
PEER_COL_CLIENT,
|
|
PEER_COL_PROGRESS,
|
|
PEER_COL_IS_ENCRYPTED,
|
|
PEER_COL_STATUS,
|
|
N_PEER_COLS
|
|
};
|
|
|
|
static const char* peer_column_names[N_PEER_COLS] =
|
|
{
|
|
N_("Address"),
|
|
/* 'download speed' column header. terse to keep the column narrow. */
|
|
N_("Down"),
|
|
/* 'upload speed' column header. terse to keep the column narrow. */
|
|
N_("Up"),
|
|
N_("Client"),
|
|
/* 'percent done' column header. terse to keep the column narrow. */
|
|
N_("%"),
|
|
" ",
|
|
N_("Status")
|
|
};
|
|
|
|
static int compare_peers (const void * a, const void * b)
|
|
{
|
|
const tr_peer_stat * pa = a;
|
|
const tr_peer_stat * pb = b;
|
|
return strcmp (pa->addr, pb->addr);
|
|
}
|
|
static int compare_addr_to_peer (const void * a, const void * b)
|
|
{
|
|
const char * addr = (const char *) a;
|
|
const tr_peer_stat * peer = b;
|
|
return strcmp (addr, peer->addr);
|
|
}
|
|
|
|
static void
|
|
peer_row_set (GtkListStore * store,
|
|
GtkTreeIter * iter,
|
|
const tr_peer_stat * peer)
|
|
{
|
|
const char * client = peer->client;
|
|
|
|
if (!client || !strcmp(client,"Unknown Client"))
|
|
client = " ";
|
|
|
|
gtk_list_store_set( store, iter,
|
|
PEER_COL_ADDRESS, peer->addr,
|
|
PEER_COL_CLIENT, client,
|
|
PEER_COL_IS_ENCRYPTED, peer->isEncrypted,
|
|
PEER_COL_PROGRESS, (int)(100.0*peer->progress),
|
|
PEER_COL_DOWNLOAD_RATE, peer->rateToClient,
|
|
PEER_COL_UPLOAD_RATE, peer->rateToPeer,
|
|
PEER_COL_STATUS, peer->flagStr,
|
|
-1);
|
|
}
|
|
|
|
static void
|
|
append_peers_to_model (GtkListStore * store,
|
|
const tr_peer_stat * peers,
|
|
int n_peers)
|
|
{
|
|
int i;
|
|
for (i=0; i<n_peers; ++i) {
|
|
GtkTreeIter iter;
|
|
gtk_list_store_append( store, &iter );
|
|
peer_row_set (store, &iter, &peers[i]);
|
|
}
|
|
}
|
|
|
|
static GtkTreeModel*
|
|
peer_model_new (tr_torrent * tor)
|
|
{
|
|
GtkListStore * m = gtk_list_store_new (N_PEER_COLS,
|
|
G_TYPE_STRING, /* addr */
|
|
G_TYPE_FLOAT, /* downloadFromRate */
|
|
G_TYPE_FLOAT, /* uploadToRate */
|
|
G_TYPE_STRING, /* client */
|
|
G_TYPE_INT, /* progress [0..100] */
|
|
G_TYPE_BOOLEAN, /* isEncrypted */
|
|
G_TYPE_STRING ); /* flagString */
|
|
|
|
int n_peers = 0;
|
|
tr_peer_stat * peers = tr_torrentPeers (tor, &n_peers);
|
|
qsort (peers, n_peers, sizeof(tr_peer_stat), compare_peers);
|
|
append_peers_to_model (m, peers, n_peers);
|
|
tr_torrentPeersFree( peers, 0 );
|
|
return GTK_TREE_MODEL (m);
|
|
}
|
|
|
|
static void
|
|
render_encrypted (GtkTreeViewColumn * column UNUSED,
|
|
GtkCellRenderer * renderer,
|
|
GtkTreeModel * tree_model,
|
|
GtkTreeIter * iter,
|
|
gpointer data UNUSED)
|
|
{
|
|
gboolean is_encrypted = FALSE;
|
|
gtk_tree_model_get (tree_model, iter, PEER_COL_IS_ENCRYPTED, &is_encrypted,
|
|
-1);
|
|
g_object_set (renderer, "xalign", (gfloat)0.0,
|
|
"yalign", (gfloat)0.5,
|
|
"stock-id", (is_encrypted ? "transmission-lock" : NULL),
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
render_ul_rate (GtkTreeViewColumn * column UNUSED,
|
|
GtkCellRenderer * renderer,
|
|
GtkTreeModel * tree_model,
|
|
GtkTreeIter * iter,
|
|
gpointer data UNUSED)
|
|
{
|
|
float rate = 0.0;
|
|
gtk_tree_model_get (tree_model, iter, PEER_COL_UPLOAD_RATE, &rate, -1);
|
|
if( rate < 0.01 )
|
|
g_object_set (renderer, "text", "", NULL);
|
|
else {
|
|
char speedStr[64];
|
|
tr_strlspeed( speedStr, rate, sizeof(speedStr) );
|
|
g_object_set( renderer, "text", speedStr, NULL );
|
|
}
|
|
}
|
|
|
|
static void
|
|
render_dl_rate (GtkTreeViewColumn * column UNUSED,
|
|
GtkCellRenderer * renderer,
|
|
GtkTreeModel * tree_model,
|
|
GtkTreeIter * iter,
|
|
gpointer data UNUSED)
|
|
{
|
|
float rate = 0.0;
|
|
gtk_tree_model_get (tree_model, iter, PEER_COL_DOWNLOAD_RATE, &rate, -1);
|
|
if( rate < 0.01 )
|
|
g_object_set (renderer, "text", "", NULL);
|
|
else {
|
|
char speedStr[64];
|
|
tr_strlspeed( speedStr, rate, sizeof(speedStr) );
|
|
g_object_set( renderer, "text", speedStr, NULL );
|
|
}
|
|
}
|
|
|
|
static void
|
|
render_client (GtkTreeViewColumn * column UNUSED,
|
|
GtkCellRenderer * renderer,
|
|
GtkTreeModel * tree_model,
|
|
GtkTreeIter * iter,
|
|
gpointer data UNUSED)
|
|
{
|
|
char * client = NULL;
|
|
gtk_tree_model_get (tree_model, iter, PEER_COL_CLIENT, &client,
|
|
-1);
|
|
g_object_set (renderer, "text", (client ? client : ""), NULL);
|
|
g_free (client);
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
TrTorrent * gtor;
|
|
GtkTreeModel * model; /* same object as store, but recast */
|
|
GtkListStore * store; /* same object as model, but recast */
|
|
GtkListStore * webseeds;
|
|
GtkWidget * completeness;
|
|
GtkWidget * seeders_lb;
|
|
GtkWidget * leechers_lb;
|
|
GtkWidget * completed_lb;
|
|
}
|
|
PeerData;
|
|
|
|
static void
|
|
fmtpeercount (GtkWidget * l, int count)
|
|
{
|
|
if( 0 > count ) {
|
|
gtk_label_set_text( GTK_LABEL(l), "?" );
|
|
} else {
|
|
char str[16];
|
|
g_snprintf( str, sizeof str, "%'d", count );
|
|
gtk_label_set_text( GTK_LABEL(l), str );
|
|
}
|
|
}
|
|
|
|
static void
|
|
refresh_peers (GtkWidget * top)
|
|
{
|
|
int i;
|
|
int n_peers;
|
|
GtkTreeIter iter;
|
|
PeerData * p = (PeerData*) g_object_get_data (G_OBJECT(top), "peer-data");
|
|
tr_torrent * tor = tr_torrent_handle ( p->gtor );
|
|
GtkTreeModel * model = p->model;
|
|
GtkListStore * store = p->store;
|
|
tr_peer_stat * peers;
|
|
const tr_stat * stat = tr_torrent_stat( p->gtor );
|
|
const tr_info * inf = tr_torrent_info( p->gtor );
|
|
|
|
if( inf->webseedCount )
|
|
{
|
|
float * speeds = tr_torrentWebSpeeds( tor );
|
|
for( i=0; i<inf->webseedCount; ++i )
|
|
{
|
|
GtkTreeIter iter;
|
|
gtk_tree_model_iter_nth_child( GTK_TREE_MODEL( p->webseeds ), &iter, NULL, i );
|
|
gtk_list_store_set( p->webseeds, &iter, WEBSEED_COL_DOWNLOAD_RATE, speeds[i],
|
|
-1 );
|
|
}
|
|
tr_free( speeds );
|
|
}
|
|
|
|
/**
|
|
*** merge the peer diffs into the tree model.
|
|
***
|
|
*** this is more complicated than creating a new model,
|
|
*** but is also (a) more efficient and (b) doesn't undo
|
|
*** the view's visible area and sorting on every refresh.
|
|
**/
|
|
|
|
n_peers = 0;
|
|
peers = tr_torrentPeers (tor, &n_peers);
|
|
qsort (peers, n_peers, sizeof(tr_peer_stat), compare_peers);
|
|
|
|
if (gtk_tree_model_get_iter_first (model, &iter)) do
|
|
{
|
|
char * addr = NULL;
|
|
tr_peer_stat * peer = NULL;
|
|
gtk_tree_model_get (model, &iter, PEER_COL_ADDRESS, &addr, -1);
|
|
peer = bsearch (addr, peers, n_peers, sizeof(tr_peer_stat),
|
|
compare_addr_to_peer);
|
|
g_free (addr);
|
|
|
|
if (peer) /* update a pre-existing row */
|
|
{
|
|
const int pos = peer - peers;
|
|
const int n_rhs = n_peers - (pos+1);
|
|
g_assert (n_rhs >= 0);
|
|
|
|
peer_row_set (store, &iter, peer);
|
|
|
|
/* remove it from the tr_peer_stat list */
|
|
g_memmove (peer, peer+1, sizeof(tr_peer_stat)*n_rhs);
|
|
--n_peers;
|
|
}
|
|
else if (!gtk_list_store_remove (store, &iter))
|
|
break; /* we removed the model's last item */
|
|
}
|
|
while (gtk_tree_model_iter_next (model, &iter));
|
|
|
|
append_peers_to_model (store, peers, n_peers); /* all these are new */
|
|
|
|
#ifdef SHOW_PIECES
|
|
if (GDK_IS_DRAWABLE (p->completeness->window))
|
|
refresh_pieces (p->completeness, NULL, p->gtor);
|
|
#endif
|
|
|
|
fmtpeercount (p->seeders_lb, stat->seeders );
|
|
fmtpeercount (p->leechers_lb, stat->leechers );
|
|
fmtpeercount (p->completed_lb, stat->timesCompleted );
|
|
|
|
free( peers );
|
|
}
|
|
|
|
#if GTK_CHECK_VERSION(2,12,0)
|
|
static gboolean
|
|
onPeerViewQueryTooltip( GtkWidget * widget,
|
|
gint x,
|
|
gint y,
|
|
gboolean keyboard_tip,
|
|
GtkTooltip * tooltip,
|
|
gpointer user_data UNUSED )
|
|
{
|
|
gboolean show_tip = FALSE;
|
|
GtkTreeModel * model;
|
|
GtkTreeIter iter;
|
|
|
|
if( gtk_tree_view_get_tooltip_context( GTK_TREE_VIEW( widget ),
|
|
&x, &y, keyboard_tip,
|
|
&model, NULL, &iter ) )
|
|
{
|
|
const char * pch;
|
|
char * str = NULL;
|
|
GString * gstr = g_string_new( NULL );
|
|
gtk_tree_model_get( model, &iter, PEER_COL_STATUS, &str, -1 );
|
|
for( pch=str; pch && *pch; ++pch ) {
|
|
const char * txt = NULL;
|
|
switch( *pch ) {
|
|
case 'O': txt = _( "Optimistic unchoke" ); break;
|
|
case 'D': txt = _( "Downloading from this peer" ); break;
|
|
case 'd': txt = _( "We would download from this peer if they would let us" ); break;
|
|
case 'U': txt = _( "Uploading to peer" ); break;
|
|
case 'u': txt = _( "We would upload to this peer if they asked" ); break;
|
|
case 'K': txt = _( "Peer has unchoked us, but we're not interested" ); break;
|
|
case '?': txt = _( "We unchoked this peer, but they're not interested" ); break;
|
|
case 'E': txt = _( "Encrypted connection" ); break;
|
|
case 'X': txt = _( "Peer was discovered through Peer Exchange (PEX)" ); break;
|
|
case 'I': txt = _( "Peer is an incoming connection" ); break;
|
|
}
|
|
if( txt )
|
|
g_string_append_printf( gstr, "%c: %s\n", *pch, txt );
|
|
}
|
|
if( gstr->len ) /* remove the last linefeed */
|
|
g_string_set_size( gstr, gstr->len-1 );
|
|
gtk_tooltip_set_text( tooltip, gstr->str );
|
|
g_string_free( gstr, TRUE );
|
|
g_free( str );
|
|
show_tip = TRUE;
|
|
}
|
|
|
|
return show_tip;
|
|
|
|
}
|
|
#endif
|
|
|
|
static GtkWidget*
|
|
peer_page_new ( TrTorrent * gtor )
|
|
{
|
|
guint i;
|
|
GtkTreeModel *m;
|
|
GtkWidget *v, *w, *ret, *sw, *l, *vbox, *hbox;
|
|
GtkWidget *webtree = NULL;
|
|
tr_torrent * tor = tr_torrent_handle (gtor);
|
|
PeerData * p = g_new (PeerData, 1);
|
|
const tr_info * inf = tr_torrent_info( gtor );
|
|
|
|
/* TODO: make this configurable? */
|
|
int view_columns[] = { PEER_COL_IS_ENCRYPTED,
|
|
PEER_COL_UPLOAD_RATE,
|
|
PEER_COL_DOWNLOAD_RATE,
|
|
PEER_COL_PROGRESS,
|
|
PEER_COL_STATUS,
|
|
PEER_COL_ADDRESS,
|
|
PEER_COL_CLIENT };
|
|
|
|
|
|
if( inf->webseedCount )
|
|
{
|
|
GtkTreeViewColumn * c;
|
|
GtkCellRenderer * r;
|
|
const char * t;
|
|
GtkWidget * fr;
|
|
|
|
m = webseed_model_new( tr_torrent_handle( gtor ) );
|
|
webtree = gtk_tree_view_new_with_model( m );
|
|
gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( webtree ), TRUE );
|
|
p->webseeds = GTK_LIST_STORE( m );
|
|
g_object_unref( G_OBJECT( m ) );
|
|
|
|
t = _( webseed_column_names[WEBSEED_COL_URL] );
|
|
r = gtk_cell_renderer_text_new ();
|
|
g_object_set( G_OBJECT( r ), "ellipsize", PANGO_ELLIPSIZE_END, NULL );
|
|
c = gtk_tree_view_column_new_with_attributes( t, r, "text", WEBSEED_COL_URL, NULL);
|
|
g_object_set( G_OBJECT( c ), "expand", TRUE, NULL );
|
|
gtk_tree_view_column_set_sort_column_id( c, WEBSEED_COL_URL );
|
|
gtk_tree_view_append_column( GTK_TREE_VIEW( webtree ), c );
|
|
|
|
t = _( webseed_column_names[WEBSEED_COL_DOWNLOAD_RATE] );
|
|
r = gtk_cell_renderer_text_new ();
|
|
c = gtk_tree_view_column_new_with_attributes (t, r, "text", WEBSEED_COL_DOWNLOAD_RATE, NULL);
|
|
gtk_tree_view_column_set_cell_data_func (c, r, render_dl_rate,
|
|
NULL, NULL);
|
|
gtk_tree_view_column_set_sort_column_id( c, WEBSEED_COL_DOWNLOAD_RATE );
|
|
gtk_tree_view_append_column( GTK_TREE_VIEW( webtree ), c );
|
|
|
|
fr = gtk_frame_new( NULL );
|
|
gtk_frame_set_shadow_type( GTK_FRAME( fr ), GTK_SHADOW_IN );
|
|
gtk_container_add( GTK_CONTAINER( fr ), webtree );
|
|
webtree = fr;
|
|
}
|
|
|
|
m = peer_model_new (tor);
|
|
v = GTK_WIDGET( g_object_new( GTK_TYPE_TREE_VIEW,
|
|
"model", m,
|
|
"rules-hint", TRUE,
|
|
#if GTK_CHECK_VERSION(2,12,0)
|
|
"has-tooltip", TRUE,
|
|
#endif
|
|
NULL ) );
|
|
#if GTK_CHECK_VERSION(2,12,0)
|
|
g_signal_connect( v, "query-tooltip",
|
|
G_CALLBACK(onPeerViewQueryTooltip), NULL );
|
|
#endif
|
|
gtk_widget_set_size_request( v, 550, 0 );
|
|
g_object_unref (G_OBJECT(m));
|
|
|
|
for (i=0; i<G_N_ELEMENTS(view_columns); ++i)
|
|
{
|
|
const int col = view_columns[i];
|
|
const char * t = _(peer_column_names[col]);
|
|
gboolean resizable = TRUE;
|
|
GtkTreeViewColumn * c;
|
|
GtkCellRenderer * r;
|
|
|
|
switch (col)
|
|
{
|
|
case PEER_COL_ADDRESS:
|
|
r = gtk_cell_renderer_text_new ();
|
|
c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
|
|
break;
|
|
|
|
case PEER_COL_CLIENT:
|
|
r = gtk_cell_renderer_text_new ();
|
|
c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
|
|
gtk_tree_view_column_set_cell_data_func (c, r, render_client,
|
|
NULL, NULL);
|
|
break;
|
|
|
|
case PEER_COL_PROGRESS:
|
|
r = gtk_cell_renderer_progress_new ();
|
|
c = gtk_tree_view_column_new_with_attributes (t, r, "value", PEER_COL_PROGRESS, NULL);
|
|
break;
|
|
|
|
case PEER_COL_IS_ENCRYPTED:
|
|
resizable = FALSE;
|
|
r = gtk_cell_renderer_pixbuf_new ();
|
|
c = gtk_tree_view_column_new_with_attributes (t, r, NULL);
|
|
gtk_tree_view_column_set_sizing (c, GTK_TREE_VIEW_COLUMN_FIXED);
|
|
gtk_tree_view_column_set_fixed_width (c, 20);
|
|
gtk_tree_view_column_set_cell_data_func (c, r, render_encrypted,
|
|
NULL, NULL);
|
|
break;
|
|
|
|
case PEER_COL_DOWNLOAD_RATE:
|
|
r = gtk_cell_renderer_text_new ();
|
|
c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
|
|
gtk_tree_view_column_set_cell_data_func (c, r, render_dl_rate,
|
|
NULL, NULL);
|
|
break;
|
|
|
|
case PEER_COL_UPLOAD_RATE:
|
|
r = gtk_cell_renderer_text_new ();
|
|
c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
|
|
gtk_tree_view_column_set_cell_data_func (c, r, render_ul_rate,
|
|
NULL, NULL);
|
|
break;
|
|
|
|
case PEER_COL_STATUS:
|
|
r = gtk_cell_renderer_text_new( );
|
|
c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
|
|
break;
|
|
|
|
default:
|
|
abort ();
|
|
}
|
|
|
|
gtk_tree_view_column_set_resizable (c, resizable);
|
|
gtk_tree_view_column_set_sort_column_id (c, col);
|
|
gtk_tree_view_append_column (GTK_TREE_VIEW(v), c);
|
|
}
|
|
|
|
/* the 'expander' column has a 10-pixel margin on the left
|
|
that doesn't look quite correct in any of these columns...
|
|
so create a non-visible column and assign it as the
|
|
'expander column. */
|
|
{
|
|
GtkTreeViewColumn *c = gtk_tree_view_column_new ();
|
|
gtk_tree_view_column_set_visible (c, FALSE);
|
|
gtk_tree_view_append_column (GTK_TREE_VIEW(v), c);
|
|
gtk_tree_view_set_expander_column (GTK_TREE_VIEW(v), c);
|
|
}
|
|
|
|
w = sw = gtk_scrolled_window_new (NULL, NULL);
|
|
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(w),
|
|
GTK_POLICY_NEVER,
|
|
GTK_POLICY_AUTOMATIC);
|
|
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(w),
|
|
GTK_SHADOW_IN);
|
|
gtk_container_add (GTK_CONTAINER(w), v);
|
|
|
|
|
|
vbox = gtk_vbox_new (FALSE, GUI_PAD);
|
|
gtk_container_set_border_width (GTK_CONTAINER(vbox), GUI_PAD_BIG);
|
|
|
|
if( webtree )
|
|
gtk_box_pack_start( GTK_BOX( vbox ), webtree, FALSE, FALSE, 0 );
|
|
|
|
//h = gtk_hbox_new (FALSE, GUI_PAD);
|
|
//gtk_box_pack_start_defaults (GTK_BOX(h), sw);
|
|
gtk_box_pack_start_defaults( GTK_BOX( vbox ), sw );
|
|
|
|
hbox = gtk_hbox_new (FALSE, GUI_PAD);
|
|
l = gtk_label_new (NULL);
|
|
gtk_label_set_markup (GTK_LABEL(l), _( "<b>Seeders:</b>" ) );
|
|
gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
|
|
l = p->seeders_lb = gtk_label_new (NULL);
|
|
gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
|
|
gtk_box_pack_start_defaults (GTK_BOX(hbox),
|
|
gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f));
|
|
l = gtk_label_new (NULL);
|
|
gtk_label_set_markup (GTK_LABEL(l), _( "<b>Leechers:</b>" ) );
|
|
gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
|
|
l = p->leechers_lb = gtk_label_new (NULL);
|
|
gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
|
|
gtk_box_pack_start_defaults (GTK_BOX(hbox),
|
|
gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f));
|
|
l = gtk_label_new (NULL);
|
|
gtk_label_set_markup (GTK_LABEL(l), _( "<b>Times Completed:</b>" ) );
|
|
gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
|
|
l = p->completed_lb = gtk_label_new (NULL);
|
|
gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
|
|
gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
|
|
|
|
ret = vbox;
|
|
p->gtor = gtor;
|
|
p->model = m;
|
|
p->store = GTK_LIST_STORE(m);
|
|
g_object_set_data_full (G_OBJECT(ret), "peer-data", p, g_free);
|
|
return ret;
|
|
}
|
|
|
|
/****
|
|
***** INFO TAB
|
|
****/
|
|
|
|
static void
|
|
refresh_time_lb( GtkWidget * l, time_t t )
|
|
{
|
|
const char * never = _( "Never" );
|
|
if( !t )
|
|
gtk_label_set_text( GTK_LABEL( l ), never );
|
|
else {
|
|
char * str = gtr_localtime( t );
|
|
gtk_label_set_text( GTK_LABEL( l ), str );
|
|
g_free( str );
|
|
}
|
|
}
|
|
|
|
static GtkWidget*
|
|
info_page_new (tr_torrent * tor)
|
|
{
|
|
int row = 0;
|
|
GtkWidget *t = hig_workarea_create ();
|
|
GtkWidget *l, *w, *fr;
|
|
char *pch;
|
|
char sizeStr[128];
|
|
char countStr[128];
|
|
char buf[256];
|
|
GtkTextBuffer * b;
|
|
const tr_info * info = tr_torrentInfo(tor);
|
|
|
|
hig_workarea_add_section_title (t, &row, _("Details"));
|
|
|
|
g_snprintf( countStr, sizeof( countStr ),
|
|
ngettext( "%'d Piece", "%'d Pieces", info->pieceCount ),
|
|
info->pieceCount );
|
|
tr_strlsize( sizeStr, info->pieceSize, sizeof(sizeStr) );
|
|
g_snprintf( buf, sizeof( buf ),
|
|
/* %1$s is number of pieces;
|
|
%2$s is how big each piece is */
|
|
_( "%1$s @ %2$s" ),
|
|
countStr, sizeStr );
|
|
|
|
l = gtk_label_new (buf);
|
|
hig_workarea_add_row (t, &row, _("Pieces:"), l, NULL);
|
|
|
|
l = g_object_new( GTK_TYPE_LABEL, "label", info->hashString, "selectable", TRUE,
|
|
"ellipsize", PANGO_ELLIPSIZE_END, NULL );
|
|
hig_workarea_add_row (t, &row, _("Hash:"), l, NULL);
|
|
|
|
pch = (info->isPrivate )
|
|
? _("Private to this tracker -- PEX disabled")
|
|
: _("Public torrent");
|
|
l = gtk_label_new (pch);
|
|
hig_workarea_add_row (t, &row, _("Privacy:"), l, NULL);
|
|
|
|
b = gtk_text_buffer_new (NULL);
|
|
if( info->comment )
|
|
gtk_text_buffer_set_text (b, info->comment, -1);
|
|
w = gtk_text_view_new_with_buffer (b);
|
|
gtk_widget_set_size_request (w, 0u, 100u);
|
|
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW(w), GTK_WRAP_WORD);
|
|
gtk_text_view_set_editable (GTK_TEXT_VIEW(w), FALSE);
|
|
fr = gtk_frame_new (NULL);
|
|
gtk_frame_set_shadow_type (GTK_FRAME(fr), GTK_SHADOW_IN);
|
|
gtk_container_add (GTK_CONTAINER(fr), w);
|
|
w = hig_workarea_add_row (t, &row, _("Comment:"), fr, NULL);
|
|
gtk_misc_set_alignment (GTK_MISC(w), 0.0f, 0.0f);
|
|
|
|
hig_workarea_add_section_divider (t, &row);
|
|
hig_workarea_add_section_title (t, &row, _("Origins"));
|
|
|
|
l = gtk_label_new (*info->creator ? info->creator : _("Unknown"));
|
|
gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
|
|
hig_workarea_add_row (t, &row, _("Creator:"), l, NULL);
|
|
|
|
l = gtk_label_new( NULL );
|
|
refresh_time_lb( l, info->dateCreated );
|
|
hig_workarea_add_row (t, &row, _("Date:"), l, NULL);
|
|
|
|
hig_workarea_add_section_divider (t, &row);
|
|
hig_workarea_add_section_title (t, &row, _("Location"));
|
|
|
|
l = g_object_new( GTK_TYPE_LABEL, "label", tr_torrentGetDownloadDir( tor ), "selectable", TRUE,
|
|
"ellipsize", PANGO_ELLIPSIZE_END, NULL );
|
|
hig_workarea_add_row (t, &row, _( "Destination folder:" ), l, NULL);
|
|
|
|
l = g_object_new( GTK_TYPE_LABEL, "label", info->torrent, "selectable", TRUE,
|
|
"ellipsize", PANGO_ELLIPSIZE_END, NULL );
|
|
hig_workarea_add_row (t, &row, _( "Torrent file:" ), l, NULL);
|
|
|
|
hig_workarea_finish (t, &row);
|
|
return t;
|
|
}
|
|
|
|
/****
|
|
***** ACTIVITY TAB
|
|
****/
|
|
|
|
typedef struct
|
|
{
|
|
GtkWidget * state_lb;
|
|
GtkWidget * progress_lb;
|
|
GtkWidget * have_lb;
|
|
GtkWidget * dl_lb;
|
|
GtkWidget * ul_lb;
|
|
GtkWidget * failed_lb;
|
|
GtkWidget * ratio_lb;
|
|
GtkWidget * err_lb;
|
|
GtkWidget * swarm_lb;
|
|
GtkWidget * date_added_lb;
|
|
GtkWidget * last_activity_lb;
|
|
GtkWidget * availability_da;
|
|
TrTorrent * gtor;
|
|
}
|
|
Activity;
|
|
|
|
static void
|
|
refresh_activity (GtkWidget * top)
|
|
{
|
|
Activity * a = g_object_get_data (G_OBJECT(top), "activity-data");
|
|
char *pch;
|
|
char sizeStr[64];
|
|
char sizeStr2[64];
|
|
char buf[128];
|
|
const tr_stat * stat = tr_torrent_stat( a->gtor );
|
|
const double complete = stat->percentComplete * 100.0;
|
|
const double done = stat->percentDone * 100.0;
|
|
|
|
pch = tr_torrent_status_str( a->gtor );
|
|
gtk_label_set_text (GTK_LABEL(a->state_lb), pch);
|
|
g_free (pch);
|
|
|
|
if( (int)complete == (int)done )
|
|
pch = g_strdup_printf( _( "%.1f%%" ), complete );
|
|
else
|
|
/* %1$.1f is percent of how much of what we want's been downloaded,
|
|
%2$.1f is percent of how much of the whole torrent we've downloaded */
|
|
pch = g_strdup_printf( _( "%1$.1f%% (%2$.1f%% selected)" ), complete, done );
|
|
gtk_label_set_text (GTK_LABEL(a->progress_lb), pch);
|
|
g_free (pch);
|
|
|
|
tr_strlsize( sizeStr, stat->haveValid + stat->haveUnchecked, sizeof(sizeStr) );
|
|
tr_strlsize( sizeStr2, stat->haveValid, sizeof(sizeStr2) );
|
|
/* %1$s is total size of what we've saved to disk
|
|
%2$s is how much of it's passed the checksum test */
|
|
g_snprintf( buf, sizeof(buf), _("%1$s (%2$s verified)"), sizeStr, sizeStr2 );
|
|
gtk_label_set_text( GTK_LABEL( a->have_lb ), buf );
|
|
|
|
tr_strlsize( sizeStr, stat->downloadedEver, sizeof(sizeStr) );
|
|
gtk_label_set_text( GTK_LABEL(a->dl_lb), sizeStr );
|
|
|
|
tr_strlsize( sizeStr, stat->uploadedEver, sizeof(sizeStr) );
|
|
gtk_label_set_text( GTK_LABEL(a->ul_lb), sizeStr );
|
|
|
|
tr_strlsize( sizeStr, stat->corruptEver, sizeof( sizeStr ) );
|
|
gtk_label_set_text( GTK_LABEL( a->failed_lb ), sizeStr );
|
|
|
|
tr_strlratio( buf, stat->ratio, sizeof( buf ) );
|
|
gtk_label_set_text( GTK_LABEL( a->ratio_lb ), buf );
|
|
|
|
tr_strlspeed( buf, stat->swarmSpeed, sizeof(buf) );
|
|
gtk_label_set_text (GTK_LABEL(a->swarm_lb), buf );
|
|
|
|
gtk_label_set_text (GTK_LABEL(a->err_lb),
|
|
*stat->errorString ? stat->errorString : _("None"));
|
|
|
|
refresh_time_lb( a->date_added_lb, stat->addedDate );
|
|
|
|
refresh_time_lb( a->last_activity_lb, stat->activityDate );
|
|
|
|
#ifdef SHOW_PIECES
|
|
if (GDK_IS_DRAWABLE (a->availability_da->window))
|
|
refresh_pieces (a->availability_da, NULL, a->gtor);
|
|
#endif
|
|
}
|
|
|
|
|
|
static GtkWidget*
|
|
activity_page_new (TrTorrent * gtor)
|
|
{
|
|
Activity * a = g_new (Activity, 1);
|
|
int row = 0;
|
|
GtkWidget *t = hig_workarea_create ();
|
|
GtkWidget *l;
|
|
|
|
a->gtor = gtor;
|
|
|
|
hig_workarea_add_section_title (t, &row, _("Transfer"));
|
|
|
|
l = a->state_lb = gtk_label_new (NULL);
|
|
hig_workarea_add_row (t, &row, _("State:"), l, NULL);
|
|
|
|
l = a->progress_lb = gtk_label_new (NULL);
|
|
hig_workarea_add_row (t, &row, _("Progress:"), l, NULL);
|
|
|
|
l = a->have_lb = gtk_label_new (NULL);
|
|
/* "Have" refers to how much of the torrent we have */
|
|
hig_workarea_add_row (t, &row, _("Have:"), l, NULL);
|
|
|
|
l = a->dl_lb = gtk_label_new (NULL);
|
|
hig_workarea_add_row (t, &row, _("Downloaded:"), l, NULL);
|
|
|
|
l = a->ul_lb = gtk_label_new (NULL);
|
|
hig_workarea_add_row (t, &row, _("Uploaded:"), l, NULL);
|
|
|
|
/* how much downloaded data was corrupt */
|
|
l = a->failed_lb = gtk_label_new (NULL);
|
|
hig_workarea_add_row (t, &row, _("Failed DL:"), l, NULL);
|
|
|
|
l = a->ratio_lb = gtk_label_new (NULL);
|
|
hig_workarea_add_row (t, &row, _("Ratio:"), l, NULL);
|
|
|
|
l = a->swarm_lb = gtk_label_new (NULL);
|
|
hig_workarea_add_row (t, &row, _("Swarm rate:"), l, NULL);
|
|
|
|
l = a->err_lb = gtk_label_new (NULL);
|
|
hig_workarea_add_row (t, &row, _("Error:"), l, NULL);
|
|
|
|
#ifdef SHOW_PIECES
|
|
hig_workarea_add_section_divider (t, &row);
|
|
hig_workarea_add_section_title (t, &row, _("Completion"));
|
|
|
|
w = a->availability_da = gtk_drawing_area_new ();
|
|
gtk_widget_set_size_request (w, 0u, 100u);
|
|
g_object_set_data (G_OBJECT(w), "draw-mode", GINT_TO_POINTER(DRAW_PROG));
|
|
g_signal_connect (w, "expose-event", G_CALLBACK(refresh_pieces), gtor);
|
|
hig_workarea_add_wide_control( t, &row, w );
|
|
#endif
|
|
|
|
hig_workarea_add_section_divider (t, &row);
|
|
hig_workarea_add_section_title (t, &row, _("Dates"));
|
|
|
|
l = a->date_added_lb = gtk_label_new (NULL);
|
|
hig_workarea_add_row (t, &row, _("Started at:"), l, NULL);
|
|
|
|
l = a->last_activity_lb = gtk_label_new (NULL);
|
|
hig_workarea_add_row (t, &row, _("Last activity at:"), l, NULL);
|
|
|
|
hig_workarea_add_section_divider (t, &row);
|
|
hig_workarea_finish (t, &row);
|
|
g_object_set_data_full (G_OBJECT(t), "activity-data", a, g_free);
|
|
return t;
|
|
}
|
|
|
|
|
|
/****
|
|
***** OPTIONS
|
|
****/
|
|
|
|
static void
|
|
speed_toggled_cb( GtkToggleButton * tb, gpointer gtor, int up_or_down )
|
|
{
|
|
tr_torrent * tor = tr_torrent_handle (gtor);
|
|
gboolean b = gtk_toggle_button_get_active(tb);
|
|
tr_torrentSetSpeedMode( tor, up_or_down, b ? TR_SPEEDLIMIT_SINGLE
|
|
: TR_SPEEDLIMIT_GLOBAL );
|
|
}
|
|
static void
|
|
ul_speed_toggled_cb (GtkToggleButton *tb, gpointer gtor)
|
|
{
|
|
speed_toggled_cb( tb, gtor, TR_UP );
|
|
}
|
|
static void
|
|
dl_speed_toggled_cb (GtkToggleButton *tb, gpointer gtor)
|
|
{
|
|
speed_toggled_cb( tb, gtor, TR_DOWN );
|
|
}
|
|
|
|
|
|
static void
|
|
sensitize_from_check_cb (GtkToggleButton *toggle, gpointer w)
|
|
{
|
|
gtk_widget_set_sensitive (GTK_WIDGET(w),
|
|
gtk_toggle_button_get_active(toggle));
|
|
}
|
|
|
|
static void
|
|
setSpeedLimit( GtkSpinButton* spin, gpointer gtor, int up_or_down )
|
|
{
|
|
tr_torrent * tor = tr_torrent_handle (gtor);
|
|
int kb_sec = gtk_spin_button_get_value_as_int (spin);
|
|
tr_torrentSetSpeedLimit( tor, up_or_down, kb_sec );
|
|
}
|
|
static void
|
|
ul_speed_spun_cb (GtkSpinButton *spin, gpointer gtor)
|
|
{
|
|
setSpeedLimit( spin, gtor, TR_UP );
|
|
}
|
|
static void
|
|
dl_speed_spun_cb (GtkSpinButton *spin, gpointer gtor)
|
|
{
|
|
setSpeedLimit( spin, gtor, TR_DOWN );
|
|
}
|
|
|
|
static void
|
|
max_peers_spun_cb( GtkSpinButton * spin, gpointer gtor )
|
|
{
|
|
const uint16_t n = gtk_spin_button_get_value( spin );
|
|
tr_torrentSetPeerLimit( tr_torrent_handle( gtor ), n );
|
|
}
|
|
|
|
static GtkWidget*
|
|
options_page_new ( TrTorrent * gtor )
|
|
{
|
|
uint16_t maxConnectedPeers;
|
|
int i, row;
|
|
gboolean b;
|
|
GtkAdjustment *a;
|
|
GtkWidget *t, *w, *tb;
|
|
tr_torrent * tor = tr_torrent_handle (gtor);
|
|
|
|
row = 0;
|
|
t = hig_workarea_create ();
|
|
hig_workarea_add_section_title (t, &row, _("Bandwidth") );
|
|
|
|
tb = gtk_check_button_new_with_mnemonic (_("Limit _download speed (KB/s):"));
|
|
b = tr_torrentGetSpeedMode(tor,TR_DOWN) == TR_SPEEDLIMIT_SINGLE;
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(tb), b );
|
|
g_signal_connect (tb, "toggled", G_CALLBACK(dl_speed_toggled_cb), gtor);
|
|
|
|
i = tr_torrentGetSpeedLimit( tor, TR_DOWN );
|
|
a = (GtkAdjustment*) gtk_adjustment_new (i, 0.0, G_MAXDOUBLE, 1, 1, 1);
|
|
w = gtk_spin_button_new (a, 1, 0);
|
|
g_signal_connect (w, "value-changed", G_CALLBACK(dl_speed_spun_cb), gtor);
|
|
g_signal_connect (tb, "toggled", G_CALLBACK(sensitize_from_check_cb), w);
|
|
sensitize_from_check_cb (GTK_TOGGLE_BUTTON(tb), w);
|
|
hig_workarea_add_row_w (t, &row, tb, w, NULL);
|
|
|
|
tb = gtk_check_button_new_with_mnemonic (_("Limit _upload speed (KB/s):"));
|
|
b = tr_torrentGetSpeedMode(tor,TR_UP) == TR_SPEEDLIMIT_SINGLE;
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(tb), b );
|
|
g_signal_connect (tb, "toggled", G_CALLBACK(ul_speed_toggled_cb), gtor);
|
|
|
|
i = tr_torrentGetSpeedLimit( tor, TR_UP );
|
|
a = (GtkAdjustment*) gtk_adjustment_new (i, 0.0, G_MAXDOUBLE, 1, 1, 1);
|
|
w = gtk_spin_button_new (a, 1, 0);
|
|
g_signal_connect (w, "value-changed", G_CALLBACK(ul_speed_spun_cb), gtor);
|
|
g_signal_connect (tb, "toggled", G_CALLBACK(sensitize_from_check_cb), w);
|
|
sensitize_from_check_cb (GTK_TOGGLE_BUTTON(tb), w);
|
|
hig_workarea_add_row_w (t, &row, tb, w, NULL);
|
|
|
|
hig_workarea_add_section_divider (t, &row);
|
|
hig_workarea_add_section_title (t, &row, _("Peer Connections"));
|
|
|
|
maxConnectedPeers = tr_torrentGetPeerLimit( tor );
|
|
w = gtk_spin_button_new_with_range( 1, 3000, 5 );
|
|
gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), maxConnectedPeers );
|
|
hig_workarea_add_row( t, &row, _( "_Maximum peers:" ), w, w );
|
|
g_signal_connect( w, "value-changed", G_CALLBACK( max_peers_spun_cb ), gtor );
|
|
|
|
hig_workarea_finish (t, &row);
|
|
return t;
|
|
}
|
|
|
|
static void
|
|
refresh_options (GtkWidget * top UNUSED)
|
|
{
|
|
}
|
|
|
|
/****
|
|
***** TRACKER
|
|
****/
|
|
|
|
#define TRACKER_PAGE "tracker-page"
|
|
|
|
struct tracker_page
|
|
{
|
|
TrTorrent * gtor;
|
|
|
|
GtkTreeView * view;
|
|
GtkListStore * store;
|
|
GtkTreeSelection * sel;
|
|
|
|
GtkWidget * add_button;
|
|
GtkWidget * remove_button;
|
|
GtkWidget * save_button;
|
|
GtkWidget * revert_button;
|
|
|
|
GtkWidget * last_scrape_time_lb;
|
|
GtkWidget * last_scrape_response_lb;
|
|
GtkWidget * next_scrape_countdown_lb;
|
|
|
|
GtkWidget * last_announce_time_lb;
|
|
GtkWidget * last_announce_response_lb;
|
|
GtkWidget * next_announce_countdown_lb;
|
|
GtkWidget * manual_announce_countdown_lb;
|
|
};
|
|
|
|
static GtkWidget*
|
|
tracker_page_new( TrTorrent * gtor )
|
|
{
|
|
GtkWidget * t;
|
|
GtkWidget * l;
|
|
GtkWidget * w;
|
|
int row = 0;
|
|
const char * s;
|
|
struct tracker_page * page = g_new0( struct tracker_page, 1 );
|
|
const tr_info * info = tr_torrent_info (gtor);
|
|
|
|
page->gtor = gtor;
|
|
|
|
t = hig_workarea_create( );
|
|
hig_workarea_add_section_title( t, &row, _( "Trackers" ) );
|
|
|
|
w = tracker_list_new( gtor );
|
|
hig_workarea_add_wide_control( t, &row, w );
|
|
|
|
hig_workarea_add_section_divider( t, &row );
|
|
hig_workarea_add_section_title( t, &row, _( "Scrape" ) );
|
|
|
|
s = _( "Last scrape at:" );
|
|
l = gtk_label_new( NULL );
|
|
page->last_scrape_time_lb = l;
|
|
hig_workarea_add_row( t, &row, s, l, NULL );
|
|
|
|
s = _( "Tracker responded:");
|
|
l = gtk_label_new( NULL );
|
|
page->last_scrape_response_lb = l;
|
|
hig_workarea_add_row( t, &row, s, l, NULL );
|
|
|
|
s = _( "Next scrape in:" );
|
|
l = gtk_label_new( NULL );
|
|
page->next_scrape_countdown_lb = l;
|
|
hig_workarea_add_row( t, &row, s, l, NULL );
|
|
|
|
hig_workarea_add_section_divider( t, &row );
|
|
hig_workarea_add_section_title( t, &row, _( "Announce" ) );
|
|
|
|
l = gtk_label_new( info->trackers[0].announce );
|
|
gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
|
|
hig_workarea_add_row (t, &row, _( "Tracker:" ), l, NULL);
|
|
|
|
s = _( "Last announce at:" );
|
|
l = gtk_label_new( NULL );
|
|
page->last_announce_time_lb = l;
|
|
hig_workarea_add_row( t, &row, s, l, NULL );
|
|
|
|
s = _( "Tracker responded:");
|
|
l = gtk_label_new( NULL );
|
|
page->last_announce_response_lb = l;
|
|
hig_workarea_add_row( t, &row, s, l, NULL );
|
|
|
|
s = _( "Next announce in:" );
|
|
l = gtk_label_new( NULL );
|
|
page->next_announce_countdown_lb = l;
|
|
hig_workarea_add_row( t, &row, s, l, NULL );
|
|
|
|
/* how long until the tracker will honor user
|
|
* pressing the "ask for more peers" button */
|
|
s = _( "Manual announce allowed in:" );
|
|
l = gtk_label_new( NULL );
|
|
page->manual_announce_countdown_lb = l;
|
|
hig_workarea_add_row( t, &row, s, l, NULL );
|
|
|
|
hig_workarea_finish( t, &row );
|
|
g_object_set_data_full( G_OBJECT( t ), TRACKER_PAGE, page, g_free );
|
|
return t;
|
|
}
|
|
|
|
static void
|
|
refresh_countdown_lb( GtkWidget * w, time_t t,
|
|
const char * countdown_done )
|
|
{
|
|
const time_t now = time( NULL );
|
|
GtkLabel * l = GTK_LABEL( w );
|
|
|
|
if( t == 1 )
|
|
gtk_label_set_text( l, _( "In progress" ) );
|
|
else if( t < now )
|
|
gtk_label_set_text( l, countdown_done );
|
|
else {
|
|
char buf[1024];
|
|
const int seconds = t - now;
|
|
tr_strltime( buf, seconds, sizeof( buf ) );
|
|
gtk_label_set_text( l, buf );
|
|
}
|
|
}
|
|
|
|
static void
|
|
refresh_tracker( GtkWidget * w )
|
|
{
|
|
GtkWidget * l;
|
|
time_t t;
|
|
struct tracker_page * page = g_object_get_data( G_OBJECT( w ), TRACKER_PAGE );
|
|
const tr_stat * torStat = tr_torrent_stat( page->gtor );
|
|
|
|
l = page->last_scrape_time_lb;
|
|
t = torStat->lastScrapeTime;
|
|
refresh_time_lb( l, t );
|
|
|
|
l = page->last_scrape_response_lb;
|
|
gtk_label_set_text( GTK_LABEL( l ), torStat->scrapeResponse );
|
|
|
|
l = page->next_scrape_countdown_lb;
|
|
t = torStat->nextScrapeTime;
|
|
refresh_countdown_lb( l, t, _( "Never" ) );
|
|
|
|
l = page->last_announce_time_lb;
|
|
t = torStat->lastAnnounceTime;
|
|
refresh_time_lb( l, t );
|
|
|
|
l = page->last_announce_response_lb;
|
|
gtk_label_set_text( GTK_LABEL( l ), torStat->announceResponse );
|
|
|
|
l = page->next_announce_countdown_lb;
|
|
t = torStat->nextAnnounceTime;
|
|
refresh_countdown_lb( l, t, _( "Never" ) );
|
|
|
|
l = page->manual_announce_countdown_lb;
|
|
t = torStat->manualAnnounceTime;
|
|
refresh_countdown_lb( l, t, _( "Now" ) );
|
|
}
|
|
|
|
/****
|
|
***** DIALOG
|
|
****/
|
|
|
|
static void
|
|
torrent_destroyed (gpointer dialog, GObject * dead_torrent UNUSED)
|
|
{
|
|
gtk_widget_destroy (GTK_WIDGET(dialog));
|
|
}
|
|
|
|
static void
|
|
remove_tag (gpointer tag)
|
|
{
|
|
g_source_remove (GPOINTER_TO_UINT(tag)); /* stop the periodic refresh */
|
|
}
|
|
|
|
static void
|
|
response_cb (GtkDialog *dialog, int response UNUSED, gpointer gtor)
|
|
{
|
|
g_object_weak_unref (G_OBJECT(gtor), torrent_destroyed, dialog);
|
|
gtk_widget_destroy (GTK_WIDGET(dialog));
|
|
}
|
|
|
|
static gboolean
|
|
periodic_refresh (gpointer data)
|
|
{
|
|
refresh_tracker (g_object_get_data (G_OBJECT(data), "tracker-top"));
|
|
refresh_peers (g_object_get_data (G_OBJECT(data), "peers-top"));
|
|
refresh_activity (g_object_get_data (G_OBJECT(data), "activity-top"));
|
|
refresh_options (g_object_get_data (G_OBJECT(data), "options-top"));
|
|
return TRUE;
|
|
}
|
|
|
|
GtkWidget*
|
|
torrent_inspector_new ( GtkWindow * parent, TrTorrent * gtor )
|
|
{
|
|
guint tag;
|
|
GtkWidget *d, *n, *w;
|
|
tr_torrent * tor = tr_torrent_handle (gtor);
|
|
char sizeStr[64];
|
|
char title[512];
|
|
const tr_info * info = tr_torrent_info (gtor);
|
|
|
|
/* create the dialog */
|
|
tr_strlsize( sizeStr, info->totalSize, sizeof(sizeStr) );
|
|
/* %1$s is torrent name
|
|
%2$s its file size */
|
|
g_snprintf( title, sizeof(title), _( "Details for %1$s (%2$s)" ), info->name, sizeStr );
|
|
d = gtk_dialog_new_with_buttons (title, parent, 0,
|
|
GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
|
|
NULL);
|
|
gtk_window_set_role (GTK_WINDOW(d), "tr-info" );
|
|
g_signal_connect (d, "response", G_CALLBACK (response_cb), gtor);
|
|
gtk_dialog_set_has_separator( GTK_DIALOG( d ), FALSE );
|
|
gtk_container_set_border_width( GTK_CONTAINER( d ), GUI_PAD );
|
|
g_object_weak_ref (G_OBJECT(gtor), torrent_destroyed, d);
|
|
|
|
|
|
/* add the notebook */
|
|
n = gtk_notebook_new ();
|
|
gtk_container_set_border_width ( GTK_CONTAINER ( n ), GUI_PAD );
|
|
|
|
w = activity_page_new (gtor);
|
|
g_object_set_data (G_OBJECT(d), "activity-top", w);
|
|
gtk_notebook_append_page (GTK_NOTEBOOK(n), w,
|
|
gtk_label_new (_("Activity")));
|
|
|
|
w = peer_page_new (gtor);
|
|
g_object_set_data (G_OBJECT(d), "peers-top", w);
|
|
gtk_notebook_append_page (GTK_NOTEBOOK(n), w,
|
|
gtk_label_new (_("Peers")));
|
|
|
|
w = tracker_page_new( gtor );
|
|
g_object_set_data( G_OBJECT( d ), "tracker-top", w );
|
|
gtk_notebook_append_page( GTK_NOTEBOOK( n ), w,
|
|
gtk_label_new( _( "Tracker" ) ) );
|
|
|
|
gtk_notebook_append_page (GTK_NOTEBOOK(n),
|
|
info_page_new (tor),
|
|
gtk_label_new (_("Information")));
|
|
|
|
w = file_list_new( gtor );
|
|
gtk_container_set_border_width( GTK_CONTAINER( w ), GUI_PAD_BIG );
|
|
g_object_set_data (G_OBJECT(d), "files-top", w);
|
|
gtk_notebook_append_page (GTK_NOTEBOOK(n), w,
|
|
gtk_label_new (_("Files")));
|
|
|
|
w = options_page_new (gtor);
|
|
g_object_set_data (G_OBJECT(d), "options-top", w);
|
|
gtk_notebook_append_page (GTK_NOTEBOOK(n), w,
|
|
gtk_label_new (_("Options")));
|
|
|
|
gtk_box_pack_start_defaults (GTK_BOX(GTK_DIALOG(d)->vbox), n);
|
|
|
|
tag = g_timeout_add (UPDATE_INTERVAL_MSEC, periodic_refresh, d);
|
|
g_object_set_data_full (G_OBJECT(d), "tag",
|
|
GUINT_TO_POINTER(tag), remove_tag);
|
|
|
|
/* return the results */
|
|
periodic_refresh (d);
|
|
gtk_widget_show_all (GTK_DIALOG(d)->vbox);
|
|
return d;
|
|
}
|