source: trunk/qt/details.cc @ 8764

Last change on this file since 8764 was 8764, checked in by charles, 13 years ago

(trunk gtk,qt) fix possible division by zero in the torrent properties dialog

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