source: trunk/qt/details.cc @ 8754

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

(trunk qt) typo fix

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