source: trunk/qt/details.cc @ 10454

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

(trunk qt) tweak paused/stopped/finished in the details dialog

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