source: branches/2.0x/qt/details.cc @ 10867

Last change on this file since 10867 was 10867, checked in by Longinus00, 12 years ago

(2.0x qt) #3313:Long orign label can make torrent properites window arbitrarily wide

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