]> granicus.if.org Git - transmission/commitdiff
(trunk Qt) sync the "trackers" tab with the GTK+ client and more.
authorCharles Kerr <charles@transmissionbt.com>
Tue, 27 Jul 2010 19:43:32 +0000 (19:43 +0000)
committerCharles Kerr <charles@transmissionbt.com>
Tue, 27 Jul 2010 19:43:32 +0000 (19:43 +0000)
17 files changed:
doc/rpc-spec.txt
libtransmission/rpcimpl.c
qt/details.cc
qt/details.h
qt/favicon.cc
qt/favicon.h
qt/qtr.pro
qt/session.cc
qt/session.h
qt/torrent.cc
qt/torrent.h
qt/tracker-delegate.cc [new file with mode: 0644]
qt/tracker-delegate.h [new file with mode: 0644]
qt/tracker-model.cc [new file with mode: 0644]
qt/tracker-model.h [new file with mode: 0644]
qt/transmission_en.ts
qt/transmission_ru.ts

index 26d65dddc041375b27d655ac5e81172f67fc85be..c47b12afa7d3a30ea19cbff3b7ad5b2767657eb4 100644 (file)
    "seedIdleMode"        | number     which seeding inactivity to use.  See tr_inactvelimit
    "seedRatioLimit"      | double     torrent-level seeding ratio
    "seedRatioMode"       | number     which ratio to use.  See tr_ratiolimit
-   "trackerAdd"          | object     (see below)
-   "trackerEdit"         | object     (see below)
-   "trackerRemove"       | object     (see below)
+   "trackerAdd"          | array      strings of URLs to add
+   "trackerRemove"       | array      strings of URLs to remove
+   "trackerReplace"      | array      pairs of old/new announce URLs
    "uploadLimit"         | number     maximum upload speed (KBps)
    "uploadLimited"       | boolean    true if "uploadLimit" is honored
-                         |
-   ----------------------+---------------------------------+
-   trackerAdd            | an object containing:           |
-                         +-----------------------+---------+
-                         | announce              | string  | announce URL of the tracker
-                         | tier (optional)       | number  | tier to add the tracker to
-   ----------------------+---------------------------------+
-   trackerEdit           | an object containing:           |
-                         +-----------------------+---------+
-                         | announce (or id)      | string  | announce URL of the tracker to modify
-                         | id (or announce)      | number  | trackerId of the tracker to modify (see trackerStats)
-                         +-----------------------+---------+
-                         | announce-new          | string  | new announce URL for the tracker
-                         | tier                  | number  | tier to change the tracker to
-   ----------------------+---------------------------------+
-   trackerRemove         | an object containing:           |
-                         +-----------------------+---------+
-                         | announce (or id)      | string  | announce URL of the tracker to remove
-                         | id (or announce)      | number  | trackerId of the tracker to remove (see trackerStats)
-                         +-----------------------+---------+
 
    Just as an empty "ids" value is shorthand for "all ids", using an empty array
    for "files-wanted", "files-unwanted", "priority-high", "priority-low", or
index d324575e90ac50cd22bb81b5cd50284032e19f96..eaa9418d272f4b3c94a869b89091502aba4d7926 100644 (file)
@@ -757,20 +757,17 @@ setFileDLs( tr_torrent * tor,
 }
 
 static tr_bool
-findTrackerById( const tr_info * inf,
-                 uint32_t id,
-                 int * index )
+findAnnounceUrl( const tr_tracker_info * t, int n, const char * url, int * pos )
 {
     int i;
     tr_bool found = FALSE;
 
-    for( i = 0; i < inf->trackerCount; ++i )
+    for( i=0; i<n; ++i )
     {
-        const tr_tracker_info * t = &inf->trackers[i];
-        if( t->id == id )
+        if( !strcmp( t[i].announce, url ) )
         {
-            if( index ) *index = i;
             found = TRUE;
+            if( pos ) *pos = i;
             break;
         }
     }
@@ -778,196 +775,157 @@ findTrackerById( const tr_info * inf,
     return found;
 }
 
-static tr_bool
-findTrackerByURL( const tr_info * inf,
-                  const char * url,
-                  int * index )
+static int
+copyTrackers( tr_tracker_info * tgt, const tr_tracker_info * src, int n )
 {
     int i;
-    tr_bool found = FALSE;
-
-    for( i = 0; i < inf->trackerCount; ++i )
+    int maxTier = -1;
+   
+    for( i=0; i<n; ++i ) 
     {
-        const tr_tracker_info * t = &inf->trackers[i];
-        if( !strcmp( t->announce, url ) )
-        {
-            if( index ) *index = i;
-            found = TRUE;
-            break;
-        }
+        tgt[i].tier = src[i].tier;
+        tgt[i].announce = tr_strdup( src[i].announce );
+        maxTier = MAX( maxTier, src[i].tier );
     }
 
-    return found;
+    return maxTier;
+}
+
+static void
+freeTrackers( tr_tracker_info * trackers, int n )
+{
+    int i;
+
+    for( i=0; i<n; ++i )
+        tr_free( trackers[i].announce );
+
+    tr_free( trackers );
 }
 
 static const char*
-addTracker( tr_torrent * tor,
-            tr_benc    * tracker )
+addTrackerUrls( tr_torrent * tor, tr_benc * urls )
 {
     int i;
-    int64_t tmp;
-    tr_bool duplicate = FALSE;
-    const char * errmsg = NULL;
-    const char * announce;
+    int n;
+    int tier;
+    tr_benc * val;
+    tr_tracker_info * trackers;
+    tr_bool changed = FALSE;
     const tr_info * inf = tr_torrentInfo( tor );
+    const char * errmsg = NULL;
 
-    if( !tr_bencDictFindStr( tracker, "announce", &announce ) )
-        return "no announce url supplied";
-
-    duplicate = findTrackerByURL( inf, announce, NULL );
+    /* make a working copy of the existing announce list */
+    n = inf->trackerCount;
+    trackers = tr_new0( tr_tracker_info, n + tr_bencListSize( urls ) );
+    tier = copyTrackers( trackers, inf->trackers, n );
 
-    if( !duplicate )
+    /* and add the new ones */
+    i = 0;
+    while(( val = tr_bencListChild( urls, i++ ) ))
     {
-        int tier, trackerCount;
-        tr_tracker_info * trackers = tr_new0( tr_tracker_info, inf->trackerCount + 1 );
-
-        if( tr_bencDictFindInt( tracker, "tier", &tmp ) )
-            tier = (int)tmp;
-        else
-            tier = -1;
+        const char * announce = NULL;
 
-        for( i = 0; i < inf->trackerCount; ++i )
+        if(    tr_bencGetStr( val, &announce )
+            && tr_urlIsValid( announce )
+            && !findAnnounceUrl( trackers, n, announce, NULL ) )
         {
-            const tr_tracker_info * t = &inf->trackers[i];
-            trackers[i].tier = t->tier;
-            trackers[i].announce = tr_strdup( t->announce );
+            trackers[n].tier = ++tier; /* add a new tier */
+            trackers[n].announce = tr_strdup( announce );
+            ++n;
+            changed = TRUE;
         }
-        trackers[i].tier = tier < 0 ? trackers[i-1].tier + 1 : tier;
-        trackers[i].announce = tr_strdup( announce );
-        trackerCount = inf->trackerCount + 1;
-
-        if( !tr_torrentSetAnnounceList( tor, trackers, trackerCount ) )
-            errmsg = "tracker URL was invalid";
-
-        for( i = 0; i < trackerCount; ++i )
-            tr_free( trackers[i].announce );
-        tr_free( trackers );
     }
-    else
-        errmsg = "tracker already exists";
 
+    if( !changed )
+        errmsg = "invalid argument";
+    else if( !tr_torrentSetAnnounceList( tor, trackers, n ) )
+        errmsg = "error setting announce list";
+
+    freeTrackers( trackers, n );
     return errmsg;
 }
 
 static const char*
-editTracker( tr_torrent * tor,
-             tr_benc    * tracker )
+replaceTrackerUrls( tr_torrent * tor, tr_benc * urls )
 {
-    int trackerIndex;
-    int64_t tmp;
-    tr_bool found = FALSE;
-    const char * errmsg = NULL;
-    const char * announce;
+    int i;
+    tr_benc * pair[2];
+    tr_tracker_info * trackers;
+    tr_bool changed = FALSE;
     const tr_info * inf = tr_torrentInfo( tor );
+    const int n = inf->trackerCount;
+    const char * errmsg = NULL;
 
-    if( tr_bencDictFindInt( tracker, "id", &tmp ) )
-        found = findTrackerById( inf, (uint32_t)tmp, &trackerIndex );
-    else if( tr_bencDictFindStr( tracker, "announce", &announce ) )
-        found = findTrackerByURL( inf, announce, &trackerIndex );
-    else
-        errmsg = "no tracker supplied";
+    /* make a working copy of the existing announce list */
+    trackers = tr_new0( tr_tracker_info, n );
+    copyTrackers( trackers, inf->trackers, n );
 
-    if( found )
+    /* make the substitutions... */
+    i = 0;
+    while(((pair[0] = tr_bencListChild(urls,i))) &&
+          ((pair[1] = tr_bencListChild(urls,i+1))))
     {
-        int tier;
-        const char * new;
-        tr_bool rename = FALSE;
-        tr_bool move = FALSE;
-
-        if( tr_bencDictFindStr( tracker, "announce-new", &new ) )
+        const char * oldval;
+        const char * newval;
+
+        if(    tr_bencGetStr( pair[0], &oldval )
+            && tr_bencGetStr( pair[1], &newval )
+            && strcmp( oldval, newval )
+            && tr_urlIsValid( newval )
+            && findAnnounceUrl( trackers, n, oldval, &i ) )
         {
-            rename = !findTrackerByURL( inf, new, NULL );
-            if( !rename )
-                errmsg = "tracker already exists";
-        }
-        if( tr_bencDictFindInt( tracker, "tier", &tmp ) )
-        {
-            tier = (int)tmp;
-            move = TRUE;
+            tr_free( trackers[i].announce );
+            trackers[i].announce = tr_strdup( newval );
+            changed = TRUE;
         }
 
-        if( ( rename || move ) && !errmsg )
-        {
-            int i, trackerCount;
-            tr_tracker_info * trackers = tr_new0( tr_tracker_info, inf->trackerCount );
-
-            for( i = 0; i < inf->trackerCount; ++i )
-            {
-                const tr_tracker_info * t = &inf->trackers[i];
-                if( i != trackerIndex )
-                {
-                    trackers[i].tier = t->tier;
-                    trackers[i].announce = tr_strdup( t->announce );
-                }
-                else
-                {
-                    trackers[i].tier = move ? tier : t->tier;
-                    trackers[i].announce = tr_strdup( rename ? new : t->announce );
-                }
-            }
-            trackerCount = i;
-
-            if( !tr_torrentSetAnnounceList( tor, trackers, trackerCount ) )
-                errmsg = "error setting announce list";
-
-            for( i = 0; i < trackerCount; ++i )
-                tr_free( trackers[i].announce );
-            tr_free( trackers );
-        }
-        else if( !errmsg )
-            errmsg = "no operation supplied";
+        i += 2;
     }
-    else
-        errmsg = "tracker doesn't exists";
 
+    if( !changed )
+        errmsg = "invalid argument";
+    else if( !tr_torrentSetAnnounceList( tor, trackers, n ) )
+        errmsg = "error setting announce list";
+
+    freeTrackers( trackers, n );
     return errmsg;
 }
 
 static const char*
-removeTracker( tr_torrent * tor,
-               tr_benc * tracker  )
+removeTrackerUrls( tr_torrent * tor, tr_benc * urls )
 {
-    int trackerIndex;
-    int64_t tmp;
-    tr_bool found = FALSE;
-    const char * errmsg = NULL;
-    const char * announce;
+    int i;
+    int n;
+    tr_benc * val;
+    tr_tracker_info * trackers;
+    tr_bool changed = FALSE;
     const tr_info * inf = tr_torrentInfo( tor );
+    const char * errmsg = NULL;
 
-    if( tr_bencDictFindInt( tracker, "id", &tmp ) )
-        found = findTrackerById( inf, (uint32_t)tmp, &trackerIndex );
-    else if( tr_bencDictFindStr( tracker, "announce", &announce ) )
-        found = findTrackerByURL( inf, announce, &trackerIndex );
-    else
-        errmsg = "no tracker supplied";
+    /* make a working copy of the existing announce list */
+    n = inf->trackerCount;
+    trackers = tr_new0( tr_tracker_info, n );
+    copyTrackers( trackers, inf->trackers, n );
 
-    if( found )
+    /* remove the ones specified in the urls list */
+    i = 0;
+    while(( val = tr_bencListChild( urls, i++ ))) 
     {
-        int i, j, trackerCount;
-        tr_tracker_info * trackers = tr_new0( tr_tracker_info, inf->trackerCount - 1 );
-
-        for( i = 0, j = 0; i < inf->trackerCount; ++i )
+        int pos;
+        const char * url;
+        if( tr_bencGetStr( val, &url ) && findAnnounceUrl( trackers, n, url, &pos ) )
         {
-            if( i != trackerIndex )
-            {
-                const tr_tracker_info * t = &inf->trackers[i];
-                trackers[j].tier = t->tier;
-                trackers[j].announce = tr_strdup( t->announce );
-                ++j;
-            }
+            tr_removeElementFromArray( trackers, pos, sizeof( tr_tracker_info ), n-- );
+            changed = TRUE;
         }
-        trackerCount = j;
-
-        if( !tr_torrentSetAnnounceList( tor, trackers, trackerCount ) )
-            errmsg = "error setting announce list";
-
-        for( i = 0; i < trackerCount; ++i )
-            tr_free( trackers[i].announce );
-        tr_free( trackers );
     }
-    else
-        errmsg = "tracker doesn't exists";
 
+    if( !changed )
+        errmsg = "invalid argument";
+    else if( !tr_torrentSetAnnounceList( tor, trackers, n ) )
+        errmsg = "error setting announce list";
+
+    freeTrackers( trackers, n );
     return errmsg;
 }
 
@@ -988,7 +946,7 @@ torrentSet( tr_session               * session,
         int64_t      tmp;
         double       d;
         tr_benc *    files;
-        tr_benc *    tracker;
+        tr_benc *    urls;
         tr_bool      boolVal;
         tr_torrent * tor = torrents[i];
 
@@ -1025,12 +983,12 @@ torrentSet( tr_session               * session,
             tr_torrentSetRatioLimit( tor, d );
         if( tr_bencDictFindInt( args_in, "seedRatioMode", &tmp ) )
             tr_torrentSetRatioMode( tor, tmp );
-        if( !errmsg && tr_bencDictFindDict( args_in, "trackerAdd", &tracker ) )
-            errmsg = addTracker( tor, tracker );
-        if( !errmsg && tr_bencDictFindDict( args_in, "trackerEdit", &tracker ) )
-            errmsg = editTracker( tor, tracker );
-        if( !errmsg && tr_bencDictFindDict( args_in, "trackerRemove", &tracker ) )
-            errmsg = removeTracker( tor, tracker );
+        if( !errmsg && tr_bencDictFindList( args_in, "trackerAdd", &urls ) )
+            errmsg = addTrackerUrls( tor, urls );
+        if( !errmsg && tr_bencDictFindList( args_in, "trackerRemove", &urls ) )
+            errmsg = removeTrackerUrls( tor, urls );
+        if( !errmsg && tr_bencDictFindList( args_in, "trackerReplace", &urls ) )
+            errmsg = replaceTrackerUrls( tor, urls );
         notify( session, TR_RPC_TORRENT_CHANGED, tor );
     }
 
index 87e593632d6610fed6d49bdd9522e057319a9ffb..eaf0d53273c947c6b7c75c3bfa65e5cbe0ec452e 100644 (file)
@@ -12,7 +12,6 @@
 
 #include <cassert>
 #include <ctime>
-#include <iostream>
 
 #include <QCheckBox>
 #include <QComboBox>
 #include <QHBoxLayout>
 #include <QHeaderView>
 #include <QInputDialog>
+#include <QItemSelectionModel>
 #include <QLabel>
+#include <QList>
+#include <QMap>
 #include <QMessageBox>
 #include <QPushButton>
 #include <QRadioButton>
 #include <QResizeEvent>
 #include <QSpinBox>
+#include <QStringList>
 #include <QStyle>
 #include <QTabWidget>
 #include <QTextBrowser>
@@ -53,6 +56,8 @@
 #include "squeezelabel.h"
 #include "torrent.h"
 #include "torrent-model.h"
+#include "tracker-delegate.h"
+#include "tracker-model.h"
 
 class Prefs;
 class Session;
@@ -163,7 +168,13 @@ Details :: Details( Session& session, Prefs& prefs, TorrentModel& model, QWidget
     layout->addWidget( buttons );
     QWidget::setAttribute( Qt::WA_DeleteOnClose, true );
 
+    QList<int> initKeys;
+    initKeys << Prefs :: SHOW_TRACKER_SCRAPES;
+    foreach( int key, initKeys )
+        refreshPref( key );
+
     connect( &myTimer, SIGNAL(timeout()), this, SLOT(onTimer()));
+    connect( &myPrefs, SIGNAL(changed(int)), this, SLOT(refreshPref(int)) );
 
     onTimer( );
     myTimer.setSingleShot( false );
@@ -190,7 +201,6 @@ Details :: setIds( const QSet<int>& ids )
     }
 
     myFileTreeView->clear( );
-
     myIds = ids;
 
     // listen to the new torrents
@@ -206,6 +216,24 @@ Details :: setIds( const QSet<int>& ids )
     onTimer( );
 }
 
+void
+Details :: refreshPref( int key )
+{
+    QString str;
+
+    switch( key )
+    {
+        case Prefs::SHOW_TRACKER_SCRAPES:
+            myTrackerDelegate->setShowMore( myPrefs.getBool( key ) );
+            myTrackerView->update( );
+            break;
+
+        default:
+            break;
+    }
+}
+
+
 /***
 ****
 ***/
@@ -219,6 +247,12 @@ Details :: timeToStringRounded( int seconds )
 
 void
 Details :: onTimer( )
+{
+    getNewData( );
+}
+
+void
+Details :: getNewData( )
 {
     if( !myIds.empty( ) )
     {
@@ -680,190 +714,11 @@ Details :: refresh( )
         myIdleSpin->blockSignals( false );
     }
 
-    // tracker tab
-    //
-    QMap<QString,QTreeWidgetItem*> trackerTiers;
-    QMap<QString,QTreeWidgetItem*> trackerItems;
-    const time_t now( time( 0 ) );
-    const bool showScrape = myPrefs.getBool( Prefs::SHOW_TRACKER_SCRAPES );
-    foreach( const Torrent * t, torrents )
-    {
-        const QString idStr( QString::number( t->id( ) ) );
-        const TrackerStatsList trackerStats = t->trackerStats( );
-
-        foreach( const TrackerStat& trackerStat, trackerStats )
-        {
-            QFont font;
-            QString str;
-            const QString tierKey( QString::number(trackerStat.tier) );
-            QTreeWidgetItem * tier = (QTreeWidgetItem*) myTrackerTiers.value( tierKey, 0 );
-
-            if( tier == 0 ) // check if has tier been created this pass
-                tier = (QTreeWidgetItem*) trackerTiers.value( tierKey, 0 );
-
-            if( tier == 0 ) // new tier
-            {
-                QFont tierFont;
-                tier = new QTreeWidgetItem( myTrackerTree );
-                myTrackerTree->addTopLevelItem( tier );
-                str = "Tier: " + QString::number( trackerStat.tier + 1 );
-                tier->setText( 0, str );
-                tierFont.setBold( true );
-                tier->setFont( 0, tierFont );
-            }
-
-            const QString key( idStr + tierKey + ":" + QString::number( trackerStat.id ) );
-            QTreeWidgetItem * item = (QTreeWidgetItem*) myTrackerItems.value( key, 0 );
-
-            if( item == 0 ) // new tracker
-            {
-                item = new QTreeWidgetItem( tier );
-                tier->addChild( item );
-                if( tier->childCount() == 1 )
-                    tier->setExpanded( true );
-            }
-            str = trackerStat.host;
+    ///
+    ///  Tracker tab
+    ///
 
-            if( trackerStat.isBackup )
-            {
-                font.setItalic( true );
-                if( showScrape )
-                {
-                    str += "\n";
-                    str += "Tracker will be used as a backup";
-                }
-            }
-            else
-            {
-                font.setItalic( false );
-                if( trackerStat.hasAnnounced )
-                {
-                    const QString tstr( timeToStringRounded( now - trackerStat.lastAnnounceTime ) );
-                    str += "\n";
-                    if( trackerStat.lastAnnounceSucceeded )
-                    {
-                        str += tr( "Got a list of %1 peers %2 ago" )
-                            .arg( trackerStat.lastAnnouncePeerCount )
-                            .arg( tstr );
-                    }
-                    else if( trackerStat.lastAnnounceTimedOut )
-                    {
-                        str += tr( "Peer list request timed out %1 ago; will retry" )
-                            .arg( tstr );
-                    }
-                    else
-                    {
-                        str += tr( "Got an error %1 ago" )
-                            .arg( tstr );
-                    }
-                }
-                switch( trackerStat.announceState )
-                {
-                    case TR_TRACKER_INACTIVE:
-                        if( trackerStat.hasAnnounced )
-                        {
-                            str += "\n";
-                            str += tr( "No updates scheduled" );
-                        }
-                        break;
-                    case TR_TRACKER_WAITING:
-                        {
-                            const QString tstr( timeToStringRounded( trackerStat.nextAnnounceTime - now ) );
-                            str += "\n";
-                            str += tr( "Asking for more peers in %1" )
-                                .arg( tstr );
-                        }
-                        break;
-                    case TR_TRACKER_QUEUED:
-                        str += "\n";
-                        str += tr( "Queued to ask for more peers" );
-                        break;
-                    case TR_TRACKER_ACTIVE:
-                        {
-                            const QString tstr( timeToStringRounded( now - trackerStat.lastAnnounceStartTime ) );
-                            str += "\n";
-                            str += tr( "Asking for more peers now... %1" )
-                                .arg( tstr );
-                        }
-                        break;
-                }
-                if( showScrape )
-                {
-                    if( trackerStat.hasScraped )
-                    {
-                        const QString tstr( timeToStringRounded( now - trackerStat.lastScrapeTime ) );
-                        str += "\n";
-                        if( trackerStat.lastScrapeSucceeded )
-                        {
-                            str += tr( "Tracker had %1 seeders and %2 leechers %3 ago" )
-                                .arg( trackerStat.seederCount )
-                                .arg( trackerStat.leecherCount )
-                                .arg( tstr );
-                        }
-                        else
-                        {
-                            str += tr( "Got a scrape error %1 ago" )
-                                .arg( tstr );
-                        }
-                    }
-                    switch( trackerStat.scrapeState )
-                    {
-                        case TR_TRACKER_INACTIVE:
-                            break;
-                        case TR_TRACKER_WAITING:
-                            {
-                                const QString tstr( timeToStringRounded( trackerStat.nextScrapeTime - now ) );
-                                str += "\n";
-                                str += tr( "Asking for peer counts in %1" )
-                                    .arg( tstr );
-                            }
-                            break;
-                        case TR_TRACKER_QUEUED:
-                            str += "\n";
-                            str += tr( "Queued to ask for peer counts" );
-                            break;
-                        case TR_TRACKER_ACTIVE:
-                            {
-                                const QString tstr( timeToStringRounded( now - trackerStat.lastScrapeStartTime ) );
-                                str += "\n";
-                                str += tr( "Asking for peer counts now... %1" )
-                                    .arg( tstr );
-                            }
-                            break;
-                    }
-                }
-            }
-            item->setText( 0, str );
-            item->setFont( 0, font );
-            item->setData( 0, TRACKERID, trackerStat.id );
-            item->setData( 0, TRACKERURL, trackerStat.announce );
-            item->setData( 0, TRACKERTIER, trackerStat.tier );
-            item->setData( 0, TORRENTID, t->id() );
-
-            tier->setData( 0, TRACKERID, -1 );
-            tier->setData( 0, TRACKERURL, QString() );
-            tier->setData( 0, TRACKERTIER, trackerStat.tier );
-            tier->setData( 0, TORRENTID, torrents.count() > 1 ? -1 : t->id() );
-
-            trackerTiers.insert( tierKey, tier );
-            trackerItems.insert( key, item );
-        }
-    }
-    QList<QTreeWidgetItem*> tierList = trackerTiers.values();
-    QList<QTreeWidgetItem*> itemList = trackerItems.values();
-    for( int i = 0; i < myTrackerTree->topLevelItemCount(); ++i )
-    {
-        QTreeWidgetItem * tier = myTrackerTree->topLevelItem( i );
-        for( int j = 0; j < tier->childCount(); ++j )
-        {
-            if( !itemList.contains( tier->child( j ) ) ) // tracker has disappeared
-                delete tier->takeChild( j-- );
-        }
-        if( !tierList.contains( tier ) ) // tier has disappeared
-            delete myTrackerTree->takeTopLevelItem( i-- );
-    }
-    myTrackerTiers = trackerTiers;
-    myTrackerItems = trackerItems;
+    myTrackerModel->refresh( myModel, myIds );
 
     ///
     ///  Peers tab
@@ -1014,26 +869,31 @@ void
 Details :: onHonorsSessionLimitsToggled( bool val )
 {
     mySession.torrentSet( myIds, "honorsSessionLimits", val );
+    getNewData( );
 }
 void
 Details :: onDownloadLimitedToggled( bool val )
 {
     mySession.torrentSet( myIds, "downloadLimited", val );
+    getNewData( );
 }
 void
 Details :: onDownloadLimitChanged( int val )
 {
     mySession.torrentSet( myIds, "downloadLimit", val );
+    getNewData( );
 }
 void
 Details :: onUploadLimitedToggled( bool val )
 {
     mySession.torrentSet( myIds, "uploadLimited", val );
+    getNewData( );
 }
 void
 Details :: onUploadLimitChanged( int val )
 {
     mySession.torrentSet( myIds, "uploadLimit", val );
+    getNewData( );
 }
 
 void
@@ -1041,12 +901,14 @@ Details :: onIdleModeChanged( int index )
 {
     const int val = myIdleCombo->itemData( index ).toInt( );
     mySession.torrentSet( myIds, "seedIdleMode", val );
+    getNewData( );
 }
 
 void
 Details :: onIdleLimitChanged( int val )
 {
     mySession.torrentSet( myIds, "seedIdleLimit", val );
+    getNewData( );
 }
 
 void
@@ -1060,12 +922,14 @@ void
 Details :: onRatioLimitChanged( double val )
 {
     mySession.torrentSet( myIds, "seedRatioLimit", val );
+    getNewData( );
 }
 
 void
 Details :: onMaxPeersChanged( int val )
 {
     mySession.torrentSet( myIds, "peer-limit", val );
+    getNewData( );
 }
 
 void
@@ -1075,120 +939,114 @@ Details :: onBandwidthPriorityChanged( int index )
     {
         const int priority = myBandwidthPriorityCombo->itemData(index).toInt( );
         mySession.torrentSet( myIds, "bandwidthPriority", priority );
+        getNewData( );
     }
 }
 
 void
 Details :: onTrackerSelectionChanged( )
 {
-    const QList<QTreeWidgetItem*> items = myTrackerTree->selectedItems();
-    if( items.count() == 1 )
-        myEditTrackerButton->setEnabled( items.first()->data( 0, TRACKERID ).toInt() >= 0 );
-    else
-        myEditTrackerButton->setEnabled( false );
-    myRemoveTrackerButton->setEnabled( !items.isEmpty() );
+    const int selectionCount = myTrackerView->selectionModel()->selectedRows().size();
+    myEditTrackerButton->setEnabled( selectionCount == 1 );
+    myRemoveTrackerButton->setEnabled( selectionCount > 0 );
 }
 
-bool
-Details :: findTrackerByURL( const QString& url, int torId )
+void
+Details :: onAddTrackerClicked( )
 {
-    bool duplicate = false;
-    foreach( QTreeWidgetItem * tracker, myTrackerItems.values() )
+    bool ok = false;
+    const QString url = QInputDialog::getText( this,
+                                               tr( "Add URL " ),
+                                               tr( "Add tracker announce URL:" ),
+                                               QLineEdit::Normal, QString(), &ok );
+    if( !ok )
     {
-        if( tracker->data( 0, TRACKERURL ).toString() == url &&
-          ( torId == -1 || tracker->data( 0, TORRENTID ).toInt() == torId ) )
-        {
-            duplicate = true;
-            break;
-        }
+        // user pressed "cancel" -- noop
     }
-    return duplicate;
-}
-
-void
-Details :: onAddTrackerPushed( )
-{
-    const QString urlString = QInputDialog::getText( this,
-                                                     tr( "Add tracker announce URL " ),
-                                                     NULL );
-    if( !urlString.isEmpty() )
+    else if( !QUrl(url).isValid( ) )
     {
-        if( !findTrackerByURL( urlString, -1 ) )
-        {
-            QByteArray url = urlString.toUtf8();
-            tr_benc top;
+        QMessageBox::warning( this, tr( "Error" ), tr( "Invalid URL \"%1\"" ).arg( url ) );
+    }
+    else
+    {
+        QSet<int> ids;
 
-            tr_bencInitDict( &top, 1 );
-            tr_bencDictAddStr( &top, "announce", url );
+        foreach( int id, myIds )
+            if( myTrackerModel->find( id, url ) == -1 )
+                ids.insert( id );
 
-            mySession.torrentSet( myIds, "trackerAdd", &top );
+        if( ids.empty( ) ) // all the torrents already have this tracker
+        {
+            QMessageBox::warning( this, tr( "Error" ), tr( "Tracker already exists." ) );
         }
         else
-            QMessageBox::warning( this, "Error", "Tracker already exists." );
+        {
+            QStringList urls;
+            urls << url;
+            mySession.torrentSet( ids, "trackerAdd", urls );
+            getNewData( );
+        }
     }
 }
 
 void
-Details :: onEditTrackerPushed( )
+Details :: onEditTrackerClicked( )
 {
-    const QTreeWidgetItem * item = myTrackerTree->selectedItems().first();
-    const QString urlString = QInputDialog::getText( this,
-                                                     tr( "Edit tracker announce URL " ),
-                                                     NULL,
-                                                     QLineEdit::Normal,
-                                                     item->data( 0, TRACKERURL ).toString() );
-    if( !urlString.isEmpty() )
+    QItemSelectionModel * selectionModel = myTrackerView->selectionModel( );
+    QModelIndexList selectedRows = selectionModel->selectedRows( );
+    assert( selectedRows.size( ) == 1 );
+    QModelIndex i = selectionModel->currentIndex( );
+    const TrackerInfo trackerInfo = myTrackerModel->data( i, TrackerModel::TrackerRole ).value<TrackerInfo>();
+
+    bool ok = false;
+    const QString newval = QInputDialog::getText( this,
+                                                  tr( "Edit URL " ),
+                                                  tr( "Edit tracker announce URL:" ),
+                                                  QLineEdit::Normal,
+                                                  trackerInfo.st.announce, &ok );
+
+    if( !ok )
     {
-        const int torId = item->data( 0, TORRENTID ).toInt();
-        if( !findTrackerByURL( urlString, torId ) )
-        {
-            QByteArray url = urlString.toUtf8();
-            QSet<int> ids;
-            tr_benc top;
+        // user pressed "cancel" -- noop
+    }
+    else if( !QUrl(newval).isValid( ) )
+    {
+        QMessageBox::warning( this, tr( "Error" ), tr( "Invalid URL \"%1\"" ).arg( newval ) );
+    }
+    else
+    {
+        QSet<int> ids;
+        ids << trackerInfo.torrentId;
 
-            ids << torId;
-            tr_bencInitDict( &top, 2 );
-            tr_bencDictAddStr( &top, "announce", item->data( 0, TRACKERURL ).toByteArray() );
-            tr_bencDictAddStr( &top, "announce-new", url );
+        QStringList urls;
+        urls << trackerInfo.st.announce;
+        urls << newval;
 
-            mySession.torrentSet( ids, "trackerEdit", &top );
-        }
-        else
-            QMessageBox::warning( this, "Error", "Tracker already exists." );
+        mySession.torrentSet( ids, "trackerReplace", urls );
+        getNewData( );
     }
 }
 
 void
-Details :: removeTracker( const QTreeWidgetItem * item )
+Details :: onRemoveTrackerClicked( )
 {
-    QByteArray url = item->data( 0, TRACKERURL ).toByteArray();
-    const int torId = item->data( 0, TORRENTID ).toInt();
-    QSet<int> ids;
-    tr_benc top;
-
-    ids << torId;
-    tr_bencInitDict( &top, 1 );
-    tr_bencDictAddStr( &top, "announce", url );
-
-    mySession.torrentSet( ids, "trackerRemove", &top );
-}
+    // make a map of torrentIds to announce URLs to remove
+    QItemSelectionModel * selectionModel = myTrackerView->selectionModel( );
+    QModelIndexList selectedRows = selectionModel->selectedRows( );
+    QMap<int,QStringList> torrentId_to_urls;
+    foreach( QModelIndex i, selectedRows )
+    {
+        const TrackerInfo inf = myTrackerModel->data( i, TrackerModel::TrackerRole ).value<TrackerInfo>();
+        torrentId_to_urls[ inf.torrentId ].append( inf.st.announce );
+    }
 
-void
-Details :: onRemoveTrackerPushed( )
-{
-    const QList<QTreeWidgetItem*> items = myTrackerTree->selectedItems();
-    QSet<int> removedTiers;
-    foreach( const QTreeWidgetItem * item, items ) {
-        const bool isTier = item->data( 0, TRACKERID ).toInt() == -1;
-        const int curTier = item->data( 0, TRACKERTIER ).toInt();
-        if( isTier )
-        {
-            removedTiers << curTier;
-            for( int i = 0; i < item->childCount(); ++i )
-                removeTracker( item->child( i ) );
-        }
-        else if( !removedTiers.contains( curTier ) ) // skip trackers removed by clearing a tier
-            removeTracker( item );
+    // batch all of a tracker's torrents into one command
+    foreach( int id, torrentId_to_urls.keys( ) )
+    {
+        QSet<int> ids;
+        ids << id;
+        mySession.torrentSet( ids, "trackerRemove", torrentId_to_urls.value( id ) );
+        getNewData( );
     }
 }
 
@@ -1306,25 +1164,24 @@ Details :: createTrackerTab( )
 
     v2->setSpacing( HIG::PAD );
 
-    QStringList headers;
-    headers << tr("Trackers");
-    myTrackerTree = new QTreeWidget;
-    myTrackerTree->setHeaderLabels( headers );
-    myTrackerTree->setSelectionMode( QTreeWidget::ExtendedSelection );
-    myTrackerTree->setRootIsDecorated( false );
-    myTrackerTree->setIndentation( 2 );
-    myTrackerTree->setItemsExpandable( false );
-    myTrackerTree->setTextElideMode( Qt::ElideRight );
-    myTrackerTree->setAlternatingRowColors( true );
-    connect( myTrackerTree, SIGNAL(itemSelectionChanged()), this, SLOT(onTrackerSelectionChanged()));
-    h->addWidget( myTrackerTree, 1 );
+    myTrackerView = new QTreeView;
+    myTrackerView->setModel( myTrackerModel = new TrackerModel );
+    myTrackerView->setHeaderHidden( true );
+    myTrackerView->setSelectionMode( QTreeWidget::ExtendedSelection );
+    myTrackerView->setRootIsDecorated( false );
+    myTrackerView->setIndentation( 2 );
+    myTrackerView->setItemsExpandable( false );
+    myTrackerView->setAlternatingRowColors( true );
+    myTrackerView->setItemDelegate( myTrackerDelegate = new TrackerDelegate( ) );
+    connect( myTrackerView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, SLOT(onTrackerSelectionChanged()));
+    h->addWidget( myTrackerView, 1 );
 
     p = new QPushButton();
     p->setIcon( getStockIcon( "list-add", QStyle::SP_DialogOpenButton ) );
     p->setToolTip( "Add Tracker" );
     myAddTrackerButton = p;
     v2->addWidget( p, 1 );
-    connect( p, SIGNAL(clicked(bool)), this, SLOT(onAddTrackerPushed()));
+    connect( p, SIGNAL(clicked(bool)), this, SLOT(onAddTrackerClicked()));
 
     p = new QPushButton();
     p->setIcon( getStockIcon( "document-properties", QStyle::SP_DesktopIcon ) );
@@ -1333,7 +1190,7 @@ Details :: createTrackerTab( )
     p->setEnabled( false );
     myEditTrackerButton = p;
     v2->addWidget( p, 1 );
-    connect( p, SIGNAL(clicked(bool)), this, SLOT(onEditTrackerPushed()));
+    connect( p, SIGNAL(clicked(bool)), this, SLOT(onEditTrackerClicked()));
 
     p = new QPushButton();
     p->setIcon( getStockIcon( "list-remove", QStyle::SP_TrashIcon ) );
@@ -1341,7 +1198,7 @@ Details :: createTrackerTab( )
     p->setEnabled( false );
     myRemoveTrackerButton = p;
     v2->addWidget( p, 1 );
-    connect( p, SIGNAL(clicked(bool)), this, SLOT(onRemoveTrackerPushed()));
+    connect( p, SIGNAL(clicked(bool)), this, SLOT(onRemoveTrackerClicked()));
 
     v2->addStretch( 1 );
 
@@ -1428,6 +1285,7 @@ Details :: onFilePriorityChanged( const QSet<int>& indices, int priority )
         default:           key = "priority-normal"; break;
     }
     mySession.torrentSet( myIds, key, indices.toList( ) );
+    getNewData( );
 }
 
 void
@@ -1435,4 +1293,5 @@ Details :: onFileWantedChanged( const QSet<int>& indices, bool wanted )
 {
     QString key( wanted ? "files-wanted" : "files-unwanted" );
     mySession.torrentSet( myIds, key, indices.toList( ) );
+    getNewData( );
 }
index 0d5071d8056e8fcbaa50d7173ee9f51dac2ce399..716e3961890fd7eb3eb19e35e08a9c1496904900 100644 (file)
@@ -36,19 +36,15 @@ class QTreeWidgetItem;
 class Session;
 class Torrent;
 class TorrentModel;
+class TrackerDelegate;
+class TrackerModel;
 
 class Details: public QDialog
 {
         Q_OBJECT
 
     private:
-        enum
-        {
-            TRACKERID = Qt::UserRole,
-            TRACKERURL,
-            TRACKERTIER,
-            TORRENTID
-        };
+        void getNewData( );
 
     private slots:
         void onTorrentChanged( );
@@ -71,8 +67,6 @@ class Details: public QDialog
         QString timeToStringRounded( int seconds );
         QString trimToDesiredWidth( const QString& str );
         void enableWhenChecked( QCheckBox *, QWidget * );
-        bool findTrackerByURL( const QString& url, int torId );
-        void removeTracker( const QTreeWidgetItem * item );
 
     private:
         Session& mySession;
@@ -126,16 +120,21 @@ class Details: public QDialog
         QLabel * myAnnounceResponseLabel;
         QLabel * myAnnounceManualLabel;
 
-        QTreeWidget * myTrackerTree;
+        TrackerModel * myTrackerModel;
+        TrackerDelegate * myTrackerDelegate;
+        QTreeView * myTrackerView;
+        //QMap<QString,QTreeWidgetItem*> myTrackerTiers;
+        //QMap<QString,QTreeWidgetItem*> myTrackerItems;
+
         QTreeWidget * myPeerTree;
-        QMap<QString,QTreeWidgetItem*> myTrackerTiers;
-        QMap<QString,QTreeWidgetItem*> myTrackerItems;
         QMap<QString,QTreeWidgetItem*> myPeers;
+
         QWidgetList myWidgets;
 
         FileTreeView * myFileTreeView;
 
     private slots:
+        void refreshPref( int key );
         void onBandwidthPriorityChanged( int );
         void onFilePriorityChanged( const QSet<int>& fileIndices, int );
         void onFileWantedChanged( const QSet<int>& fileIndices, bool );
@@ -150,9 +149,9 @@ class Details: public QDialog
         void onIdleLimitChanged( int );
         void onShowTrackerScrapesToggled( bool );
         void onTrackerSelectionChanged( );
-        void onAddTrackerPushed( );
-        void onEditTrackerPushed( );
-        void onRemoveTrackerPushed( );
+        void onAddTrackerClicked( );
+        void onEditTrackerClicked( );
+        void onRemoveTrackerClicked( );
         void onMaxPeersChanged( int );
         void refresh( );
 };
index 2e36bc88600e1c82cd63b09b89943da18c0c65ef..4650d82bba50263de549b996fd8f6ce5045951bc 100644 (file)
@@ -97,7 +97,9 @@ Favicons :: add( const QUrl& url )
     if( !myPixmaps.contains( host ) )
     {
         // add a placholder s.t. we only ping the server once per session
-        myPixmaps.insert( host, QPixmap( ) );
+        QPixmap tmp( 16, 16 );
+        tmp.fill( Qt::transparent );
+        myPixmaps.insert( host, tmp );
 
         // try to download the favicon
         const QString path = "http://" + host + "/favicon.";
index dcd3f12a336f81d4e476770a29e5a033c2f0a823..2341cfc43f46842d40289af62e573aaf0551d8b0 100644 (file)
@@ -26,6 +26,10 @@ class Favicons: public QObject
 {
         Q_OBJECT;
 
+    public:
+
+        static QString getHost( const QUrl& url );
+
     public:
 
         Favicons();
@@ -46,8 +50,6 @@ class Favicons: public QObject
         QNetworkAccessManager * myNAM;
         QMap<QString,QPixmap> myPixmaps;
 
-        QString getHost( const QUrl& url );
-
         QString getCacheDir( );
         void ensureCacheDirHasBeenScanned( );
 
index c8a3bac3a48e47953554fc7dda78bae53065a75c..44356a3cfb219b323535f7d10053e1b2360eb070 100644 (file)
@@ -36,7 +36,8 @@ SOURCES += about.cc app.cc dbus-adaptor.cc details.cc favicon.cc file-tree.cc \
            relocate.cc session.cc session-dialog.cc squeezelabel.cc \
            stats-dialog.cc torrent.cc torrent-delegate.cc \
            torrent-delegate-min.cc torrent-filter.cc torrent-model.cc \
-           triconpushbutton.cc utils.cc watchdir.cc
+           tracker-delegate.cc tracker-model.cc triconpushbutton.cc \
+           utils.cc watchdir.cc
 HEADERS += $$replace(SOURCES, .cc, .h)
 HEADERS += speed.h types.h
 
index 28d7c87b87cf4c9cd5fc23e6d1f432232c4a4d18..9aac79bb9a6e192d9e319480024d9bbefcc672c5 100644 (file)
@@ -22,6 +22,7 @@
 #include <QNetworkReply>
 #include <QNetworkRequest>
 #include <QSet>
+#include <QStringList>
 #include <QStyle>
 #include <QTextStream>
 
@@ -406,30 +407,31 @@ Session :: torrentSet( const QSet<int>& ids, const QString& key, bool value )
 }
 
 void
-Session :: torrentSet( const QSet<int>& ids, const QString& key, const QList<int>& value )
+Session :: torrentSet( const QSet<int>& ids, const QString& key, const QStringList& value )
 {
     tr_benc top;
     tr_bencInitDict( &top, 2 );
     tr_bencDictAddStr( &top, "method", "torrent-set" );
-    tr_benc * args( tr_bencDictAddDict( &top, "arguments", 2 ) );
+    tr_benc * args = tr_bencDictAddDict( &top, "arguments", 2 );
     addOptionalIds( args, ids );
     tr_benc * list( tr_bencDictAddList( args, key.toUtf8().constData(), value.size( ) ) );
-    foreach( int i, value )
-        tr_bencListAddInt( list, i );
+    foreach( const QString str, value )
+        tr_bencListAddStr( list, str.toUtf8().constData() );
     exec( &top );
     tr_bencFree( &top );
 }
 
 void
-Session :: torrentSet( const QSet<int>& ids, const QString& key, const tr_benc * value )
+Session :: torrentSet( const QSet<int>& ids, const QString& key, const QList<int>& value )
 {
     tr_benc top;
     tr_bencInitDict( &top, 2 );
     tr_bencDictAddStr( &top, "method", "torrent-set" );
     tr_benc * args( tr_bencDictAddDict( &top, "arguments", 2 ) );
     addOptionalIds( args, ids );
-    tr_benc * child( tr_bencDictAdd( args, key.toUtf8().constData() ) );
-    memcpy( child, value, sizeof(tr_benc) );
+    tr_benc * list( tr_bencDictAddList( args, key.toUtf8().constData(), value.size( ) ) );
+    foreach( int i, value )
+        tr_bencListAddInt( list, i );
     exec( &top );
     tr_bencFree( &top );
 }
index 88735290ead05e9fa414995c3121787dabc7c4b6..532f1105232350abd295db12e5f214d560c6ae6e 100644 (file)
@@ -21,6 +21,8 @@
 #include <QString>
 #include <QUrl>
 
+class QStringList;
+
 #include <libtransmission/transmission.h>
 
 extern "C"
@@ -98,7 +100,7 @@ class Session: public QObject
         void torrentSet( const QSet<int>& ids, const QString& key, int val );
         void torrentSet( const QSet<int>& ids, const QString& key, double val );
         void torrentSet( const QSet<int>& ids, const QString& key, const QList<int>& val );
-        void torrentSet( const QSet<int>& ids, const QString& key, const tr_benc * value );
+        void torrentSet( const QSet<int>& ids, const QString& key, const QStringList& val );
         void torrentSetLocation( const QSet<int>& ids, const QString& path, bool doMove );
 
 
index 71d3760e875027075275c50467267f8dd2f61f4d..ef0d5f200dbd298dd8d23bf5b8fc4c116a8c72fb 100644 (file)
@@ -566,8 +566,10 @@ Torrent :: update( tr_benc * d )
             int64_t i;
             const char * str;
             TrackerStat trackerStat;
-            if( tr_bencDictFindStr( child, "announce", &str ) )
+            if( tr_bencDictFindStr( child, "announce", &str ) ) {
                 trackerStat.announce = QString::fromUtf8( str );
+                dynamic_cast<MyApp*>(QApplication::instance())->favicons.add( QUrl( trackerStat.announce ) );
+            }
             if( tr_bencDictFindInt( child, "announceState", &i ) )
                 trackerStat.announceState = i;
             if( tr_bencDictFindInt( child, "downloadCount", &i ) )
@@ -705,3 +707,11 @@ Torrent :: getError( ) const
 
     return s;
 }
+
+QPixmap
+TrackerStat :: getFavicon( ) const
+{
+    MyApp * myApp = dynamic_cast<MyApp*>(QApplication::instance());
+    return myApp->favicons.find( QUrl( announce ) );
+}
+
index 02a7a5b1ec8aa87bbdc5ff42090339dc7a254027..98226ab7364a2e9e1f1c90ae29add5f42c00c9c6 100644 (file)
@@ -34,6 +34,7 @@ extern "C"
 }
 
 class Prefs;
+class QPixmap;
 class QStyle;
 
 struct Peer
@@ -86,6 +87,7 @@ struct TrackerStat
     int scrapeState;
     int seederCount;
     int tier;
+    QPixmap getFavicon( ) const;
 };
 
 typedef QList<TrackerStat> TrackerStatsList;
diff --git a/qt/tracker-delegate.cc b/qt/tracker-delegate.cc
new file mode 100644 (file)
index 0000000..4556a34
--- /dev/null
@@ -0,0 +1,304 @@
+/*
+ * This file Copyright (C) 2009-2010 Mnemosyne LLC
+ *
+ * This file is licensed by the GPL version 2.  Works owned by the
+ * Transmission project are granted a special exemption to clause 2(b)
+ * so that the bulk of its code can remain under the MIT license.
+ * This exemption does not extend to derived works not owned by
+ * the Transmission project.
+ *
+ * $Id: torrent-delegate.cc 11051 2010-07-24 23:51:02Z charles $
+ */
+
+#include <iostream>
+
+#include <QApplication>
+#include <QBrush>
+#include <QFont>
+#include <QFontMetrics>
+#include <QIcon>
+#include <QModelIndex>
+#include <QPainter>
+#include <QPixmap>
+#include <QPixmapCache>
+#include <QStyleOptionProgressBarV2>
+#include <QTextDocument>
+#include <QUrl>
+
+#include "favicon.h"
+#include "formatter.h"
+#include "torrent.h"
+#include "tracker-delegate.h"
+#include "tracker-model.h"
+
+/***
+****
+***/
+
+namespace
+{
+    const int mySpacing = 6;
+    const QSize myMargin( 10, 6 );
+}
+
+QSize
+TrackerDelegate :: margin( const QStyle& style ) const
+{
+    Q_UNUSED( style );
+
+    return myMargin;
+}
+
+/***
+****
+***/
+
+QSize
+TrackerDelegate :: sizeHint( const QStyleOptionViewItem& option, const TrackerInfo& info ) const
+{
+    Q_UNUSED( option );
+
+    QPixmap favicon = info.st.getFavicon( );
+    
+    const QString text = TrackerDelegate :: getText( info );
+    QTextDocument textDoc;
+    textDoc.setHtml( text );
+    const QSize textSize = textDoc.size().toSize();
+
+    return QSize( myMargin.width() + favicon.width() + mySpacing + textSize.width() + myMargin.width(),
+                  myMargin.height() + qMax<int>( favicon.height(), textSize.height() ) + myMargin.height() );
+}
+
+QSize
+TrackerDelegate :: sizeHint( const QStyleOptionViewItem  & option,
+                             const QModelIndex           & index ) const
+{
+    const TrackerInfo trackerInfo = index.model()->data( index, TrackerModel::TrackerRole ).value<TrackerInfo>();
+    return sizeHint( option, trackerInfo );
+}
+
+void
+TrackerDelegate :: paint( QPainter                    * painter,
+                          const QStyleOptionViewItem  & option,
+                          const QModelIndex           & index) const
+{
+    const TrackerInfo trackerInfo = index.model()->data( index, TrackerModel::TrackerRole ).value<TrackerInfo>();
+    painter->save( );
+    painter->setClipRect( option.rect );
+    drawBackground( painter, option, index );
+    drawTracker( painter, option, trackerInfo );
+    drawFocus(painter, option, option.rect );
+    painter->restore( );
+}
+
+void
+TrackerDelegate :: drawTracker( QPainter                    * painter,
+                                const QStyleOptionViewItem  & option,
+                                const TrackerInfo           & inf ) const
+{
+    painter->save( );
+
+    QPixmap icon = inf.st.getFavicon( );
+    QRect iconArea( option.rect.x() + myMargin.width(),
+                    option.rect.y() + myMargin.height(),
+                    icon.width(),
+                    icon.height() );
+    painter->drawPixmap( iconArea.x(), iconArea.y()+4, icon );
+
+    const int textWidth = option.rect.width() - myMargin.width()*2 - mySpacing - icon.width();
+    const int textX = myMargin.width() + icon.width() + mySpacing;
+    const QString text = getText( inf );
+    QTextDocument textDoc;
+    textDoc.setHtml( text );
+    const QRect textRect( textX, iconArea.y(), textWidth, option.rect.height() - myMargin.height()*2 );
+    painter->translate( textRect.topLeft( ) );
+    textDoc.drawContents( painter, textRect.translated( -textRect.topLeft( ) ) );
+
+    painter->restore( );
+}
+
+void
+TrackerDelegate :: setShowMore( bool b )
+{
+    myShowMore = b;
+}
+
+namespace
+{
+    QString timeToStringRounded( int seconds )
+    {
+        if( seconds > 60 ) seconds -= ( seconds % 60 );
+        return Formatter::timeToString ( seconds );
+    }
+}
+
+QString
+TrackerDelegate :: getText( const TrackerInfo& inf ) const
+{
+    QString key;
+    QString str;
+    const time_t now( time( 0 ) );
+    const QString err_markup_begin = "<span style=\"color:red\">";
+    const QString err_markup_end = "</span>";
+    const QString timeout_markup_begin = "<span style=\"color:#224466\">";
+    const QString timeout_markup_end = "</span>";
+    const QString success_markup_begin = "<span style=\"color:#008B00\">";
+    const QString success_markup_end = "</span>";
+
+    // hostname
+    const QString host = Favicons::getHost( QUrl( inf.st.announce ) );
+    str += inf.st.isBackup ? "<i>" : "<b>";
+    str += host;
+    if( !key.isEmpty( ) ) str += " - " + key;
+    str += inf.st.isBackup ? "</i>" : "</b>";
+
+    // announce & scrape info
+    if( !inf.st.isBackup )
+    {
+        if( inf.st.hasAnnounced )
+        {
+            const QString tstr( timeToStringRounded( now - inf.st.lastAnnounceTime ) );
+            str += "<br/>\n";
+            if( inf.st.lastAnnounceSucceeded )
+            {
+                str += tr( "Got a list of %1%2 peers%3 %4 ago" )
+                           .arg( success_markup_begin )
+                           .arg( inf.st.lastAnnouncePeerCount )
+                           .arg( success_markup_end )
+                           .arg( tstr );
+            }
+            else if( inf.st.lastAnnounceTimedOut )
+            {
+                str += tr( "Peer list request timed out %1%2%3 ago; will retry" )
+                           .arg( timeout_markup_begin )
+                           .arg( tstr )
+                           .arg( timeout_markup_end );
+            }
+            else
+            {
+                str += tr( "Got an error %1'%2'%3 %4 ago" )
+                           .arg( err_markup_begin )
+                           .arg( tstr )
+                           .arg( err_markup_end )
+                           .arg( tstr );
+            }
+        }
+
+        switch( inf.st.announceState )
+        {
+            case TR_TRACKER_INACTIVE:
+                if( inf.st.hasAnnounced ) {
+                    str += "<br/>\n";
+                    str += tr( "No updates scheduled" );
+                }
+                break;
+
+            case TR_TRACKER_WAITING: {
+                const QString tstr( timeToStringRounded( inf.st.nextAnnounceTime - now ) );
+                str += "<br/>\n";
+                str += tr( "Asking for more peers in %1" ).arg( tstr );
+                break;
+            }
+
+            case TR_TRACKER_QUEUED:
+                str += "<br/>\n";
+                str += tr( "Queued to ask for more peers" );
+                break;
+
+            case TR_TRACKER_ACTIVE: {
+                const QString tstr( timeToStringRounded( now - inf.st.lastAnnounceStartTime ) );
+                str += "<br/>\n";
+                str += tr( "Asking for more peers now... <small>%1</small>" ).arg( tstr );
+                break;
+            }
+        }
+
+        if( myShowMore )
+        {
+            if( inf.st.hasScraped )
+            {
+                str += "<br/>\n";
+                const QString tstr( timeToStringRounded( now - inf.st.lastScrapeTime ) );
+                if( inf.st.lastScrapeSucceeded )
+                {
+                    str += tr( "Tracker had %1%2 seeders%3 and %4%5 leechers%6 %7 ago" )
+                               .arg( success_markup_begin )
+                               .arg( inf.st.seederCount )
+                               .arg( success_markup_end )
+                               .arg( success_markup_begin )
+                               .arg( inf.st.leecherCount )
+                               .arg( success_markup_end )
+                               .arg( tstr );
+                }
+                else
+                {
+                    str += tr( "Got a scrape error %1'%2'%3 %4 ago" )
+                               .arg( err_markup_begin )
+                               .arg( inf.st.lastScrapeResult )
+                               .arg( err_markup_end )
+                               .arg( tstr );
+                }
+            }
+
+            switch( inf.st.scrapeState )
+            {
+                case TR_TRACKER_INACTIVE:
+                    break;
+
+                case TR_TRACKER_WAITING: {
+                    str += "<br/>\n";
+                    const QString tstr( timeToStringRounded( inf.st.nextScrapeTime - now ) );
+                    str += tr( "Asking for peer counts in %1" ).arg( tstr );
+                    break;
+                }
+
+                case TR_TRACKER_QUEUED: {
+                    str += "<br/>\n";
+                    str += tr( "Queued to ask for peer counts" );
+                    break;
+                }
+
+                case TR_TRACKER_ACTIVE: {
+                    str += "<br/>\n";
+                    const QString tstr( timeToStringRounded( now - inf.st.lastScrapeStartTime ) );
+                    str += tr( "Asking for peer counts now... <small>%1</small>" ).arg( tstr );
+                    break;
+                }
+            }
+        }
+    }
+
+    return str;
+}
+
+#if 0
+
+    if( inf.isBackup )
+        str += "<i>";
+    QString announce;
+    int announceState;
+    int downloadCount;
+    bool hasAnnounced; bool hasScraped;
+    QString host;
+    int id;
+    bool isBackup;
+    int lastAnnouncePeerCount;
+    int lastAnnounceResult;
+    int lastAnnounceStartTime;
+    bool lastAnnounceSucceeded;
+    int lastAnnounceTime;
+    bool lastAnnounceTimedOut;
+    QString lastScrapeResult;
+    int lastScrapeStartTime;
+    bool lastScrapeSucceeded;
+    int lastScrapeTime;
+    bool lastScrapeTimedOut;
+    int leecherCount;
+    int nextAnnounceTime;
+    int nextScrapeTime;
+    int scrapeState;
+    int seederCount;
+    int tier;
+
+}
+#endif
diff --git a/qt/tracker-delegate.h b/qt/tracker-delegate.h
new file mode 100644 (file)
index 0000000..840a604
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * This file Copyright (C) 2009-2010 Mnemosyne LLC
+ *
+ * This file is licensed by the GPL version 2.  Works owned by the
+ * Transmission project are granted a special exemption to clause 2(b)
+ * so that the bulk of its code can remain under the MIT license.
+ * This exemption does not extend to derived works not owned by
+ * the Transmission project.
+ *
+ * $Id: torrent-delegate.h 9868 2010-01-04 21:00:47Z charles $
+ */
+
+#ifndef QTR_TORRENT_DELEGATE_H
+#define QTR_TORRENT_DELEGATE_H
+
+#include <QItemDelegate>
+#include <QSize>
+
+class QPainter;
+class QStyleOptionViewItem;
+class QStyle;
+class Session;
+class TrackerInfo;
+
+class TrackerDelegate: public QItemDelegate
+{
+        Q_OBJECT
+
+    public:
+        TrackerDelegate( QObject * parent=0 ): QItemDelegate(parent), myShowMore(false) { }
+        virtual ~TrackerDelegate( ) { }
+
+    public:
+        QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const;
+        void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
+
+    public:
+        void setShowMore( bool b );
+
+    protected:
+        QString getText( const TrackerInfo& ) const; 
+        QSize margin( const QStyle& style ) const;
+        virtual QSize sizeHint( const QStyleOptionViewItem&, const TrackerInfo& ) const;
+        void drawTracker( QPainter*, const QStyleOptionViewItem&, const TrackerInfo& ) const;
+
+    private:
+        bool myShowMore;
+};
+
+#endif
diff --git a/qt/tracker-model.cc b/qt/tracker-model.cc
new file mode 100644 (file)
index 0000000..7be9da9
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * This file Copyright (C) 2010 Mnemosyne LLC
+ *
+ * This file is licensed by the GPL version 2.  Works owned by the
+ * Transmission project are granted a special exemption to clause 2(b)
+ * so that the bulk of its code can remain under the MIT license.
+ * This exemption does not extend to derived works not owned by
+ * the Transmission project.
+ *
+ * $Id$
+ */
+
+#include <algorithm> // std::sort()
+
+#include <QUrl>
+
+#include "app.h" // MyApp
+#include "tracker-model.h"
+
+int
+TrackerModel :: rowCount( const QModelIndex& parent ) const
+{
+    Q_UNUSED( parent );
+
+    return parent.isValid() ? 0 : myRows.size();
+}
+
+QVariant
+TrackerModel :: data( const QModelIndex& index, int role ) const
+{
+    QVariant var;
+
+    const int row = index.row( );
+    if( ( 0<=row ) && ( row<myRows.size( ) ) )
+    {
+        const TrackerInfo& trackerInfo = myRows.at( row );
+
+        switch( role )
+        {
+            case Qt::DisplayRole:
+                var = QString( trackerInfo.st.announce );
+                break;
+
+            case Qt::DecorationRole:
+                var = trackerInfo.st.getFavicon( );
+                break;
+
+            case TrackerRole:
+                var = qVariantFromValue( trackerInfo );
+                break;
+
+            default:
+                break;
+        }
+    }
+
+    return var;
+}
+
+/***
+****
+***/
+
+struct CompareTrackers {
+    bool operator()( const TrackerInfo& a, const TrackerInfo& b ) const {
+        if( a.torrentId != b.torrentId ) return a.torrentId < b.torrentId;
+        if( a.st.tier != b.st.tier ) return a.st.tier < b.st.tier;
+        return a.st.announce < b.st.announce;
+    }
+};
+
+void
+TrackerModel :: refresh( const TorrentModel& torrentModel, const QSet<int>& ids )
+{
+    // build a list of the TrackerInfos
+    QVector<TrackerInfo> trackers;
+    foreach( int id, ids ) {
+        const Torrent * tor = torrentModel.getTorrentFromId( id );
+        if( tor != 0 ) {
+            const TrackerStatsList trackerList = tor->trackerStats( );
+            foreach( const TrackerStat& st, trackerList ) {
+                TrackerInfo trackerInfo;
+                trackerInfo.st = st;
+                trackerInfo.torrentId = id;
+                trackers.append( trackerInfo );
+            }
+        }
+    }
+
+    // sort 'em
+    CompareTrackers comp;
+    std::sort( trackers.begin(), trackers.end(), comp );
+
+    // merge 'em with the existing list
+    int old_index = 0;
+    int new_index = 0;
+
+    while( ( old_index < myRows.size() ) || ( new_index < trackers.size() ) )
+    {
+        if( old_index == myRows.size() )
+        {
+            // add this new row
+            beginInsertRows( QModelIndex( ), old_index, old_index );
+            myRows.insert( old_index, trackers.at( new_index ) );
+            endInsertRows( );
+            ++old_index;
+            ++new_index;
+        }
+        else if( new_index == trackers.size() )
+        {
+            // remove this old row
+            beginRemoveRows( QModelIndex( ), old_index, old_index );
+            myRows.remove( old_index );
+            endRemoveRows( );
+        }
+        else if( comp( myRows.at(old_index), trackers.at(new_index) ) )
+        {
+            // remove this old row
+            beginRemoveRows( QModelIndex( ), old_index, old_index );
+            myRows.remove( old_index );
+            endRemoveRows( );
+        }
+        else if( comp( trackers.at(new_index), myRows.at(old_index) ) )
+        {
+            // add this new row
+            beginInsertRows( QModelIndex( ), old_index, old_index );
+            myRows.insert( old_index, trackers.at( new_index ) );
+            endInsertRows( );
+            ++old_index;
+            ++new_index;
+        }
+        else // update existing row
+        {
+            myRows[old_index].st = trackers.at(new_index).st;
+            QModelIndex topLeft;
+            QModelIndex bottomRight;
+            dataChanged( index(old_index,0), index(old_index,0) );
+            ++old_index;
+            ++new_index;
+        }
+    }
+}
+
+int
+TrackerModel :: find( int torrentId, const QString& url ) const
+{
+    for( int i=0, n=myRows.size(); i<n; ++i ) {
+        const TrackerInfo& inf = myRows.at(i);
+        if( ( inf.torrentId == torrentId ) && ( url == inf.st.announce ) )
+            return i;
+    }
+
+    return -1;
+}
diff --git a/qt/tracker-model.h b/qt/tracker-model.h
new file mode 100644 (file)
index 0000000..0b6bc30
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * This file Copyright (C) 2010 Mnemosyne LLC
+ *
+ * This file is licensed by the GPL version 2.  Works owned by the
+ * Transmission project are granted a special exemption to clause 2(b)
+ * so that the bulk of its code can remain under the MIT license.
+ * This exemption does not extend to derived works not owned by
+ * the Transmission project.
+ *
+ * $Id$
+ */
+
+#ifndef QTR_TRACKER_MODEL_H
+#define QTR_TRACKER_MODEL_H
+
+#include <QAbstractListModel>
+#include <QSet>
+#include <QVector>
+
+#include "torrent.h"
+#include "torrent-model.h"
+
+struct TrackerInfo
+{
+    TrackerStat st;
+    int torrentId;
+};
+Q_DECLARE_METATYPE(TrackerInfo)
+
+class TrackerModel: public QAbstractListModel
+{
+        Q_OBJECT
+
+        typedef QVector<TrackerInfo> rows_t;
+        rows_t myRows;
+
+    public:
+        void refresh( const TorrentModel&, const QSet<int>& ids );
+        int find( int torrentId, const QString& url ) const;
+
+    public:
+        virtual int rowCount( const QModelIndex& parent = QModelIndex() ) const;
+        virtual QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const;
+        enum Role { TrackerRole = Qt::UserRole };
+
+    public:
+        TrackerModel( ) { }
+        virtual ~TrackerModel( ) { }
+};
+
+#endif
index 20fc5ae34b68993c2143cde42a37985ac359dbb5..53b18ffb1d395f56feee4c9c8fadd9b2fe0ae2e1 100644 (file)
 <context>
     <name>Details</name>
     <message>
-        <location filename="details.cc" line="145"/>
+        <location filename="details.cc" line="151"/>
         <source>Torrent Properties</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="149"/>
+        <location filename="details.cc" line="155"/>
         <source>Information</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="151"/>
+        <location filename="details.cc" line="157"/>
         <source>Peers</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="153"/>
+        <location filename="details.cc" line="159"/>
         <source>Tracker</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="155"/>
+        <location filename="details.cc" line="161"/>
         <source>Files</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="157"/>
+        <location filename="details.cc" line="163"/>
         <source>Options</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="256"/>
+        <location filename="details.cc" line="287"/>
         <source>None</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="257"/>
+        <location filename="details.cc" line="288"/>
         <source>Mixed</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="258"/>
-        <location filename="details.cc" line="441"/>
+        <location filename="details.cc" line="289"/>
+        <location filename="details.cc" line="472"/>
         <source>Unknown</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="290"/>
+        <location filename="details.cc" line="321"/>
         <source>Finished</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="292"/>
+        <location filename="details.cc" line="323"/>
         <source>Paused</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="328"/>
+        <location filename="details.cc" line="359"/>
         <source>%1 (%2%)</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="332"/>
+        <location filename="details.cc" line="363"/>
         <source>%1 (%2%); %3 Unverified</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="363"/>
+        <location filename="details.cc" line="394"/>
         <source>%1 (+%2 corrupt)</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="461"/>
+        <location filename="details.cc" line="492"/>
         <source>Active now</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="463"/>
+        <location filename="details.cc" line="494"/>
         <source>%1 ago</source>
         <translation type="unfinished"></translation>
     </message>
     <message numerus="yes">
-        <location filename="details.cc" line="504"/>
+        <location filename="details.cc" line="535"/>
         <source>%1 (%Ln pieces @ %2)</source>
         <translation>
             <numerusform>%1 (%Ln piece @ %2)</numerusform>
         </translation>
     </message>
     <message numerus="yes">
-        <location filename="details.cc" line="508"/>
+        <location filename="details.cc" line="539"/>
         <source>%1 (%Ln pieces)</source>
         <translation>
             <numerusform>%1 (%Ln piece)</numerusform>
         </translation>
     </message>
     <message>
-        <location filename="details.cc" line="532"/>
+        <location filename="details.cc" line="563"/>
         <source>Private to this tracker -- DHT and PEX disabled</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="533"/>
+        <location filename="details.cc" line="564"/>
         <source>Public torrent</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="572"/>
+        <location filename="details.cc" line="603"/>
         <source>Created by %1</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="574"/>
+        <location filename="details.cc" line="605"/>
         <source>Created on %1</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="576"/>
+        <location filename="details.cc" line="607"/>
         <source>Created by %1 on %2</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="735"/>
+        <location filename="details.cc" line="778"/>
         <source>Got a list of %1 peers %2 ago</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="741"/>
+        <location filename="details.cc" line="784"/>
         <source>Peer list request timed out %1 ago; will retry</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="746"/>
+        <location filename="details.cc" line="789"/>
         <source>Got an error %1 ago</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="756"/>
+        <location filename="details.cc" line="799"/>
         <source>No updates scheduled</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="763"/>
+        <location filename="details.cc" line="806"/>
         <source>Asking for more peers in %1</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="769"/>
+        <location filename="details.cc" line="812"/>
         <source>Queued to ask for more peers</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="775"/>
+        <location filename="details.cc" line="818"/>
         <source>Asking for more peers now... %1</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="788"/>
+        <location filename="details.cc" line="831"/>
         <source>Tracker had %1 seeders and %2 leechers %3 ago</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="795"/>
+        <location filename="details.cc" line="838"/>
         <source>Got a scrape error %1 ago</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="807"/>
+        <location filename="details.cc" line="850"/>
         <source>Asking for peer counts in %1</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="813"/>
+        <location filename="details.cc" line="856"/>
         <source>Queued to ask for peer counts</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="819"/>
+        <location filename="details.cc" line="862"/>
         <source>Asking for peer counts now... %1</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="883"/>
-        <location filename="details.cc" line="904"/>
+        <location filename="details.cc" line="926"/>
+        <location filename="details.cc" line="947"/>
         <source>Encrypted connection</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="897"/>
+        <location filename="details.cc" line="940"/>
         <source>Optimistic unchoke</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="898"/>
+        <location filename="details.cc" line="941"/>
         <source>Downloading from this peer</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="899"/>
+        <location filename="details.cc" line="942"/>
         <source>We would download from this peer if they would let us</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="900"/>
+        <location filename="details.cc" line="943"/>
         <source>Uploading to peer</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="901"/>
+        <location filename="details.cc" line="944"/>
         <source>We would upload to this peer if they asked</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="902"/>
+        <location filename="details.cc" line="945"/>
         <source>Peer has unchoked us, but we&apos;re not interested</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="903"/>
+        <location filename="details.cc" line="946"/>
         <source>We unchoked this peer, but they&apos;re not interested</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="905"/>
+        <location filename="details.cc" line="948"/>
         <source>Peer was discovered through DHT</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="906"/>
+        <location filename="details.cc" line="949"/>
         <source>Peer was discovered through Peer Exchange (PEX)</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="907"/>
+        <location filename="details.cc" line="950"/>
         <source>Peer is an incoming connection</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="963"/>
+        <location filename="details.cc" line="1006"/>
         <source>Activity</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="964"/>
+        <location filename="details.cc" line="1007"/>
         <source>Torrent size:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="965"/>
+        <location filename="details.cc" line="1008"/>
         <source>Have:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="966"/>
+        <location filename="details.cc" line="1009"/>
         <source>Availability:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="967"/>
+        <location filename="details.cc" line="1010"/>
         <source>Downloaded:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="968"/>
+        <location filename="details.cc" line="1011"/>
         <source>Uploaded:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="969"/>
+        <location filename="details.cc" line="1012"/>
         <source>Ratio:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="970"/>
+        <location filename="details.cc" line="1013"/>
         <source>State:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="971"/>
+        <location filename="details.cc" line="1014"/>
         <source>Running time:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="972"/>
+        <location filename="details.cc" line="1015"/>
         <source>Remaining time:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="973"/>
+        <location filename="details.cc" line="1016"/>
         <source>Last activity:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="974"/>
+        <location filename="details.cc" line="1017"/>
         <source>Error:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="978"/>
+        <location filename="details.cc" line="1021"/>
         <source>Details</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="979"/>
+        <location filename="details.cc" line="1022"/>
         <source>Location:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="980"/>
+        <location filename="details.cc" line="1023"/>
         <source>Hash:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="981"/>
+        <location filename="details.cc" line="1024"/>
         <source>Privacy:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="982"/>
+        <location filename="details.cc" line="1025"/>
         <source>Origin:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="984"/>
+        <location filename="details.cc" line="1027"/>
         <source>Comment:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1100"/>
+        <location filename="details.cc" line="1125"/>
         <source>Add tracker announce URL </source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1124"/>
+        <location filename="details.cc" line="1137"/>
+        <location filename="details.cc" line="1165"/>
+        <source>Error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="details.cc" line="1137"/>
+        <source>Tracker already exists.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="details.cc" line="1158"/>
         <source>Edit tracker announce URL </source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1196"/>
+        <location filename="details.cc" line="1165"/>
+        <source>Invalid URL &quot;%1&quot;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="details.cc" line="1213"/>
         <source>Speed</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1198"/>
+        <location filename="details.cc" line="1215"/>
         <source>Honor global &amp;limits</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1203"/>
+        <location filename="details.cc" line="1220"/>
         <source>Limit &amp;download speed (%1):</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1213"/>
+        <location filename="details.cc" line="1230"/>
         <source>Limit &amp;upload speed (%1):</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1224"/>
+        <location filename="details.cc" line="1241"/>
         <source>High</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1225"/>
+        <location filename="details.cc" line="1242"/>
         <source>Normal</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1226"/>
+        <location filename="details.cc" line="1243"/>
         <source>Low</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1228"/>
+        <location filename="details.cc" line="1245"/>
         <source>Torrent &amp;priority:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1232"/>
-        <source>Seed-Until Ratio</source>
+        <location filename="details.cc" line="1249"/>
+        <source>Seeding Limits</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1234"/>
-        <source>Use &amp;global settings</source>
+        <location filename="details.cc" line="1254"/>
+        <location filename="details.cc" line="1268"/>
+        <source>Use Global Settings</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1240"/>
-        <source>Seed &amp;regardless of ratio</source>
+        <location filename="details.cc" line="1255"/>
+        <source>Seed regardless of ratio</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1248"/>
-        <source>&amp;Seed torrent until its ratio reaches:</source>
+        <location filename="details.cc" line="1256"/>
+        <source>Stop seeding at ratio:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1261"/>
-        <source>Peer Connections</source>
+        <location filename="details.cc" line="1263"/>
+        <source>&amp;Ratio:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1267"/>
-        <source>&amp;Maximum peers:</source>
+        <location filename="details.cc" line="1269"/>
+        <source>Seed regardless of activity</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="details.cc" line="1270"/>
+        <source>Stop seeding if idle for N minutes:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1297"/>
-        <source>Trackers</source>
+        <location filename="details.cc" line="1277"/>
+        <source>&amp;Idle:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1340"/>
+        <location filename="details.cc" line="1281"/>
+        <source>Peer Connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="details.cc" line="1287"/>
+        <source>&amp;Maximum peers:</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="details.cc" line="1359"/>
         <source>Show &amp;more details</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1362"/>
+        <location filename="details.cc" line="1381"/>
         <source>Up</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1362"/>
+        <location filename="details.cc" line="1381"/>
         <source>Down</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1362"/>
+        <location filename="details.cc" line="1381"/>
         <source>%</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1362"/>
+        <location filename="details.cc" line="1381"/>
         <source>Status</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1362"/>
+        <location filename="details.cc" line="1381"/>
         <source>Address</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1362"/>
+        <location filename="details.cc" line="1381"/>
         <source>Client</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
         <location filename="formatter.cc" line="38"/>
         <source>B/s</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
     <message>
         <location filename="formatter.cc" line="39"/>
         <source>kB/s</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
     <message>
         <location filename="formatter.cc" line="40"/>
         <source>MB/s</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
     <message>
         <location filename="formatter.cc" line="41"/>
         <source>GB/s</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
     <message>
         <location filename="formatter.cc" line="42"/>
         <source>TB/s</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
     <message>
         <location filename="formatter.cc" line="50"/>
         <location filename="formatter.cc" line="62"/>
         <source>B</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
     <message>
         <location filename="formatter.cc" line="51"/>
         <source>KB</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
     <message>
         <location filename="formatter.cc" line="52"/>
         <source>MB</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
     <message>
         <location filename="formatter.cc" line="53"/>
         <source>GB</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
     <message>
         <location filename="formatter.cc" line="54"/>
         <source>TB</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
     <message>
         <location filename="formatter.cc" line="63"/>
         <source>KiB</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
     <message>
         <location filename="formatter.cc" line="64"/>
         <source>MiB</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
     <message>
         <location filename="formatter.cc" line="65"/>
         <source>GiB</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
     <message>
         <location filename="formatter.cc" line="66"/>
         <source>TiB</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
     <message>
         <location filename="formatter.cc" line="98"/>
         <location filename="formatter.cc" line="110"/>
         <location filename="formatter.cc" line="122"/>
         <source>None</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
     <message numerus="yes">
         <location filename="formatter.cc" line="159"/>
         <source>%Ln day(s)</source>
-        <translation type="unfinished">
+        <translation>
             <numerusform>%Ln day</numerusform>
             <numerusform>%Ln days</numerusform>
         </translation>
     <message numerus="yes">
         <location filename="formatter.cc" line="160"/>
         <source>%Ln hour(s)</source>
-        <translation type="unfinished">
+        <translation>
             <numerusform>%Ln hour</numerusform>
             <numerusform>%Ln hours</numerusform>
         </translation>
     <message numerus="yes">
         <location filename="formatter.cc" line="161"/>
         <source>%Ln minute(s)</source>
-        <translation type="unfinished">
+        <translation>
             <numerusform>%Ln minute</numerusform>
             <numerusform>%Ln minutes</numerusform>
         </translation>
     <message numerus="yes">
         <location filename="formatter.cc" line="162"/>
         <source>%Ln second(s)</source>
-        <translation type="unfinished">
+        <translation>
             <numerusform>%Ln second</numerusform>
             <numerusform>%Ln seconds</numerusform>
         </translation>
         <location filename="formatter.cc" line="176"/>
         <location filename="formatter.cc" line="183"/>
         <source>%1, %2</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
 </context>
 <context>
     <message>
         <location filename="license.cc" line="22"/>
         <source>License</source>
-        <translation type="unfinished"></translation>
+        <translation></translation>
     </message>
 </context>
 <context>
     </message>
     <message>
         <location filename="mainwin.ui" line="336"/>
-        <source>Alt+M</source>
+        <source>Alt+C</source>
+        <oldsource>Alt+M</oldsource>
         <translation type="unfinished"></translation>
     </message>
     <message>
@@ -1463,7 +1496,7 @@ To add another primary URL, add it after a blank line.</source>
     </message>
     <message>
         <location filename="prefs-dialog.cc" line="373"/>
-        <location filename="prefs-dialog.cc" line="730"/>
+        <location filename="prefs-dialog.cc" line="735"/>
         <source>Status unknown</source>
         <translation type="unfinished"></translation>
     </message>
@@ -1502,7 +1535,6 @@ To add another primary URL, add it after a blank line.</source>
         <source>&lt;b&gt;Update succeeded!&lt;/b&gt;&lt;p&gt;Blocklist now has %Ln rules.</source>
         <translation>
             <numerusform></numerusform>
-            <numerusform></numerusform>
         </translation>
     </message>
     <message>
@@ -1542,7 +1574,7 @@ To add another primary URL, add it after a blank line.</source>
     </message>
     <message>
         <location filename="prefs-dialog.cc" line="475"/>
-        <location filename="prefs-dialog.cc" line="625"/>
+        <location filename="prefs-dialog.cc" line="630"/>
         <source>Privacy</source>
         <translation type="unfinished"></translation>
     </message>
@@ -1653,51 +1685,57 @@ To add another primary URL, add it after a blank line.</source>
     </message>
     <message>
         <location filename="prefs-dialog.cc" line="598"/>
-        <source>Seeding</source>
+        <source>Seeding Limits</source>
+        <oldsource>Seeding</oldsource>
         <translation type="unfinished"></translation>
     </message>
     <message>
         <location filename="prefs-dialog.cc" line="600"/>
-        <source>&amp;Seed torrent until its ratio reaches:</source>
+        <source>Stop seeding at &amp;ratio:</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="prefs-dialog.cc" line="605"/>
+        <source>Stop seeding if idle for &amp;N minutes:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="prefs-dialog.cc" line="620"/>
+        <location filename="prefs-dialog.cc" line="625"/>
         <source>Transmission Preferences</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="prefs-dialog.cc" line="623"/>
+        <location filename="prefs-dialog.cc" line="628"/>
         <source>Torrents</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="prefs-dialog.cc" line="624"/>
+        <location filename="prefs-dialog.cc" line="629"/>
         <source>Speed</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="prefs-dialog.cc" line="626"/>
+        <location filename="prefs-dialog.cc" line="631"/>
         <source>Network</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="prefs-dialog.cc" line="627"/>
+        <location filename="prefs-dialog.cc" line="632"/>
         <source>Web</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="prefs-dialog.cc" line="656"/>
+        <location filename="prefs-dialog.cc" line="661"/>
         <source>Not supported by remote sessions</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="prefs-dialog.cc" line="682"/>
+        <location filename="prefs-dialog.cc" line="687"/>
         <source>Enable &amp;blocklist</source>
         <translation type="unfinished"></translation>
     </message>
     <message numerus="yes">
-        <location filename="prefs-dialog.cc" line="684"/>
+        <location filename="prefs-dialog.cc" line="689"/>
         <source>Enable &amp;blocklist (%Ln rules)</source>
         <translation>
             <numerusform>Enable &amp;blocklist (%Ln rule)</numerusform>
@@ -1761,7 +1799,7 @@ To add another primary URL, add it after a blank line.</source>
 <context>
     <name>Session</name>
     <message>
-        <location filename="session.cc" line="765"/>
+        <location filename="session.cc" line="769"/>
         <source>Add Torrent</source>
         <translation type="unfinished"></translation>
     </message>
@@ -1868,47 +1906,47 @@ To add another primary URL, add it after a blank line.</source>
 <context>
     <name>Torrent</name>
     <message>
-        <location filename="torrent.cc" line="677"/>
+        <location filename="torrent.cc" line="683"/>
         <source>Waiting to verify local data</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="torrent.cc" line="678"/>
+        <location filename="torrent.cc" line="684"/>
         <source>Verifying local data</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="torrent.cc" line="679"/>
+        <location filename="torrent.cc" line="685"/>
         <source>Downloading</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="torrent.cc" line="680"/>
+        <location filename="torrent.cc" line="686"/>
         <source>Seeding</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="torrent.cc" line="681"/>
+        <location filename="torrent.cc" line="687"/>
         <source>Finished</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="torrent.cc" line="681"/>
+        <location filename="torrent.cc" line="687"/>
         <source>Paused</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="torrent.cc" line="694"/>
+        <location filename="torrent.cc" line="700"/>
         <source>Tracker gave a warning: %1</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="torrent.cc" line="695"/>
+        <location filename="torrent.cc" line="701"/>
         <source>Tracker gave an error: %1</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="torrent.cc" line="696"/>
+        <location filename="torrent.cc" line="702"/>
         <source>Error: %1</source>
         <translation type="unfinished"></translation>
     </message>
@@ -1947,7 +1985,7 @@ To add another primary URL, add it after a blank line.</source>
     </message>
     <message>
         <location filename="torrent-delegate.cc" line="150"/>
-        <location filename="torrent-delegate.cc" line="250"/>
+        <location filename="torrent-delegate.cc" line="252"/>
         <source> - </source>
         <translation type="unfinished"></translation>
     </message>
@@ -1961,38 +1999,35 @@ To add another primary URL, add it after a blank line.</source>
         <source>Remaining time unknown</source>
         <translation type="unfinished"></translation>
     </message>
-    <message>
-        <location filename="torrent-delegate.cc" line="174"/>
-        <source>Down: %1, Up: %2</source>
-        <translation type="unfinished"></translation>
-    </message>
     <message>
         <location filename="torrent-delegate.cc" line="176"/>
-        <source>Down: %1</source>
+        <source>%1 %2, %3 %4</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
         <location filename="torrent-delegate.cc" line="178"/>
-        <source>Up: %1</source>
+        <location filename="torrent-delegate.cc" line="180"/>
+        <source>%1 %2</source>
+        <oldsource>Up: %1</oldsource>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="torrent-delegate.cc" line="180"/>
+        <location filename="torrent-delegate.cc" line="182"/>
         <source>Idle</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="torrent-delegate.cc" line="193"/>
+        <location filename="torrent-delegate.cc" line="195"/>
         <source>Verifying local data (%1% tested)</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="torrent-delegate.cc" line="199"/>
+        <location filename="torrent-delegate.cc" line="201"/>
         <source>Ratio: %1, </source>
         <translation type="unfinished"></translation>
     </message>
     <message numerus="yes">
-        <location filename="torrent-delegate.cc" line="230"/>
+        <location filename="torrent-delegate.cc" line="232"/>
         <source>Downloading from %1 of %n connected peer(s)</source>
         <translation>
             <numerusform>Downloading from %1 peer</numerusform>
@@ -2000,15 +2035,14 @@ To add another primary URL, add it after a blank line.</source>
         </translation>
     </message>
     <message numerus="yes">
-        <location filename="torrent-delegate.cc" line="233"/>
+        <location filename="torrent-delegate.cc" line="235"/>
         <source>Downloading metadata from %n peer(s) (%1% done)</source>
         <translation type="unfinished">
             <numerusform></numerusform>
-            <numerusform></numerusform>
         </translation>
     </message>
     <message numerus="yes">
-        <location filename="torrent-delegate.cc" line="238"/>
+        <location filename="torrent-delegate.cc" line="240"/>
         <source>Seeding to %1 of %n connected peer(s)</source>
         <translation>
             <numerusform>Seeding to %1 peer</numerusform>
@@ -2237,6 +2271,69 @@ To add another primary URL, add it after a blank line.</source>
         </translation>
     </message>
 </context>
+<context>
+    <name>TrackerDelegate</name>
+    <message>
+        <location filename="tracker-delegate.cc" line="161"/>
+        <source>Got a list of %1%2 peers%3 %4 ago</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="169"/>
+        <source>Peer list request timed out %1%2%3 ago; will retry</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="176"/>
+        <source>Got an error %1&apos;%2&apos;%3 %4 ago</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="189"/>
+        <source>No updates scheduled</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="196"/>
+        <source>Asking for more peers in %1</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="202"/>
+        <source>Queued to ask for more peers</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="208"/>
+        <source>Asking for more peers now... &lt;small&gt;%1&lt;/small&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="221"/>
+        <source>Tracker had %1%2 seeders%3 and %4%5 leechers%6 %7 ago</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="232"/>
+        <source>Got a scrape error %1&apos;%2&apos;%3 %4 ago</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="248"/>
+        <source>Asking for peer counts in %1</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="254"/>
+        <source>Queued to ask for peer counts</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="261"/>
+        <source>Asking for peer counts now... &lt;small&gt;%1&lt;/small&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
 <context>
     <name>Utils</name>
     <message numerus="yes">
index 0f89c560c49e9f8aab10f41fda028363af7f72b3..328cb2186aadb6124bad0576f101bc63893221d7 100644 (file)
@@ -1853,73 +1853,73 @@ second %s is the version number
 <context>
     <name>Details</name>
     <message>
-        <location filename="details.cc" line="145"/>
+        <location filename="details.cc" line="151"/>
         <source>Torrent Properties</source>
         <translation>Свойства торрента</translation>
     </message>
     <message>
-        <location filename="details.cc" line="149"/>
+        <location filename="details.cc" line="155"/>
         <source>Information</source>
         <translation>Сведения</translation>
     </message>
     <message>
-        <location filename="details.cc" line="151"/>
+        <location filename="details.cc" line="157"/>
         <source>Peers</source>
         <translation>Узлы</translation>
     </message>
     <message>
-        <location filename="details.cc" line="153"/>
+        <location filename="details.cc" line="159"/>
         <source>Tracker</source>
         <translation>Трекер</translation>
     </message>
     <message>
-        <location filename="details.cc" line="155"/>
+        <location filename="details.cc" line="161"/>
         <source>Files</source>
         <translation>Файлы</translation>
     </message>
     <message>
-        <location filename="details.cc" line="157"/>
+        <location filename="details.cc" line="163"/>
         <source>Options</source>
         <translation>Параметры</translation>
     </message>
     <message>
-        <location filename="details.cc" line="256"/>
+        <location filename="details.cc" line="287"/>
         <source>None</source>
         <translation>Н/Д</translation>
     </message>
     <message>
-        <location filename="details.cc" line="257"/>
+        <location filename="details.cc" line="288"/>
         <source>Mixed</source>
         <translation>Смешанный</translation>
     </message>
     <message>
-        <location filename="details.cc" line="258"/>
-        <location filename="details.cc" line="441"/>
+        <location filename="details.cc" line="289"/>
+        <location filename="details.cc" line="472"/>
         <source>Unknown</source>
         <translation>Неизвестно</translation>
     </message>
     <message>
-        <location filename="details.cc" line="290"/>
+        <location filename="details.cc" line="321"/>
         <source>Finished</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="292"/>
+        <location filename="details.cc" line="323"/>
         <source>Paused</source>
         <translation type="unfinished">Приостановлен</translation>
     </message>
     <message>
-        <location filename="details.cc" line="328"/>
+        <location filename="details.cc" line="359"/>
         <source>%1 (%2%)</source>
         <translation></translation>
     </message>
     <message>
-        <location filename="details.cc" line="332"/>
+        <location filename="details.cc" line="363"/>
         <source>%1 (%2%); %3 Unverified</source>
         <translation>%1 (%2%); %3 непроверен</translation>
     </message>
     <message>
-        <location filename="details.cc" line="363"/>
+        <location filename="details.cc" line="394"/>
         <source>%1 (+%2 corrupt)</source>
         <translation>%1 (+%2 испорчен)</translation>
     </message>
@@ -1928,17 +1928,17 @@ second %s is the version number
         <translation type="obsolete">Остановлен</translation>
     </message>
     <message>
-        <location filename="details.cc" line="461"/>
+        <location filename="details.cc" line="492"/>
         <source>Active now</source>
         <translation>Активизирован</translation>
     </message>
     <message>
-        <location filename="details.cc" line="463"/>
+        <location filename="details.cc" line="494"/>
         <source>%1 ago</source>
         <translation>%1 назад</translation>
     </message>
     <message numerus="yes">
-        <location filename="details.cc" line="504"/>
+        <location filename="details.cc" line="535"/>
         <source>%1 (%Ln pieces @ %2)</source>
         <translation>
             <numerusform>%1 (%Ln часть @ %2)</numerusform>
@@ -1947,7 +1947,7 @@ second %s is the version number
         </translation>
     </message>
     <message numerus="yes">
-        <location filename="details.cc" line="508"/>
+        <location filename="details.cc" line="539"/>
         <source>%1 (%Ln pieces)</source>
         <translation>
             <numerusform>%1 (%Ln часть)</numerusform>
@@ -1956,27 +1956,27 @@ second %s is the version number
         </translation>
     </message>
     <message>
-        <location filename="details.cc" line="532"/>
+        <location filename="details.cc" line="563"/>
         <source>Private to this tracker -- DHT and PEX disabled</source>
         <translation>Приватно для этого трекера -- DHT и PEX выключены</translation>
     </message>
     <message>
-        <location filename="details.cc" line="533"/>
+        <location filename="details.cc" line="564"/>
         <source>Public torrent</source>
         <translation>Публичный торрент</translation>
     </message>
     <message>
-        <location filename="details.cc" line="572"/>
+        <location filename="details.cc" line="603"/>
         <source>Created by %1</source>
         <translation>Создано при помощи %1</translation>
     </message>
     <message>
-        <location filename="details.cc" line="574"/>
+        <location filename="details.cc" line="605"/>
         <source>Created on %1</source>
         <translation>Создано %1</translation>
     </message>
     <message>
-        <location filename="details.cc" line="576"/>
+        <location filename="details.cc" line="607"/>
         <source>Created by %1 on %2</source>
         <translation>Создано %2 при помощи %1</translation>
     </message>
@@ -1985,248 +1985,304 @@ second %s is the version number
         <translation type="obsolete">Сейчас</translation>
     </message>
     <message>
-        <location filename="details.cc" line="735"/>
+        <location filename="details.cc" line="778"/>
         <source>Got a list of %1 peers %2 ago</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="741"/>
+        <location filename="details.cc" line="784"/>
         <source>Peer list request timed out %1 ago; will retry</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="746"/>
+        <location filename="details.cc" line="789"/>
         <source>Got an error %1 ago</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="756"/>
+        <location filename="details.cc" line="799"/>
         <source>No updates scheduled</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="763"/>
+        <location filename="details.cc" line="806"/>
         <source>Asking for more peers in %1</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="769"/>
+        <location filename="details.cc" line="812"/>
         <source>Queued to ask for more peers</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="775"/>
+        <location filename="details.cc" line="818"/>
         <source>Asking for more peers now... %1</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="788"/>
+        <location filename="details.cc" line="831"/>
         <source>Tracker had %1 seeders and %2 leechers %3 ago</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="795"/>
+        <location filename="details.cc" line="838"/>
         <source>Got a scrape error %1 ago</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="807"/>
+        <location filename="details.cc" line="850"/>
         <source>Asking for peer counts in %1</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="813"/>
+        <location filename="details.cc" line="856"/>
         <source>Queued to ask for peer counts</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="819"/>
+        <location filename="details.cc" line="862"/>
         <source>Asking for peer counts now... %1</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="883"/>
-        <location filename="details.cc" line="904"/>
+        <location filename="details.cc" line="926"/>
+        <location filename="details.cc" line="947"/>
         <source>Encrypted connection</source>
         <translation>Зашифрованное соединение</translation>
     </message>
     <message>
-        <location filename="details.cc" line="897"/>
+        <location filename="details.cc" line="940"/>
         <source>Optimistic unchoke</source>
         <translation>Благоприятная передача</translation>
     </message>
     <message>
-        <location filename="details.cc" line="898"/>
+        <location filename="details.cc" line="941"/>
         <source>Downloading from this peer</source>
         <translation>Загрузка с этого узла</translation>
     </message>
     <message>
-        <location filename="details.cc" line="899"/>
+        <location filename="details.cc" line="942"/>
         <source>We would download from this peer if they would let us</source>
         <translation>Возможен приём данных от этого узла, если он позволит</translation>
     </message>
     <message>
-        <location filename="details.cc" line="900"/>
+        <location filename="details.cc" line="943"/>
         <source>Uploading to peer</source>
         <translation>Передача узлу</translation>
     </message>
     <message>
-        <location filename="details.cc" line="901"/>
+        <location filename="details.cc" line="944"/>
         <source>We would upload to this peer if they asked</source>
         <translation>Возможна раздача данных этому узлу, если он будет заинтересован</translation>
     </message>
     <message>
-        <location filename="details.cc" line="902"/>
+        <location filename="details.cc" line="945"/>
         <source>Peer has unchoked us, but we&apos;re not interested</source>
         <translation>Узел согласен передавать данные, но мы не заинтересованы</translation>
     </message>
     <message>
-        <location filename="details.cc" line="903"/>
+        <location filename="details.cc" line="946"/>
         <source>We unchoked this peer, but they&apos;re not interested</source>
         <translation>Передача узлу была разрешена, но он не заинтересован</translation>
     </message>
     <message>
-        <location filename="details.cc" line="905"/>
+        <location filename="details.cc" line="948"/>
         <source>Peer was discovered through DHT</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="906"/>
+        <location filename="details.cc" line="949"/>
         <source>Peer was discovered through Peer Exchange (PEX)</source>
         <translation>Узел был обнаружен с помощью обмена узлами (PEX)</translation>
     </message>
     <message>
-        <location filename="details.cc" line="907"/>
+        <location filename="details.cc" line="950"/>
         <source>Peer is an incoming connection</source>
         <translation>Узел работает в режиме приёма</translation>
     </message>
     <message>
-        <location filename="details.cc" line="963"/>
+        <location filename="details.cc" line="1006"/>
         <source>Activity</source>
         <translation>Активность</translation>
     </message>
     <message>
-        <location filename="details.cc" line="964"/>
+        <location filename="details.cc" line="1007"/>
         <source>Torrent size:</source>
         <translation>Размер торрента:</translation>
     </message>
     <message>
-        <location filename="details.cc" line="965"/>
+        <location filename="details.cc" line="1008"/>
         <source>Have:</source>
         <translation>В наличии:</translation>
     </message>
     <message>
-        <location filename="details.cc" line="966"/>
+        <location filename="details.cc" line="1009"/>
         <source>Availability:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="967"/>
+        <location filename="details.cc" line="1010"/>
         <source>Downloaded:</source>
         <translation>Загружено:</translation>
     </message>
     <message>
-        <location filename="details.cc" line="968"/>
+        <location filename="details.cc" line="1011"/>
         <source>Uploaded:</source>
         <translation>Роздано:</translation>
     </message>
     <message>
-        <location filename="details.cc" line="969"/>
+        <location filename="details.cc" line="1012"/>
         <source>Ratio:</source>
         <translation>Рейтинг:</translation>
     </message>
     <message>
-        <location filename="details.cc" line="970"/>
+        <location filename="details.cc" line="1013"/>
         <source>State:</source>
         <translation>Состояние:</translation>
     </message>
     <message>
-        <location filename="details.cc" line="971"/>
+        <location filename="details.cc" line="1014"/>
         <source>Running time:</source>
         <translation>Длительность:</translation>
     </message>
     <message>
-        <location filename="details.cc" line="972"/>
+        <location filename="details.cc" line="1015"/>
         <source>Remaining time:</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="973"/>
+        <location filename="details.cc" line="1016"/>
         <source>Last activity:</source>
         <translation>Последняя активность:</translation>
     </message>
     <message>
-        <location filename="details.cc" line="974"/>
+        <location filename="details.cc" line="1017"/>
         <source>Error:</source>
         <translation>Ошибка:</translation>
     </message>
     <message>
-        <location filename="details.cc" line="978"/>
+        <location filename="details.cc" line="1021"/>
         <source>Details</source>
         <translation>Подробности</translation>
     </message>
     <message>
-        <location filename="details.cc" line="979"/>
+        <location filename="details.cc" line="1022"/>
         <source>Location:</source>
         <translation>Местонахождение:</translation>
     </message>
     <message>
-        <location filename="details.cc" line="980"/>
+        <location filename="details.cc" line="1023"/>
         <source>Hash:</source>
         <translation>Хеш:</translation>
     </message>
     <message>
-        <location filename="details.cc" line="981"/>
+        <location filename="details.cc" line="1024"/>
         <source>Privacy:</source>
         <translation>Конфиденциальность:</translation>
     </message>
     <message>
-        <location filename="details.cc" line="982"/>
+        <location filename="details.cc" line="1025"/>
         <source>Origin:</source>
         <translation>Происхождение:</translation>
     </message>
     <message>
-        <location filename="details.cc" line="984"/>
+        <location filename="details.cc" line="1027"/>
         <source>Comment:</source>
         <translation>Комментарий:</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1100"/>
+        <location filename="details.cc" line="1125"/>
         <source>Add tracker announce URL </source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1124"/>
+        <location filename="details.cc" line="1137"/>
+        <location filename="details.cc" line="1165"/>
+        <source>Error</source>
+        <translation type="unfinished">ошибок</translation>
+    </message>
+    <message>
+        <location filename="details.cc" line="1137"/>
+        <source>Tracker already exists.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="details.cc" line="1158"/>
         <source>Edit tracker announce URL </source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1196"/>
+        <location filename="details.cc" line="1165"/>
+        <source>Invalid URL &quot;%1&quot;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="details.cc" line="1213"/>
         <source>Speed</source>
         <translation>Скорость</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1198"/>
+        <location filename="details.cc" line="1215"/>
         <source>Honor global &amp;limits</source>
         <translation>Использовать &amp;глобальные ограничения</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1203"/>
+        <location filename="details.cc" line="1220"/>
         <source>Limit &amp;download speed (%1):</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1213"/>
+        <location filename="details.cc" line="1230"/>
         <source>Limit &amp;upload speed (%1):</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="details.cc" line="1297"/>
+        <location filename="details.cc" line="1249"/>
+        <source>Seeding Limits</source>
+        <translation type="unfinished">Раздача</translation>
+    </message>
+    <message>
+        <location filename="details.cc" line="1254"/>
+        <location filename="details.cc" line="1268"/>
+        <source>Use Global Settings</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="details.cc" line="1255"/>
+        <source>Seed regardless of ratio</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="details.cc" line="1256"/>
+        <source>Stop seeding at ratio:</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="details.cc" line="1263"/>
+        <source>&amp;Ratio:</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="details.cc" line="1269"/>
+        <source>Seed regardless of activity</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="details.cc" line="1270"/>
+        <source>Stop seeding if idle for N minutes:</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="details.cc" line="1277"/>
+        <source>&amp;Idle:</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
         <source>Trackers</source>
-        <translation type="unfinished">Трекеры</translation>
+        <translation type="obsolete">Трекеры</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1340"/>
+        <location filename="details.cc" line="1359"/>
         <source>Show &amp;more details</source>
         <translation type="unfinished"></translation>
     </message>
@@ -2239,52 +2295,48 @@ second %s is the version number
         <translation type="obsolete">Ограничить скорость &amp;раздачи (КБ/с):</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1224"/>
+        <location filename="details.cc" line="1241"/>
         <source>High</source>
         <translation>Высокий</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1225"/>
+        <location filename="details.cc" line="1242"/>
         <source>Normal</source>
         <translation>Обычный</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1226"/>
+        <location filename="details.cc" line="1243"/>
         <source>Low</source>
         <translation>Низкий</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1228"/>
+        <location filename="details.cc" line="1245"/>
         <source>Torrent &amp;priority:</source>
         <translation>&amp;Приоритет торрента:</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1232"/>
         <source>Seed-Until Ratio</source>
-        <translation>Рейтинг для завершения раздачи</translation>
+        <translation type="obsolete">Рейтинг для завершения раздачи</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1234"/>
         <source>Use &amp;global settings</source>
-        <translation>Использовать &amp;глобальные настройки</translation>
+        <translation type="obsolete">Использовать &amp;глобальные настройки</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1240"/>
         <source>Seed &amp;regardless of ratio</source>
-        <translation>Раздавать &amp;несмотря на рейтинг</translation>
+        <translation type="obsolete">Раздавать &amp;несмотря на рейтинг</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1248"/>
         <source>&amp;Seed torrent until its ratio reaches:</source>
-        <translation>&amp;Раздавать до достижения рейтинга:</translation>
+        <translation type="obsolete">&amp;Раздавать до достижения рейтинга:</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1261"/>
+        <location filename="details.cc" line="1281"/>
         <source>Peer Connections</source>
         <translation>Соединения с узлами</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1267"/>
+        <location filename="details.cc" line="1287"/>
         <source>&amp;Maximum peers:</source>
         <translation>&amp;Максимальное количество узлов:</translation>
     </message>
@@ -2325,32 +2377,32 @@ second %s is the version number
         <translation type="obsolete">Запрос дополнительных узлов можно будет сделать через:</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1362"/>
+        <location filename="details.cc" line="1381"/>
         <source>Up</source>
         <translation>Раздача</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1362"/>
+        <location filename="details.cc" line="1381"/>
         <source>Down</source>
         <translation>Приём</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1362"/>
+        <location filename="details.cc" line="1381"/>
         <source>%</source>
         <translation>%</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1362"/>
+        <location filename="details.cc" line="1381"/>
         <source>Status</source>
         <translation>Состояние</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1362"/>
+        <location filename="details.cc" line="1381"/>
         <source>Address</source>
         <translation>Адрес</translation>
     </message>
     <message>
-        <location filename="details.cc" line="1362"/>
+        <location filename="details.cc" line="1381"/>
         <source>Client</source>
         <translation>Клиент</translation>
     </message>
@@ -2786,8 +2838,9 @@ second %s is the version number
     </message>
     <message>
         <location filename="mainwin.ui" line="336"/>
-        <source>Alt+M</source>
-        <translation></translation>
+        <source>Alt+C</source>
+        <oldsource>Alt+M</oldsource>
+        <translation type="unfinished"></translation>
     </message>
     <message>
         <location filename="mainwin.ui" line="344"/>
@@ -3436,7 +3489,7 @@ To add another primary URL, add it after a blank line.</source>
     </message>
     <message>
         <location filename="prefs-dialog.cc" line="373"/>
-        <location filename="prefs-dialog.cc" line="730"/>
+        <location filename="prefs-dialog.cc" line="735"/>
         <source>Status unknown</source>
         <translation>Статус неизвестен</translation>
     </message>
@@ -3516,7 +3569,7 @@ To add another primary URL, add it after a blank line.</source>
     </message>
     <message>
         <location filename="prefs-dialog.cc" line="475"/>
-        <location filename="prefs-dialog.cc" line="625"/>
+        <location filename="prefs-dialog.cc" line="630"/>
         <source>Privacy</source>
         <translation>Конфиденциальность</translation>
     </message>
@@ -3610,6 +3663,16 @@ To add another primary URL, add it after a blank line.</source>
         <source>Call scrip&amp;t when torrent is completed</source>
         <translation type="unfinished"></translation>
     </message>
+    <message>
+        <location filename="prefs-dialog.cc" line="600"/>
+        <source>Stop seeding at &amp;ratio:</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="prefs-dialog.cc" line="605"/>
+        <source>Stop seeding if idle for &amp;N minutes:</source>
+        <translation type="unfinished"></translation>
+    </message>
     <message>
         <source>Adding Torrents</source>
         <translation type="obsolete">Добавление торрентов</translation>
@@ -3639,51 +3702,51 @@ To add another primary URL, add it after a blank line.</source>
     </message>
     <message>
         <location filename="prefs-dialog.cc" line="598"/>
-        <source>Seeding</source>
+        <source>Seeding Limits</source>
+        <oldsource>Seeding</oldsource>
         <translation type="unfinished">Раздача</translation>
     </message>
     <message>
-        <location filename="prefs-dialog.cc" line="600"/>
         <source>&amp;Seed torrent until its ratio reaches:</source>
-        <translation>&amp;Раздавать до достижения рейтинга:</translation>
+        <translation type="obsolete">&amp;Раздавать до достижения рейтинга:</translation>
     </message>
     <message>
-        <location filename="prefs-dialog.cc" line="620"/>
+        <location filename="prefs-dialog.cc" line="625"/>
         <source>Transmission Preferences</source>
         <translation>Параметры Transmission</translation>
     </message>
     <message>
-        <location filename="prefs-dialog.cc" line="623"/>
+        <location filename="prefs-dialog.cc" line="628"/>
         <source>Torrents</source>
         <translation>Торренты</translation>
     </message>
     <message>
-        <location filename="prefs-dialog.cc" line="624"/>
+        <location filename="prefs-dialog.cc" line="629"/>
         <source>Speed</source>
         <translation>Скорость</translation>
     </message>
     <message>
-        <location filename="prefs-dialog.cc" line="626"/>
+        <location filename="prefs-dialog.cc" line="631"/>
         <source>Network</source>
         <translation>Сеть</translation>
     </message>
     <message>
-        <location filename="prefs-dialog.cc" line="627"/>
+        <location filename="prefs-dialog.cc" line="632"/>
         <source>Web</source>
         <translation>Веб-интерфейс</translation>
     </message>
     <message>
-        <location filename="prefs-dialog.cc" line="656"/>
+        <location filename="prefs-dialog.cc" line="661"/>
         <source>Not supported by remote sessions</source>
         <translation>Не поддерживается удаленными сеансами</translation>
     </message>
     <message>
-        <location filename="prefs-dialog.cc" line="682"/>
+        <location filename="prefs-dialog.cc" line="687"/>
         <source>Enable &amp;blocklist</source>
         <translation>&amp;Включить черный список</translation>
     </message>
     <message numerus="yes">
-        <location filename="prefs-dialog.cc" line="684"/>
+        <location filename="prefs-dialog.cc" line="689"/>
         <source>Enable &amp;blocklist (%Ln rules)</source>
         <translation>
             <numerusform>Включить &amp;чёрный список (%Ln правило)</numerusform>
@@ -3745,7 +3808,7 @@ To add another primary URL, add it after a blank line.</source>
 <context>
     <name>Session</name>
     <message>
-        <location filename="session.cc" line="765"/>
+        <location filename="session.cc" line="769"/>
         <source>Add Torrent</source>
         <translation>Добавить торрент</translation>
     </message>
@@ -3853,47 +3916,47 @@ To add another primary URL, add it after a blank line.</source>
 <context>
     <name>Torrent</name>
     <message>
-        <location filename="torrent.cc" line="677"/>
+        <location filename="torrent.cc" line="683"/>
         <source>Waiting to verify local data</source>
         <translation>Ожидается проверка локальных данных</translation>
     </message>
     <message>
-        <location filename="torrent.cc" line="678"/>
+        <location filename="torrent.cc" line="684"/>
         <source>Verifying local data</source>
         <translation>Проверка локальных данных</translation>
     </message>
     <message>
-        <location filename="torrent.cc" line="679"/>
+        <location filename="torrent.cc" line="685"/>
         <source>Downloading</source>
         <translation>Приём</translation>
     </message>
     <message>
-        <location filename="torrent.cc" line="680"/>
+        <location filename="torrent.cc" line="686"/>
         <source>Seeding</source>
         <translation>Раздача</translation>
     </message>
     <message>
-        <location filename="torrent.cc" line="681"/>
+        <location filename="torrent.cc" line="687"/>
         <source>Paused</source>
         <translation>Приостановлен</translation>
     </message>
     <message>
-        <location filename="torrent.cc" line="681"/>
+        <location filename="torrent.cc" line="687"/>
         <source>Finished</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="torrent.cc" line="694"/>
+        <location filename="torrent.cc" line="700"/>
         <source>Tracker gave a warning: %1</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="torrent.cc" line="695"/>
+        <location filename="torrent.cc" line="701"/>
         <source>Tracker gave an error: %1</source>
         <translation type="unfinished"></translation>
     </message>
     <message>
-        <location filename="torrent.cc" line="696"/>
+        <location filename="torrent.cc" line="702"/>
         <source>Error: %1</source>
         <translation type="unfinished"></translation>
     </message>
@@ -3932,7 +3995,7 @@ To add another primary URL, add it after a blank line.</source>
     </message>
     <message>
         <location filename="torrent-delegate.cc" line="150"/>
-        <location filename="torrent-delegate.cc" line="250"/>
+        <location filename="torrent-delegate.cc" line="252"/>
         <source> - </source>
         <translation> - </translation>
     </message>
@@ -3947,22 +4010,27 @@ To add another primary URL, add it after a blank line.</source>
         <translation>Оставшееся время неизвестно</translation>
     </message>
     <message>
-        <location filename="torrent-delegate.cc" line="174"/>
+        <location filename="torrent-delegate.cc" line="176"/>
+        <source>%1 %2, %3 %4</source>
+        <translation type="unfinished">%1, %3 %4 {1 %2,?}</translation>
+    </message>
+    <message>
         <source>Down: %1, Up: %2</source>
-        <translation>Приём: %1, раздача: %2</translation>
+        <translation type="obsolete">Приём: %1, раздача: %2</translation>
     </message>
     <message>
-        <location filename="torrent-delegate.cc" line="176"/>
         <source>Down: %1</source>
-        <translation>Приём: %1</translation>
+        <translation type="obsolete">Приём: %1</translation>
     </message>
     <message>
         <location filename="torrent-delegate.cc" line="178"/>
-        <source>Up: %1</source>
-        <translation>Раздача: %1</translation>
+        <location filename="torrent-delegate.cc" line="180"/>
+        <source>%1 %2</source>
+        <oldsource>Up: %1</oldsource>
+        <translation type="unfinished">Раздача: %1</translation>
     </message>
     <message>
-        <location filename="torrent-delegate.cc" line="180"/>
+        <location filename="torrent-delegate.cc" line="182"/>
         <source>Idle</source>
         <translation>Нет активности</translation>
     </message>
@@ -3975,17 +4043,17 @@ To add another primary URL, add it after a blank line.</source>
         <translation type="obsolete">Ожидается проверка локальных данных</translation>
     </message>
     <message>
-        <location filename="torrent-delegate.cc" line="193"/>
+        <location filename="torrent-delegate.cc" line="195"/>
         <source>Verifying local data (%1% tested)</source>
         <translation>Проверка локальных данных (%1% проверено)</translation>
     </message>
     <message>
-        <location filename="torrent-delegate.cc" line="199"/>
+        <location filename="torrent-delegate.cc" line="201"/>
         <source>Ratio: %1, </source>
         <translation>Рейтинг: %1</translation>
     </message>
     <message numerus="yes">
-        <location filename="torrent-delegate.cc" line="230"/>
+        <location filename="torrent-delegate.cc" line="232"/>
         <source>Downloading from %1 of %n connected peer(s)</source>
         <translation>
             <numerusform>Приём от %1 из %n подключённого узла</numerusform>
@@ -3994,7 +4062,7 @@ To add another primary URL, add it after a blank line.</source>
         </translation>
     </message>
     <message numerus="yes">
-        <location filename="torrent-delegate.cc" line="233"/>
+        <location filename="torrent-delegate.cc" line="235"/>
         <source>Downloading metadata from %n peer(s) (%1% done)</source>
         <translation type="unfinished">
             <numerusform></numerusform>
@@ -4003,7 +4071,7 @@ To add another primary URL, add it after a blank line.</source>
         </translation>
     </message>
     <message numerus="yes">
-        <location filename="torrent-delegate.cc" line="238"/>
+        <location filename="torrent-delegate.cc" line="240"/>
         <source>Seeding to %1 of %n connected peer(s)</source>
         <translation>
             <numerusform>Раздача к %1 из %n подключённого узла</numerusform>
@@ -4238,6 +4306,69 @@ To add another primary URL, add it after a blank line.</source>
         <translation>Последний ответ от сервера %1 назад</translation>
     </message>
 </context>
+<context>
+    <name>TrackerDelegate</name>
+    <message>
+        <location filename="tracker-delegate.cc" line="161"/>
+        <source>Got a list of %1%2 peers%3 %4 ago</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="169"/>
+        <source>Peer list request timed out %1%2%3 ago; will retry</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="176"/>
+        <source>Got an error %1&apos;%2&apos;%3 %4 ago</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="189"/>
+        <source>No updates scheduled</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="196"/>
+        <source>Asking for more peers in %1</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="202"/>
+        <source>Queued to ask for more peers</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="208"/>
+        <source>Asking for more peers now... &lt;small&gt;%1&lt;/small&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="221"/>
+        <source>Tracker had %1%2 seeders%3 and %4%5 leechers%6 %7 ago</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="232"/>
+        <source>Got a scrape error %1&apos;%2&apos;%3 %4 ago</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="248"/>
+        <source>Asking for peer counts in %1</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="254"/>
+        <source>Queued to ask for peer counts</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="tracker-delegate.cc" line="261"/>
+        <source>Asking for peer counts now... &lt;small&gt;%1&lt;/small&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
 <context>
     <name>Utils</name>
     <message>