Changeset 8279


Ignore:
Timestamp:
Apr 24, 2009, 1:37:04 AM (13 years ago)
Author:
charles
Message:

(trunk gtk) #1473: Properties dialog should let you edit/view multiple torrents at once

Location:
trunk
Files:
14 edited

Legend:

Unmodified
Added
Removed
  • trunk/NEWS

    r8269 r8279  
    2121   + Show the file icon in the list
    2222   + Allow sorting of the torrent list by size and by ETA
     23   + Properties dialog now lets you edit/view multiple torrents at once
    2324- Qt
    2425   + New beta Qt client
  • trunk/gtk/add-dialog.c

    r8122 r8279  
    9999    if( data->gtor )
    100100    {
    101         file_list_set_torrent( data->list, NULL );
    102 
     101        file_list_clear( data->list );
    103102        tr_torrent_set_remove_flag( data->gtor, TRUE );
    104103        g_object_unref( G_OBJECT( data->gtor ) );
     
    144143updateTorrent( struct AddData * o )
    145144{
    146     if( o->gtor )
    147         tr_torrentSetDownloadDir( tr_torrent_handle(
    148                                       o->gtor ), o->downloadDir );
    149 
    150     file_list_set_torrent( o->list, o->gtor );
     145    if( !o->gtor )
     146        file_list_clear( o->list );
     147    else {
     148        tr_torrent * tor = tr_torrent_handle( o->gtor );
     149        tr_torrentSetDownloadDir( tor, o->downloadDir );
     150        file_list_set_torrent( o->list, tr_torrentId( tor ) );
     151    }
    151152}
    152153
     
    293294    data->filename = g_strdup( tr_ctorGetSourceFile( ctor ) );
    294295    data->downloadDir = g_strdup( str );
    295     data->list = file_list_new( NULL );
     296    data->list = file_list_new( core, 0 );
    296297    data->trash_check =
    297298        gtk_check_button_new_with_mnemonic( _( "_Move source file to Trash" ) );
  • trunk/gtk/details.c

    r8262 r8279  
    1111 */
    1212
     13#include <assert.h>
    1314#include <errno.h>
    1415#include <math.h> /* ceil() */
     
    3031#include "util.h"
    3132
     33#define DETAILS_KEY "details-data"
     34
    3235#define UPDATE_INTERVAL_SECONDS 2
    3336
    34 struct ResponseData
    35 {
    36     gpointer    gtor;
    37     TrCore    * core;
    38     guint       handler;
     37struct DetailsImpl
     38{
     39    GtkWidget * peersPage;
     40    GtkWidget * trackerPage;
     41    GtkWidget * activityPage;
     42
     43    GtkWidget * honorLimitsCheck;
     44    GtkWidget * upLimitedCheck;
     45    GtkWidget * upLimitSpin;
     46    GtkWidget * downLimitedCheck;
     47    GtkWidget * downLimitSpin;
     48    GtkWidget * bandwidthCombo;
     49    GtkWidget * seedGlobalRadio;
     50    GtkWidget * seedForeverRadio;
     51    GtkWidget * seedCustomRadio;
     52    GtkWidget * seedCustomSpin;
     53    GtkWidget * maxPeersSpin;
     54
     55    guint honorLimitsCheckTag;
     56    guint upLimitedCheckTag;
     57    guint downLimitedCheckTag;
     58    guint downLimitSpinTag;
     59    guint upLimitSpinTag;
     60    guint bandwidthComboTag;
     61    guint seedForeverRadioTag;
     62    guint seedGlobalRadioTag;
     63    guint seedCustomRadioTag;
     64    guint seedCustomSpinTag;
     65
     66    GtkWidget * state_lb;
     67    GtkWidget * progress_lb;
     68    GtkWidget * have_lb;
     69    GtkWidget * dl_lb;
     70    GtkWidget * ul_lb;
     71    GtkWidget * failed_lb;
     72    GtkWidget * ratio_lb;
     73    GtkWidget * error_lb;
     74    GtkWidget * swarm_lb;
     75    GtkWidget * date_added_lb;
     76    GtkWidget * last_activity_lb;
     77
     78    GtkWidget * pieces_lb;
     79    GtkWidget * hash_lb;
     80    GtkWidget * privacy_lb;
     81    GtkWidget * creator_lb;
     82    GtkWidget * date_created_lb;
     83    GtkWidget * destination_lb;
     84    GtkWidget * torrentfile_lb;
     85    GtkTextBuffer * comment_buffer;
     86
     87    GHashTable * peer_hash;
     88    GHashTable * webseed_hash;
     89    GtkListStore * peer_store;
     90    GtkListStore * webseed_store;
     91    GtkWidget * seeders_lb;
     92    GtkWidget * leechers_lb;
     93    GtkWidget * completed_lb;
     94    GtkWidget * webseed_view;
     95
     96    GtkWidget * tracker_list;
     97    GtkWidget * last_scrape_time_lb;
     98    GtkWidget * last_scrape_response_lb;
     99    GtkWidget * next_scrape_countdown_lb;
     100    GtkWidget * last_announce_time_lb;
     101    GtkWidget * last_announce_response_lb;
     102    GtkWidget * next_announce_countdown_lb;
     103    GtkWidget * manual_announce_countdown_lb;
     104
     105    GtkWidget * file_list;
     106
     107    GSList * ids;
     108    TrCore * core;
     109    guint periodic_refresh_tag;
     110    guint prefs_changed_tag;
    39111};
    40112
     113static tr_torrent**
     114getTorrents( struct DetailsImpl * d, int * setmeCount )
     115{
     116    int n = g_slist_length( d->ids );
     117    int torrentCount = 0;
     118    tr_session * session = tr_core_session( d->core );
     119    tr_torrent ** torrents = g_new( tr_torrent*, n );
     120    GSList * l;
     121
     122    for( l=d->ids; l!=NULL; l=l->next ) {
     123        const int id = GPOINTER_TO_INT( l->data );
     124        tr_torrent * tor = tr_torrentFindFromId( session, id );
     125        if( tor )
     126            torrents[torrentCount++] = tor;
     127    }
     128
     129    *setmeCount = torrentCount;
     130    return torrents;
     131}
     132
    41133/****
     134*****
     135*****  OPTIONS TAB
     136*****
     137****/
     138
     139static void
     140set_togglebutton_if_different( GtkWidget * w, guint tag, gboolean value )
     141{
     142    GtkToggleButton * toggle = GTK_TOGGLE_BUTTON( w );
     143    const gboolean currentValue = gtk_toggle_button_get_active( toggle );
     144    if( currentValue != value )
     145    {
     146        g_signal_handler_block( toggle, tag );
     147        gtk_toggle_button_set_active( toggle, value );
     148        g_signal_handler_unblock( toggle, tag );
     149    }
     150}
     151
     152static void
     153set_int_spin_if_different( GtkWidget * w, guint tag, int value )
     154{
     155    GtkSpinButton * spin = GTK_SPIN_BUTTON( w );
     156    const int currentValue = gtk_spin_button_get_value_as_int( spin );
     157    if( currentValue != value )
     158    {
     159        g_signal_handler_block( spin, tag );
     160        gtk_spin_button_set_value( spin, value );
     161        g_signal_handler_unblock( spin, tag );
     162    }
     163}
     164
     165static void
     166set_double_spin_if_different( GtkWidget * w, guint tag, double value )
     167{
     168    GtkSpinButton * spin = GTK_SPIN_BUTTON( w );
     169    const double currentValue = gtk_spin_button_get_value( spin );
     170    if( ( (int)(currentValue*100) != (int)(value*100) ) )
     171    {
     172        g_signal_handler_block( spin, tag );
     173        gtk_spin_button_set_value( spin, value );
     174        g_signal_handler_unblock( spin, tag );
     175    }
     176}
     177
     178static void
     179set_int_combo_if_different( GtkWidget * w, guint tag, int column, int value )
     180{
     181    GtkComboBox * combobox = GTK_COMBO_BOX( w );
     182    GtkTreeModel * model = gtk_combo_box_get_model( combobox );
     183    GtkTreeIter iter;
     184    int i;
     185    int currentValue;
     186
     187    /* do the value and current value match? */
     188    if( gtk_combo_box_get_active_iter( combobox, &iter ) ) {
     189        gtk_tree_model_get( model, &iter, column, &currentValue, -1 );
     190        if( currentValue == value )
     191            return;
     192    }
     193
     194    /* find the one to select */
     195    i = 0;
     196    while(( gtk_tree_model_iter_nth_child( model, &iter, NULL, i++ ))) {
     197        gtk_tree_model_get( model, &iter, column, &currentValue, -1 );
     198        if( currentValue == value ) {
     199            g_signal_handler_block( combobox, tag );
     200            gtk_combo_box_set_active_iter( combobox, &iter );
     201            g_signal_handler_unblock( combobox, tag );
     202            return;
     203        }
     204    }
     205}
     206
     207static void
     208unset_combo( GtkWidget * w, guint tag )
     209{
     210    GtkComboBox * combobox = GTK_COMBO_BOX( w );
     211
     212    g_signal_handler_block( combobox, tag );
     213    gtk_combo_box_set_active( combobox, -1 );
     214    g_signal_handler_unblock( combobox, tag );
     215}
     216
     217static void
     218refreshOptions( struct DetailsImpl * di, tr_torrent ** torrents, int n )
     219{
     220    /***
     221    ****  Options Page
     222    ***/
     223
     224    /* honorLimitsCheck */
     225    if( n ) {
     226        const gboolean baseline = tr_torrentUsesSessionLimits( torrents[0] );
     227        int i;
     228        for( i=1; i<n; ++i )
     229            if( baseline != tr_torrentUsesSessionLimits( torrents[i] ) )
     230                break;
     231        if( i == n )
     232            set_togglebutton_if_different( di->honorLimitsCheck, di->honorLimitsCheckTag, baseline );
     233    }
     234   
     235    /* downLimitedCheck */
     236    if( n ) {
     237        const gboolean baseline = tr_torrentUsesSpeedLimit( torrents[0], TR_DOWN );
     238        int i;
     239        for( i=1; i<n; ++i )
     240            if( baseline != tr_torrentUsesSpeedLimit( torrents[i], TR_DOWN ) )
     241                break;
     242        if( i == n )
     243            set_togglebutton_if_different( di->downLimitedCheck, di->downLimitedCheckTag, baseline );
     244    }
     245
     246    /* downLimitSpin */
     247    if( n ) {
     248        const int baseline = tr_torrentGetSpeedLimit( torrents[0], TR_DOWN );
     249        int i;
     250        for( i=1; i<n; ++i )
     251            if( baseline != tr_torrentGetSpeedLimit( torrents[i], TR_DOWN ) )
     252                break;
     253        if( i == n )
     254            set_int_spin_if_different( di->downLimitSpin, di->downLimitSpinTag, baseline );
     255    }
     256   
     257    /* upLimitedCheck */
     258    if( n ) {
     259        const gboolean baseline = tr_torrentUsesSpeedLimit( torrents[0], TR_UP );
     260        int i;
     261        for( i=1; i<n; ++i )
     262            if( baseline != tr_torrentUsesSpeedLimit( torrents[i], TR_UP ) )
     263                break;
     264        if( i == n )
     265            set_togglebutton_if_different( di->upLimitedCheck, di->upLimitedCheckTag, baseline );
     266    }
     267
     268    /* upLimitSpin */
     269    if( n ) {
     270        const int baseline = tr_torrentGetSpeedLimit( torrents[0], TR_UP );
     271        int i;
     272        for( i=1; i<n; ++i )
     273            if( baseline != tr_torrentGetSpeedLimit( torrents[i], TR_UP ) )
     274                break;
     275        if( i == n )
     276            set_int_spin_if_different( di->upLimitSpin, di->upLimitSpinTag, baseline );
     277    }
     278
     279    /* bandwidthCombo */
     280    if( n ) {
     281        const int baseline = tr_torrentGetPriority( torrents[0] );
     282        int i;
     283        for( i=1; i<n; ++i )
     284            if( baseline != tr_torrentGetPriority( torrents[i] ) )
     285                break;
     286        if( i == n )
     287            set_int_combo_if_different( di->bandwidthCombo, di->bandwidthComboTag, 0, baseline );
     288        else
     289            unset_combo( di->bandwidthCombo, di->bandwidthComboTag );
     290    }
     291
     292    /* seedGlobalRadio */
     293    /* seedForeverRadio */
     294    /* seedCustomRadio */
     295    if( n ) {
     296        guint tag;
     297        const int baseline = tr_torrentGetRatioMode( torrents[0] );
     298        int i;
     299        for( i=1; i<n; ++i )
     300            if( baseline != (int)tr_torrentGetRatioMode( torrents[i] ) )
     301                break;
     302        if( i == n ) {
     303            GtkWidget * w;
     304            switch( baseline ) {
     305                case TR_RATIOLIMIT_SINGLE: w = di->seedCustomRadio; tag = di->seedCustomRadioTag; break;
     306                case TR_RATIOLIMIT_GLOBAL: w = di->seedGlobalRadio; tag = di->seedGlobalRadioTag; break;
     307                case TR_RATIOLIMIT_UNLIMITED: w = di->seedForeverRadio; tag = di->seedForeverRadioTag; break;
     308            }
     309            set_togglebutton_if_different( w, tag, TRUE );
     310        }
     311    }
     312
     313    /* seedCustomSpin */
     314    if( n ) {
     315        const double baseline = tr_torrentGetRatioLimit( torrents[0] );
     316        int i;
     317        for( i=1; i<n; ++i )
     318            if( (int)(100*baseline) != (int)(100*tr_torrentGetRatioLimit(torrents[i])) )
     319                break;
     320        if( i == n )
     321            set_double_spin_if_different( di->seedCustomSpin, di->seedCustomSpinTag, baseline );
     322    }
     323}
     324
     325static void
     326torrent_set_bool( struct DetailsImpl * di, const char * key, gboolean value )
     327{
     328    GSList *l;
     329    tr_benc top, *args, *ids;
     330
     331    tr_bencInitDict( &top, 2 );
     332    tr_bencDictAddStr( &top, "method", "torrent-set" );
     333    args = tr_bencDictAddDict( &top, "arguments", 2 );
     334    tr_bencDictAddBool( args, key, value );
     335    ids = tr_bencDictAddList( args, "ids", g_slist_length(di->ids) );
     336    for( l=di->ids; l; l=l->next )
     337        tr_bencListAddInt( ids, GPOINTER_TO_INT( l->data ) );
     338
     339    tr_core_exec( di->core, &top );
     340    tr_bencFree( &top );
     341}
     342
     343static void
     344torrent_set_int( struct DetailsImpl * di, const char * key, int value )
     345{
     346    GSList *l;
     347    tr_benc top, *args, *ids;
     348
     349    tr_bencInitDict( &top, 2 );
     350    tr_bencDictAddStr( &top, "method", "torrent-set" );
     351    args = tr_bencDictAddDict( &top, "arguments", 2 );
     352    tr_bencDictAddInt( args, key, value );
     353    ids = tr_bencDictAddList( args, "ids", g_slist_length(di->ids) );
     354    for( l=di->ids; l; l=l->next )
     355        tr_bencListAddInt( ids, GPOINTER_TO_INT( l->data ) );
     356
     357    tr_core_exec( di->core, &top );
     358    tr_bencFree( &top );
     359}
     360
     361static void
     362torrent_set_real( struct DetailsImpl * di, const char * key, double value )
     363{
     364    GSList *l;
     365    tr_benc top, *args, *ids;
     366
     367    tr_bencInitDict( &top, 2 );
     368    tr_bencDictAddStr( &top, "method", "torrent-set" );
     369    args = tr_bencDictAddDict( &top, "arguments", 2 );
     370    tr_bencDictAddReal( args, key, value );
     371    ids = tr_bencDictAddList( args, "ids", g_slist_length(di->ids) );
     372    for( l=di->ids; l; l=l->next )
     373        tr_bencListAddInt( ids, GPOINTER_TO_INT( l->data ) );
     374
     375    tr_core_exec( di->core, &top );
     376    tr_bencFree( &top );
     377}
     378
     379static void
     380up_speed_toggled_cb( GtkToggleButton * tb, gpointer data )
     381{
     382    torrent_set_bool( data, "uploadLimited", gtk_toggle_button_get_active( tb ) );
     383}
     384
     385static void
     386down_speed_toggled_cb( GtkToggleButton *tb, gpointer data )
     387{
     388    torrent_set_bool( data, "downloadLimited", gtk_toggle_button_get_active( tb ) );
     389}
     390
     391static void
     392global_speed_toggled_cb( GtkToggleButton * tb, gpointer data )
     393{
     394    torrent_set_bool( data, "honorsSessionLimits", gtk_toggle_button_get_active( tb ) );
     395}
     396
     397#define RATIO_MODE_KEY "ratio-mode"
     398
     399static void
     400ratio_mode_changed_cb( GtkToggleButton * tb, struct DetailsImpl * data )
     401{
     402    if( gtk_toggle_button_get_active( tb ) )
     403    {
     404        const int mode = GPOINTER_TO_INT( g_object_get_data( G_OBJECT( tb ), RATIO_MODE_KEY ) );
     405        torrent_set_int( data, "seedRatioMode", mode );
     406    }
     407}
     408
     409static void
     410up_speed_spun_cb( GtkSpinButton * spin, struct DetailsImpl * di )
     411{
     412    torrent_set_int( di, "uploadLimit", gtk_spin_button_get_value_as_int( spin ) );
     413}
     414
     415static void
     416down_speed_spun_cb( GtkSpinButton * spin, struct DetailsImpl * di )
     417{
     418    torrent_set_int( di, "downloadLimit", gtk_spin_button_get_value_as_int( spin ) );
     419}
     420
     421static void
     422ratio_spun_cb( GtkSpinButton * spin, struct DetailsImpl * di )
     423{
     424    torrent_set_real( di, "seedRatioLimit", gtk_spin_button_get_value( spin ) );
     425}
     426
     427static void
     428max_peers_spun_cb( GtkSpinButton * spin, struct DetailsImpl * di )
     429{
     430    torrent_set_int( di, "peer-limit", gtk_spin_button_get_value( spin ) );
     431}
     432
     433static char*
     434get_global_ratio_radiobutton_string( void )
     435{
     436    char * s;
     437    const gboolean b = pref_flag_get( TR_PREFS_KEY_RATIO_ENABLED );
     438    const double d = pref_double_get( TR_PREFS_KEY_RATIO );
     439
     440    if( b )
     441        s = g_strdup_printf( _( "Use _Global setting  (currently: stop seeding when a torrent's ratio reaches %.2f)" ), d );
     442    else
     443        s = g_strdup( _( "Use _Global setting  (currently: seed regardless of ratio)" ) );
     444
     445    return s;
     446}
     447
     448static void
     449prefsChanged( TrCore * core UNUSED, const char *  key, gpointer rb )
     450{
     451    if( !strcmp( key, TR_PREFS_KEY_RATIO_ENABLED ) || !strcmp( key, TR_PREFS_KEY_RATIO ) )
     452    {
     453        char * s = get_global_ratio_radiobutton_string( );
     454        gtk_button_set_label( GTK_BUTTON( rb ), s );
     455        g_free( s );
     456    }
     457}
     458
     459static void
     460onPriorityComboChanged( GtkComboBox * w, struct DetailsImpl * di )
     461{
     462    GtkTreeIter iter;
     463
     464    if( gtk_combo_box_get_active_iter( w, &iter ) )
     465    {
     466        int val = 0;
     467        gtk_tree_model_get( gtk_combo_box_get_model( w ), &iter, 0, &val, -1 );
     468        torrent_set_int( di, "bandwidthPriority", val );
     469    }
     470}
     471
     472static GtkWidget*
     473new_priority_combo( struct DetailsImpl * di )
     474{
     475    int i;
     476    int selIndex;
     477    guint tag;
     478    GtkWidget * w;
     479    GtkCellRenderer * r;
     480    GtkListStore * store;
     481    const struct {
     482        int value;
     483        const char * text;
     484    } items[] = {
     485        { TR_PRI_LOW,    N_( "Low" )  },
     486        { TR_PRI_NORMAL, N_( "Normal" ) },
     487        { TR_PRI_HIGH,   N_( "High" )  }
     488    };
     489
     490    /* build a store for encryption */
     491    selIndex = -1;
     492    store = gtk_list_store_new( 2, G_TYPE_INT, G_TYPE_STRING );
     493    for( i=0; i<(int)G_N_ELEMENTS(items); ++i ) {
     494        GtkTreeIter iter;
     495        gtk_list_store_append( store, &iter );
     496        gtk_list_store_set( store, &iter, 0, items[i].value, 1, _( items[i].text ), -1 );
     497    }
     498
     499    /* build the widget */
     500    w = gtk_combo_box_new_with_model( GTK_TREE_MODEL( store ) );
     501    r = gtk_cell_renderer_text_new( );
     502    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
     503    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( w ), r, "text", 1, NULL );
     504    tag = g_signal_connect( w, "changed", G_CALLBACK( onPriorityComboChanged ), di );
     505    di->bandwidthComboTag = tag;
     506
     507    /* cleanup */
     508    g_object_unref( G_OBJECT( store ) );
     509    return w;
     510}
     511
     512
     513static GtkWidget*
     514options_page_new( struct DetailsImpl * d )
     515{
     516    guint tag;
     517    int i, row;
     518    char *s;
     519    GSList *group;
     520    GtkWidget *t, *w, *tb, *h;
     521
     522    row = 0;
     523    t = hig_workarea_create( );
     524    hig_workarea_add_section_title( t, &row, _( "Speed" ) );
     525
     526        tb = hig_workarea_add_wide_checkbutton( t, &row, _( "Honor global _limits" ), FALSE );
     527        d->honorLimitsCheck = tb;
     528        tag = g_signal_connect( tb, "toggled", G_CALLBACK( global_speed_toggled_cb ), d );
     529        d->honorLimitsCheckTag = tag;
     530
     531        tb = gtk_check_button_new_with_mnemonic( _( "Limit _download speed (KB/s):" ) );
     532        gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( tb ), FALSE );
     533        d->downLimitedCheck = tb;
     534        tag = g_signal_connect( tb, "toggled", G_CALLBACK( down_speed_toggled_cb ), d );
     535        d->downLimitedCheckTag = tag;
     536
     537        w = gtk_spin_button_new_with_range( 1, INT_MAX, 5 );
     538        gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), i );
     539        tag = g_signal_connect( w, "value-changed", G_CALLBACK( down_speed_spun_cb ), d );
     540        d->downLimitSpinTag = tag;
     541        hig_workarea_add_row_w( t, &row, tb, w, NULL );
     542        d->downLimitSpin = w;
     543
     544        tb = gtk_check_button_new_with_mnemonic( _( "Limit _upload speed (KB/s):" ) );
     545        d->upLimitedCheck = tb;
     546        tag = g_signal_connect( tb, "toggled", G_CALLBACK( up_speed_toggled_cb ), d );
     547        d->upLimitedCheckTag = tag;
     548
     549        w = gtk_spin_button_new_with_range( 1, INT_MAX, 5 );
     550        gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), i );
     551        tag = g_signal_connect( w, "value-changed", G_CALLBACK( up_speed_spun_cb ), d );
     552        d->upLimitSpinTag = tag;
     553        hig_workarea_add_row_w( t, &row, tb, w, NULL );
     554        d->upLimitSpin = w;
     555
     556        w = new_priority_combo( d );
     557        hig_workarea_add_row( t, &row, _( "_Bandwidth priority:" ), w, NULL );
     558        d->bandwidthCombo = w;
     559
     560    hig_workarea_add_section_divider( t, &row );
     561    hig_workarea_add_section_title( t, &row, _( "Seed-Until Ratio" ) );
     562
     563        group = NULL;
     564        s = get_global_ratio_radiobutton_string( );
     565        w = gtk_radio_button_new_with_mnemonic( group, s );
     566        tag = g_signal_connect( d->core, "prefs-changed", G_CALLBACK( prefsChanged ), w );
     567        d->prefs_changed_tag = tag;
     568        group = gtk_radio_button_get_group( GTK_RADIO_BUTTON( w ) );
     569        hig_workarea_add_wide_control( t, &row, w );
     570        g_free( s );
     571        g_object_set_data( G_OBJECT( w ), RATIO_MODE_KEY, GINT_TO_POINTER( TR_RATIOLIMIT_GLOBAL ) );
     572        tag = g_signal_connect( w, "toggled", G_CALLBACK( ratio_mode_changed_cb ), d );
     573        d->seedGlobalRadio = w;
     574        d->seedGlobalRadioTag = tag;
     575
     576        w = gtk_radio_button_new_with_mnemonic( group, _( "Seed _regardless of ratio" ) );
     577        group = gtk_radio_button_get_group( GTK_RADIO_BUTTON( w ) );
     578        hig_workarea_add_wide_control( t, &row, w );
     579        g_object_set_data( G_OBJECT( w ), RATIO_MODE_KEY, GINT_TO_POINTER( TR_RATIOLIMIT_UNLIMITED ) );
     580        tag = g_signal_connect( w, "toggled", G_CALLBACK( ratio_mode_changed_cb ), d );
     581        d->seedForeverRadio = w;
     582        d->seedForeverRadioTag = tag;
     583
     584        h = gtk_hbox_new( FALSE, GUI_PAD );
     585        w = gtk_radio_button_new_with_mnemonic( group, _( "_Stop seeding when a torrent's ratio reaches" ) );
     586        d->seedCustomRadio = w;
     587        g_object_set_data( G_OBJECT( w ), RATIO_MODE_KEY, GINT_TO_POINTER( TR_RATIOLIMIT_SINGLE ) );
     588        tag = g_signal_connect( w, "toggled", G_CALLBACK( ratio_mode_changed_cb ), d );
     589        d->seedCustomRadioTag = tag;
     590        group = gtk_radio_button_get_group( GTK_RADIO_BUTTON( w ) );
     591        gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
     592        w = gtk_spin_button_new_with_range( 0.5, INT_MAX, .05 );
     593        gtk_spin_button_set_digits( GTK_SPIN_BUTTON( w ), 2 );
     594        tag = g_signal_connect( w, "value-changed", G_CALLBACK( ratio_spun_cb ), d );
     595        gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
     596        hig_workarea_add_wide_control( t, &row, h );
     597        d->seedCustomSpin = w;
     598        d->seedCustomSpinTag = tag;
     599   
     600    hig_workarea_add_section_divider( t, &row );
     601    hig_workarea_add_section_title( t, &row, _( "Peer Connections" ) );
     602
     603        w = gtk_spin_button_new_with_range( 1, 3000, 5 );
     604        hig_workarea_add_row( t, &row, _( "_Maximum peers:" ), w, w );
     605        g_signal_connect( w, "value-changed", G_CALLBACK( max_peers_spun_cb ), d );
     606        d->maxPeersSpin = w;
     607
     608    hig_workarea_finish( t, &row );
     609    return t;
     610}
     611
     612/****
     613*****
     614*****  ACTIVITY TAB
     615*****
     616****/
     617
     618static const char * activityString( int activity )
     619{
     620    switch( activity )
     621    {
     622        case TR_STATUS_CHECK_WAIT: return _( "Waiting to verify local data" ); break;
     623        case TR_STATUS_CHECK:      return _( "Verifying local data" ); break;
     624        case TR_STATUS_DOWNLOAD:   return _( "Downloading" ); break;
     625        case TR_STATUS_SEED:       return _( "Seeding" ); break;
     626        case TR_STATUS_STOPPED:    return _( "Paused" ); break;
     627    }
     628
     629    return "";
     630}
     631
     632static void
     633refreshActivity( struct DetailsImpl * di, tr_torrent ** torrents, int n )
     634{
     635    int i;
     636    const char * str;
     637    const char * none = _( "None" );
     638    const char * mixed = _( "Mixed" );
     639    char buf[512];
     640    const tr_stat ** stats = g_new( const tr_stat*, n );
     641    for( i=0; i<n; ++i )
     642        stats[i] = tr_torrentStatCached( torrents[i] );
     643
     644    /* state_lb */
     645    if( n <= 0 )
     646        str = none;
     647    else {
     648        const int baseline = stats[0]->activity;
     649        for( i=1; i<n; ++i )
     650            if( baseline != (int)stats[i]->activity )
     651                break;
     652        str = i==n ? activityString(baseline) : _( "Mixed" );
     653    }
     654    gtk_label_set_text( GTK_LABEL( di->state_lb ), str );
     655
     656
     657    /* progress_lb */
     658    if( n <= 0 )
     659        str = none;
     660    else {
     661        double sizeWhenDone = 0;
     662        double leftUntilDone = 0;
     663        for( i=0; i<n; ++i ) {
     664            sizeWhenDone += stats[i]->sizeWhenDone;
     665            leftUntilDone += stats[i]->leftUntilDone;
     666        }
     667        g_snprintf( buf, sizeof( buf ), _( "%.1f%%" ), 100.0*((sizeWhenDone-leftUntilDone)/sizeWhenDone) );
     668        str = buf;
     669    }
     670    gtk_label_set_text( GTK_LABEL( di->progress_lb ), str );
     671
     672
     673    /* have_lb */
     674    if( n <= 0 )
     675        str = none;
     676    else {
     677        char buf1[128];
     678        char buf2[128];
     679        double haveUnchecked = 0;
     680        double haveValid = 0;
     681        double verifiedPieces = 0;
     682        for( i=0; i<n; ++i ) {
     683            haveUnchecked += stats[i]->haveUnchecked;
     684            haveValid += stats[i]->haveValid;
     685            verifiedPieces += stats[i]->haveValid / tr_torrentInfo(torrents[i])->pieceSize;
     686        }
     687        tr_strlsize( buf1, haveValid + haveUnchecked, sizeof( buf1 ) );
     688        tr_strlsize( buf2, haveValid, sizeof( buf2 ) );
     689        i = (int) ceil( verifiedPieces );
     690        g_snprintf( buf, sizeof( buf ), ngettext( "%1$s (%2$s verified in %3$d piece)",
     691                                                  "%1$s (%2$s verified in %3$d pieces)", verifiedPieces ),
     692                                        buf1, buf2, i );
     693        str = buf;
     694    }
     695    gtk_label_set_text( GTK_LABEL( di->have_lb ), str );
     696
     697   
     698    /* dl_lb */
     699    if( n <= 0 )
     700        str = none;
     701    else {
     702        uint64_t sum = 0;
     703        for( i=0; i<n; ++i ) sum += stats[i]->downloadedEver;
     704        tr_strlsize( buf, sum, sizeof( buf ) );
     705        str = buf;
     706    }
     707    gtk_label_set_text( GTK_LABEL( di->dl_lb ), str );
     708
     709   
     710    /* ul_lb */
     711    if( n <= 0 )
     712        str = none;
     713    else {
     714        uint64_t sum = 0;
     715        for( i=0; i<n; ++i ) sum += stats[i]->uploadedEver;
     716        tr_strlsize( buf, sum, sizeof( buf ) );
     717        str = buf;
     718    }
     719    gtk_label_set_text( GTK_LABEL( di->ul_lb ), str );
     720
     721
     722    /* corrupt ever */
     723    if( n <= 0 )
     724        str = none;
     725    else {
     726        uint64_t sum = 0;
     727        for( i=0; i<n; ++i ) sum += stats[i]->corruptEver;
     728        tr_strlsize( buf, sum, sizeof( buf ) );
     729        str = buf;
     730    }
     731    gtk_label_set_text( GTK_LABEL( di->failed_lb ), str );
     732
     733
     734    /* ratio */
     735    if( n <= 0 )
     736        str = none;
     737    else {
     738        uint64_t downloadedEver = 0;
     739        uint64_t uploadedEver = 0;
     740        for( i=0; i<n; ++i ) {
     741            downloadedEver += stats[i]->downloadedEver;
     742            uploadedEver += stats[i]->uploadedEver;
     743        }
     744        tr_strlratio( buf, tr_getRatio( uploadedEver, downloadedEver ), sizeof( buf ) );
     745        str = buf;
     746    }
     747    gtk_label_set_text( GTK_LABEL( di->ratio_lb ), str );
     748
     749
     750    /* swarmspeed */
     751    if( n <= 0 )
     752        str = none;
     753    else {
     754        double swarmSpeed = 0;
     755        for( i=0; i<n; ++i )
     756            swarmSpeed += stats[i]->swarmSpeed;
     757        tr_strlspeed( buf, swarmSpeed, sizeof( buf ) );
     758        str = buf;
     759    }
     760    gtk_label_set_text( GTK_LABEL( di->swarm_lb ), str );
     761
     762
     763    /* error */
     764    if( n <= 0 )
     765        str = none;
     766    else {
     767        const char * baseline = stats[0]->errorString;
     768        for( i=1; i<n; ++i )
     769            if( strcmp( baseline, stats[i]->errorString ) )
     770                break;
     771        str = i==n ? baseline : mixed;
     772    }
     773    if( !str || !*str )
     774        str = none;
     775    gtk_label_set_text( GTK_LABEL( di->error_lb ), str );
     776
     777
     778    /* date added */
     779    if( n <= 0 )
     780        str = none;
     781    else {
     782        const time_t baseline = stats[0]->addedDate;
     783        for( i=1; i<n; ++i )
     784            if( baseline != stats[i]->addedDate )
     785                break;
     786        if( i==n ) {
     787            char * tmp = gtr_localtime( baseline );
     788            g_strlcpy( buf, tmp, sizeof( buf ) );
     789            g_free( tmp );
     790            str = buf;
     791        } else {
     792            str = mixed;
     793        }
     794    }
     795    gtk_label_set_text( GTK_LABEL( di->date_added_lb ), str );
     796
     797
     798    /* activity date */
     799    if( n <= 0 )
     800        str = none;
     801    else {
     802        const time_t baseline = stats[0]->activityDate;
     803        for( i=1; i<n; ++i )
     804            if( baseline != stats[i]->activityDate )
     805                break;
     806        if( i!=n )
     807            str = mixed;
     808        else {
     809            char * tmp = gtr_localtime( baseline );
     810            g_strlcpy( buf, tmp, sizeof( buf ) );
     811            str = buf;
     812            tr_free( tmp );
     813        }
     814    }
     815    gtk_label_set_text( GTK_LABEL( di->last_activity_lb ), str );
     816
     817    g_free( stats );
     818}
     819
     820static GtkWidget*
     821activity_page_new( struct DetailsImpl * di )
     822{
     823    int  row = 0;
     824    GtkWidget * l;
     825    GtkWidget * t = hig_workarea_create( );
     826
     827    hig_workarea_add_section_title( t, &row, _( "Transfer" ) );
     828
     829    l = di->state_lb = gtk_label_new( NULL );
     830    hig_workarea_add_row( t, &row, _( "State:" ), l, NULL );
     831
     832    l = di->progress_lb = gtk_label_new( NULL );
     833    hig_workarea_add_row( t, &row, _( "Progress:" ), l, NULL );
     834
     835    l = di->have_lb = gtk_label_new( NULL );
     836    /* "Have" refers to how much of the torrent we have */
     837    hig_workarea_add_row( t, &row, _( "Have:" ), l, NULL );
     838
     839    l = di->dl_lb = gtk_label_new( NULL );
     840    hig_workarea_add_row( t, &row, _( "Downloaded:" ), l, NULL );
     841
     842    l = di->ul_lb = gtk_label_new( NULL );
     843    hig_workarea_add_row( t, &row, _( "Uploaded:" ), l, NULL );
     844
     845    /* how much downloaded data was corrupt */
     846    l = di->failed_lb = gtk_label_new( NULL );
     847    hig_workarea_add_row( t, &row, _( "Failed DL:" ), l, NULL );
     848
     849    l = di->ratio_lb = gtk_label_new( NULL );
     850    hig_workarea_add_row( t, &row, _( "Ratio:" ), l, NULL );
     851
     852    l = di->swarm_lb = gtk_label_new( NULL );
     853    hig_workarea_add_row( t, &row, _( "Swarm rate:" ), l, NULL );
     854
     855    l = di->error_lb = gtk_label_new( NULL );
     856    hig_workarea_add_row( t, &row, _( "Error:" ), l, NULL );
     857
     858    hig_workarea_add_section_divider( t, &row );
     859    hig_workarea_add_section_title( t, &row, _( "Dates" ) );
     860
     861    l = di->date_added_lb = gtk_label_new( NULL );
     862    hig_workarea_add_row( t, &row, _( "Started at:" ), l, NULL );
     863
     864    l = di->last_activity_lb = gtk_label_new( NULL );
     865    hig_workarea_add_row( t, &row, _( "Last activity at:" ), l, NULL );
     866
     867    hig_workarea_add_section_divider( t, &row );
     868    hig_workarea_finish( t, &row );
     869    return t;
     870}
     871
     872/****
     873*****
     874*****  INFO TAB
     875*****
     876****/
     877
     878static void
     879refreshInfo( struct DetailsImpl * di, tr_torrent ** torrents, int n )
     880{
     881    int i;
     882    char buf[128];
     883    const char * str;
     884    const char * none = _( "None" );
     885    const char * mixed = _( "Mixed" );
     886    const tr_info ** infos = g_new( const tr_info*, n );
     887
     888    /* info */
     889    for( i=0; i<n; ++i )
     890        infos[i] = tr_torrentInfo( torrents[i] );
     891
     892    /* pieces_lb */
     893    if( n <= 0 )
     894        str = none;
     895    else {
     896        int sum = 0;
     897        const int baseline = infos[0]->pieceSize;
     898        for( i=0; i<n; ++i )
     899            sum += infos[i]->pieceCount;
     900        g_snprintf( buf, sizeof( buf ), ngettext( "%'d Piece", "%'d Pieces", sum ), sum );
     901        for( i=1; i<n; ++i )
     902            if( baseline != (int)infos[i]->pieceSize )
     903                break;
     904        if( i==n ) {
     905            char tmp1[64];
     906            char tmp2[64];
     907            tr_strlcpy( tmp1, buf, sizeof( tmp1 ) );
     908            tr_strlsize( tmp2, baseline, sizeof( tmp2 ) );
     909            g_snprintf( buf, sizeof( buf ), _( "%1$s @ %2$s" ), tmp1, tmp2 );
     910        }
     911        str = buf;
     912    }
     913    gtk_label_set_text( GTK_LABEL( di->pieces_lb ), str );
     914   
     915
     916    /* hash_lb */
     917    if( n<=0 )
     918        str = none;
     919    else if ( n==1 )
     920        str = infos[0]->hashString;
     921    else
     922        str = mixed;
     923    gtk_label_set_text( GTK_LABEL( di->hash_lb ), str );
     924
     925
     926    /* privacy_lb */
     927    if( n<=0 )
     928        str = none;
     929    else {
     930        const tr_bool baseline = infos[0]->isPrivate;
     931        for( i=1; i<n; ++i )
     932            if( baseline != infos[i]->isPrivate )
     933                break;
     934        if( i==n )
     935            str = baseline ? _( "Private to this tracker -- PEX disabled" ) : _( "Public torrent" );
     936        else
     937            str = mixed;
     938    }
     939    gtk_label_set_text( GTK_LABEL( di->privacy_lb ), str );
     940
     941
     942    /* comment_buffer */
     943    if( n<=0 )
     944        str = "";
     945    else {
     946        const char * baseline = infos[0]->comment ? infos[0]->comment : "";
     947        for( i=1; i<n; ++i ) {
     948            const char * tmp = infos[i]->comment ? infos[i]->comment : "";
     949            if( strcmp( baseline, tmp ) )
     950                break;
     951        }
     952        if( i==n )
     953            str = baseline;
     954        else
     955            str = mixed;
     956    }
     957    gtk_text_buffer_set_text( di->comment_buffer, str, -1 );
     958
     959
     960    /* creator_lb */
     961    if( n<=0 )
     962        str = none;
     963    else {
     964        const char * baseline = infos[0]->creator ? infos[0]->creator : "";
     965        for( i=1; i<n; ++i ) {
     966            const char * tmp = infos[i]->creator ? infos[i]->creator : "";
     967            if( strcmp( baseline, tmp ) )
     968                break;
     969        }
     970        if( i==n )
     971            str = baseline;
     972        else
     973            str = mixed;
     974    }
     975    if( !str || !*str )
     976        str = _( "Unknown" );
     977    gtk_label_set_text( GTK_LABEL( di->creator_lb ), str );
     978
     979
     980    /* date_created_lb */
     981    if( n<=0 )
     982        str = none;
     983    else {
     984        const time_t baseline = infos[0]->dateCreated;
     985        for( i=1; i<n; ++i )
     986            if( baseline != infos[i]->dateCreated )
     987                break;
     988        if( i!=n )
     989            str = mixed;
     990        else {
     991            char * tmp = gtr_localtime( baseline );
     992            tr_strlcpy( buf, tmp, sizeof( buf ) );
     993            str = buf;
     994            g_free( tmp );
     995        }
     996    }
     997    gtk_label_set_text( GTK_LABEL( di->date_created_lb ), str );
     998   
     999
     1000    /* destination_lb */       
     1001    if( n<=0 )
     1002        str = none;
     1003    else {
     1004        const char * baseline = tr_torrentGetDownloadDir( torrents[0] );
     1005        for( i=1; i<n; ++i )
     1006            if( strcmp( baseline, tr_torrentGetDownloadDir( torrents[i] ) ) )
     1007                break;
     1008        if( i==n )
     1009            str = baseline;
     1010        else
     1011            str = mixed;
     1012    }
     1013    gtk_label_set_text( GTK_LABEL( di->destination_lb ), str );
     1014
     1015
     1016    /* torrentfile_lb */
     1017    if( n<=0 )
     1018        str = none;
     1019    else if( n==1 )
     1020        str = infos[0]->torrent;
     1021    else
     1022        str = mixed;
     1023    gtk_label_set_text( GTK_LABEL( di->torrentfile_lb ), str );
     1024}
     1025
     1026static GtkWidget*
     1027info_page_new( struct DetailsImpl * di )
     1028{
     1029    int row = 0;
     1030    GtkTextBuffer * b;
     1031    GtkWidget *l, *w, *fr;
     1032    GtkWidget *t = hig_workarea_create( );
     1033
     1034    hig_workarea_add_section_title( t, &row, _( "Details" ) );
     1035
     1036        /* pieces */
     1037        l = di->pieces_lb = gtk_label_new( NULL );
     1038        hig_workarea_add_row( t, &row, _( "Pieces:" ), l, NULL );
     1039
     1040        /* hash */
     1041        l = g_object_new( GTK_TYPE_LABEL, "selectable", TRUE,
     1042                                          "ellipsize", PANGO_ELLIPSIZE_END,
     1043                                           NULL );
     1044        hig_workarea_add_row( t, &row, _( "Hash:" ), l, NULL );
     1045        di->hash_lb = l;
     1046
     1047        /* privacy */
     1048        l = gtk_label_new( NULL );
     1049        hig_workarea_add_row( t, &row, _( "Privacy:" ), l, NULL );
     1050        di->privacy_lb = l;
     1051
     1052        /* comment */
     1053        b = di->comment_buffer = gtk_text_buffer_new( NULL );
     1054        w = gtk_text_view_new_with_buffer( b );
     1055        gtk_widget_set_size_request( w, 0u, 100u );
     1056        gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW( w ), GTK_WRAP_WORD );
     1057        gtk_text_view_set_editable( GTK_TEXT_VIEW( w ), FALSE );
     1058        fr = gtk_frame_new( NULL );
     1059        gtk_frame_set_shadow_type( GTK_FRAME( fr ), GTK_SHADOW_IN );
     1060        gtk_container_add( GTK_CONTAINER( fr ), w );
     1061        w = hig_workarea_add_row( t, &row, _( "Comment:" ), fr, NULL );
     1062        gtk_misc_set_alignment( GTK_MISC( w ), 0.0f, 0.0f );
     1063
     1064    hig_workarea_add_section_divider( t, &row );
     1065    hig_workarea_add_section_title( t, &row, _( "Origins" ) );
     1066
     1067        l = di->creator_lb = gtk_label_new( NULL );
     1068        gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
     1069        hig_workarea_add_row( t, &row, _( "Creator:" ), l, NULL );
     1070
     1071        l = di->date_created_lb = gtk_label_new( NULL );
     1072        hig_workarea_add_row( t, &row, _( "Date:" ), l, NULL );
     1073
     1074    hig_workarea_add_section_divider( t, &row );
     1075    hig_workarea_add_section_title( t, &row, _( "Location" ) );
     1076
     1077        l = g_object_new( GTK_TYPE_LABEL, "selectable", TRUE,
     1078                                          "ellipsize", PANGO_ELLIPSIZE_END,
     1079                                          NULL );
     1080        hig_workarea_add_row( t, &row, _( "Destination:" ), l, NULL );
     1081        di->destination_lb = l;
     1082
     1083        l = g_object_new( GTK_TYPE_LABEL, "selectable", TRUE,
     1084                                          "ellipsize", PANGO_ELLIPSIZE_END,
     1085                                          NULL );
     1086        hig_workarea_add_row( t, &row, _( "Torrent file:" ), l, NULL );
     1087        di->torrentfile_lb = l;
     1088
     1089    hig_workarea_finish( t, &row );
     1090    return t;
     1091}
     1092
     1093/****
     1094*****
    421095*****  PEERS TAB
     1096*****
    431097****/
    441098
     
    471101    WEBSEED_COL_URL,
    481102    WEBSEED_COL_DOWNLOAD_RATE,
     1103    WEBSEED_COL_WAS_UPDATED,
    491104    N_WEBSEED_COLS
    501105};
    511106
    52 static const char * webseed_column_names[N_WEBSEED_COLS] =
    53 {
    54     N_( "Web Seeds" ),
    55     /* 'download speed' column header. terse to keep the column narrow. */
    56     N_( "Down" )
    57 };
    58 
    59 static GtkTreeModel*
    60 webseed_model_new( const tr_torrent * tor )
    61 {
    62     int             i;
    63     const tr_info * inf = tr_torrentInfo( tor );
    64     float *         speeds = tr_torrentWebSpeeds( tor );
    65     GtkListStore *  store = gtk_list_store_new( N_WEBSEED_COLS,
    66                                                 G_TYPE_STRING,
    67                                                 G_TYPE_FLOAT );
    68 
    69     for( i = 0; i < inf->webseedCount; ++i )
     1107static const char*
     1108getWebseedColumnNames( int column )
     1109{
     1110    switch( column )
    701111    {
    71         GtkTreeIter iter;
    72         gtk_list_store_append( store, &iter );
    73         gtk_list_store_set( store, &iter,
    74                             WEBSEED_COL_URL, inf->webseeds[i],
    75                             WEBSEED_COL_DOWNLOAD_RATE, speeds[i],
    76                             -1 );
    77     }
    78 
    79     tr_free( speeds );
    80     return GTK_TREE_MODEL( store );
     1112        case WEBSEED_COL_URL: return _( "Webseeds" );
     1113        case WEBSEED_COL_DOWNLOAD_RATE: return _( "Down" );
     1114        default: return "";
     1115    }
     1116}
     1117
     1118static GtkListStore*
     1119webseed_model_new( void )
     1120{
     1121    return gtk_list_store_new( N_WEBSEED_COLS,
     1122                               G_TYPE_STRING,
     1123                               G_TYPE_FLOAT,
     1124                               G_TYPE_BOOLEAN );
    811125}
    821126
     
    851129    PEER_COL_ADDRESS,
    861130    PEER_COL_ADDRESS_COLLATED,
    87     PEER_COL_DOWNLOAD_RATE,
    88     PEER_COL_UPLOAD_RATE,
     1131    PEER_COL_DOWNLOAD_RATE_DOUBLE,
     1132    PEER_COL_DOWNLOAD_RATE_STRING,
     1133    PEER_COL_UPLOAD_RATE_DOUBLE,
     1134    PEER_COL_UPLOAD_RATE_STRING,
    891135    PEER_COL_CLIENT,
    901136    PEER_COL_PROGRESS,
    911137    PEER_COL_IS_ENCRYPTED,
    921138    PEER_COL_STATUS,
     1139    PEER_COL_WAS_UPDATED,
     1140    PEER_COL_KEY,
    931141    N_PEER_COLS
    941142};
    951143
    96 static const char* peer_column_names[N_PEER_COLS] =
    97 {
    98     N_( "Address" ),
    99     "",
    100     /* 'download speed' column header. terse to keep the column narrow. */
    101     N_( "Down" ),
    102     /* 'upload speed' column header.  terse to keep the column narrow. */
    103     N_( "Up" ),
    104     N_( "Client" ),
    105     /* 'percent done' column header. terse to keep the column narrow. */
    106     N_( "%" ),
    107     " ",
    108     N_( "Status" )
    109 };
    110 
    111 static int
    112 compare_peers( const void * a, const void * b )
    113 {
    114     const tr_peer_stat * pa = a;
    115     const tr_peer_stat * pb = b;
    116 
    117     return strcmp( pa->addr, pb->addr );
    118 }
    119 
    120 static int
    121 compare_addr_to_peer( const void * addr, const void * b )
    122 {
    123     const tr_peer_stat * peer = b;
    124 
    125     return strcmp( addr, peer->addr );
    126 }
    127 
    128 static void
    129 peer_row_set( GtkListStore        * store,
    130               GtkTreeIter         * iter,
    131               const tr_peer_stat  * peer )
     1144static const char*
     1145getPeerColumnName( int column )
     1146{
     1147    switch( column )
     1148    {
     1149        case PEER_COL_ADDRESS: return _( "Address" );
     1150        case PEER_COL_DOWNLOAD_RATE_STRING:
     1151        case PEER_COL_DOWNLOAD_RATE_DOUBLE: return _( "Down" );
     1152        case PEER_COL_UPLOAD_RATE_STRING:
     1153        case PEER_COL_UPLOAD_RATE_DOUBLE: return _( "Up" );
     1154        case PEER_COL_CLIENT: return _( "Client" );
     1155        case PEER_COL_PROGRESS: return _( "%" );
     1156        case PEER_COL_STATUS: return _( "Status" );
     1157        default: return "";
     1158    }
     1159}
     1160
     1161static GtkListStore*
     1162peer_store_new( void )
     1163{
     1164    return gtk_list_store_new( N_PEER_COLS,
     1165                               G_TYPE_STRING,   /* address */
     1166                               G_TYPE_STRING,   /* collated address */
     1167                               G_TYPE_FLOAT,    /* download speed float */
     1168                               G_TYPE_STRING,   /* download speed string */
     1169                               G_TYPE_FLOAT,    /* upload speed float */
     1170                               G_TYPE_STRING,   /* upload speed string  */
     1171                               G_TYPE_STRING,   /* client */
     1172                               G_TYPE_INT,      /* progress [0..100] */
     1173                               G_TYPE_BOOLEAN,  /* isEncrypted */
     1174                               G_TYPE_STRING,   /* flagString */
     1175                               G_TYPE_BOOLEAN,  /* was-updated */
     1176                               G_TYPE_STRING);  /* key */
     1177}
     1178
     1179static void
     1180init_peer( GtkListStore * store, GtkTreeIter * iter, const char * key, const tr_peer_stat * peer )
    1321181{
    1331182    int quads[4];
    134     char buf[128];
     1183    char upload_speed[128];
     1184    char collated_name[128];
     1185    char download_speed[128];
    1351186    const char * client = peer->client;
    1361187
    1371188    if( !client || !strcmp( client, "Unknown Client" ) )
    138         client = " ";
    139 
    140     if( sscanf( peer->addr, "%d.%d.%d.%d", quads, quads+1, quads+2, quads+3 ) == 4 )
    141         g_snprintf( buf, sizeof( buf ), "%03d.%03d.%03d.%03d", quads[0], quads[1], quads[2], quads[3] );
     1189        client = "";
     1190
     1191    tr_strlspeed( upload_speed, peer->rateToPeer, sizeof( upload_speed ) );
     1192    tr_strlspeed( download_speed, peer->rateToClient, sizeof( download_speed ) );
     1193    if( sscanf( peer->addr, "%d.%d.%d.%d", quads, quads+1, quads+2, quads+3 ) != 4 )
     1194        g_strlcpy( collated_name, peer->addr, sizeof( collated_name ) );
    1421195    else
    143         g_strlcpy( buf, peer->addr, sizeof( buf ) );
     1196        g_snprintf( collated_name, sizeof( collated_name ),
     1197                    "%03d.%03d.%03d.%03d", quads[0], quads[1], quads[2], quads[3] );
    1441198
    1451199    gtk_list_store_set( store, iter,
    1461200                        PEER_COL_ADDRESS, peer->addr,
    147                         PEER_COL_ADDRESS_COLLATED, buf,
     1201                        PEER_COL_ADDRESS_COLLATED, collated_name,
    1481202                        PEER_COL_CLIENT, client,
    1491203                        PEER_COL_IS_ENCRYPTED, peer->isEncrypted,
    150                         PEER_COL_PROGRESS, (int)( 100.0 * peer->progress ),
    151                         PEER_COL_DOWNLOAD_RATE, peer->rateToClient,
    152                         PEER_COL_UPLOAD_RATE, peer->rateToPeer,
    153                         PEER_COL_STATUS, peer->flagStr,
     1204                        PEER_COL_KEY, key,
    1541205                        -1 );
    1551206}
    1561207
    1571208static void
    158 append_peers_to_model( GtkListStore *       store,
    159                        const tr_peer_stat * peers,
    160                        int                  n_peers )
    161 {
    162     int i;
    163 
    164     for( i = 0; i < n_peers; ++i )
    165     {
    166         GtkTreeIter iter;
    167         gtk_list_store_append( store, &iter );
    168         peer_row_set( store, &iter, &peers[i] );
    169     }
    170 }
    171 
    172 static GtkTreeModel*
    173 peer_model_new( tr_torrent * tor )
    174 {
    175     GtkListStore * m = gtk_list_store_new( N_PEER_COLS,
    176                                            G_TYPE_STRING,   /* address */
    177                                            G_TYPE_STRING,   /* collated address */
    178                                            G_TYPE_FLOAT,    /* downloadFromRate */
    179                                            G_TYPE_FLOAT,    /* uploadToRate */
    180                                            G_TYPE_STRING,   /* client */
    181                                            G_TYPE_INT,      /* progress [0..100] */
    182                                            G_TYPE_BOOLEAN,  /* isEncrypted */
    183                                            G_TYPE_STRING ); /* flagString */
    184 
    185     int            n_peers = 0;
    186     tr_peer_stat * peers = tr_torrentPeers( tor, &n_peers );
    187 
    188     qsort( peers, n_peers, sizeof( tr_peer_stat ), compare_peers );
    189     append_peers_to_model( m, peers, n_peers );
    190     tr_torrentPeersFree( peers, 0 );
    191     return GTK_TREE_MODEL( m );
    192 }
    193 
    194 static void
    1951209render_encrypted( GtkTreeViewColumn  * column UNUSED,
    196                   GtkCellRenderer *          renderer,
    197                   GtkTreeModel *              tree_model,
    198                   GtkTreeIter *              iter,
     1210                  GtkCellRenderer    * renderer,
     1211                  GtkTreeModel       * tree_model,
     1212                  GtkTreeIter        * iter,
    1991213                  gpointer             data   UNUSED )
    2001214{
     
    2081222}
    2091223
    210 static void
    211 render_speed( GtkCellRenderer  * renderer,
    212               GtkTreeModel     * tree_model,
    213               GtkTreeIter      * iter,
    214               int                col )
    215 {
    216     float rate = 0.0;
    217     char str[64];
    218     gtk_tree_model_get( tree_model, iter, col, &rate, -1 );
    219     if( rate < 0.01 )
    220         *str = '\0';
    221     else
    222         tr_strlspeed( str, rate, sizeof( str ) );
    223     g_object_set( renderer, "text", str, NULL );
    224 }
    225 
    226 static void
    227 render_ul_rate( GtkTreeViewColumn  * column UNUSED,
    228                 GtkCellRenderer    * renderer,
    229                 GtkTreeModel       * tree_model,
    230                 GtkTreeIter        * iter,
    231                 gpointer             data   UNUSED )
    232 {
    233     render_speed( renderer, tree_model, iter, PEER_COL_UPLOAD_RATE );
    234 }
    235 
    236 static void
    237 render_dl_rate( GtkTreeViewColumn  * column UNUSED,
    238                 GtkCellRenderer    * renderer,
    239                 GtkTreeModel       * tree_model,
    240                 GtkTreeIter        * iter,
    241                 gpointer             data   UNUSED )
    242 {
    243     render_speed( renderer, tree_model, iter, PEER_COL_DOWNLOAD_RATE );
    244 }
    245 
    246 static void
    247 render_client( GtkTreeViewColumn   * column UNUSED,
    248                GtkCellRenderer *            renderer,
    249                GtkTreeModel *               tree_model,
    250                GtkTreeIter *                iter,
    251                gpointer              data   UNUSED )
    252 {
    253     char * client = NULL;
    254 
    255     gtk_tree_model_get( tree_model, iter, PEER_COL_CLIENT, &client, -1 );
    256     g_object_set( renderer, "text", ( client ? client : "" ), NULL );
    257     g_free( client );
    258 }
    259 
    260 typedef struct
    261 {
    262     TrTorrent *     gtor;
    263     GtkTreeModel *  model; /* same object as store, but recast */
    264     GtkListStore *  store; /* same object as model, but recast */
    265     GtkListStore *  webseeds;
    266     GtkWidget *     completeness;
    267     GtkWidget *     seeders_lb;
    268     GtkWidget *     leechers_lb;
    269     GtkWidget *     completed_lb;
    270     GtkWidget *     peer_tree_view;
    271 }
    272 PeerData;
    273 
    274 static void
    275 fmtpeercount( GtkWidget * l,
    276               int         count )
    277 {
    278     if( 0 > count )
    279     {
    280         gtk_label_set_text( GTK_LABEL( l ), "?" );
    281     }
    282     else
    283     {
    284         char str[16];
    285         g_snprintf( str, sizeof str, "%'d", count );
    286         gtk_label_set_text( GTK_LABEL( l ), str );
    287     }
    288 }
    289 
    290 static void
    291 refresh_peers( GtkWidget * top )
    292 {
    293     int             i;
    294     int             n_peers;
    295     GtkTreeIter     iter;
    296     PeerData *      p = (PeerData*) g_object_get_data( G_OBJECT( top ), "peer-data" );
    297     tr_torrent *    tor = tr_torrent_handle( p->gtor );
    298     GtkTreeModel *  model = p->model;
    299     GtkListStore *  store = p->store;
    300     tr_peer_stat *  peers;
    301     const tr_stat * stat = tr_torrent_stat( p->gtor );
    302     const tr_info * inf = tr_torrent_info( p->gtor );
    303 
    304     if( inf->webseedCount )
    305     {
    306         float * speeds = tr_torrentWebSpeeds( tor );
    307         for( i = 0; i < inf->webseedCount; ++i )
    308         {
    309             GtkTreeIter iter;
    310             gtk_tree_model_iter_nth_child( GTK_TREE_MODEL( p->webseeds ), &iter, NULL, i );
    311             gtk_list_store_set( p->webseeds, &iter, WEBSEED_COL_DOWNLOAD_RATE, speeds[i], -1 );
    312         }
    313         tr_free( speeds );
    314     }
    315 
    316     /**
    317     ***  merge the peer diffs into the tree model.
    318     ***
    319     ***  this is more complicated than creating a new model,
    320     ***  but is also (a) more efficient and (b) doesn't undo
    321     ***  the view's visible area and sorting on every refresh.
    322     **/
    323 
    324     n_peers = 0;
    325     peers = tr_torrentPeers( tor, &n_peers );
    326     qsort( peers, n_peers, sizeof( tr_peer_stat ), compare_peers );
    327 
    328     if( gtk_tree_model_get_iter_first( model, &iter ) ) do
    329         {
    330             char *         addr = NULL;
    331             tr_peer_stat * peer = NULL;
    332             gtk_tree_model_get( model, &iter, PEER_COL_ADDRESS, &addr, -1 );
    333             peer = bsearch( addr, peers, n_peers, sizeof( tr_peer_stat ),
    334                             compare_addr_to_peer );
    335             g_free( addr );
    336 
    337             if( peer ) /* update a pre-existing row */
    338             {
    339                 const int pos = peer - peers;
    340                 const int n_rhs = n_peers - ( pos + 1 );
    341                 g_assert( n_rhs >= 0 );
    342 
    343                 peer_row_set( store, &iter, peer );
    344 
    345                 /* remove it from the tr_peer_stat list */
    346                 g_memmove( peer, peer + 1, sizeof( tr_peer_stat ) * n_rhs );
    347                 --n_peers;
    348             }
    349             else if( !gtk_list_store_remove( store, &iter ) )
    350                 break; /* we removed the model's last item */
    351         }
    352         while( gtk_tree_model_iter_next( model, &iter ) );
    353 
    354     append_peers_to_model( store, peers, n_peers ); /* all these are new */
    355 
    356     fmtpeercount( p->seeders_lb, stat->seeders );
    357     fmtpeercount( p->leechers_lb, stat->leechers );
    358     fmtpeercount( p->completed_lb, stat->timesCompleted );
    359 
    360     free( peers );
    361 
    362     gtk_widget_queue_draw( p->peer_tree_view );
    363 }
    364 
    365 #if GTK_CHECK_VERSION( 2, 12, 0 )
     1224#if GTK_CHECK_VERSION( 2,12,0 )
    3661225static gboolean
    3671226onPeerViewQueryTooltip( GtkWidget *            widget,
     
    4131272    return show_tip;
    4141273}
    415 
    4161274#endif
    4171275
     1276static void
     1277refreshPeerRow( GtkListStore * store, GtkTreeIter * iter, const tr_peer_stat * peer )
     1278{
     1279    char upload_speed[128];
     1280    char download_speed[128];
     1281
     1282    if( peer->rateToPeer > 0.01 )
     1283        tr_strlspeed( upload_speed, peer->rateToPeer, sizeof( upload_speed ) );
     1284    else
     1285        *upload_speed = '\0';
     1286
     1287    if( peer->rateToClient > 0.01 )
     1288        tr_strlspeed( download_speed, peer->rateToClient, sizeof( download_speed ) );
     1289    else
     1290        *download_speed = '\0';
     1291
     1292    gtk_list_store_set( store, iter,
     1293                        PEER_COL_PROGRESS, (int)( 100.0 * peer->progress ),
     1294                        PEER_COL_DOWNLOAD_RATE_DOUBLE, peer->rateToClient,
     1295                        PEER_COL_DOWNLOAD_RATE_STRING, download_speed,
     1296                        PEER_COL_UPLOAD_RATE_DOUBLE, peer->rateToPeer,
     1297                        PEER_COL_UPLOAD_RATE_STRING, upload_speed,
     1298                        PEER_COL_STATUS, peer->flagStr,
     1299                        PEER_COL_WAS_UPDATED, TRUE,
     1300                        -1 );
     1301}
     1302
     1303static void
     1304refreshPeerList( struct DetailsImpl * di, tr_torrent ** torrents, int n )
     1305{
     1306    int i;
     1307    int * peerCount;
     1308    GtkTreeIter iter;
     1309    GtkTreeModel * model;
     1310    struct tr_peer_stat ** peers;
     1311
     1312    /* step 1: get all the peers */
     1313    peers = g_new( struct tr_peer_stat*, n );
     1314    peerCount = g_new( int, n );
     1315    for( i=0; i<n; ++i )
     1316        peers[i] = tr_torrentPeers( torrents[i], &peerCount[i] );
     1317
     1318    /* step 2: mark all the peers in the list as not-updated */
     1319    model = GTK_TREE_MODEL( di->peer_store );
     1320    if( gtk_tree_model_get_iter_first( model, &iter ) ) do
     1321        gtk_list_store_set( di->peer_store, &iter, PEER_COL_WAS_UPDATED, FALSE, -1 );
     1322    while( gtk_tree_model_iter_next( model, &iter ) );
     1323
     1324    /* step 3: add any new peers */
     1325    for( i=0; i<n; ++i ) {
     1326        int j;
     1327        const tr_torrent * tor = torrents[i];
     1328        for( j=0; j<peerCount[i]; ++j ) {
     1329            const tr_peer_stat * s = &peers[i][j];
     1330            char key[128];
     1331            g_snprintf( key, sizeof( key ), "%d.%s", tr_torrentId(tor), s->addr );
     1332            if( g_hash_table_lookup( di->peer_hash, key ) == NULL ) {
     1333                GtkTreePath * path;
     1334                gtk_list_store_append( di->peer_store, &iter );
     1335                init_peer( di->peer_store, &iter, key, s );
     1336                /* fprintf( stderr, "adding peer key %s\n", key ); */
     1337                path = gtk_tree_model_get_path( model, &iter );
     1338                g_hash_table_insert( di->peer_hash, g_strdup( key ), gtk_tree_row_reference_new( model, path ) );
     1339                gtk_tree_path_free( path );
     1340            }
     1341        }
     1342    }
     1343
     1344    /* step 4: update the peers */
     1345    for( i=0; i<n; ++i ) {
     1346        int j;
     1347        const tr_torrent * tor = torrents[i];
     1348        for( j=0; j<peerCount[i]; ++j ) {
     1349            const tr_peer_stat * s = &peers[i][j];
     1350            char key[128];
     1351            GtkTreeRowReference * ref;
     1352            GtkTreePath * path;
     1353            g_snprintf( key, sizeof( key ), "%d.%s", tr_torrentId(tor), s->addr );
     1354            ref = g_hash_table_lookup( di->peer_hash, key );
     1355            assert( ref != NULL );
     1356            path = gtk_tree_row_reference_get_path( ref );
     1357            assert( path );
     1358            gtk_tree_model_get_iter( model, &iter, path );
     1359            refreshPeerRow( di->peer_store, &iter, s );
     1360            gtk_tree_path_free( path );
     1361        }
     1362    }
     1363
     1364    /* step 5: remove peers that have disappeared */
     1365    model = GTK_TREE_MODEL( di->peer_store );
     1366    if( gtk_tree_model_get_iter_first( model, &iter ) ) {
     1367        gboolean more = TRUE;
     1368        while( more ) {
     1369            gboolean wasUpdated;
     1370            gtk_tree_model_get( model, &iter, PEER_COL_WAS_UPDATED, &wasUpdated, -1 );
     1371            if( wasUpdated )
     1372                more = gtk_tree_model_iter_next( model, &iter );
     1373            else {
     1374                char * key;
     1375                gtk_tree_model_get( model, &iter, PEER_COL_KEY, &key, -1 );
     1376                /* fprintf( stderr, "removing key %s\n", key ); */
     1377                g_hash_table_remove( di->peer_hash, key );
     1378                more = gtk_list_store_remove( di->peer_store, &iter );
     1379                g_free( key );
     1380            }
     1381        }
     1382    }
     1383
     1384    /* step 6: cleanup */
     1385    for( i=0; i<n; ++i )
     1386        tr_torrentPeersFree( peers[i], peerCount[i] );
     1387    tr_free( peers );
     1388    tr_free( peerCount );
     1389}
     1390
     1391static void
     1392refreshWebseedList( struct DetailsImpl * di, tr_torrent ** torrents, int n )
     1393{
     1394    int i;
     1395    int total = 0;
     1396    GtkTreeIter iter;
     1397    GHashTable * hash = di->webseed_hash;
     1398    GtkListStore * store = di->webseed_store;
     1399    GtkTreeModel * model = GTK_TREE_MODEL( store );
     1400
     1401    /* step 1: mark all webseeds as not-updated */
     1402    if( gtk_tree_model_get_iter_first( model, &iter ) ) do
     1403        gtk_list_store_set( store, &iter, WEBSEED_COL_WAS_UPDATED, FALSE, -1 );
     1404    while( gtk_tree_model_iter_next( model, &iter ) );
     1405
     1406    /* step 2: add any new webseeds */
     1407    for( i=0; i<n; ++i ) {
     1408        int j;
     1409        const tr_torrent * tor = torrents[i];
     1410        const tr_info * inf = tr_torrentInfo( tor );
     1411        total += inf->webseedCount;
     1412        for( j=0; j<inf->webseedCount; ++j ) {
     1413            char key[256];
     1414            const char * url = inf->webseeds[j];
     1415            g_snprintf( key, sizeof( key ), "%d.%s", tr_torrentId( tor ), url );
     1416            if( g_hash_table_lookup( hash, key ) == NULL ) {
     1417                GtkTreePath * path;
     1418                gtk_list_store_append( store, &iter );
     1419                gtk_list_store_set( store, &iter, WEBSEED_COL_URL, url, -1 );
     1420                path = gtk_tree_model_get_path( model, &iter );
     1421                g_hash_table_insert( hash, g_strdup( key ), gtk_tree_row_reference_new( model, path ) );
     1422                gtk_tree_path_free( path );
     1423            }
     1424        }
     1425    }
     1426
     1427    /* step 3: update the webseeds */
     1428    for( i=0; i<n; ++i ) {
     1429        int j;
     1430        const tr_torrent * tor = torrents[i];
     1431        const tr_info * inf = tr_torrentInfo( tor );
     1432        float * speeds = tr_torrentWebSpeeds( tor );
     1433        for( j=0; j<inf->webseedCount; ++j ) {
     1434            char key[256];
     1435            const char * url = inf->webseeds[j];
     1436            GtkTreePath * path;
     1437            GtkTreeRowReference * ref;
     1438            g_snprintf( key, sizeof( key ), "%d.%s", tr_torrentId( tor ), url );
     1439            ref = g_hash_table_lookup( hash, key );
     1440            path = gtk_tree_row_reference_get_path( ref );
     1441            gtk_tree_model_get_iter( model, &iter, path );
     1442            gtk_list_store_set( store, &iter, WEBSEED_COL_DOWNLOAD_RATE, (int)(100.0*speeds[j]), -1 );
     1443            gtk_tree_path_free( path );
     1444        }
     1445        tr_free( speeds );
     1446    }
     1447
     1448    /* step 4: remove webseeds that have disappeared */
     1449    if( gtk_tree_model_get_iter_first( model, &iter ) ) {
     1450        gboolean more = TRUE;
     1451        while( more ) {
     1452            gboolean wasUpdated;
     1453            gtk_tree_model_get( model, &iter, WEBSEED_COL_WAS_UPDATED, &wasUpdated, -1 );
     1454            if( wasUpdated )
     1455                more = gtk_tree_model_iter_next( model, &iter );
     1456            else {
     1457                char * key;
     1458                gtk_tree_model_get( model, &iter, PEER_COL_KEY, &key, -1 );
     1459                g_hash_table_remove( hash, key );
     1460                more = gtk_list_store_remove( store, &iter );
     1461                g_free( key );
     1462            }
     1463        }
     1464    }
     1465
     1466    if( total > 0 )
     1467        gtk_widget_show( di->webseed_view );
     1468    else
     1469        gtk_widget_hide( di->webseed_view );
     1470}
     1471
     1472static void
     1473refreshPeers( struct DetailsImpl * di, tr_torrent ** torrents, int n )
     1474{
     1475    int i;
     1476    char buf[512];
     1477    const char * none = _( "None" );
     1478
     1479    /* seeders_lb */
     1480    /* leechers_lb */
     1481    /* completed_lb */
     1482    if( n<=0 ) {
     1483        gtk_label_set_text( GTK_LABEL( di->seeders_lb ), none );
     1484        gtk_label_set_text( GTK_LABEL( di->leechers_lb ), none );
     1485        gtk_label_set_text( GTK_LABEL( di->completed_lb ), none );
     1486    } else {
     1487        int seeders = 0;
     1488        int leechers = 0;
     1489        int completed = 0;
     1490        for( i=0; i<n; ++i ) {
     1491            const tr_stat * s = tr_torrentStat( torrents[i] );
     1492            seeders = s->seeders;
     1493            leechers = s->leechers;
     1494            completed += s->timesCompleted;
     1495        }
     1496        g_snprintf( buf, sizeof( buf ), "%'d", seeders );
     1497        gtk_label_set_text( GTK_LABEL( di->seeders_lb ), buf );
     1498        g_snprintf( buf, sizeof( buf ), "%'d", leechers );
     1499        gtk_label_set_text( GTK_LABEL( di->leechers_lb ), buf );
     1500        g_snprintf( buf, sizeof( buf ), "%'d", completed );
     1501        gtk_label_set_text( GTK_LABEL( di->completed_lb ), buf );
     1502    }
     1503
     1504    refreshPeerList( di, torrents, n );
     1505    refreshWebseedList( di, torrents, n );
     1506}
     1507
    4181508static GtkWidget*
    419 peer_page_new( TrTorrent * gtor )
    420 {
    421     guint           i;
    422     GtkTreeModel *  m;
    423     GtkWidget *     v, *w, *ret, *sw, *l, *vbox, *hbox;
    424     GtkWidget *     webtree = NULL;
    425     tr_torrent *    tor = tr_torrent_handle( gtor );
    426     PeerData *      p = g_new( PeerData, 1 );
    427     const tr_info * inf = tr_torrent_info( gtor );
     1509peer_page_new( struct DetailsImpl * di )
     1510{
     1511    guint i;
     1512    GtkListStore *store;
     1513    GtkWidget *v, *w, *ret, *sw, *l, *vbox, *hbox;
     1514    GtkWidget *webtree = NULL;
    4281515
    4291516    /* TODO: make this configurable? */
    4301517    int view_columns[] = { PEER_COL_IS_ENCRYPTED,
    431                            PEER_COL_UPLOAD_RATE,
    432                            PEER_COL_DOWNLOAD_RATE,
     1518                           PEER_COL_UPLOAD_RATE_STRING,
     1519                           PEER_COL_DOWNLOAD_RATE_STRING,
    4331520                           PEER_COL_PROGRESS,
    4341521                           PEER_COL_STATUS,
     
    4371524
    4381525
    439     if( inf->webseedCount )
    4401526    {
     1527        GtkListStore * store;
    4411528        GtkTreeViewColumn * c;
    4421529        GtkCellRenderer *   r;
     
    4451532        GtkWidget *         v;
    4461533
    447         m = webseed_model_new( tr_torrent_handle( gtor ) );
    448         v = gtk_tree_view_new_with_model( m );
     1534        store = di->webseed_store = webseed_model_new( );
     1535        v = gtk_tree_view_new_with_model( GTK_TREE_MODEL( store ) );
    4491536        g_signal_connect( v, "button-release-event", G_CALLBACK( on_tree_view_button_released ), NULL );
    4501537        gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( v ), TRUE );
    451         p->webseeds = GTK_LIST_STORE( m );
    452         g_object_unref( G_OBJECT( m ) );
    453 
    454         t = _( webseed_column_names[WEBSEED_COL_URL] );
     1538        //p->webseeds = GTK_LIST_STORE( GTK_TREE_MODEL( store ) );
     1539        g_object_unref( G_OBJECT( store ) );
     1540
     1541        t = getWebseedColumnNames( WEBSEED_COL_URL );
    4551542        r = gtk_cell_renderer_text_new( );
    4561543        g_object_set( G_OBJECT( r ), "ellipsize", PANGO_ELLIPSIZE_END, NULL );
     
    4601547        gtk_tree_view_append_column( GTK_TREE_VIEW( v ), c );
    4611548
    462         t = _( webseed_column_names[WEBSEED_COL_DOWNLOAD_RATE] );
     1549        t = getWebseedColumnNames( WEBSEED_COL_DOWNLOAD_RATE );
    4631550        r = gtk_cell_renderer_text_new( );
    4641551        c = gtk_tree_view_column_new_with_attributes( t, r, "text", WEBSEED_COL_DOWNLOAD_RATE, NULL );
    465         gtk_tree_view_column_set_cell_data_func( c, r, render_dl_rate, NULL, NULL );
    4661552        gtk_tree_view_column_set_sort_column_id( c, WEBSEED_COL_DOWNLOAD_RATE );
    4671553        gtk_tree_view_append_column( GTK_TREE_VIEW( v ), c );
     
    4731559
    4741560        webtree = w;
    475     }
    476 
    477     m  = peer_model_new( tor );
     1561        di->webseed_view = w;
     1562    }
     1563
     1564    store  = di->peer_store = peer_store_new( );
    4781565    v = GTK_WIDGET( g_object_new( GTK_TYPE_TREE_VIEW,
    479                                   "model",  gtk_tree_model_sort_new_with_model( m ),
     1566                                  "model",  gtk_tree_model_sort_new_with_model( GTK_TREE_MODEL( store ) ),
    4801567                                  "rules-hint", TRUE,
    481 #if GTK_CHECK_VERSION( 2, 12, 0 )
     1568#if GTK_CHECK_VERSION( 2,12,0 )
    4821569                                  "has-tooltip", TRUE,
    4831570#endif
    4841571                                  NULL ) );
    485     p->peer_tree_view = v;
    486 
    487 #if GTK_CHECK_VERSION( 2, 12, 0 )
     1572
     1573#if GTK_CHECK_VERSION( 2,12,0 )
    4881574    g_signal_connect( v, "query-tooltip",
    4891575                      G_CALLBACK( onPeerViewQueryTooltip ), NULL );
    4901576#endif
    491     g_object_unref( G_OBJECT( m ) );
     1577    g_object_unref( G_OBJECT( store ) );
    4921578    g_signal_connect( v, "button-release-event",
    4931579                      G_CALLBACK( on_tree_view_button_released ), NULL );
     
    4961582    {
    4971583        const int           col = view_columns[i];
     1584        const char *        t = getPeerColumnName( col );
    4981585        int                 sort_col = col;
    499         const char *        t = _( peer_column_names[col] );
    5001586        GtkTreeViewColumn * c;
    5011587        GtkCellRenderer *   r;
     
    5121598                r = gtk_cell_renderer_text_new( );
    5131599                c = gtk_tree_view_column_new_with_attributes( t, r, "text", col, NULL );
    514                 gtk_tree_view_column_set_cell_data_func( c, r, render_client, NULL, NULL );
    5151600                break;
    5161601
     
    5281613                break;
    5291614
    530             case PEER_COL_DOWNLOAD_RATE:
     1615            case PEER_COL_DOWNLOAD_RATE_STRING:
    5311616                r = gtk_cell_renderer_text_new( );
    5321617                c = gtk_tree_view_column_new_with_attributes( t, r, "text", col, NULL );
    533                 gtk_tree_view_column_set_cell_data_func( c, r, render_dl_rate, NULL, NULL );
    534                 break;
    535 
    536             case PEER_COL_UPLOAD_RATE:
     1618                sort_col = PEER_COL_DOWNLOAD_RATE_DOUBLE;
     1619                break;
     1620
     1621            case PEER_COL_UPLOAD_RATE_STRING:
    5371622                r = gtk_cell_renderer_text_new( );
    5381623                c = gtk_tree_view_column_new_with_attributes( t, r, "text", col, NULL );
    539                 gtk_tree_view_column_set_cell_data_func( c, r, render_ul_rate, NULL, NULL );
     1624                sort_col = PEER_COL_UPLOAD_RATE_DOUBLE;
    5401625                break;
    5411626
     
    5901675    gtk_label_set_markup( GTK_LABEL( l ), _( "<b>Seeders:</b>" ) );
    5911676    gtk_box_pack_start( GTK_BOX( hbox ), l, FALSE, FALSE, 0 );
    592     l = p->seeders_lb = gtk_label_new( NULL );
     1677    l = di->seeders_lb = gtk_label_new( NULL );
    5931678    gtk_box_pack_start( GTK_BOX( hbox ), l, FALSE, FALSE, 0 );
    5941679    gtk_box_pack_start( GTK_BOX( hbox ), gtk_alignment_new( 0.0f, 0.0f, 0.0f, 0.0f ), TRUE, TRUE, 0 );
     
    5961681    gtk_label_set_markup( GTK_LABEL( l ), _( "<b>Leechers:</b>" ) );
    5971682    gtk_box_pack_start( GTK_BOX( hbox ), l, FALSE, FALSE, 0 );
    598     l = p->leechers_lb = gtk_label_new( NULL );
     1683    l = di->leechers_lb = gtk_label_new( NULL );
    5991684    gtk_box_pack_start( GTK_BOX( hbox ), l, FALSE, FALSE, 0 );
    6001685    gtk_box_pack_start( GTK_BOX( hbox ), gtk_alignment_new( 0.0f, 0.0f, 0.0f, 0.0f ), TRUE, TRUE, 0 );
     
    6021687    gtk_label_set_markup( GTK_LABEL( l ), _( "<b>Times Completed:</b>" ) );
    6031688    gtk_box_pack_start( GTK_BOX( hbox ), l, FALSE, FALSE, 0 );
    604     l = p->completed_lb = gtk_label_new( NULL );
     1689    l = di->completed_lb = gtk_label_new( NULL );
    6051690    gtk_box_pack_start( GTK_BOX( hbox ), l, FALSE, FALSE, 0 );
    6061691    gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 );
    6071692
     1693    /* ip-to-GtkTreeRowReference */
     1694    di->peer_hash = g_hash_table_new_full( g_str_hash,
     1695                                           g_str_equal,
     1696                                           (GDestroyNotify)g_free,
     1697                                           (GDestroyNotify)gtk_tree_row_reference_free );
     1698
     1699    /* url-to-GtkTreeRowReference */
     1700    di->webseed_hash = g_hash_table_new_full( g_str_hash,
     1701                                              g_str_equal,
     1702                                              (GDestroyNotify)g_free,
     1703                                              (GDestroyNotify)gtk_tree_row_reference_free );
     1704                           
    6081705    ret = vbox;
    609     p->gtor = gtor;
    610     p->model = m;
    611     p->store = GTK_LIST_STORE( m );
    612     g_object_set_data_full( G_OBJECT( ret ), "peer-data", p, g_free );
    6131706    return ret;
    6141707}
    6151708
    616 /****
    617 *****  INFO TAB
    618 ****/
    619 
    620 static void
    621 refresh_time_lb( GtkWidget * l,
    622                  time_t      t )
    623 {
    624     const char * never = _( "Never" );
    625 
    626     if( !t )
    627         gtk_label_set_text( GTK_LABEL( l ), never );
    628     else
    629     {
    630         char * str = gtr_localtime( t );
    631         gtk_label_set_text( GTK_LABEL( l ), str );
    632         g_free( str );
    633     }
    634 }
    635 
    636 static GtkWidget*
    637 info_page_new( tr_torrent * tor )
    638 {
    639     int             row = 0;
    640     GtkWidget *     t = hig_workarea_create( );
    641     GtkWidget *     l, *w, *fr;
    642     char *          pch;
    643     char            sizeStr[128];
    644     char            countStr[128];
    645     char            buf[256];
    646     GtkTextBuffer * b;
    647     const tr_info * info = tr_torrentInfo( tor );
    648 
    649     hig_workarea_add_section_title( t, &row, _( "Details" ) );
    650 
    651     g_snprintf( countStr, sizeof( countStr ),
    652                 ngettext( "%'d Piece", "%'d Pieces", info->pieceCount ),
    653                 info->pieceCount );
    654     tr_strlsize( sizeStr, info->pieceSize, sizeof( sizeStr ) );
    655     g_snprintf( buf, sizeof( buf ),
    656                 /* %1$s is number of pieces;
    657                    %2$s is how big each piece is */
    658                 _( "%1$s @ %2$s" ),
    659                 countStr, sizeStr );
    660 
    661     l = gtk_label_new( buf );
    662     hig_workarea_add_row( t, &row, _( "Pieces:" ), l, NULL );
    663 
    664     l = g_object_new( GTK_TYPE_LABEL, "label", info->hashString,
    665                       "selectable", TRUE,
    666                       "ellipsize", PANGO_ELLIPSIZE_END,
    667                       NULL );
    668     hig_workarea_add_row( t, &row, _( "Hash:" ), l, NULL );
    669 
    670     pch = ( info->isPrivate )
    671           ? _( "Private to this tracker -- PEX disabled" )
    672           : _( "Public torrent" );
    673     l = gtk_label_new( pch );
    674     hig_workarea_add_row( t, &row, _( "Privacy:" ), l, NULL );
    675 
    676     b = gtk_text_buffer_new( NULL );
    677     if( info->comment )
    678         gtk_text_buffer_set_text( b, info->comment, -1 );
    679     w = gtk_text_view_new_with_buffer( b );
    680     gtk_widget_set_size_request( w, 0u, 100u );
    681     gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW( w ), GTK_WRAP_WORD );
    682     gtk_text_view_set_editable( GTK_TEXT_VIEW( w ), FALSE );
    683     fr = gtk_frame_new( NULL );
    684     gtk_frame_set_shadow_type( GTK_FRAME( fr ), GTK_SHADOW_IN );
    685     gtk_container_add( GTK_CONTAINER( fr ), w );
    686     w = hig_workarea_add_row( t, &row, _( "Comment:" ), fr, NULL );
    687     gtk_misc_set_alignment( GTK_MISC( w ), 0.0f, 0.0f );
    688 
    689     hig_workarea_add_section_divider( t, &row );
    690     hig_workarea_add_section_title( t, &row, _( "Origins" ) );
    691 
    692     l = gtk_label_new( *info->creator ? info->creator : _( "Unknown" ) );
    693     gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
    694     hig_workarea_add_row( t, &row, _( "Creator:" ), l, NULL );
    695 
    696     l = gtk_label_new( NULL );
    697     refresh_time_lb( l, info->dateCreated );
    698     hig_workarea_add_row( t, &row, _( "Date:" ), l, NULL );
    699 
    700     hig_workarea_add_section_divider( t, &row );
    701     hig_workarea_add_section_title( t, &row, _( "Location" ) );
    702 
    703     l = g_object_new( GTK_TYPE_LABEL, "label", tr_torrentGetDownloadDir( tor ),
    704                                       "selectable", TRUE,
    705                                       "ellipsize", PANGO_ELLIPSIZE_END,
    706                                       NULL );
    707     hig_workarea_add_row( t, &row, _( "Destination folder:" ), l, NULL );
    708 
    709     l = g_object_new( GTK_TYPE_LABEL, "label", info->torrent,
    710                                       "selectable", TRUE,
    711                                       "ellipsize", PANGO_ELLIPSIZE_END,
    712                                       NULL );
    713     hig_workarea_add_row( t, &row, _( "Torrent file:" ), l, NULL );
    714 
    715     hig_workarea_finish( t, &row );
    716     return t;
    717 }
    718 
    719 /****
    720 *****  ACTIVITY TAB
    721 ****/
    722 
    723 typedef struct
    724 {
    725     GtkWidget * state_lb;
    726     GtkWidget * progress_lb;
    727     GtkWidget * have_lb;
    728     GtkWidget * dl_lb;
    729     GtkWidget * ul_lb;
    730     GtkWidget * failed_lb;
    731     GtkWidget * ratio_lb;
    732     GtkWidget * err_lb;
    733     GtkWidget * swarm_lb;
    734     GtkWidget * date_added_lb;
    735     GtkWidget * last_activity_lb;
    736     GtkWidget * availability_da;
    737     TrTorrent * gtor;
    738 }
    739 Activity;
    740 
    741 static void
    742 refresh_activity( GtkWidget * top )
    743 {
    744     int i;
    745     char * pch;
    746     char buf1[128];
    747     char buf2[128];
    748     Activity * a = g_object_get_data( G_OBJECT( top ), "activity-data" );
    749     const tr_stat * stat = tr_torrent_stat( a->gtor );
    750     const tr_info * info = tr_torrent_info( a->gtor );
    751     const double complete = stat->percentComplete * 100.0;
    752     const double done = stat->percentDone * 100.0;
    753     const double verifiedPieceCount = (double)stat->haveValid / info->pieceSize;
    754 
    755     pch = tr_torrent_status_str( a->gtor );
    756     gtk_label_set_text( GTK_LABEL( a->state_lb ), pch );
    757     g_free( pch );
    758 
    759     if( (int)complete == (int)done )
    760         pch = g_strdup_printf( _( "%.1f%%" ), complete );
    761     else
    762         /* %1$.1f is percent of how much of what we want's been downloaded,
    763          * %2$.1f is percent of how much of the whole torrent we've downloaded */
    764         pch = g_strdup_printf( _( "%1$.1f%% (%2$.1f%% selected)" ),
    765                                complete, done );
    766     gtk_label_set_text( GTK_LABEL( a->progress_lb ), pch );
    767     g_free( pch );
    768 
    769     i = (int) ceil( verifiedPieceCount );
    770     tr_strlsize( buf1, stat->haveValid + stat->haveUnchecked, sizeof( buf1 ) );
    771     tr_strlsize( buf2, stat->haveValid, sizeof( buf2 ) );
    772     /* %1$s is total size of what we've saved to disk
    773      * %2$s is how much of it's passed the checksum test
    774      * %3$s is how many pieces are verified */
    775     if( !i )
    776         pch = tr_strdup( buf1 );
    777     else
    778         pch = g_strdup_printf( ngettext( "%1$s (%2$s verified in %3$d piece)",
    779                                          "%1$s (%2$s verified in %3$d pieces)", i ),
    780                                buf1, buf2, i );
    781     gtk_label_set_text( GTK_LABEL( a->have_lb ), pch );
    782     g_free( pch );
    783 
    784     tr_strlsize( buf1, stat->downloadedEver, sizeof( buf1 ) );
    785     gtk_label_set_text( GTK_LABEL( a->dl_lb ), buf1 );
    786 
    787     tr_strlsize( buf1, stat->uploadedEver, sizeof( buf1 ) );
    788     gtk_label_set_text( GTK_LABEL( a->ul_lb ), buf1 );
    789 
    790     tr_strlsize( buf1, stat->corruptEver, sizeof( buf1 ) );
    791     gtk_label_set_text( GTK_LABEL( a->failed_lb ), buf1 );
    792 
    793     tr_strlratio( buf1, stat->ratio, sizeof( buf1 ) );
    794     gtk_label_set_text( GTK_LABEL( a->ratio_lb ), buf1 );
    795 
    796     tr_strlspeed( buf1, stat->swarmSpeed, sizeof( buf1 ) );
    797     gtk_label_set_text( GTK_LABEL( a->swarm_lb ), buf1 );
    798 
    799     gtk_label_set_text( GTK_LABEL( a->err_lb ),
    800                         *stat->errorString ? stat->errorString : _( "None" ) );
    801 
    802     refresh_time_lb( a->date_added_lb, stat->addedDate );
    803 
    804     refresh_time_lb( a->last_activity_lb, stat->activityDate );
    805 }
    806 
    807 static GtkWidget*
    808 activity_page_new( TrTorrent * gtor )
    809 {
    810     Activity * a = g_new( Activity, 1 );
    811     int        row = 0;
    812     GtkWidget *t = hig_workarea_create( );
    813     GtkWidget *l;
    814 
    815     a->gtor = gtor;
    816 
    817     hig_workarea_add_section_title( t, &row, _( "Transfer" ) );
    818 
    819     l = a->state_lb = gtk_label_new( NULL );
    820     hig_workarea_add_row( t, &row, _( "State:" ), l, NULL );
    821 
    822     l = a->progress_lb = gtk_label_new( NULL );
    823     hig_workarea_add_row( t, &row, _( "Progress:" ), l, NULL );
    824 
    825     l = a->have_lb = gtk_label_new( NULL );
    826     /* "Have" refers to how much of the torrent we have */
    827     hig_workarea_add_row( t, &row, _( "Have:" ), l, NULL );
    828 
    829     l = a->dl_lb = gtk_label_new( NULL );
    830     hig_workarea_add_row( t, &row, _( "Downloaded:" ), l, NULL );
    831 
    832     l = a->ul_lb = gtk_label_new( NULL );
    833     hig_workarea_add_row( t, &row, _( "Uploaded:" ), l, NULL );
    834 
    835     /* how much downloaded data was corrupt */
    836     l = a->failed_lb = gtk_label_new( NULL );
    837     hig_workarea_add_row( t, &row, _( "Failed DL:" ), l, NULL );
    838 
    839     l = a->ratio_lb = gtk_label_new( NULL );
    840     hig_workarea_add_row( t, &row, _( "Ratio:" ), l, NULL );
    841 
    842     l = a->swarm_lb = gtk_label_new( NULL );
    843     hig_workarea_add_row( t, &row, _( "Swarm rate:" ), l, NULL );
    844 
    845     l = a->err_lb = gtk_label_new( NULL );
    846     hig_workarea_add_row( t, &row, _( "Error:" ), l, NULL );
    847 
    848     hig_workarea_add_section_divider( t, &row );
    849     hig_workarea_add_section_title( t, &row, _( "Dates" ) );
    850 
    851     l = a->date_added_lb = gtk_label_new( NULL );
    852     hig_workarea_add_row( t, &row, _( "Started at:" ), l, NULL );
    853 
    854     l = a->last_activity_lb = gtk_label_new( NULL );
    855     hig_workarea_add_row( t, &row, _( "Last activity at:" ), l, NULL );
    856 
    857     hig_workarea_add_section_divider( t, &row );
    858     hig_workarea_finish( t, &row );
    859     g_object_set_data_full( G_OBJECT( t ), "activity-data", a, g_free );
    860     return t;
    861 }
    862 
    863 /****
    864 *****  OPTIONS
    865 ****/
    866 
    867 static void
    868 up_speed_toggled_cb( GtkToggleButton *tb,
    869                      gpointer         gtor )
    870 {
    871     tr_torrent * tor = tr_torrent_handle( gtor );
    872     const gboolean b = gtk_toggle_button_get_active( tb );
    873 
    874     tr_torrentUseSpeedLimit( tor, TR_UP, b );
    875 }
    876 
    877 static void
    878 down_speed_toggled_cb( GtkToggleButton *tb,
    879                        gpointer         gtor )
    880 {
    881     tr_torrent * tor = tr_torrent_handle( gtor );
    882     const gboolean b = gtk_toggle_button_get_active( tb );
    883 
    884     tr_torrentUseSpeedLimit( tor, TR_DOWN, b );
    885 }
    886 
    887 static void
    888 global_speed_toggled_cb( GtkToggleButton * tb, gpointer gtor )
    889 {
    890     tr_torrent * tor = tr_torrent_handle( gtor );
    891     const gboolean b = gtk_toggle_button_get_active( tb );
    892 
    893     tr_torrentUseSessionLimits( tor, b );
    894 }
    895 
    896 #define RATIO_MODE_KEY "ratio-mode"
    897 
    898 static void
    899 ratio_mode_changed_cb( GtkToggleButton * tb, gpointer gtor )
    900 {
    901     if( gtk_toggle_button_get_active( tb ) )
    902     {
    903         tr_torrent * tor = tr_torrent_handle( gtor );
    904         const int mode = GPOINTER_TO_INT( g_object_get_data( G_OBJECT( tb ), RATIO_MODE_KEY ) );
    905         tr_torrentSetRatioMode( tor, mode );
    906     }
    907 }
    908 
    909 static void
    910 sensitize_from_check_cb( GtkToggleButton * toggle, gpointer w )
    911 {
    912     gtk_widget_set_sensitive( GTK_WIDGET( w ),
    913                               gtk_toggle_button_get_active( toggle ) );
    914 }
    915 
    916 static void
    917 setSpeedLimit( GtkSpinButton* spin, gpointer gtor, int up_or_down )
    918 {
    919     tr_torrent * tor = tr_torrent_handle( gtor );
    920     const int kb_sec = gtk_spin_button_get_value_as_int( spin );
    921 
    922     tr_torrentSetSpeedLimit( tor, up_or_down, kb_sec );
    923 }
    924 
    925 static void
    926 up_speed_spun_cb( GtkSpinButton * spin, gpointer gtor )
    927 {
    928     setSpeedLimit( spin, gtor, TR_UP );
    929 }
    930 
    931 static void
    932 down_speed_spun_cb( GtkSpinButton * spin, gpointer gtor )
    933 {
    934     setSpeedLimit( spin, gtor, TR_DOWN );
    935 }
    936 
    937 static void
    938 ratio_spun_cb( GtkSpinButton * spin, gpointer gtor )
    939 {
    940     tr_torrent * tor = tr_torrent_handle( gtor );
    941     float        ratio = gtk_spin_button_get_value( spin );
    942 
    943     tr_torrentSetRatioLimit( tor, ratio );
    944 }
    945 
    946 static void
    947 max_peers_spun_cb( GtkSpinButton * spin, gpointer gtor )
    948 {
    949     const uint16_t n = gtk_spin_button_get_value( spin );
    950 
    951     tr_torrentSetPeerLimit( tr_torrent_handle( gtor ), n );
    952 }
    953 
    954 static char*
    955 get_global_ratio_radiobutton_string( void )
    956 {
    957     char * s;
    958     const gboolean b = pref_flag_get( TR_PREFS_KEY_RATIO_ENABLED );
    959     const double d = pref_double_get( TR_PREFS_KEY_RATIO );
    960 
    961     if( b )
    962         s = g_strdup_printf( _( "Use _Global setting  (currently: stop seeding when a torrent's ratio reaches %.2f)" ), d );
    963     else
    964         s = g_strdup( _( "Use _Global setting  (currently: seed regardless of ratio)" ) );
    965 
    966     return s;
    967 }
    968 
    969 static void
    970 prefsChanged( TrCore * core UNUSED, const char *  key, gpointer rb )
    971 {
    972     if( !strcmp( key, TR_PREFS_KEY_RATIO_ENABLED ) || !strcmp( key, TR_PREFS_KEY_RATIO ) )
    973     {
    974         char * s = get_global_ratio_radiobutton_string( );
    975         gtk_button_set_label( GTK_BUTTON( rb ), s );
    976         g_free( s );
    977     }
    978 }
    979 
    980 static void
    981 onIntComboChanged( GtkComboBox * w, tr_torrent * tor )
    982 {
    983     GtkTreeIter iter;
    984 
    985     if( gtk_combo_box_get_active_iter( w, &iter ) )
    986     {
    987         int val = 0;
    988         gtk_tree_model_get( gtk_combo_box_get_model( w ), &iter, 0, &val, -1 );
    989         tr_torrentSetPriority( tor, val );
    990     }
    991 }
    992 
    993 static GtkWidget*
    994 new_bandwidth_combo( tr_torrent * tor )
    995 {
    996     int i;
    997     int selIndex;
    998     GtkWidget * w;
    999     GtkCellRenderer * r;
    1000     GtkListStore * store;
    1001     const tr_priority_t currentValue = tr_torrentGetPriority( tor );
    1002     const struct {
    1003         int value;
    1004         const char * text;
    1005     } items[] = {
    1006         { TR_PRI_LOW,    N_( "Low" )  },
    1007         { TR_PRI_NORMAL, N_( "Normal" ) },
    1008         { TR_PRI_HIGH,   N_( "High" )  }
    1009     };
    1010 
    1011     /* build a store for encryption */
    1012     selIndex = -1;
    1013     store = gtk_list_store_new( 2, G_TYPE_INT, G_TYPE_STRING );
    1014     for( i=0; i<(int)G_N_ELEMENTS(items); ++i ) {
    1015         GtkTreeIter iter;
    1016         gtk_list_store_append( store, &iter );
    1017         gtk_list_store_set( store, &iter, 0, items[i].value, 1, _( items[i].text ), -1 );
    1018         if( items[i].value == currentValue )
    1019             selIndex = i;
    1020     }
    1021 
    1022     /* build the widget */
    1023     w = gtk_combo_box_new_with_model( GTK_TREE_MODEL( store ) );
    1024     r = gtk_cell_renderer_text_new( );
    1025     gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
    1026     gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( w ), r, "text", 1, NULL );
    1027     if( selIndex >= 0 )
    1028         gtk_combo_box_set_active( GTK_COMBO_BOX( w ), selIndex );
    1029     g_signal_connect( w, "changed", G_CALLBACK( onIntComboChanged ), tor );
    1030 
    1031     /* cleanup */
    1032     g_object_unref( G_OBJECT( store ) );
    1033     return w;
    1034 }
    1035 
    1036 
    1037 static GtkWidget*
    1038 options_page_new( struct ResponseData * data )
    1039 {
    1040     uint16_t     maxConnectedPeers;
    1041     int          i, row;
    1042     double       d;
    1043     gboolean     b;
    1044     char       * s;
    1045     GSList     * group;
    1046     GtkWidget  * t, *w, *tb, *h;
    1047     tr_ratiolimit mode;
    1048     TrCore     * core = data->core;
    1049     TrTorrent  * gtor = data->gtor;
    1050     tr_torrent * tor = tr_torrent_handle( gtor );
    1051 
    1052     row = 0;
    1053     t = hig_workarea_create( );
    1054     hig_workarea_add_section_title( t, &row, _( "Speed" ) );
    1055 
    1056         b = tr_torrentUsesSessionLimits( tor );
    1057         tb = hig_workarea_add_wide_checkbutton( t, &row, _( "Honor global _limits" ), b );
    1058         g_signal_connect( tb, "toggled", G_CALLBACK( global_speed_toggled_cb ), gtor );
    1059 
    1060         tb = gtk_check_button_new_with_mnemonic( _( "Limit _download speed (KB/s):" ) );
    1061         b = tr_torrentUsesSpeedLimit( tor, TR_DOWN );
    1062         gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( tb ), b );
    1063         g_signal_connect( tb, "toggled", G_CALLBACK( down_speed_toggled_cb ), gtor );
    1064 
    1065         i = tr_torrentGetSpeedLimit( tor, TR_DOWN );
    1066         w = gtk_spin_button_new_with_range( 1, INT_MAX, 5 );
    1067         gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), i );
    1068         g_signal_connect( w, "value-changed", G_CALLBACK( down_speed_spun_cb ), gtor );
    1069         g_signal_connect( tb, "toggled", G_CALLBACK( sensitize_from_check_cb ), w );
    1070         sensitize_from_check_cb( GTK_TOGGLE_BUTTON( tb ), w );
    1071         hig_workarea_add_row_w( t, &row, tb, w, NULL );
    1072 
    1073         tb = gtk_check_button_new_with_mnemonic( _( "Limit _upload speed (KB/s):" ) );
    1074         b = tr_torrentUsesSpeedLimit( tor, TR_UP );
    1075         gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( tb ), b );
    1076         g_signal_connect( tb, "toggled", G_CALLBACK( up_speed_toggled_cb ), gtor );
    1077 
    1078         i = tr_torrentGetSpeedLimit( tor, TR_UP );
    1079         w = gtk_spin_button_new_with_range( 1, INT_MAX, 5 );
    1080         gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), i );
    1081         g_signal_connect( w, "value-changed", G_CALLBACK( up_speed_spun_cb ), gtor );
    1082         g_signal_connect( tb, "toggled", G_CALLBACK( sensitize_from_check_cb ), w );
    1083         sensitize_from_check_cb( GTK_TOGGLE_BUTTON( tb ), w );
    1084         hig_workarea_add_row_w( t, &row, tb, w, NULL );
    1085 
    1086         w = new_bandwidth_combo( tor );
    1087         hig_workarea_add_row( t, &row, _( "_Bandwidth priority:" ), w, NULL );
    1088 
    1089     hig_workarea_add_section_divider( t, &row );
    1090     hig_workarea_add_section_title( t, &row, _( "Seed-Until Ratio" ) );
    1091 
    1092         group = NULL;
    1093         mode = tr_torrentGetRatioMode( tor );
    1094         s = get_global_ratio_radiobutton_string( );
    1095         w = gtk_radio_button_new_with_mnemonic( group, s );
    1096         data->handler = g_signal_connect( core, "prefs-changed", G_CALLBACK( prefsChanged ), w );
    1097         group = gtk_radio_button_get_group( GTK_RADIO_BUTTON( w ) );
    1098         gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w ), mode == TR_RATIOLIMIT_GLOBAL);
    1099         hig_workarea_add_wide_control( t, &row, w );
    1100         g_free( s );
    1101         g_object_set_data( G_OBJECT( w ), RATIO_MODE_KEY, GINT_TO_POINTER( TR_RATIOLIMIT_GLOBAL ) );
    1102         g_signal_connect( w, "toggled", G_CALLBACK( ratio_mode_changed_cb ), gtor );
    1103 
    1104         w = gtk_radio_button_new_with_mnemonic( group, _( "Seed _regardless of ratio" ) );
    1105         group = gtk_radio_button_get_group( GTK_RADIO_BUTTON( w ) );
    1106         gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w ), mode == TR_RATIOLIMIT_UNLIMITED);
    1107         hig_workarea_add_wide_control( t, &row, w );
    1108         g_object_set_data( G_OBJECT( w ), RATIO_MODE_KEY, GINT_TO_POINTER( TR_RATIOLIMIT_UNLIMITED ) );
    1109         g_signal_connect( w, "toggled", G_CALLBACK( ratio_mode_changed_cb ), gtor );
    1110 
    1111         h = gtk_hbox_new( FALSE, GUI_PAD );
    1112         w = gtk_radio_button_new_with_mnemonic( group, _( "_Stop seeding when a torrent's ratio reaches" ) );
    1113         g_object_set_data( G_OBJECT( w ), RATIO_MODE_KEY, GINT_TO_POINTER( TR_RATIOLIMIT_SINGLE ) );
    1114         gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w ), mode == TR_RATIOLIMIT_SINGLE);
    1115         g_signal_connect( w, "toggled", G_CALLBACK( ratio_mode_changed_cb ), gtor );
    1116         group = gtk_radio_button_get_group( GTK_RADIO_BUTTON( w ) );
    1117         gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
    1118         d = tr_torrentGetRatioLimit( tor );
    1119         w = gtk_spin_button_new_with_range( 0.5, INT_MAX, .05 );
    1120         gtk_spin_button_set_digits( GTK_SPIN_BUTTON( w ), 2 );
    1121         gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), d );
    1122         g_signal_connect( w, "value-changed", G_CALLBACK( ratio_spun_cb ), gtor );
    1123         gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
    1124         hig_workarea_add_wide_control( t, &row, h );
    1125    
    1126     hig_workarea_add_section_divider( t, &row );
    1127     hig_workarea_add_section_title( t, &row, _( "Peer Connections" ) );
    1128 
    1129     maxConnectedPeers = tr_torrentGetPeerLimit( tor );
    1130     w = gtk_spin_button_new_with_range( 1, 3000, 5 );
    1131     gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), maxConnectedPeers );
    1132     hig_workarea_add_row( t, &row, _( "_Maximum peers:" ), w, w );
    1133     g_signal_connect( w, "value-changed", G_CALLBACK(
    1134                           max_peers_spun_cb ), gtor );
    1135 
    1136     hig_workarea_finish( t, &row );
    1137     return t;
    1138 }
     1709
    11391710
    11401711/****
     
    11461717struct tracker_page
    11471718{
    1148     TrTorrent *         gtor;
    1149 
    1150     GtkTreeView *       view;
    1151     GtkListStore *      store;
    1152     GtkTreeSelection *  sel;
    1153 
    1154     GtkWidget *         add_button;
    1155     GtkWidget *         remove_button;
    1156     GtkWidget *         save_button;
    1157     GtkWidget *         revert_button;
    1158 
    1159     GtkWidget *         last_scrape_time_lb;
    1160     GtkWidget *         last_scrape_response_lb;
    1161     GtkWidget *         next_scrape_countdown_lb;
    1162 
    1163     GtkWidget *         last_announce_time_lb;
    1164     GtkWidget *         last_announce_response_lb;
    1165     GtkWidget *         next_announce_countdown_lb;
    1166     GtkWidget *         manual_announce_countdown_lb;
     1719    GtkTreeView * view;
     1720    GtkListStore * store;
     1721    GtkTreeSelection * sel;
     1722
     1723    GtkWidget * add_button;
     1724    GtkWidget * remove_button;
     1725    GtkWidget * save_button;
     1726    GtkWidget * revert_button;
     1727
    11671728};
    11681729
     1730static void
     1731refreshTracker( struct DetailsImpl * di, tr_torrent ** torrents, int n )
     1732{
     1733    int i;
     1734    char buf[256];
     1735    const char * str;
     1736    const char * none = _("None" );
     1737    const char * mixed = _( "Mixed" );
     1738    const char * noneSent = _( "None sent" );
     1739    const char * inProgress = _( "In progress" );
     1740    const time_t now = time( NULL );
     1741    const tr_stat ** stats;
     1742
     1743    stats = g_new( const tr_stat*, n );
     1744    for( i=0; i<n; ++i )
     1745        stats[i] = tr_torrentStatCached( torrents[i] );
     1746
     1747
     1748    /* last_scrape_time_lb */
     1749    if( n<1 )
     1750        str = none;
     1751    else {
     1752        const time_t baseline = stats[0]->lastScrapeTime;
     1753        for( i=1; i<n; ++i )
     1754            if( baseline != stats[i]->lastScrapeTime )
     1755                break;
     1756        if( i!=n )
     1757            str = mixed;
     1758        else if( baseline==0 )
     1759            str = noneSent;
     1760        else {
     1761            char * tmp = gtr_localtime( baseline );
     1762            g_strlcpy( buf, tmp, sizeof( buf ) );
     1763            g_free( tmp );
     1764            str = buf;
     1765        }
     1766    }
     1767    gtk_label_set_text( GTK_LABEL( di->last_scrape_time_lb ), str );
     1768
     1769
     1770    /* last_scrape_response_lb */
     1771    if( n<1 )
     1772        str = none;
     1773    else {
     1774        const char * baseline = stats[0]->scrapeResponse;
     1775        for( i=1; i<n; ++i )
     1776            if( strcmp( baseline, stats[i]->scrapeResponse ) )
     1777                break;
     1778        if( i==n )
     1779            str = baseline;
     1780        else
     1781            str = mixed;
     1782    }
     1783    gtk_label_set_text( GTK_LABEL( di->last_scrape_response_lb ), str );
     1784
     1785
     1786    /* next_scrape_countdown_lb */
     1787    if( n<1 )
     1788        str = none;
     1789    else {
     1790        const time_t baseline = stats[0]->nextScrapeTime;
     1791        for( i=1; i<n; ++i )
     1792            if( baseline != stats[i]->nextScrapeTime )
     1793                break;
     1794        if( i!=n )
     1795            str = mixed;
     1796        else if( baseline <= now )
     1797            str = inProgress;
     1798        else
     1799            str = tr_strltime( buf, baseline - now, sizeof( buf ) );
     1800    }
     1801    gtk_label_set_text( GTK_LABEL( di->next_scrape_countdown_lb ), str );
     1802
     1803
     1804    /* last_announce_time_lb */
     1805    if( n<1 )
     1806        str = none;
     1807    else {
     1808        const time_t baseline = stats[0]->lastAnnounceTime;
     1809        for( i=1; i<n; ++i )
     1810            if( baseline != stats[i]->lastAnnounceTime )
     1811                break;
     1812        if( i!=n )
     1813            str = mixed;
     1814        else if( baseline==0 )
     1815            str = noneSent;
     1816        else {
     1817            char * tmp = gtr_localtime( baseline );
     1818            g_strlcpy( buf, tmp, sizeof( buf ) );
     1819            g_free( tmp );
     1820            str = buf;
     1821        }
     1822    }
     1823    gtk_label_set_text( GTK_LABEL( di->last_announce_time_lb ), str );
     1824
     1825
     1826    /* last_announce_response_lb */
     1827    if( n<1 )
     1828        str = none;
     1829    else {
     1830        const char * baseline = stats[0]->announceResponse;
     1831        for( i=1; i<n; ++i )
     1832            if( strcmp( baseline, stats[i]->announceResponse ) )
     1833                break;
     1834        if( i==n )
     1835            str = baseline;
     1836        else
     1837            str = mixed;
     1838    }
     1839    gtk_label_set_text( GTK_LABEL( di->last_announce_response_lb ), str );
     1840
     1841
     1842    /* next_announce_countdown_lb */
     1843    if( n<1 )
     1844        str = none;
     1845    else {
     1846        const time_t baseline = stats[0]->nextAnnounceTime;
     1847        for( i=1; i<n; ++i )
     1848            if( baseline != stats[i]->nextAnnounceTime )
     1849                break;
     1850        if( i!=n )
     1851            str = mixed;
     1852        else if( baseline==0 )
     1853            str = none;
     1854        else if( baseline==1 || baseline<=now )
     1855            str = inProgress;
     1856        else
     1857            str = tr_strltime( buf, baseline - now, sizeof( buf ) );
     1858    }
     1859    gtk_label_set_text( GTK_LABEL( di->next_announce_countdown_lb ), str );
     1860
     1861
     1862    /* manual_announce_countdown_lb */
     1863    if( n<1 )
     1864        str = none;
     1865    else {
     1866        const time_t baseline = stats[0]->manualAnnounceTime;
     1867        for( i=1; i<n; ++i )
     1868            if( baseline != stats[i]->manualAnnounceTime )
     1869                break;
     1870        if( i!=n )
     1871            str = mixed;
     1872        else if( baseline<1 )
     1873            str = none;
     1874        else if( baseline<=now )
     1875            str = inProgress;
     1876        else
     1877            str = tr_strltime( buf, baseline - now, sizeof( buf ) );
     1878    }
     1879    gtk_label_set_text( GTK_LABEL( di->manual_announce_countdown_lb ), str );
     1880
     1881
     1882    /* tracker list */
     1883}
     1884
    11691885static GtkWidget*
    1170 tracker_page_new( TrTorrent * gtor )
    1171 {
    1172     GtkWidget *           t;
    1173     GtkWidget *           l;
    1174     GtkWidget *           w;
    1175     int                   row = 0;
    1176     const char *          s;
    1177     struct tracker_page * page = g_new0( struct tracker_page, 1 );
    1178     const tr_info *       info = tr_torrent_info( gtor );
    1179 
    1180     page->gtor = gtor;
     1886tracker_page_new( struct DetailsImpl * di )
     1887{
     1888    int row = 0;
     1889    const char * s;
     1890    GtkWidget *t, *l, *w;
    11811891
    11821892    t = hig_workarea_create( );
    11831893    hig_workarea_add_section_title( t, &row, _( "Trackers" ) );
    11841894
    1185     w = tracker_list_new( gtor );
    1186     hig_workarea_add_wide_control( t, &row, w );
     1895        w = tracker_list_new( tr_core_session( di->core ), -1, FALSE );
     1896        hig_workarea_add_wide_control( t, &row, w );
     1897        di->tracker_list = w;
    11871898
    11881899    hig_workarea_add_section_divider( t, &row );
    11891900    hig_workarea_add_section_title( t, &row, _( "Scrape" ) );
    11901901
    1191     s = _( "Last scrape at:" );
    1192     l = gtk_label_new( NULL );
    1193     page->last_scrape_time_lb = l;
    1194     hig_workarea_add_row( t, &row, s, l, NULL );
    1195 
    1196     s = _( "Tracker responded:" );
    1197     l = gtk_label_new( NULL );
    1198     page->last_scrape_response_lb = l;
    1199     hig_workarea_add_row( t, &row, s, l, NULL );
    1200 
    1201     s = _( "Next scrape in:" );
    1202     l = gtk_label_new( NULL );
    1203     page->next_scrape_countdown_lb = l;
    1204     hig_workarea_add_row( t, &row, s, l, NULL );
     1902        s = _( "Last scrape at:" );
     1903        l = gtk_label_new( NULL );
     1904        di->last_scrape_time_lb = l;
     1905        hig_workarea_add_row( t, &row, s, l, NULL );
     1906
     1907        s = _( "Tracker responded:" );
     1908        l = gtk_label_new( NULL );
     1909        di->last_scrape_response_lb = l;
     1910        hig_workarea_add_row( t, &row, s, l, NULL );
     1911
     1912        s = _( "Next scrape in:" );
     1913        l = gtk_label_new( NULL );
     1914        di->next_scrape_countdown_lb = l;
     1915        hig_workarea_add_row( t, &row, s, l, NULL );
    12051916
    12061917    hig_workarea_add_section_divider( t, &row );
    12071918    hig_workarea_add_section_title( t, &row, _( "Announce" ) );
    12081919
    1209     l = gtk_label_new( info->trackers[0].announce );
    1210     gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
    1211     hig_workarea_add_row( t, &row, _( "Tracker:" ), l, NULL );
    1212 
    1213     s = _( "Last announce at:" );
    1214     l = gtk_label_new( NULL );
    1215     page->last_announce_time_lb = l;
    1216     hig_workarea_add_row( t, &row, s, l, NULL );
    1217 
    1218     s = _( "Tracker responded:" );
    1219     l = gtk_label_new( NULL );
    1220     page->last_announce_response_lb = l;
    1221     hig_workarea_add_row( t, &row, s, l, NULL );
    1222 
    1223     s = _( "Next announce in:" );
    1224     l = gtk_label_new( NULL );
    1225     page->next_announce_countdown_lb = l;
    1226     hig_workarea_add_row( t, &row, s, l, NULL );
    1227 
    1228     /* how long until the tracker will honor user
    1229     * pressing the "ask for more peers" button */
    1230     s = _( "Manual announce allowed in:" );
    1231     l = gtk_label_new( NULL );
    1232     page->manual_announce_countdown_lb = l;
    1233     hig_workarea_add_row( t, &row, s, l, NULL );
     1920        l = gtk_label_new( NULL );
     1921        gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
     1922        hig_workarea_add_row( t, &row, _( "Tracker:" ), l, NULL );
     1923
     1924        s = _( "Last announce at:" );
     1925        l = gtk_label_new( NULL );
     1926        di->last_announce_time_lb = l;
     1927        hig_workarea_add_row( t, &row, s, l, NULL );
     1928
     1929        s = _( "Tracker responded:" );
     1930        l = gtk_label_new( NULL );
     1931        di->last_announce_response_lb = l;
     1932        hig_workarea_add_row( t, &row, s, l, NULL );
     1933
     1934        s = _( "Next announce in:" );
     1935        l = gtk_label_new( NULL );
     1936        di->next_announce_countdown_lb = l;
     1937        hig_workarea_add_row( t, &row, s, l, NULL );
     1938
     1939        /* how long until the tracker will honor user
     1940         * pressing the "ask for more peers" button */
     1941        s = _( "Manual announce allowed in:" );
     1942        l = gtk_label_new( NULL );
     1943        di->manual_announce_countdown_lb = l;
     1944        hig_workarea_add_row( t, &row, s, l, NULL );
    12341945
    12351946    hig_workarea_finish( t, &row );
    1236     g_object_set_data_full( G_OBJECT( t ), TRACKER_PAGE, page, g_free );
    12371947    return t;
    12381948}
    12391949
    1240 static void
    1241 refresh_countdown_lb( GtkWidget *  w,
    1242                       time_t       t,
    1243                       const char * countdown_done )
    1244 {
    1245     const time_t now = time( NULL );
    1246     GtkLabel *   l = GTK_LABEL( w );
    1247 
    1248     if( t == 1 )
    1249         gtk_label_set_text( l, _( "In progress" ) );
    1250     else if( t < now )
    1251         gtk_label_set_text( l, countdown_done );
    1252     else {
    1253         char buf[512];
    1254         const int seconds = t - now;
    1255         tr_strltime( buf, seconds, sizeof( buf ) );
    1256         gtk_label_set_text( l, buf );
    1257     }
    1258 }
    1259 
    1260 static void
    1261 refresh_tracker( GtkWidget * w )
    1262 {
    1263     GtkWidget *           l;
    1264     time_t                t;
    1265     struct tracker_page * page = g_object_get_data( G_OBJECT(
    1266                                                         w ), TRACKER_PAGE );
    1267     const tr_stat *       torStat = tr_torrent_stat( page->gtor );
    1268 
    1269     l = page->last_scrape_time_lb;
    1270     t = torStat->lastScrapeTime;
    1271     refresh_time_lb( l, t );
    1272 
    1273     l = page->last_scrape_response_lb;
    1274     gtk_label_set_text( GTK_LABEL( l ), torStat->scrapeResponse );
    1275 
    1276     l = page->next_scrape_countdown_lb;
    1277     t = torStat->nextScrapeTime;
    1278     refresh_countdown_lb( l, t, _( "Never" ) );
    1279 
    1280     l = page->last_announce_time_lb;
    1281     t = torStat->lastAnnounceTime;
    1282     refresh_time_lb( l, t );
    1283 
    1284     l = page->last_announce_response_lb;
    1285     gtk_label_set_text( GTK_LABEL( l ), torStat->announceResponse );
    1286 
    1287     l = page->next_announce_countdown_lb;
    1288     t = torStat->nextAnnounceTime;
    1289     refresh_countdown_lb( l, t, _( "Never" ) );
    1290 
    1291     l = page->manual_announce_countdown_lb;
    1292     t = torStat->manualAnnounceTime;
    1293     refresh_countdown_lb( l, t, _( "Now" ) );
    1294 }
    12951950
    12961951/****
     
    12991954
    13001955static void
    1301 torrent_destroyed( gpointer dialog, GObject * dead_torrent UNUSED )
    1302 {
    1303     gtk_widget_destroy( GTK_WIDGET( dialog ) );
    1304 }
    1305 
    1306 static void
    1307 remove_tag( gpointer tag )
    1308 {
    1309     g_source_remove( GPOINTER_TO_UINT( tag ) ); /* stop the periodic refresh */
    1310 }
    1311 
    1312 static void
    1313 response_cb( GtkDialog *  dialog,
    1314              int response UNUSED,
    1315              gpointer     data )
    1316 {
    1317     struct ResponseData *rd = data;
    1318     TrCore * core = rd->core;
    1319     gulong handler = rd-> handler;
    1320 
    1321     g_signal_handler_disconnect( core, handler );
    1322     g_object_weak_unref( G_OBJECT( rd->gtor ), torrent_destroyed, dialog );
    1323     gtk_widget_destroy( GTK_WIDGET( dialog ) );
    1324 
    1325     g_free( rd );
     1956refresh( struct DetailsImpl * di )
     1957{
     1958    int n;
     1959    tr_torrent ** torrents = getTorrents( di, &n );
     1960
     1961    refreshInfo( di, torrents, n );
     1962    refreshPeers( di, torrents, n );
     1963    refreshTracker( di, torrents, n );
     1964    refreshOptions( di, torrents, n );
     1965    refreshActivity( di, torrents, n );
     1966
     1967    g_free( torrents );
    13261968}
    13271969
     
    13291971periodic_refresh( gpointer data )
    13301972{
    1331     refresh_tracker   ( g_object_get_data( G_OBJECT( data ), "tracker-top" ) );
    1332     refresh_peers     ( g_object_get_data( G_OBJECT( data ), "peers-top" ) );
    1333     refresh_activity  ( g_object_get_data( G_OBJECT( data ), "activity-top" ) );
     1973    refresh( data );
    13341974    return TRUE;
    13351975}
    13361976
     1977static void
     1978details_impl_free( gpointer gdata )
     1979{
     1980    struct DetailsImpl * data = gdata;
     1981    g_signal_handler_disconnect( data->core, data->prefs_changed_tag );
     1982    g_source_remove( data->periodic_refresh_tag );
     1983    g_slist_free( data->ids );
     1984    g_free( data );
     1985}
     1986
     1987static void
     1988response_cb( GtkDialog * dialog, int a UNUSED, gpointer b UNUSED )
     1989{
     1990    GtkWidget * w = GTK_WIDGET( dialog );
     1991    torrent_inspector_set_torrents( w, NULL );
     1992    gtk_widget_hide( w );
     1993}
     1994
    13371995GtkWidget*
    1338 torrent_inspector_new( GtkWindow * parent,
    1339                        TrCore    * core,
    1340                        TrTorrent * gtor )
    1341 {
    1342     guint           tag;
    1343     GtkWidget *     d, *n, *w, *lb;
    1344     char            title[512];
    1345     struct ResponseData  * rd;
    1346     tr_torrent *    tor = tr_torrent_handle( gtor );
    1347     const tr_info * info = tr_torrent_info( gtor );
     1996torrent_inspector_new( GtkWindow * parent, TrCore * core )
     1997{
     1998    char title[512];
     1999    struct DetailsImpl * data;
     2000    GtkWidget * d, * n, * w, * l;
    13482001
    13492002    /* create the dialog */
    1350     rd = g_new0(struct ResponseData, 1);
    1351     rd->gtor = gtor;
    1352     rd->core = core;
    1353     g_snprintf( title, sizeof( title ), _( "%s Properties" ), info->name );
     2003    data = g_new0( struct DetailsImpl, 1 );
     2004    data->core = core;
     2005    /*FIXME g_snprintf( title, sizeof( title ), _( "%s Properties" ), info->name );*/
    13542006    d = gtk_dialog_new_with_buttons( title, parent, 0,
    13552007                                     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
    13562008                                     NULL );
    13572009    gtk_window_set_role( GTK_WINDOW( d ), "tr-info" );
    1358     g_signal_connect( d, "response", G_CALLBACK( response_cb ), rd );
     2010    g_signal_connect( d, "response", G_CALLBACK( response_cb ), NULL );
    13592011    gtk_dialog_set_has_separator( GTK_DIALOG( d ), FALSE );
    13602012    gtk_container_set_border_width( GTK_CONTAINER( d ), GUI_PAD );
    1361     g_object_weak_ref( G_OBJECT( gtor ), torrent_destroyed, d );
    1362 
    1363 
    1364     /* add the notebook */
     2013    g_object_set_data_full( G_OBJECT( d ), DETAILS_KEY, data, details_impl_free );
     2014
    13652015    n = gtk_notebook_new( );
    13662016    gtk_container_set_border_width( GTK_CONTAINER( n ), GUI_PAD );
    13672017
    1368     w = activity_page_new( gtor );
    1369     lb = gtk_label_new( _( "Activity" ) );
    1370     gtk_notebook_append_page( GTK_NOTEBOOK( n ), w, lb );
    1371     g_object_set_data( G_OBJECT( d ), "activity-top", w );
    1372 
    1373     w = peer_page_new( gtor );
    1374     lb = gtk_label_new( _( "Peers" ) );
    1375     gtk_notebook_append_page( GTK_NOTEBOOK( n ),  w, lb );
    1376     g_object_set_data( G_OBJECT( d ), "peers-top", w );
    1377 
    1378     w = tracker_page_new( gtor );
    1379     lb = gtk_label_new( _( "Tracker" ) );
    1380     gtk_notebook_append_page( GTK_NOTEBOOK( n ), w, lb );
    1381     g_object_set_data( G_OBJECT( d ), "tracker-top", w );
    1382 
    1383     w = info_page_new( tor );
    1384     lb = gtk_label_new( _( "Information" ) );
    1385     gtk_notebook_append_page( GTK_NOTEBOOK( n ), w, lb );
    1386 
    1387     w = file_list_new( gtor );
     2018    w = activity_page_new( data );
     2019    l = gtk_label_new( _( "Activity" ) );
     2020    gtk_notebook_append_page( GTK_NOTEBOOK( n ), w, l );
     2021
     2022    w = peer_page_new( data );
     2023    l = gtk_label_new( _( "Peers" ) );
     2024    gtk_notebook_append_page( GTK_NOTEBOOK( n ),  w, l );
     2025
     2026    w = tracker_page_new( data );
     2027    l = gtk_label_new( _( "Tracker" ) );
     2028    gtk_notebook_append_page( GTK_NOTEBOOK( n ), w, l );
     2029
     2030    w = info_page_new( data );
     2031    l = gtk_label_new( _( "Information" ) );
     2032    gtk_notebook_append_page( GTK_NOTEBOOK( n ), w, l );
     2033
     2034    w = file_list_new( data->core, 0 );
    13882035    gtk_container_set_border_width( GTK_CONTAINER( w ), GUI_PAD_BIG );
    1389     lb = gtk_label_new( _( "Files" ) );
    1390     gtk_notebook_append_page( GTK_NOTEBOOK( n ), w, lb );
    1391     g_object_set_data( G_OBJECT( d ), "files-top", w );
    1392 
    1393     w = options_page_new( rd );
    1394     lb = gtk_label_new( _( "Options" ) );
    1395     gtk_notebook_append_page( GTK_NOTEBOOK( n ), w, lb );
     2036    l = gtk_label_new( _( "Files" ) );
     2037    gtk_notebook_append_page( GTK_NOTEBOOK( n ), w, l );
     2038    data->file_list = w;
     2039
     2040    w = options_page_new( data );
     2041    l = gtk_label_new( _( "Options" ) );
     2042    gtk_notebook_append_page( GTK_NOTEBOOK( n ), w, l );
    13962043
    13972044    gtk_box_pack_start( GTK_BOX( GTK_DIALOG( d )->vbox ), n, TRUE, TRUE, 0 );
    13982045
    1399     tag = gtr_timeout_add_seconds( UPDATE_INTERVAL_SECONDS, periodic_refresh, d );
    1400     g_object_set_data_full( G_OBJECT( d ), "tag", GUINT_TO_POINTER( tag ), remove_tag );
    1401     periodic_refresh( d );
     2046    data->periodic_refresh_tag = gtr_timeout_add_seconds( UPDATE_INTERVAL_SECONDS, periodic_refresh, data );
     2047    periodic_refresh( data );
    14022048    gtk_widget_show_all( GTK_DIALOG( d )->vbox );
    14032049    return d;
    14042050}
    14052051
     2052void
     2053torrent_inspector_set_torrents( GtkWidget * w, GSList * ids )
     2054{
     2055    struct DetailsImpl * data = g_object_get_data( G_OBJECT( w ), DETAILS_KEY );
     2056
     2057    g_slist_free( data->ids );
     2058    data->ids = g_slist_copy( ids );
     2059
     2060    if( g_slist_length( ids ) == 1 ) {
     2061        const int id = GPOINTER_TO_INT( ids->data );
     2062        file_list_set_torrent( data->file_list, id );
     2063        tracker_list_set_torrent( data->tracker_list, id );
     2064    } else {
     2065        file_list_clear( data->file_list );
     2066        tracker_list_clear( data->tracker_list );
     2067    }
     2068
     2069    refresh( data );
     2070}
  • trunk/gtk/details.h

    r7888 r8279  
    1919
    2020GtkWidget* torrent_inspector_new( GtkWindow * parent,
    21                                   TrCore    * core,
    22                                   TrTorrent * tor );
     21                                  TrCore    * core );
     22
     23void torrent_inspector_set_torrents( GtkWidget * widgets,
     24                                     GSList    * torrent_ids );
    2325
    2426#endif /* TG_PREFS_H */
  • trunk/gtk/file-list.c

    r8211 r8279  
    4242    SUB_STATE_NORMAL        = ( 1 << 1 ),
    4343    SUB_STATE_LOW           = ( 1 << 2 ),
    44     SUB_STATE_PRIORITY_MASK =
    45         ( SUB_STATE_HIGH | SUB_STATE_NORMAL | SUB_STATE_LOW ),
     44    SUB_STATE_PRIORITY_MASK = ( SUB_STATE_HIGH | SUB_STATE_NORMAL | SUB_STATE_LOW ),
    4645    SUB_STATE_DOWNLOAD      = ( 1 << 4 ),
    4746    SUB_STATE_IGNORE        = ( 1 << 5 ),
     
    6968typedef struct
    7069{
    71     TrTorrent *     gtor;
     70    int             torrentId;
     71    TrCore *        core;
     72    tr_torrent    * tor;
    7273    GtkWidget *     top;
    7374    GtkWidget *     view;
     
    8283clearData( FileData * data )
    8384{
    84     data->gtor = NULL;
    85 
    86     if( data->timeout_tag )
    87     {
     85    data->torrentId = -1;
     86
     87    if( data->timeout_tag ) {
    8888        g_source_remove( data->timeout_tag );
    8989        data->timeout_tag = 0;
     
    200200
    201201static gboolean
    202 refreshFilesForeach( GtkTreeModel *       model,
    203                      GtkTreePath   * path UNUSED,
    204                      GtkTreeIter *        iter,
    205                      gpointer             gdata )
     202refreshFilesForeach( GtkTreeModel * model,
     203                     GtkTreePath  * path UNUSED,
     204                     GtkTreeIter  * iter,
     205                     gpointer       gdata )
    206206{
    207207    FileData *   data = gdata;
     
    209209    unsigned int index;
    210210
    211     gtk_tree_model_get( model, iter, FC_IS_FILE, &is_file, FC_INDEX, &index,
    212                         -1  );
     211    gtk_tree_model_get( model, iter, FC_IS_FILE, &is_file,
     212                                     FC_INDEX, &index,
     213                                     -1  );
    213214    if( is_file )
    214215    {
    215216        GtkTreeStore * store = GTK_TREE_STORE( model );
    216         tr_torrent *   tor = tr_torrent_handle( data->gtor );
     217        tr_torrent *   tor = data->tor;
    217218        int            download = tr_torrentGetFileDL( tor, index );
    218219        int            priority = tr_torrentGetFilePriority( tor, index );
     
    300301refresh( FileData * data )
    301302{
    302     tr_file_index_t fileCount;
    303     tr_torrent *    tor = tr_torrent_handle( data->gtor );
    304 
    305     data->refresh_file_stat = tr_torrentFiles( tor, &fileCount );
    306 
    307     gtk_tree_model_foreach( data->model, refreshFilesForeach, data );
    308     gtk_tree_model_foreach( data->model, resetSubForeach, data );
    309     gtk_tree_model_foreach( data->model, addSubForeach, data );
    310 
    311     tr_torrentFilesFree( data->refresh_file_stat, fileCount );
    312     data->refresh_file_stat = NULL;
     303    tr_torrent * tor = tr_torrentFindFromId( tr_core_session( data->core ), data->torrentId );
     304
     305    if( tor == NULL )
     306    {
     307        file_list_clear( data->top );
     308    }
     309    else
     310    {
     311        tr_file_index_t fileCount;
     312
     313        /* initialize the temporary variables */
     314        data->tor = tr_torrentFindFromId( tr_core_session( data->core ), data->torrentId );
     315        data->refresh_file_stat = tr_torrentFiles( tor, &fileCount );
     316
     317        gtk_tree_model_foreach( data->model, refreshFilesForeach, data );
     318        gtk_tree_model_foreach( data->model, resetSubForeach, data );
     319        gtk_tree_model_foreach( data->model, addSubForeach, data );
     320
     321        /* clean up the temporary variables */
     322        data->refresh_file_stat = NULL;
     323        tr_torrentFilesFree( data->refresh_file_stat, fileCount );
     324        data->tor = NULL;
     325    }
    313326}
    314327
     
    443456
    444457void
    445 file_list_set_torrent( GtkWidget * w,
    446                        TrTorrent * gtor )
     458file_list_clear( GtkWidget * w )
     459{
     460    file_list_set_torrent( w, -1 );
     461}
     462
     463void
     464file_list_set_torrent( GtkWidget * w, int torrentId )
    447465{
    448466    GtkTreeStore * store;
    449     FileData *     data;
    450 
    451     data = g_object_get_data( G_OBJECT( w ), "file-data" );
     467    FileData * data = g_object_get_data( G_OBJECT( w ), "file-data" );
    452468
    453469    /* unset the old fields */
     
    469485                                 G_TYPE_UINT64,    /* sub have */
    470486                                 G_TYPE_INT );     /* sub state */
     487
    471488    data->store = store;
    472489    data->model = GTK_TREE_MODEL( store );
    473     data->gtor = gtor;
    474 
     490    data->torrentId = torrentId;
    475491
    476492    /* populate the model */
    477     if( gtor )
    478     {
    479         tr_file_index_t i;
    480         const tr_info * inf = tr_torrent_info( gtor );
    481         tr_torrent *    tor = tr_torrent_handle( gtor );
    482 
    483         for( i = 0; inf && i < inf->fileCount; ++i )
     493    if( torrentId > 0 )
     494    {
     495        tr_session * session = tr_core_session( data->core );
     496        tr_torrent * tor = tr_torrentFindFromId( session, torrentId );
     497        if( tor != NULL )
    484498        {
    485             const char * path = inf->files[i].name;
    486             const char * base =
    487                 g_path_is_absolute( path ) ? g_path_skip_root( path ) :
    488                 path;
    489             parsepath( w, tor, store, NULL, base, i, inf->files[i].length );
     499            tr_file_index_t i;
     500            const tr_info * inf = tr_torrentInfo( tor );
     501
     502            for( i=0; i<inf->fileCount; ++i )
     503            {
     504                const char * path = inf->files[i].name;
     505                const char * base = g_path_is_absolute( path ) ? g_path_skip_root( path ) : path;
     506                parsepath( w, tor, store, NULL, base, i, inf->files[i].length );
     507            }
    490508        }
    491509
    492510        refresh( data );
    493 
    494511        data->timeout_tag = gtr_timeout_add_seconds( 2, refreshModel, data );
    495512    }
    496513
    497     gtk_tree_view_set_model( GTK_TREE_VIEW( data->view ),
    498                             GTK_TREE_MODEL( store ) );
     514    gtk_tree_view_set_model( GTK_TREE_VIEW( data->view ), GTK_TREE_MODEL( store ) );
    499515    gtk_tree_view_expand_all( GTK_TREE_VIEW( data->view ) );
    500516}
     
    657673    FileData * data = gdata;
    658674    gboolean   handled = FALSE;
     675    tr_torrent * tor = tr_torrentFindFromId( tr_core_session( data->core ), data->torrentId );
     676
     677    if( tor == NULL )
     678        return handled;
    659679
    660680    if( ( event->type == GDK_BUTTON_PRESS ) && ( event->button == 1 )
     
    721741
    722742                    /* apply that new state to the active files */
    723                     tr_torrentSetFilePriorities( tr_torrent_handle( data->
    724                                                                     gtor ),
     743                    tr_torrentSetFilePriorities( tor,
    725744                                                 (tr_file_index_t*)a->data,
    726745                                                 (tr_file_index_t)a->len,
     
    745764
    746765                    /* apply that new state to the active files */
    747                     tr_torrentSetFileDLs( tr_torrent_handle( data->gtor ),
     766                    tr_torrentSetFileDLs( tor,
    748767                                          (tr_file_index_t*)a->data,
    749768                                          (tr_file_index_t)a->len,
     
    770789
    771790GtkWidget *
    772 file_list_new( TrTorrent * gtor )
     791file_list_new( TrCore * core, int torrentId )
    773792{
    774793    GtkWidget *         ret;
     
    778797    GtkTreeSelection *  sel;
    779798    FileData *          data = g_new0( FileData, 1 );
     799
     800    data->core = core;
    780801
    781802    /* create the view */
     
    846867    data->top = scroll;
    847868    g_object_set_data_full( G_OBJECT( ret ), "file-data", data, freeData );
    848     file_list_set_torrent( ret, gtor );
     869    file_list_set_torrent( ret, torrentId );
    849870
    850871    return ret;
  • trunk/gtk/file-list.h

    r6998 r8279  
    2727
    2828#include <gtk/gtk.h>
    29 #include "tr-torrent.h"
     29#include "tr-core.h"
    3030
    31 /* create a new file list */
    32 GtkWidget * file_list_new( TrTorrent * );
     31GtkWidget * file_list_new( TrCore *, int torrentId );
    3332
    34 void        file_list_set_torrent( GtkWidget *,
    35                                    TrTorrent * );
     33void        file_list_clear( GtkWidget * );
     34
     35void        file_list_set_torrent( GtkWidget *, int torrentId );
    3636
    3737#endif
  • trunk/gtk/main.c

    r8252 r8279  
    9898struct cbdata
    9999{
    100     unsigned int  isIconified : 1;
    101     unsigned int  isClosing   : 1;
    102     guint         timer;
    103     gpointer      icon;
    104     GtkWindow *   wind;
    105     TrCore *      core;
    106     GtkWidget *   msgwin;
    107     GtkWidget *   prefs;
    108     GSList *      errqueue;
    109     GSList *      dupqueue;
    110     GHashTable *  tor2details;
    111     GHashTable *  details2tor;
    112     GtkTreeSelection * sel;
     100    gboolean            isIconified;
     101    gboolean            isClosing;
     102    guint               timer;
     103    gpointer            icon;
     104    GtkWindow         * wind;
     105    TrCore            * core;
     106    GtkWidget         * msgwin;
     107    GtkWidget         * prefs;
     108    GSList            * errqueue;
     109    GSList            * dupqueue;
     110    GtkTreeSelection  * sel;
     111    GtkWidget         * details;
    113112};
    114113
     
    190189
    191190static void
    192 refreshTorrentActions( struct cbdata * data )
     191refreshActions( struct cbdata * data )
    193192{
    194193    int canUpdate;
     
    206205    action_sensitize( "verify-torrent", counts.totalCount != 0 );
    207206    action_sensitize( "open-torrent-folder", counts.totalCount == 1 );
    208     action_sensitize( "show-torrent-properties", counts.totalCount == 1 );
     207    action_sensitize( "show-torrent-properties", counts.totalCount != 0 );
    209208
    210209    canUpdate = 0;
     
    230229
    231230static void
     231refreshDetailsDialog( struct cbdata * data )
     232{
     233    GtkTreeSelection * s = tr_window_get_selection( data->wind );
     234    GtkTreeModel * model;
     235    GSList * ids = NULL;
     236    GList * selrows = NULL;
     237    GList * l;
     238
     239    if( data->details == NULL )
     240        return;
     241
     242    /* build a list of the selected torrents' ids */
     243    s = tr_window_get_selection( data->wind );
     244    for( selrows=l=gtk_tree_selection_get_selected_rows(s,&model); l; l=l->next ) {
     245        GtkTreeIter iter;
     246        if( gtk_tree_model_get_iter( model, &iter, l->data ) ) {
     247            tr_torrent * tor;
     248            gtk_tree_model_get( model, &iter, MC_TORRENT_RAW, &tor, -1 );
     249            ids = g_slist_append( ids, GINT_TO_POINTER( tr_torrentId( tor ) ) );
     250        }
     251    }
     252
     253    torrent_inspector_set_torrents( data->details, ids );
     254
     255    /* cleanup */
     256    g_slist_free( ids );
     257    g_list_foreach( selrows, (GFunc)gtk_tree_path_free, NULL );
     258    g_list_free( selrows );
     259}
     260
     261static void
    232262selectionChangedCB( GtkTreeSelection * s UNUSED, gpointer data )
    233263{
    234     refreshTorrentActions( data );
     264    refreshActions( data );
     265    refreshDetailsDialog( data );
    235266}
    236267
     
    356387
    357388    cbdata = g_new0( struct cbdata, 1 );
    358     cbdata->tor2details = g_hash_table_new( g_str_hash, g_str_equal );
    359     cbdata->details2tor = g_hash_table_new( g_direct_hash, g_direct_equal );
    360389
    361390    /* bind the gettext domain */
     
    584613    struct cbdata * data = gdata;
    585614    if( gtk_tree_selection_path_is_selected ( data->sel, path ) )
    586         refreshTorrentActions( gdata );
     615        refreshActions( gdata );
    587616}
    588617
     
    603632    g_signal_connect( model, "row-changed", G_CALLBACK( rowChangedCB ), cbdata );
    604633    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
    605     refreshTorrentActions( cbdata );
     634    refreshActions( cbdata );
    606635
    607636    setupdrag( GTK_WIDGET( wind ), cbdata );
     
    609638
    610639static gpointer
    611 quitThreadFunc( gpointer gdata )
    612 {
    613     struct cbdata * cbdata = gdata;
    614 
    615     tr_core_close( cbdata->core );
     640quitThreadFunc( gpointer core )
     641{
     642    tr_core_close( core );
     643    gtk_main_quit( );
     644    return NULL;
     645}
     646
     647static void
     648do_exit_cb( GtkWidget *w  UNUSED,
     649            gpointer data UNUSED )
     650{
     651    exit( 0 );
     652}
     653
     654static void
     655wannaquit( void * vdata )
     656{
     657    TrCore * core;
     658    GtkWidget *r, *p, *b, *w, *c;
     659    struct cbdata *cbdata = vdata;
     660
     661    /* stop the update timer */
     662    if( cbdata->timer )
     663    {
     664        g_source_remove( cbdata->timer );
     665        cbdata->timer = 0;
     666    }
     667
     668    c = GTK_WIDGET( cbdata->wind );
     669    gtk_container_remove( GTK_CONTAINER( c ), gtk_bin_get_child( GTK_BIN( c ) ) );
     670
     671    r = gtk_alignment_new( 0.5, 0.5, 0.01, 0.01 );
     672    gtk_container_add( GTK_CONTAINER( c ), r );
     673
     674    p = gtk_table_new( 3, 2, FALSE );
     675    gtk_table_set_col_spacings( GTK_TABLE( p ), GUI_PAD_BIG );
     676    gtk_container_add( GTK_CONTAINER( r ), p );
     677
     678    w = gtk_image_new_from_stock( GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG );
     679    gtk_table_attach_defaults( GTK_TABLE( p ), w, 0, 1, 0, 2 );
     680
     681    w = gtk_label_new( NULL );
     682    gtk_label_set_markup( GTK_LABEL( w ), _( "<b>Closing Connections</b>" ) );
     683    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
     684    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 0, 1 );
     685
     686    w = gtk_label_new( _( "Sending upload/download totals to tracker..." ) );
     687    gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
     688    gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 1, 2 );
     689
     690    b = gtk_alignment_new( 0.0, 1.0, 0.01, 0.01 );
     691    w = gtr_button_new_from_stock( GTK_STOCK_QUIT, _( "_Quit Now" ) );
     692    g_signal_connect( w, "clicked", G_CALLBACK( do_exit_cb ), NULL );
     693    gtk_container_add( GTK_CONTAINER( b ), w );
     694    gtk_table_attach( GTK_TABLE( p ), b, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 10 );
     695
     696    gtk_widget_show_all( r );
     697
     698    /* clear the UI */
     699    gtk_list_store_clear( GTK_LIST_STORE( tr_core_model( cbdata->core ) ) );
    616700
    617701    /* shutdown the gui */
     702    core = cbdata->core;
     703    if( cbdata->details )
     704        gtk_widget_destroy( GTK_WIDGET( cbdata->details ) );
    618705    if( cbdata->prefs )
    619706        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
     
    623710    if( cbdata->icon )
    624711        g_object_unref( cbdata->icon );
    625     if( cbdata->errqueue )
    626     {
     712    if( cbdata->errqueue ) {
    627713        g_slist_foreach( cbdata->errqueue, (GFunc)g_free, NULL );
    628714        g_slist_free( cbdata->errqueue );
    629715    }
    630     if( cbdata->dupqueue )
    631     {
     716    if( cbdata->dupqueue ) {
    632717        g_slist_foreach( cbdata->dupqueue, (GFunc)g_free, NULL );
    633718        g_slist_free( cbdata->dupqueue );
    634719    }
    635 
    636     g_hash_table_destroy( cbdata->details2tor );
    637     g_hash_table_destroy( cbdata->tor2details );
    638720    g_free( cbdata );
    639721
    640     /* exit the gtk main loop */
    641     gtk_main_quit( );
    642     return NULL;
    643 }
    644 
    645 static void
    646 do_exit_cb( GtkWidget *w  UNUSED,
    647             gpointer data UNUSED )
    648 {
    649     exit( 0 );
    650 }
    651 
    652 static void
    653 wannaquit( void * vdata )
    654 {
    655     GtkWidget *     r, * p, * b, * w, *c;
    656     struct cbdata * cbdata = vdata;
    657 
    658     /* stop the update timer */
    659     if( cbdata->timer )
    660     {
    661         g_source_remove( cbdata->timer );
    662         cbdata->timer = 0;
    663     }
    664 
    665     c = GTK_WIDGET( cbdata->wind );
    666     gtk_container_remove( GTK_CONTAINER( c ), gtk_bin_get_child( GTK_BIN( c ) ) );
    667 
    668     r = gtk_alignment_new( 0.5, 0.5, 0.01, 0.01 );
    669     gtk_container_add( GTK_CONTAINER( c ), r );
    670 
    671     p = gtk_table_new( 3, 2, FALSE );
    672     gtk_table_set_col_spacings( GTK_TABLE( p ), GUI_PAD_BIG );
    673     gtk_container_add( GTK_CONTAINER( r ), p );
    674 
    675     w = gtk_image_new_from_stock( GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG );
    676     gtk_table_attach_defaults( GTK_TABLE( p ), w, 0, 1, 0, 2 );
    677 
    678     w = gtk_label_new( NULL );
    679     gtk_label_set_markup( GTK_LABEL( w ), _( "<b>Closing Connections</b>" ) );
    680     gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
    681     gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 0, 1 );
    682 
    683     w = gtk_label_new( _( "Sending upload/download totals to tracker..." ) );
    684     gtk_misc_set_alignment( GTK_MISC( w ), 0.0, 0.5 );
    685     gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 1, 2 );
    686 
    687     b = gtk_alignment_new( 0.0, 1.0, 0.01, 0.01 );
    688     w = gtr_button_new_from_stock( GTK_STOCK_QUIT, _( "_Quit Now" ) );
    689     g_signal_connect( w, "clicked", G_CALLBACK( do_exit_cb ), NULL );
    690     gtk_container_add( GTK_CONTAINER( b ), w );
    691     gtk_table_attach( GTK_TABLE(
    692                           p ), b, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 10 );
    693 
    694     gtk_widget_show_all( r );
    695 
    696     /* clear the UI */
    697     gtk_list_store_clear( GTK_LIST_STORE( tr_core_model( cbdata->core ) ) );
    698 
    699722    /* shut down libT */
    700     g_thread_create( quitThreadFunc, vdata, TRUE, NULL );
     723    g_thread_create( quitThreadFunc, core, TRUE, NULL );
    701724}
    702725
     
    10921115
    10931116        /* update the actions */
    1094         refreshTorrentActions( data );
     1117        refreshActions( data );
    10951118    }
    10961119
     
    11831206
    11841207static void
    1185 detailsClosed( gpointer  user_data,
    1186                GObject * details )
    1187 {
    1188     struct cbdata * data = user_data;
    1189     gpointer        hashString = g_hash_table_lookup( data->details2tor,
    1190                                                       details );
    1191 
    1192     g_hash_table_remove( data->details2tor, details );
    1193     g_hash_table_remove( data->tor2details, hashString );
    1194 }
    1195 
    1196 static void
    11971208openFolderForeach( GtkTreeModel *           model,
    11981209                   GtkTreePath  * path      UNUSED,
     
    12081219
    12091220static void
    1210 showInfoForeach( GtkTreeModel *      model,
    1211                  GtkTreePath  * path UNUSED,
    1212                  GtkTreeIter *       iter,
    1213                  gpointer            user_data )
    1214 {
    1215     const char *    hashString;
    1216     struct cbdata * data = user_data;
    1217     TrTorrent *     tor = NULL;
    1218     GtkWidget *     w;
    1219 
    1220     gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
    1221     hashString = tr_torrent_info( tor )->hashString;
    1222     w = g_hash_table_lookup( data->tor2details, hashString );
    1223     if( w != NULL )
    1224         gtk_window_present( GTK_WINDOW( w ) );
    1225     else
    1226     {
    1227         w = torrent_inspector_new( GTK_WINDOW( data->wind ), data->core, tor );
    1228         gtk_widget_show( w );
    1229         g_hash_table_insert( data->tor2details, (gpointer)hashString, w );
    1230         g_hash_table_insert( data->details2tor, w, (gpointer)hashString );
    1231         g_object_weak_ref( G_OBJECT( w ), detailsClosed, data );
    1232     }
    1233 
    1234     g_object_unref( G_OBJECT( tor ) );
    1235 }
    1236 
    1237 static void
    12381221recheckTorrentForeach( GtkTreeModel *      model,
    12391222                       GtkTreePath  * path UNUSED,
     
    13491332    else if( !strcmp( action_name, "show-torrent-properties" ) )
    13501333    {
    1351         GtkTreeSelection * s = tr_window_get_selection( data->wind );
    1352         gtk_tree_selection_selected_foreach( s, showInfoForeach, data );
     1334        if( data->details == NULL )
     1335            data->details = torrent_inspector_new( GTK_WINDOW( data->wind ), data->core );
     1336        refreshDetailsDialog( data );
     1337        gtk_widget_show( data->details );
    13531338    }
    13541339    else if( !strcmp( action_name, "update-tracker" ) )
     
    14361421        updatemodel( data );
    14371422}
    1438 
  • trunk/gtk/makemeta-ui.c

    r8181 r8279  
    427427    hig_workarea_add_section_title( t, &row, _( "Trackers" ) );
    428428
    429     w = tracker_list_new( NULL );
     429    w = tracker_list_new( session, -1, TRUE );
    430430
    431431    n = pref_int_get( ANNOUNCE_KEY"-count" );
  • trunk/gtk/tr-core.c

    r8178 r8279  
    14291429    sendRequest( core, buf, tag, blocklistResponseFunc, NULL );
    14301430}
     1431
     1432/***
     1433****
     1434***/
     1435
     1436void
     1437tr_core_exec_json( TrCore * core, const char * json )
     1438{
     1439    g_message( "executing %s", json );
     1440    sendRequest( core, json, 0, NULL, NULL );
     1441}
     1442
     1443void
     1444tr_core_exec( TrCore * core, const tr_benc * top )
     1445{
     1446    char * json = tr_bencToJSON( top );
     1447    tr_core_exec_json( core, json );
     1448    tr_free( json );
     1449}
  • trunk/gtk/tr-core.h

    r8174 r8279  
    190190void tr_core_blocklist_update( TrCore * core );
    191191
     192void tr_core_exec( TrCore * core, const tr_benc * benc );
     193
     194void tr_core_exec_json( TrCore * core, const char * json );
     195
     196
    192197/**
    193198***
  • trunk/gtk/tracker-list.c

    r7658 r8279  
    3333struct tracker_page
    3434{
    35     TrTorrent *         gtor;
     35    gboolean            isNew;
     36
     37    tr_session        * session;
     38    int                 torrentId;
    3639
    3740    GtkTreeView *       view;
     
    6063    TR_N_COLS
    6164};
     65
     66static tr_torrent * getTorrent( struct tracker_page * page )
     67{
     68    return tr_torrentFindFromId( page->session, page->torrentId );
     69}
    6270
    6371static void
     
    106114        gtk_tree_selection_count_selected_rows( sel ) > 0;
    107115    GtkTreeModel * model = GTK_TREE_MODEL( page->store );
    108     const int trackerCount = gtk_tree_model_iter_n_children( model, NULL );
    109     const gboolean ok_to_remove = !page->gtor || ( trackerCount > 1 );
     116    const int trackerCount = model ? gtk_tree_model_iter_n_children( model, NULL ) : 0;
     117    const gboolean ok_to_remove = page->isNew || ( trackerCount > 1 );
    110118    gtk_widget_set_sensitive( page->remove_button, has_selection && ok_to_remove );
    111119}
    112120
    113121static void
    114 onTrackerRemoveClicked( GtkButton * w UNUSED,
    115                         gpointer      gpage )
     122onTrackerRemoveClicked( GtkButton * w UNUSED, gpointer gpage )
    116123{
    117124    struct tracker_page * page = gpage;
     
    144151
    145152static void
    146 onTrackerAddClicked( GtkButton * w UNUSED,
    147                      gpointer      gpage )
     153onTrackerAddClicked( GtkButton * w UNUSED, gpointer gpage )
    148154{
    149155    GtkTreeIter           iter;
     
    194200
    195201        /* set the tracker list */
    196         tr_torrentSetAnnounceList( tr_torrent_handle( page->gtor ),
    197                                    trackers, n );
    198 
     202        tr_torrentSetAnnounceList( getTorrent( page ), trackers, n );
    199203
    200204        setTrackerChangeState( page, FALSE );
     
    212216{
    213217    struct tracker_page * page = gpage;
    214     GtkTreeModel *        model =
    215         tracker_model_new( tr_torrent_handle( page->gtor ) );
    216 
     218    GtkTreeModel * model;
     219
     220    model = tracker_model_new( getTorrent( page ) );
    217221    gtk_tree_view_set_model( page->view, model );
    218222    page->store = GTK_LIST_STORE( model );
    219223    g_object_unref( G_OBJECT( model ) );
     224
    220225    setTrackerChangeState( page, FALSE );
    221226}
     
    292297}
    293298
     299void
     300tracker_list_set_torrent( GtkWidget * w, int torrentId )
     301{
     302    struct tracker_page * page = g_object_get_data( G_OBJECT( w ), PAGE_KEY );
     303    GtkTreeModel * m;
     304
     305    m = tracker_model_new( tr_torrentFindFromId( page->session, torrentId ) );
     306    page->torrentId = torrentId;
     307    page->store = GTK_LIST_STORE( m );
     308    gtk_tree_view_set_model( GTK_TREE_VIEW( page->view ), m );
     309    g_object_unref( m );
     310}
     311
     312void
     313tracker_list_clear( GtkWidget * w )
     314{
     315    tracker_list_set_torrent( w, -1 );
     316}
     317
    294318GtkWidget*
    295 tracker_list_new( TrTorrent * gtor )
     319tracker_list_new( tr_session * session, int torrentId, gboolean isNew )
    296320{
    297321    GtkWidget *           w;
     
    300324    GtkWidget *           top;
    301325    GtkWidget *           fr;
    302     GtkTreeModel *        m;
    303326    GtkCellRenderer *     r;
    304327    GtkTreeViewColumn *   c;
     
    306329    struct tracker_page * page = g_new0( struct tracker_page, 1 );
    307330
    308     page->gtor = gtor;
     331    page->session = session;
    309332
    310333    top = gtk_hbox_new( FALSE, GUI_PAD );
     
    312335    buttons = gtk_vbox_new( TRUE, GUI_PAD );
    313336
    314     m = tracker_model_new( tr_torrent_handle( gtor ) );
    315     page->store = GTK_LIST_STORE( m );
    316     w = gtk_tree_view_new_with_model( m );
     337    //m = tracker_model_new( tr_torrent_handle( gtor ) );
     338    //page->store = GTK_LIST_STORE( m );
     339    w = gtk_tree_view_new( );
    317340    g_signal_connect( w, "button-release-event",
    318341                      G_CALLBACK( on_tree_view_button_released ), NULL );
     
    320343    gtk_tree_view_set_enable_search( page->view, FALSE );
    321344    r = gtk_cell_renderer_text_new( );
    322     g_object_set( G_OBJECT( r ),
    323                   "editable", TRUE,
    324                   NULL );
    325     g_signal_connect( r, "edited",
    326                       G_CALLBACK( onTierEdited ), page );
    327     c = gtk_tree_view_column_new_with_attributes( _( "Tier" ), r,
    328                                                   "text", TR_COL_TIER,
    329                                                   NULL );
     345    g_object_set( G_OBJECT( r ), "editable", TRUE, NULL );
     346    g_signal_connect( r, "edited", G_CALLBACK( onTierEdited ), page );
     347    c = gtk_tree_view_column_new_with_attributes( _( "Tier" ), r, "text", TR_COL_TIER, NULL );
    330348    gtk_tree_view_column_set_sort_column_id( c, TR_COL_TIER );
    331349    gtk_tree_view_append_column( page->view, c );
    332350    r = gtk_cell_renderer_text_new( );
    333     g_object_set( G_OBJECT( r ),
    334                   "editable", TRUE,
    335                   "ellipsize", PANGO_ELLIPSIZE_END,
    336                   NULL );
    337     g_signal_connect( r, "edited",
    338                       G_CALLBACK( onAnnounceEdited ), page );
    339     c = gtk_tree_view_column_new_with_attributes( _( "Announce URL" ), r,
    340                                                   "text", TR_COL_ANNOUNCE,
    341                                                   NULL );
     351    g_object_set( G_OBJECT( r ), "editable", TRUE, "ellipsize", PANGO_ELLIPSIZE_END, NULL );
     352    g_signal_connect( r, "edited", G_CALLBACK( onAnnounceEdited ), page );
     353    c = gtk_tree_view_column_new_with_attributes( _( "Announce URL" ), r, "text", TR_COL_ANNOUNCE, NULL );
    342354    gtk_tree_view_column_set_sort_column_id( c, TR_COL_ANNOUNCE );
    343355    gtk_tree_view_append_column( page->view, c );
    344356    w = gtk_scrolled_window_new( NULL, NULL );
    345     gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w ),
    346                                     GTK_POLICY_NEVER,
    347                                     GTK_POLICY_AUTOMATIC );
     357    gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w ), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC );
    348358    sel = gtk_tree_view_get_selection( page->view );
    349359    page->sel = sel;
    350     g_signal_connect( sel, "changed",
    351                       G_CALLBACK( onTrackerSelectionChanged ), page );
     360    g_signal_connect( sel, "changed", G_CALLBACK( onTrackerSelectionChanged ), page );
    352361    gtk_tree_selection_set_mode( sel, GTK_SELECTION_MULTIPLE );
    353362    gtk_container_add( GTK_CONTAINER( w ), GTK_WIDGET( page->view ) );
     
    356365    gtk_frame_set_shadow_type( GTK_FRAME( fr ), GTK_SHADOW_IN );
    357366    gtk_container_add( GTK_CONTAINER( fr ), w );
    358     g_object_unref( G_OBJECT( m ) );
    359367
    360368    w = gtk_button_new_from_stock( GTK_STOCK_ADD );
     
    367375    gtk_box_pack_start( GTK_BOX( buttons ), w, TRUE, TRUE, 0 );
    368376    page->remove_button = w;
    369     if( gtor )
     377
     378    if( !isNew )
    370379    {
    371380        w = gtk_button_new_from_stock( GTK_STOCK_SAVE );
     
    391400
    392401    g_object_set_data_full( G_OBJECT( top ), PAGE_KEY, page, g_free );
     402    tracker_list_set_torrent( top, torrentId );
    393403    return top;
    394404}
  • trunk/gtk/tracker-list.h

    r6998 r8279  
    33
    44#include <gtk/gtk.h>
    5 #include "tr-torrent.h"
     5#include <libtransmission/transmission.h>
    66
    7 GtkWidget*       tracker_list_new( TrTorrent * gtor );
     7void tracker_list_set_torrent( GtkWidget*, int torrentId );
     8
     9void tracker_list_clear( GtkWidget* );
     10
     11GtkWidget* tracker_list_new( tr_session*, int torrentId, gboolean isNew );
    812
    913/**
     
    1822                                int                     trackerCount );
    1923
     24
    2025#endif
  • trunk/libtransmission/torrent.h

    r8212 r8279  
    6060void        tr_torrentChangeMyPort( tr_torrent * session );
    6161
    62 tr_torrent* tr_torrentFindFromId( tr_session * session,
    63                                   int          id );
    64 
    6562tr_torrent* tr_torrentFindFromHash( tr_session *    session,
    6663                                    const uint8_t * hash );
  • trunk/libtransmission/transmission.h

    r8254 r8279  
    954954int tr_torrentId( const tr_torrent * torrent );
    955955
     956tr_torrent* tr_torrentFindFromId( tr_session * session, int id );
     957
     958
    956959/***
    957960****  Torrent speed limits
Note: See TracChangeset for help on using the changeset viewer.