source: trunk/qt/details.cc @ 10933

Last change on this file since 10933 was 10933, checked in by charles, 12 years ago

(trunk) rename the Qt client's "Units" class as "Formatter"

  • Property svn:keywords set to Date Rev Author Id
File size: 45.2 KB
Line 
1/*
2 * This file Copyright (C) 2009-2010 Mnemosyne LLC
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: details.cc 10933 2010-07-03 01:10:36Z charles $
11 */
12
13#include <cassert>
14#include <ctime>
15#include <iostream>
16
17#include <QCheckBox>
18#include <QComboBox>
19#include <QDateTime>
20#include <QDialogButtonBox>
21#include <QDoubleSpinBox>
22#include <QEvent>
23#include <QFont>
24#include <QFontMetrics>
25#include <QHBoxLayout>
26#include <QHBoxLayout>
27#include <QHeaderView>
28#include <QInputDialog>
29#include <QLabel>
30#include <QMessageBox>
31#include <QPushButton>
32#include <QRadioButton>
33#include <QResizeEvent>
34#include <QSpinBox>
35#include <QStyle>
36#include <QTabWidget>
37#include <QTextBrowser>
38#include <QTreeView>
39#include <QTreeWidget>
40#include <QTreeWidgetItem>
41#include <QVBoxLayout>
42
43#include <libtransmission/transmission.h>
44#include <libtransmission/bencode.h>
45
46#include "details.h"
47#include "file-tree.h"
48#include "formatter.h"
49#include "hig.h"
50#include "prefs.h"
51#include "qticonloader.h"
52#include "session.h"
53#include "squeezelabel.h"
54#include "torrent.h"
55#include "torrent-model.h"
56
57class Prefs;
58class Session;
59
60/****
61*****
62****/
63
64namespace
65{
66    const int REFRESH_INTERVAL_MSEC = 4000;
67
68    enum // peer columns
69    {
70        COL_LOCK,
71        COL_UP,
72        COL_DOWN,
73        COL_PERCENT,
74        COL_STATUS,
75        COL_ADDRESS,
76        COL_CLIENT,
77        N_COLUMNS
78    };
79}
80
81/***
82****
83***/
84
85class PeerItem: public QTreeWidgetItem
86{
87        Peer peer;
88        QString collatedAddress;
89        QString status;
90
91    public:
92        virtual ~PeerItem( ) { }
93        PeerItem( const Peer& p ) {
94            peer = p;
95            int q[4];
96            if( sscanf( p.address.toUtf8().constData(), "%d.%d.%d.%d", q+0, q+1, q+2, q+3 ) == 4 )
97                collatedAddress.sprintf( "%03d.%03d.%03d.%03d", q[0], q[1], q[2], q[3] );
98            else
99                collatedAddress = p.address;
100        }
101    public:
102        void refresh( const Peer& p ) { peer = p; }
103        void setStatus( const QString& s ) { status = s; }
104        virtual bool operator< ( const QTreeWidgetItem & other ) const {
105            const PeerItem * i = dynamic_cast<const PeerItem*>(&other);
106            QTreeWidget * tw( treeWidget( ) );
107            const int column = tw ? tw->sortColumn() : 0;
108            switch( column ) {
109                case COL_UP: return peer.rateToPeer < i->peer.rateToPeer;
110                case COL_DOWN: return peer.rateToClient < i->peer.rateToClient;
111                case COL_PERCENT: return peer.progress < i->peer.progress;
112                case COL_STATUS: return status < i->status;
113                case COL_CLIENT: return peer.clientName < i->peer.clientName;
114                case COL_LOCK: return peer.isEncrypted && !i->peer.isEncrypted;
115                default: return collatedAddress < i->collatedAddress;
116            }
117        }
118};
119
120/***
121****
122***/
123
124QIcon
125Details :: getStockIcon( const QString& freedesktop_name, int fallback )
126{
127    QIcon fallbackIcon;
128
129    if( fallback > 0 )
130        fallbackIcon = style()->standardIcon( QStyle::StandardPixmap( fallback ), 0, this );
131
132    return QtIconLoader::icon( freedesktop_name, fallbackIcon );
133}
134
135Details :: Details( Session& session, Prefs& prefs, TorrentModel& model, QWidget * parent ):
136    QDialog( parent, Qt::Dialog ),
137    mySession( session ),
138    myPrefs( prefs ),
139    myModel( model ),
140    myChangedTorrents( false ),
141    myHavePendingRefresh( false )
142{
143    QVBoxLayout * layout = new QVBoxLayout( this );
144
145    setWindowTitle( tr( "Torrent Properties" ) );
146
147    QTabWidget * t = new QTabWidget( this );
148    QWidget * w;
149    t->addTab( w = createInfoTab( ),      tr( "Information" ) );
150    myWidgets << w;
151    t->addTab( w = createPeersTab( ),     tr( "Peers" ) );
152    myWidgets << w;
153    t->addTab( w = createTrackerTab( ),   tr( "Tracker" ) );
154    myWidgets << w;
155    t->addTab( w = createFilesTab( ),     tr( "Files" ) );
156    myWidgets << w;
157    t->addTab( w = createOptionsTab( ),   tr( "Options" ) );
158    myWidgets << w;
159    layout->addWidget( t );
160
161    QDialogButtonBox * buttons = new QDialogButtonBox( QDialogButtonBox::Close, Qt::Horizontal, this );
162    connect( buttons, SIGNAL(rejected()), this, SLOT(close()));
163    layout->addWidget( buttons );
164    QWidget::setAttribute( Qt::WA_DeleteOnClose, true );
165
166    connect( &myTimer, SIGNAL(timeout()), this, SLOT(onTimer()));
167
168    onTimer( );
169    myTimer.setSingleShot( false );
170    myTimer.start( REFRESH_INTERVAL_MSEC );
171}
172
173Details :: ~Details( )
174{
175}
176
177void
178Details :: setIds( const QSet<int>& ids )
179{
180    if( ids == myIds )
181        return;
182
183    myChangedTorrents = true;
184
185    // stop listening to the old torrents
186    foreach( int id, myIds ) {
187        const Torrent * tor = myModel.getTorrentFromId( id );
188        if( tor )
189            disconnect( tor, SIGNAL(torrentChanged(int)), this, SLOT(onTorrentChanged()) );
190    }
191
192    myFileTreeView->clear( );
193
194    myIds = ids;
195
196    // listen to the new torrents
197    foreach( int id, myIds ) {
198        const Torrent * tor = myModel.getTorrentFromId( id );
199        if( tor )
200            connect( tor, SIGNAL(torrentChanged(int)), this, SLOT(onTorrentChanged()) );
201    }
202
203    foreach( QWidget * w, myWidgets )
204        w->setEnabled( false );
205
206    onTimer( );
207}
208
209/***
210****
211***/
212
213QString
214Details :: timeToStringRounded( int seconds )
215{
216    if( seconds > 60 ) seconds -= ( seconds % 60 );
217    return Formatter::timeToString ( seconds );
218}
219
220void
221Details :: onTimer( )
222{
223    if( !myIds.empty( ) )
224    {
225        QSet<int> infos;
226        foreach( int id, myIds ) {
227            const Torrent * tor = myModel.getTorrentFromId( id );
228            if( tor->isMagnet() )
229                infos.insert( tor->id() );
230        }
231        if( !infos.isEmpty() )
232            mySession.initTorrents( infos );
233        mySession.refreshExtraStats( myIds );
234    }
235}
236
237void
238Details :: onTorrentChanged( )
239{
240    if( !myHavePendingRefresh ) {
241        myHavePendingRefresh = true;
242        QTimer::singleShot( 100, this, SLOT(refresh()));
243    }
244}
245
246
247void
248Details :: refresh( )
249{
250    const int n = myIds.size( );
251    const bool single = n == 1;
252    const QString blank;
253    const QFontMetrics fm( fontMetrics( ) );
254    QList<const Torrent*> torrents;
255    QString string;
256    const QString none = tr( "None" );
257    const QString mixed = tr( "Mixed" );
258    const QString unknown = tr( "Unknown" );
259
260    // build a list of torrents
261    foreach( int id, myIds ) {
262        const Torrent * tor = myModel.getTorrentFromId( id );
263        if( tor )
264            torrents << tor;
265    }
266
267    ///
268    ///  activity tab
269    ///
270
271    // myStateLabel
272    if( torrents.empty( ) )
273        string = none;
274    else {
275        bool isMixed = false;
276        bool allPaused = true;
277        bool allFinished = true;
278        const tr_torrent_activity activity = torrents[0]->getActivity( );
279        foreach( const Torrent * t, torrents ) {
280            if( activity != t->getActivity( ) )
281                isMixed = true;
282            if( activity != TR_STATUS_STOPPED )
283                allPaused = allFinished = false;
284            if( !t->isFinished( ) )
285                allFinished = false;
286        }
287        if( isMixed )
288            string = mixed;
289        else if( allFinished )
290            string = tr( "Finished" );
291        else if( allPaused )
292            string = tr( "Paused" );
293        else
294            string = torrents[0]->activityString( );
295    }
296    myStateLabel->setText( string );
297    const QString stateString = string;
298
299    // myHaveLabel
300    double sizeWhenDone = 0;
301    double leftUntilDone = 0;
302    double available = 0;
303    int64_t haveTotal = 0;
304    int64_t haveVerified = 0;
305    int64_t haveUnverified = 0;
306    int64_t verifiedPieces = 0;
307    if( torrents.empty( ) )
308        string = none;
309    else {
310        foreach( const Torrent * t, torrents ) {
311            if( t->hasMetadata( ) ) {
312                haveTotal += t->haveTotal( );
313                haveUnverified += t->haveUnverified( );
314                const uint64_t v = t->haveVerified( );
315                haveVerified += v;
316                verifiedPieces += v / t->pieceSize( );
317                sizeWhenDone += t->sizeWhenDone( );
318                leftUntilDone += t->leftUntilDone( );
319                available += t->sizeWhenDone() - t->leftUntilDone() + t->desiredAvailable();
320            }
321        }
322        if( !haveVerified && !haveUnverified )
323            string = none;
324        else {
325            const double d = 100.0 * ( sizeWhenDone ? ( sizeWhenDone - leftUntilDone ) / sizeWhenDone : 1 );
326            QString pct = Formatter::percentToString( d );
327            if( !haveUnverified )
328                string = tr( "%1 (%2%)" )
329                             .arg( Formatter::sizeToString( haveVerified + haveUnverified ) )
330                             .arg( pct );
331            else
332                string = tr( "%1 (%2%); %3 Unverified" )
333                             .arg( Formatter::sizeToString( haveVerified + haveUnverified ) )
334                             .arg( pct )
335                             .arg( Formatter::sizeToString( haveUnverified ) );
336        }
337    }
338    myHaveLabel->setText( string );
339
340    // myAvailabilityLabel
341    if( torrents.empty( ) )
342        string = none;
343    else {
344        if( sizeWhenDone == 0 )
345            string = none;
346        else
347            string = QString( "%1%" ).arg( Formatter::percentToString( ( 100.0 * available ) / sizeWhenDone ) );
348    }
349    myAvailabilityLabel->setText( string );
350
351    // myDownloadedLabel
352    uint64_t d = 0, f = 0;
353    if( torrents.empty( ) )
354        string = none;
355    else {
356        foreach( const Torrent * t, torrents ) {
357            d += t->downloadedEver( );
358            f += t->failedEver( );
359        }
360        const QString dstr = Formatter::sizeToString( d );
361        const QString fstr = Formatter::sizeToString( f );
362        if( f )
363            string = tr( "%1 (+%2 corrupt)" ).arg( dstr ).arg( fstr );
364        else
365            string = dstr;
366    }
367    myDownloadedLabel->setText( string );
368
369    uint64_t u = 0;
370    if( torrents.empty( ) )
371        string = none;
372    else {
373        foreach( const Torrent * t, torrents ) u += t->uploadedEver( );
374        string = QString( Formatter::sizeToString( u ) );
375    }
376    myUploadedLabel->setText( string );
377
378    if( torrents.empty( ) )
379        string = none;
380    else if( torrents.length() == 1 )
381        string = Formatter::ratioToString( torrents.first()->ratio() );
382    else {
383        bool isMixed = false;
384        int ratioType = torrents.first()->ratio();
385        if( ratioType > 0 ) ratioType = 0;
386        foreach( const Torrent *t, torrents )
387        {
388            if( ratioType != ( t->ratio() >= 0 ? 0 : t->ratio() ) )
389            {
390                isMixed = true;
391                break;
392            }
393        }
394        if( isMixed )
395            string = mixed;
396        else if( ratioType < 0 )
397            string = Formatter::ratioToString( ratioType );
398        else
399            string = Formatter::ratioToString( (double)u / d );
400    }
401    myRatioLabel->setText( string );
402
403    const QDateTime qdt_now = QDateTime::currentDateTime( );
404
405    // myRunTimeLabel
406    if( torrents.empty( ) )
407        string = none;
408    else {
409        bool allPaused = true;
410        QDateTime baseline = torrents[0]->lastStarted( );
411        foreach( const Torrent * t, torrents ) {
412            if( baseline != t->lastStarted( ) )
413                baseline = QDateTime( );
414            if( !t->isPaused( ) )
415                allPaused = false;
416        }
417        if( allPaused )
418            string = stateString; // paused || finished
419        else if( baseline.isNull( ) )
420            string = mixed;
421        else
422            string = Formatter::timeToString( baseline.secsTo( qdt_now ) );
423    }
424    myRunTimeLabel->setText( string );
425
426
427    // myETALabel
428    string.clear( );
429    if( torrents.empty( ) )
430        string = none;
431    else {
432        int baseline = torrents[0]->getETA( );
433        foreach( const Torrent * t, torrents ) {
434            if( baseline != t->getETA( ) ) {
435                string = mixed;
436                break;
437            }
438        }
439        if( string.isEmpty( ) ) {
440            if( baseline < 0 )
441                string = tr( "Unknown" );
442            else
443                string = Formatter::timeToString( baseline );
444       }
445    }
446    myETALabel->setText( string );
447
448
449    // myLastActivityLabel
450    if( torrents.empty( ) )
451        string = none;
452    else {
453        QDateTime latest = torrents[0]->lastActivity( );
454        foreach( const Torrent * t, torrents ) {
455            const QDateTime dt = t->lastActivity( );
456            if( latest < dt )
457                latest = dt;
458        }
459        const int seconds = latest.secsTo( qdt_now );
460        if( seconds < 5 )
461            string = tr( "Active now" );
462        else
463            string = tr( "%1 ago" ).arg( Formatter::timeToString( seconds ) );
464    }
465    myLastActivityLabel->setText( string );
466
467
468    if( torrents.empty( ) )
469        string = none;
470    else {
471        string = torrents[0]->getError( );
472        foreach( const Torrent * t, torrents ) {
473            if( string != t->getError( ) ) {
474                string = mixed;
475                break;
476            }
477        }
478    }
479    if( string.isEmpty( ) )
480        string = none;
481    myErrorLabel->setText( string );
482
483
484    ///
485    /// information tab
486    ///
487
488    // mySizeLabel
489    if( torrents.empty( ) )
490        string = none;
491    else {
492        int pieces = 0;
493        uint64_t size = 0;
494        uint32_t pieceSize = torrents[0]->pieceSize( );
495        foreach( const Torrent * t, torrents ) {
496            pieces += t->pieceCount( );
497            size += t->totalSize( );
498            if( pieceSize != t->pieceSize( ) )
499                pieceSize = 0;
500        }
501        if( !size )
502            string = none;
503        else if( pieceSize > 0 )
504            string = tr( "%1 (%Ln pieces @ %2)", "", pieces )
505                     .arg( Formatter::sizeToString( size ) )
506                     .arg( Formatter::memToString( pieceSize ) );
507        else
508            string = tr( "%1 (%Ln pieces)", "", pieces )
509                     .arg( Formatter::sizeToString( size ) );
510    }
511    mySizeLabel->setText( string );
512
513    // myHashLabel
514    if( torrents.empty( ) )
515        string = none;
516    else {
517        string = torrents[0]->hashString( );
518        foreach( const Torrent * t, torrents ) {
519            if( string != t->hashString( ) ) {
520                string = mixed;
521                break;
522            }
523        }
524    }
525    myHashLabel->setText( string );
526
527    // myPrivacyLabel
528    if( torrents.empty( ) )
529        string = none;
530    else {
531        bool b = torrents[0]->isPrivate( );
532        string = b ? tr( "Private to this tracker -- DHT and PEX disabled" )
533                   : tr( "Public torrent" );
534        foreach( const Torrent * t, torrents ) {
535            if( b != t->isPrivate( ) ) {
536                string = mixed;
537                break;
538            }
539        }
540    }
541    myPrivacyLabel->setText( string );
542
543    // myCommentBrowser
544    if( torrents.empty( ) )
545        string = none;
546    else {
547        string = torrents[0]->comment( );
548        foreach( const Torrent * t, torrents ) {
549            if( string != t->comment( ) ) {
550                string = mixed;
551                break;
552            }
553        }
554    }
555    myCommentBrowser->setText( string );
556    myCommentBrowser->setMaximumHeight( QWIDGETSIZE_MAX );
557
558    // myOriginLabel
559    if( torrents.empty( ) )
560        string = none;
561    else {
562        bool mixed_creator=false, mixed_date=false;
563        const QString creator = torrents[0]->creator();
564        const QString date = torrents[0]->dateCreated().toString();
565        foreach( const Torrent * t, torrents ) {
566            mixed_creator |= ( creator != t->creator() );
567            mixed_date |=  ( date != t->dateCreated().toString() );
568        }
569        if( mixed_creator && mixed_date )
570            string = mixed;
571        else if( mixed_date )
572            string = tr( "Created by %1" ).arg( creator );
573        else if( mixed_creator || creator.isEmpty( ) )
574            string = tr( "Created on %1" ).arg( date );
575        else
576            string = tr( "Created by %1 on %2" ).arg( creator ).arg( date );
577    }
578    myOriginLabel->setText( string );
579
580    // myLocationLabel
581    if( torrents.empty( ) )
582        string = none;
583    else {
584        string = torrents[0]->getPath( );
585        foreach( const Torrent * t, torrents ) {
586            if( string != t->getPath( ) ) {
587                string = mixed;
588                break;
589            }
590        }
591    }
592    myLocationLabel->setText( string );
593
594
595    ///
596    ///  Options Tab
597    ///
598
599    if( myChangedTorrents && !torrents.empty( ) )
600    {
601        int i;
602        const Torrent * baseline = *torrents.begin();
603        const Torrent * tor;
604        bool uniform;
605        bool baselineFlag;
606        int baselineInt;
607
608        // mySessionLimitCheck
609        uniform = true;
610        baselineFlag = baseline->honorsSessionLimits( );
611        foreach( tor, torrents ) if( baselineFlag != tor->honorsSessionLimits( ) ) { uniform = false; break; }
612        mySessionLimitCheck->setChecked( uniform && baselineFlag );
613
614        // mySingleDownCheck
615        uniform = true;
616        baselineFlag = baseline->downloadIsLimited( );
617        foreach( tor, torrents ) if( baselineFlag != tor->downloadIsLimited( ) ) { uniform = false; break; }
618        mySingleDownCheck->setChecked( uniform && baselineFlag );
619
620        // mySingleUpCheck
621        uniform = true;
622        baselineFlag = baseline->uploadIsLimited( );
623        foreach( tor, torrents ) if( baselineFlag != tor->uploadIsLimited( ) ) { uniform = false; break; }
624        mySingleUpCheck->setChecked( uniform && baselineFlag );
625
626        // myBandwidthPriorityCombo
627        uniform = true;
628        baselineInt = baseline->getBandwidthPriority( );
629        foreach( tor, torrents ) if ( baselineInt != tor->getBandwidthPriority( ) ) { uniform = false; break; }
630        if( uniform )
631            i = myBandwidthPriorityCombo->findData( baselineInt );
632        else
633            i = -1;
634        myBandwidthPriorityCombo->blockSignals( true );
635        myBandwidthPriorityCombo->setCurrentIndex( i );
636        myBandwidthPriorityCombo->blockSignals( false );
637
638        mySingleDownSpin->blockSignals( true );
639        mySingleDownSpin->setValue( tor->downloadLimit().Bps() / Formatter::speed_K );
640        mySingleDownSpin->blockSignals( false );
641
642        mySingleUpSpin->blockSignals( true );
643        mySingleUpSpin->setValue( tor->uploadLimit().Bps() / Formatter::speed_K );
644        mySingleUpSpin->blockSignals( false );
645
646        myPeerLimitSpin->blockSignals( true );
647        myPeerLimitSpin->setValue( tor->peerLimit() );
648        myPeerLimitSpin->blockSignals( false );
649
650        // ratio radios
651        uniform = true;
652        baselineInt = tor->seedRatioMode( );
653        foreach( tor, torrents ) if( baselineInt != tor->seedRatioMode( ) ) { uniform = false; break; }
654        if( !uniform ) {
655            mySeedGlobalRadio->setChecked( false );
656            mySeedCustomRadio->setChecked( false );
657            mySeedForeverRadio->setChecked( false );
658        } else {
659            QRadioButton * rb;
660            switch( baselineInt ) {
661                case TR_RATIOLIMIT_GLOBAL:    rb = mySeedGlobalRadio; break;
662                case TR_RATIOLIMIT_SINGLE:    rb = mySeedCustomRadio; break;
663                case TR_RATIOLIMIT_UNLIMITED: rb = mySeedForeverRadio; break;
664            }
665            rb->setChecked( true );
666        }
667
668        mySeedCustomSpin->blockSignals( true );
669        mySeedCustomSpin->setValue( tor->seedRatioLimit( ) );
670        mySeedCustomSpin->blockSignals( false );
671    }
672
673    // tracker tab
674    //
675    QMap<QString,QTreeWidgetItem*> trackerTiers;
676    QMap<QString,QTreeWidgetItem*> trackerItems;
677    const time_t now( time( 0 ) );
678    const bool showScrape = myPrefs.getBool( Prefs::SHOW_TRACKER_SCRAPES );
679    foreach( const Torrent * t, torrents )
680    {
681        const QString idStr( QString::number( t->id( ) ) );
682        const TrackerStatsList trackerStats = t->trackerStats( );
683
684        foreach( const TrackerStat& trackerStat, trackerStats )
685        {
686            QFont font;
687            QString str;
688            const QString tierKey( QString::number(trackerStat.tier) );
689            QTreeWidgetItem * tier = (QTreeWidgetItem*) myTrackerTiers.value( tierKey, 0 );
690
691            if( tier == 0 ) // new tier
692            {
693                QFont tierFont;
694                tier = new QTreeWidgetItem( myTrackerTree );
695                myTrackerTree->addTopLevelItem( tier );
696                str = "Tier: " + QString::number( trackerStat.tier + 1 );
697                tier->setText( 0, str );
698                tierFont.setBold( true );
699                tier->setFont( 0, tierFont );
700            }
701
702            const QString key( idStr + tierKey + ":" + QString::number( trackerStat.id ) );
703            QTreeWidgetItem * item = (QTreeWidgetItem*) myTrackerItems.value( key, 0 );
704
705            if( item == 0 ) // new tracker
706            {
707                item = new QTreeWidgetItem( tier );
708                tier->addChild( item );
709                if( tier->childCount() == 1 )
710                    tier->setExpanded( true );
711            }
712            str = trackerStat.host;
713
714            if( trackerStat.isBackup )
715            {
716                font.setItalic( true );
717                if( showScrape )
718                {
719                    str += "\n";
720                    str += "Tracker will be used as a backup";
721                }
722            }
723            else
724            {
725                font.setItalic( false );
726                if( trackerStat.hasAnnounced )
727                {
728                    const QString tstr( timeToStringRounded( now - trackerStat.lastAnnounceTime ) );
729                    str += "\n";
730                    if( trackerStat.lastAnnounceSucceeded )
731                    {
732                        str += tr( "Got a list of %1 peers %2 ago" )
733                            .arg( trackerStat.lastAnnouncePeerCount )
734                            .arg( tstr );
735                    }
736                    else if( trackerStat.lastAnnounceTimedOut )
737                    {
738                        str += tr( "Peer list request timed out %1 ago; will retry" )
739                            .arg( tstr );
740                    }
741                    else
742                    {
743                        str += tr( "Got an error %1 ago" )
744                            .arg( tstr );
745                    }
746                }
747                switch( trackerStat.announceState )
748                {
749                    case TR_TRACKER_INACTIVE:
750                        if( trackerStat.hasAnnounced )
751                        {
752                            str += "\n";
753                            str += tr( "No updates scheduled" );
754                        }
755                        break;
756                    case TR_TRACKER_WAITING:
757                        {
758                            const QString tstr( timeToStringRounded( trackerStat.nextAnnounceTime - now ) );
759                            str += "\n";
760                            str += tr( "Asking for more peers in %1" )
761                                .arg( tstr );
762                        }
763                        break;
764                    case TR_TRACKER_QUEUED:
765                        str += "\n";
766                        str += tr( "Queued to ask for more peers" );
767                        break;
768                    case TR_TRACKER_ACTIVE:
769                        {
770                            const QString tstr( timeToStringRounded( now - trackerStat.lastAnnounceStartTime ) );
771                            str += "\n";
772                            str += tr( "Asking for more peers now... %1" )
773                                .arg( tstr );
774                        }
775                        break;
776                }
777                if( showScrape )
778                {
779                    if( trackerStat.hasScraped )
780                    {
781                        const QString tstr( timeToStringRounded( now - trackerStat.lastScrapeTime ) );
782                        str += "\n";
783                        if( trackerStat.lastScrapeSucceeded )
784                        {
785                            str += tr( "Tracker had %1 seeders and %2 leechers %3 ago" )
786                                .arg( trackerStat.seederCount )
787                                .arg( trackerStat.leecherCount )
788                                .arg( tstr );
789                        }
790                        else
791                        {
792                            str += tr( "Got a scrape error %1 ago" )
793                                .arg( tstr );
794                        }
795                    }
796                    switch( trackerStat.scrapeState )
797                    {
798                        case TR_TRACKER_INACTIVE:
799                            break;
800                        case TR_TRACKER_WAITING:
801                            {
802                                const QString tstr( timeToStringRounded( trackerStat.nextScrapeTime - now ) );
803                                str += "\n";
804                                str += tr( "Asking for peer counts in %1" )
805                                    .arg( tstr );
806                            }
807                            break;
808                        case TR_TRACKER_QUEUED:
809                            str += "\n";
810                            str += tr( "Queued to ask for peer counts" );
811                            break;
812                        case TR_TRACKER_ACTIVE:
813                            {
814                                const QString tstr( timeToStringRounded( now - trackerStat.lastScrapeStartTime ) );
815                                str += "\n";
816                                str += tr( "Asking for peer counts now... %1" )
817                                    .arg( tstr );
818                            }
819                            break;
820                    }
821                }
822            }
823            item->setText( 0, str );
824            item->setFont( 0, font );
825            item->setData( 0, TRACKERID, trackerStat.id );
826            item->setData( 0, TRACKERURL, trackerStat.announce );
827            item->setData( 0, TRACKERTIER, trackerStat.tier );
828            item->setData( 0, TORRENTID, t->id() );
829
830            tier->setData( 0, TRACKERID, -1 );
831            tier->setData( 0, TRACKERURL, QString() );
832            tier->setData( 0, TRACKERTIER, trackerStat.tier );
833            tier->setData( 0, TORRENTID, torrents.count() > 1 ? -1 : t->id() );
834
835            trackerTiers.insert( tierKey, tier );
836            trackerItems.insert( key, item );
837        }
838    }
839    QList<QTreeWidgetItem*> tierList = trackerTiers.values();
840    QList<QTreeWidgetItem*> itemList = trackerItems.values();
841    for( int i = 0; i < myTrackerTree->topLevelItemCount(); ++i )
842    {
843        QTreeWidgetItem * tier = myTrackerTree->topLevelItem( i );
844        for( int j = 0; j < tier->childCount(); ++j )
845        {
846            if( !itemList.contains( tier->child( j ) ) ) // tracker has disappeared
847                delete tier->takeChild( j-- );
848        }
849        if( !tierList.contains( tier ) ) // tier has disappeared
850            delete myTrackerTree->takeTopLevelItem( i-- );
851    }
852    myTrackerTiers = trackerTiers;
853    myTrackerItems = trackerItems;
854
855    ///
856    ///  Peers tab
857    ///
858
859    QMap<QString,QTreeWidgetItem*> peers2;
860    QList<QTreeWidgetItem*> newItems;
861    foreach( const Torrent * t, torrents )
862    {
863        const QString idStr( QString::number( t->id( ) ) );
864        PeerList peers = t->peers( );
865
866        foreach( const Peer& peer, peers )
867        {
868            const QString key = idStr + ":" + peer.address;
869            PeerItem * item = (PeerItem*) myPeers.value( key, 0 );
870
871            if( item == 0 ) // new peer has connected
872            {
873                static const QIcon myEncryptionIcon( ":/icons/encrypted.png" );
874                static const QIcon myEmptyIcon;
875                item = new PeerItem( peer );
876                item->setTextAlignment( COL_UP, Qt::AlignRight );
877                item->setTextAlignment( COL_DOWN, Qt::AlignRight );
878                item->setTextAlignment( COL_PERCENT, Qt::AlignRight );
879                item->setIcon( COL_LOCK, peer.isEncrypted ? myEncryptionIcon : myEmptyIcon );
880                item->setToolTip( COL_LOCK, peer.isEncrypted ? tr( "Encrypted connection" ) : "" );
881                item->setText( COL_ADDRESS, peer.address );
882                item->setText( COL_CLIENT, peer.clientName );
883                newItems << item;
884            }
885
886            const QString code = peer.flagStr;
887            item->setStatus( code );
888            item->refresh( peer );
889
890            QString codeTip;
891            foreach( QChar ch, code ) {
892                QString txt;
893                switch( ch.toAscii() ) {
894                    case 'O': txt = tr( "Optimistic unchoke" ); break;
895                    case 'D': txt = tr( "Downloading from this peer" ); break;
896                    case 'd': txt = tr( "We would download from this peer if they would let us" ); break;
897                    case 'U': txt = tr( "Uploading to peer" ); break;
898                    case 'u': txt = tr( "We would upload to this peer if they asked" ); break;
899                    case 'K': txt = tr( "Peer has unchoked us, but we're not interested" ); break;
900                    case '?': txt = tr( "We unchoked this peer, but they're not interested" ); break;
901                    case 'E': txt = tr( "Encrypted connection" ); break;
902                    case 'H': txt = tr( "Peer was discovered through DHT" ); break;
903                    case 'X': txt = tr( "Peer was discovered through Peer Exchange (PEX)" ); break;
904                    case 'I': txt = tr( "Peer is an incoming connection" ); break;
905                }
906                if( !txt.isEmpty( ) )
907                    codeTip += QString("%1: %2\n").arg(ch).arg(txt);
908            }
909
910            if( !codeTip.isEmpty() )
911                codeTip.resize( codeTip.size()-1 ); // eat the trailing linefeed
912
913            item->setText( COL_UP, peer.rateToPeer.isZero() ? "" : Formatter::speedToString( peer.rateToPeer ) );
914            item->setText( COL_DOWN, peer.rateToClient.isZero() ? "" : Formatter::speedToString( peer.rateToClient ) );
915            item->setText( COL_PERCENT, peer.progress > 0 ? QString( "%1%" ).arg( (int)( peer.progress * 100.0 ) ) : "" );
916            item->setText( COL_STATUS, code );
917            item->setToolTip( COL_STATUS, codeTip );
918
919            peers2.insert( key, item );
920        }
921    }
922    myPeerTree->addTopLevelItems( newItems );
923    foreach( QString key, myPeers.keys() ) {
924        if( !peers2.contains( key ) ) { // old peer has disconnected
925            QTreeWidgetItem * item = myPeers.value( key, 0 );
926            myPeerTree->takeTopLevelItem( myPeerTree->indexOfTopLevelItem( item ) );
927            delete item;
928        }
929    }
930    myPeers = peers2;
931
932    if( single )
933        myFileTreeView->update( torrents[0]->files( ) , myChangedTorrents );
934    else
935        myFileTreeView->clear( );
936
937    myChangedTorrents = false;
938    myHavePendingRefresh = false;
939    foreach( QWidget * w, myWidgets )
940        w->setEnabled( true );
941}
942
943void
944Details :: enableWhenChecked( QCheckBox * box, QWidget * w )
945{
946    connect( box, SIGNAL(toggled(bool)), w, SLOT(setEnabled(bool)) );
947    w->setEnabled( box->isChecked( ) );
948}
949
950
951/***
952****
953***/
954
955QWidget *
956Details :: createInfoTab( )
957{
958    HIG * hig = new HIG( this );
959
960    hig->addSectionTitle( tr( "Activity" ) );
961    hig->addRow( tr( "Torrent size:" ), mySizeLabel = new SqueezeLabel );
962    hig->addRow( tr( "Have:" ), myHaveLabel = new SqueezeLabel );
963    hig->addRow( tr( "Availability:" ), myAvailabilityLabel = new SqueezeLabel );
964    hig->addRow( tr( "Downloaded:" ), myDownloadedLabel = new SqueezeLabel );
965    hig->addRow( tr( "Uploaded:" ), myUploadedLabel = new SqueezeLabel );
966    hig->addRow( tr( "Ratio:" ), myRatioLabel = new SqueezeLabel );
967    hig->addRow( tr( "State:" ), myStateLabel = new SqueezeLabel );
968    hig->addRow( tr( "Running time:" ), myRunTimeLabel = new SqueezeLabel );
969    hig->addRow( tr( "Remaining time:" ), myETALabel = new SqueezeLabel );
970    hig->addRow( tr( "Last activity:" ), myLastActivityLabel = new SqueezeLabel );
971    hig->addRow( tr( "Error:" ), myErrorLabel = new SqueezeLabel );
972    hig->addSectionDivider( );
973
974    hig->addSectionDivider( );
975    hig->addSectionTitle( tr( "Details" ) );
976    hig->addRow( tr( "Location:" ), myLocationLabel = new SqueezeLabel );
977    hig->addRow( tr( "Hash:" ), myHashLabel = new SqueezeLabel );
978    hig->addRow( tr( "Privacy:" ), myPrivacyLabel = new SqueezeLabel );
979    hig->addRow( tr( "Origin:" ), myOriginLabel = new SqueezeLabel );
980    myOriginLabel->setMinimumWidth( 325 ); // stop long origin strings from resizing the widgit
981    hig->addRow( tr( "Comment:" ), myCommentBrowser = new QTextBrowser );
982    const int h = QFontMetrics(myCommentBrowser->font()).lineSpacing() * 4;
983    myCommentBrowser->setFixedHeight( h );
984
985    hig->finish( );
986
987    return hig;
988}
989
990/***
991****
992***/
993
994void
995Details :: onShowTrackerScrapesToggled( bool val )
996{
997    myPrefs.set( Prefs::SHOW_TRACKER_SCRAPES, val );
998}
999
1000void
1001Details :: onHonorsSessionLimitsToggled( bool val )
1002{
1003    mySession.torrentSet( myIds, "honorsSessionLimits", val );
1004}
1005void
1006Details :: onDownloadLimitedToggled( bool val )
1007{
1008    mySession.torrentSet( myIds, "downloadLimited", val );
1009}
1010void
1011Details :: onDownloadLimitChanged( int val )
1012{
1013    mySession.torrentSet( myIds, "downloadLimit", val * Formatter::speed_K  );
1014}
1015void
1016Details :: onUploadLimitedToggled( bool val )
1017{
1018    mySession.torrentSet( myIds, "uploadLimited", val );
1019}
1020void
1021Details :: onUploadLimitChanged( int val )
1022{
1023    mySession.torrentSet( myIds, "uploadLimit", val * Formatter::speed_K );
1024}
1025
1026#define RATIO_KEY "seedRatioMode"
1027
1028void
1029Details :: onSeedUntilChanged( bool b )
1030{
1031    if( b )
1032        mySession.torrentSet( myIds, RATIO_KEY, sender()->property(RATIO_KEY).toInt() );
1033}
1034
1035void
1036Details :: onSeedRatioLimitChanged( double val )
1037{
1038    QSet<int> ids;
1039
1040    foreach( int id, myIds ) {
1041        const Torrent * tor = myModel.getTorrentFromId( id );
1042        if( tor && tor->seedRatioLimit( ) )
1043            ids.insert( id );
1044    }
1045
1046    if( !ids.empty( ) )
1047        mySession.torrentSet( ids, "seedRatioLimit", val );
1048}
1049
1050void
1051Details :: onMaxPeersChanged( int val )
1052{
1053    mySession.torrentSet( myIds, "peer-limit", val );
1054}
1055
1056void
1057Details :: onBandwidthPriorityChanged( int index )
1058{
1059    if( index != -1 )
1060    {
1061        const int priority = myBandwidthPriorityCombo->itemData(index).toInt( );
1062        mySession.torrentSet( myIds, "bandwidthPriority", priority );
1063    }
1064}
1065
1066void
1067Details :: onTrackerSelectionChanged( )
1068{
1069    const QList<QTreeWidgetItem*> items = myTrackerTree->selectedItems();
1070    if( items.count() == 1 )
1071        myEditTrackerButton->setEnabled( items.first()->data( 0, TRACKERID ).toInt() >= 0 );
1072    else
1073        myEditTrackerButton->setEnabled( false );
1074    myRemoveTrackerButton->setEnabled( !items.isEmpty() );
1075}
1076
1077bool
1078Details :: findTrackerByURL( const QString& url, int torId )
1079{
1080    bool duplicate = false;
1081    foreach( QTreeWidgetItem * tracker, myTrackerItems.values() )
1082    {
1083        if( tracker->data( 0, TRACKERURL ).toString() == url &&
1084          ( torId == -1 || tracker->data( 0, TORRENTID ).toInt() == torId ) )
1085        {
1086            duplicate = true;
1087            break;
1088        }
1089    }
1090    return duplicate;
1091}
1092
1093void
1094Details :: onAddTrackerPushed( )
1095{
1096    const QString urlString = QInputDialog::getText( this,
1097                                                     tr( "Add tracker announce URL " ),
1098                                                     NULL );
1099    if( !urlString.isEmpty() )
1100    {
1101        if( !findTrackerByURL( urlString, -1 ) )
1102        {
1103            QByteArray url = urlString.toUtf8();
1104            tr_benc top;
1105
1106            tr_bencInitDict( &top, 1 );
1107            tr_bencDictAddStr( &top, "announce", url );
1108
1109            mySession.torrentSet( myIds, "trackerAdd", &top );
1110        }
1111        else
1112            QMessageBox::warning( this, "Error", "Tracker already exists." );
1113    }
1114}
1115
1116void
1117Details :: onEditTrackerPushed( )
1118{
1119    const QTreeWidgetItem * item = myTrackerTree->selectedItems().first();
1120    const QString urlString = QInputDialog::getText( this,
1121                                                     tr( "Edit tracker announce URL " ),
1122                                                     NULL,
1123                                                     QLineEdit::Normal,
1124                                                     item->data( 0, TRACKERURL ).toString() );
1125    if( !urlString.isEmpty() )
1126    {
1127        const int torId = item->data( 0, TORRENTID ).toInt();
1128        if( !findTrackerByURL( urlString, torId ) )
1129        {
1130            QByteArray url = urlString.toUtf8();
1131            QSet<int> ids;
1132            tr_benc top;
1133
1134            ids << torId;
1135            tr_bencInitDict( &top, 2 );
1136            tr_bencDictAddStr( &top, "announce", item->data( 0, TRACKERURL ).toByteArray() );
1137            tr_bencDictAddStr( &top, "announce-new", url );
1138
1139            mySession.torrentSet( ids, "trackerEdit", &top );
1140        }
1141        else
1142            QMessageBox::warning( this, "Error", "Tracker already exists." );
1143    }
1144}
1145
1146void
1147Details :: removeTracker( const QTreeWidgetItem * item )
1148{
1149    QByteArray url = item->data( 0, TRACKERURL ).toByteArray();
1150    const int torId = item->data( 0, TORRENTID ).toInt();
1151    QSet<int> ids;
1152    tr_benc top;
1153
1154    ids << torId;
1155    tr_bencInitDict( &top, 1 );
1156    tr_bencDictAddStr( &top, "announce", url );
1157
1158    mySession.torrentSet( ids, "trackerRemove", &top );
1159}
1160
1161void
1162Details :: onRemoveTrackerPushed( )
1163{
1164    const QTreeWidgetItem * item = myTrackerTree->selectedItems().first();
1165    const bool isTier = item->data( 0, TRACKERID ).toInt() == -1;
1166    if( isTier )
1167    {
1168        for( int i = 0; i < item->childCount(); ++i )
1169            removeTracker( item->child( i ) );
1170    }
1171    else
1172        removeTracker( item );
1173}
1174
1175QWidget *
1176Details :: createOptionsTab( )
1177{
1178    //QWidget * l;
1179    QSpinBox * s;
1180    QCheckBox * c;
1181    QComboBox * m;
1182    QHBoxLayout * h;
1183    QRadioButton * r;
1184    QDoubleSpinBox * ds;
1185
1186    HIG * hig = new HIG( this );
1187    hig->addSectionTitle( tr( "Speed" ) );
1188
1189    c = new QCheckBox( tr( "Honor global &limits" ) );
1190    mySessionLimitCheck = c;
1191    hig->addWideControl( c );
1192    connect( c, SIGNAL(clicked(bool)), this, SLOT(onHonorsSessionLimitsToggled(bool)) );
1193
1194    c = new QCheckBox( tr( "Limit &download speed (%1):" ).arg( Formatter::speed_K_str ) );
1195    mySingleDownCheck = c;
1196    s = new QSpinBox( );
1197    mySingleDownSpin = s;
1198    s->setRange( 0, INT_MAX );
1199    hig->addRow( c, s );
1200    enableWhenChecked( c, s );
1201    connect( c, SIGNAL(clicked(bool)), this, SLOT(onDownloadLimitedToggled(bool)) );
1202    connect( s, SIGNAL(valueChanged(int)), this, SLOT(onDownloadLimitChanged(int)));
1203
1204    c = new QCheckBox( tr( "Limit &upload speed (%1):" ).arg( Formatter::speed_K_str ) );
1205    mySingleUpCheck = c;
1206    s = new QSpinBox( );
1207    mySingleUpSpin = s;
1208    s->setRange( 0, INT_MAX );
1209    hig->addRow( c, s );
1210    enableWhenChecked( c, s );
1211    connect( c, SIGNAL(clicked(bool)), this, SLOT(onUploadLimitedToggled(bool)) );
1212    connect( s, SIGNAL(valueChanged(int)), this, SLOT(onUploadLimitChanged(int)));
1213
1214    m = new QComboBox;
1215    m->addItem( tr( "High" ),   TR_PRI_HIGH );
1216    m->addItem( tr( "Normal" ), TR_PRI_NORMAL );
1217    m->addItem( tr( "Low" ),    TR_PRI_LOW );
1218    connect( m, SIGNAL(currentIndexChanged(int)), this, SLOT(onBandwidthPriorityChanged(int)));
1219    hig->addRow( tr( "Torrent &priority:" ), m );
1220    myBandwidthPriorityCombo = m;
1221
1222    hig->addSectionDivider( );
1223    hig->addSectionTitle( tr( "Seed-Until Ratio" ) );
1224
1225    r = new QRadioButton( tr( "Use &global settings" ) );
1226    r->setProperty( RATIO_KEY, TR_RATIOLIMIT_GLOBAL );
1227    connect( r, SIGNAL(clicked(bool)), this, SLOT(onSeedUntilChanged(bool)));
1228    mySeedGlobalRadio = r;
1229    hig->addWideControl( r );
1230
1231    r = new QRadioButton( tr( "Seed &regardless of ratio" ) );
1232    r->setProperty( RATIO_KEY, TR_RATIOLIMIT_UNLIMITED );
1233    connect( r, SIGNAL(clicked(bool)), this, SLOT(onSeedUntilChanged(bool)));
1234    mySeedForeverRadio = r;
1235    hig->addWideControl( r );
1236
1237    h = new QHBoxLayout( );
1238    h->setSpacing( HIG :: PAD );
1239    r = new QRadioButton( tr( "&Seed torrent until its ratio reaches:" ) );
1240    r->setProperty( RATIO_KEY, TR_RATIOLIMIT_SINGLE );
1241    connect( r, SIGNAL(clicked(bool)), this, SLOT(onSeedUntilChanged(bool)));
1242    mySeedCustomRadio = r;
1243    h->addWidget( r );
1244    ds = new QDoubleSpinBox( );
1245    ds->setRange( 0.5, INT_MAX );
1246    connect( ds, SIGNAL(valueChanged(double)), this, SLOT(onSeedRatioLimitChanged(double)));
1247    mySeedCustomSpin = ds;
1248    h->addWidget( ds );
1249    hig->addWideControl( h );
1250
1251    hig->addSectionDivider( );
1252    hig->addSectionTitle( tr( "Peer Connections" ) );
1253
1254    s = new QSpinBox( );
1255    s->setRange( 1, 300 );
1256    connect( s, SIGNAL(valueChanged(int)), this, SLOT(onMaxPeersChanged(int)));
1257    myPeerLimitSpin = s;
1258    hig->addRow( tr( "&Maximum peers:" ), s );
1259
1260    hig->finish( );
1261
1262    return hig;
1263}
1264
1265/***
1266****
1267***/
1268
1269QWidget *
1270Details :: createTrackerTab( )
1271{
1272    QCheckBox * c;
1273    QPushButton * p;
1274    QWidget * top = new QWidget;
1275    QVBoxLayout * v = new QVBoxLayout( top );
1276    QHBoxLayout * h = new QHBoxLayout();
1277    QVBoxLayout * v2 = new QVBoxLayout();
1278
1279    v->setSpacing( HIG::PAD_BIG );
1280    v->setContentsMargins( HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG );
1281
1282    h->setSpacing( HIG::PAD );
1283    h->setContentsMargins( HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL );
1284
1285    v2->setSpacing( HIG::PAD );
1286
1287    QStringList headers;
1288    headers << tr("Trackers");
1289    myTrackerTree = new QTreeWidget;
1290    myTrackerTree->setHeaderLabels( headers );
1291    myTrackerTree->setSelectionMode( QTreeWidget::SingleSelection );
1292    myTrackerTree->setRootIsDecorated( false );
1293    myTrackerTree->setIndentation( 2 );
1294    myTrackerTree->setItemsExpandable( false );
1295    myTrackerTree->setTextElideMode( Qt::ElideRight );
1296    myTrackerTree->setAlternatingRowColors( true );
1297    connect( myTrackerTree, SIGNAL(itemSelectionChanged()), this, SLOT(onTrackerSelectionChanged()));
1298    h->addWidget( myTrackerTree, 1 );
1299
1300    p = new QPushButton();
1301    p->setIcon( getStockIcon( "list-add", QStyle::SP_DialogOpenButton ) );
1302    p->setToolTip( "Add Tracker" );
1303    myAddTrackerButton = p;
1304    v2->addWidget( p, 1 );
1305    connect( p, SIGNAL(clicked(bool)), this, SLOT(onAddTrackerPushed()));
1306
1307    p = new QPushButton();
1308    p->setIcon( getStockIcon( "document-properties", QStyle::SP_DesktopIcon ) );
1309    p->setToolTip( "Edit Tracker" );
1310    myAddTrackerButton = p;
1311    p->setEnabled( false );
1312    myEditTrackerButton = p;
1313    v2->addWidget( p, 1 );
1314    connect( p, SIGNAL(clicked(bool)), this, SLOT(onEditTrackerPushed()));
1315
1316    p = new QPushButton();
1317    p->setIcon( getStockIcon( "list-remove", QStyle::SP_TrashIcon ) );
1318    p->setToolTip( "Remove Trackers" );
1319    p->setEnabled( false );
1320    myRemoveTrackerButton = p;
1321    v2->addWidget( p, 1 );
1322    connect( p, SIGNAL(clicked(bool)), this, SLOT(onRemoveTrackerPushed()));
1323
1324    v2->addStretch( 1 );
1325
1326    h->addLayout( v2, 1 );
1327    h->setStretch( 1, 0 );
1328
1329    v->addLayout( h, 1 );
1330
1331    c = new QCheckBox( tr( "Show &more details" ) );
1332    c->setChecked( myPrefs.getBool( Prefs::SHOW_TRACKER_SCRAPES ) );
1333    myShowTrackerScrapesCheck = c;
1334    v->addWidget( c, 1 );
1335    connect( c, SIGNAL(clicked(bool)), this, SLOT(onShowTrackerScrapesToggled(bool)) );
1336
1337    return top;
1338}
1339
1340/***
1341****
1342***/
1343
1344QWidget *
1345Details :: createPeersTab( )
1346{
1347    QWidget * top = new QWidget;
1348    QVBoxLayout * v = new QVBoxLayout( top );
1349    v->setSpacing( HIG :: PAD_BIG );
1350    v->setContentsMargins( HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG );
1351
1352    QStringList headers;
1353    headers << QString() << tr("Up") << tr("Down") << tr("%") << tr("Status") << tr("Address") << tr("Client");
1354    myPeerTree = new QTreeWidget;
1355    myPeerTree->setUniformRowHeights( true );
1356    myPeerTree->setHeaderLabels( headers );
1357    myPeerTree->setColumnWidth( 0, 20 );
1358    myPeerTree->setSortingEnabled( true );
1359    myPeerTree->sortByColumn( COL_ADDRESS, Qt::AscendingOrder );
1360    myPeerTree->setRootIsDecorated( false );
1361    myPeerTree->setTextElideMode( Qt::ElideRight );
1362    v->addWidget( myPeerTree, 1 );
1363
1364    const QFontMetrics m( font( ) );
1365    QSize size = m.size( 0, "1024 MiB/s" );
1366    myPeerTree->setColumnWidth( COL_UP, size.width( ) );
1367    myPeerTree->setColumnWidth( COL_DOWN, size.width( ) );
1368    size = m.size( 0, " 100% " );
1369    myPeerTree->setColumnWidth( COL_PERCENT, size.width( ) );
1370    size = m.size( 0, "ODUK?EXI" );
1371    myPeerTree->setColumnWidth( COL_STATUS, size.width( ) );
1372    size = m.size( 0, "888.888.888.888" );
1373    myPeerTree->setColumnWidth( COL_ADDRESS, size.width( ) );
1374    size = m.size( 0, "Some BitTorrent Client" );
1375    myPeerTree->setColumnWidth( COL_CLIENT, size.width( ) );
1376    myPeerTree->setAlternatingRowColors( true );
1377
1378    return top;
1379}
1380
1381/***
1382****
1383***/
1384
1385QWidget *
1386Details :: createFilesTab( )
1387{
1388    myFileTreeView = new FileTreeView( );
1389
1390    connect( myFileTreeView, SIGNAL(      priorityChanged(const QSet<int>&, int)),
1391             this,           SLOT(  onFilePriorityChanged(const QSet<int>&, int)));
1392
1393    connect( myFileTreeView, SIGNAL(      wantedChanged(const QSet<int>&, bool)),
1394             this,           SLOT(  onFileWantedChanged(const QSet<int>&, bool)));
1395
1396    return myFileTreeView;
1397}
1398
1399void
1400Details :: onFilePriorityChanged( const QSet<int>& indices, int priority )
1401{
1402    QString key;
1403    switch( priority ) {
1404        case TR_PRI_LOW:   key = "priority-low"; break;
1405        case TR_PRI_HIGH:  key = "priority-high"; break;
1406        default:           key = "priority-normal"; break;
1407    }
1408    mySession.torrentSet( myIds, key, indices.toList( ) );
1409}
1410
1411void
1412Details :: onFileWantedChanged( const QSet<int>& indices, bool wanted )
1413{
1414    QString key( wanted ? "files-wanted" : "files-unwanted" );
1415    mySession.torrentSet( myIds, key, indices.toList( ) );
1416}
Note: See TracBrowser for help on using the repository browser.