source: trunk/qt/details.cc @ 8256

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

(trunk qt) fix feedback loop between the gui and libT for the properties dialog's seed ratio

File size: 29.6 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:$
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        PeerItem( ) { }
90        virtual ~PeerItem( ) { }
91
92    public:
93        void setStatus( const QString& s ) {
94            status = s;
95        }
96        void setPeer( const Peer& p ) {
97            peer = p;
98            int quads[4];
99            if( sscanf( p.address.toUtf8().constData(), "%d.%d.%d.%d", quads+0, quads+1, quads+2, quads+3 ) == 4 )
100                collatedAddress.sprintf( "%03d.%03d.%03d.%03d", quads[0], quads[1], quads[2], quads[3] );
101            else
102                collatedAddress = p.address;
103        }
104        virtual bool operator< ( const QTreeWidgetItem & other ) const {
105            const PeerItem * that = dynamic_cast<const PeerItem*>(&other);
106            QTreeWidget * tw( treeWidget( ) );
107            const int column = tw ? tw->sortColumn() : 0;
108            switch( column ) {
109                case COL_UP: return peer.rateToPeer < that->peer.rateToPeer;
110                case COL_DOWN: return peer.rateToClient < that->peer.rateToClient;
111                case COL_PERCENT: return peer.progress < that->peer.progress;
112                case COL_STATUS: return status < that->status;
113                case COL_ADDRESS: return collatedAddress < that->collatedAddress;
114                case COL_CLIENT: return peer.clientName < that->peer.clientName;
115                default: /*COL_LOCK*/ return peer.isEncrypted && !that->peer.isEncrypted;
116            }
117        }
118};
119
120/***
121****
122***/
123
124Details :: Details( Session& session, TorrentModel& model, QWidget * parent ):
125    QDialog( parent, Qt::Dialog ),
126    mySession( session ),
127    myModel( model ),
128    myHavePendingRefresh( false )
129{
130    QVBoxLayout * layout = new QVBoxLayout( this );
131
132    setWindowTitle( tr( "Torrent Properties" ) );
133
134    QTabWidget * t = new QTabWidget( this );
135    QWidget * w;
136    t->addTab( w = createActivityTab( ),  tr( "Activity" ) );
137    myWidgets << w;
138    t->addTab( w = createPeersTab( ),     tr( "Peers" ) );
139    myWidgets << w;
140    t->addTab( w = createTrackerTab( ),   tr( "Tracker" ) );
141    myWidgets << w;
142    t->addTab( w = createInfoTab( ),      tr( "Information" ) );
143    myWidgets << w;
144    t->addTab( w = createFilesTab( ),     tr( "Files" ) );
145    myWidgets << w;
146    t->addTab( w = createOptionsTab( ),   tr( "Options" ) );
147    myWidgets << w;
148    layout->addWidget( t );
149
150    QDialogButtonBox * buttons = new QDialogButtonBox( QDialogButtonBox::Close, Qt::Horizontal, this );
151    connect( buttons, SIGNAL(rejected()), this, SLOT(deleteLater()) ); // "close" triggers rejected
152    layout->addWidget( buttons );
153
154    connect( &myTimer, SIGNAL(timeout()), this, SLOT(onTimer()) );
155
156    onTimer( );
157    myTimer.setSingleShot( false );
158    myTimer.start( REFRESH_INTERVAL_MSEC );
159}
160   
161Details :: ~Details( )
162{
163}
164
165void
166Details :: setIds( const QSet<int>& ids )
167{
168    if( ids == myIds )
169        return;
170
171    // stop listening to the old torrents
172    foreach( int id, myIds ) {
173        const Torrent * tor = myModel.getTorrentFromId( id );
174        if( tor )
175            disconnect( tor, SIGNAL(torrentChanged(int)), this, SLOT(onTorrentChanged()) );
176    }
177
178    myIds = ids;
179
180    // listen to the new torrents
181    foreach( int id, myIds ) {
182        const Torrent * tor = myModel.getTorrentFromId( id );
183        if( tor )
184            connect( tor, SIGNAL(torrentChanged(int)), this, SLOT(onTorrentChanged()) );
185    }
186
187    foreach( QWidget * w, myWidgets )
188        w->setEnabled( false );
189   
190    onTimer( );
191}
192
193/***
194****
195***/
196
197void
198Details :: onTimer( )
199{
200    if( !myIds.empty( ) )
201        mySession.refreshExtraStats( myIds );
202}
203
204void
205Details :: onTorrentChanged( )
206{
207    if( !myHavePendingRefresh ) {
208        myHavePendingRefresh = true;
209        QTimer::singleShot( 100, this, SLOT(refresh()));
210    }
211}
212
213
214void
215Details :: refresh( )
216{
217    int i;
218    QLocale locale;
219    const int n = myIds.size( );
220    const bool single = n == 1;
221    const QString blank;
222    const QFontMetrics fm( fontMetrics( ) );
223    QSet<const Torrent*> torrents;
224    const Torrent * tor;
225    QSet<QString> strings;
226    QString string;
227
228    // build a list of torrents
229    foreach( int id, myIds ) {
230        const Torrent * tor = myModel.getTorrentFromId( id );
231        if( tor )
232            torrents << tor;
233    }
234
235    ///
236    ///  activity tab
237    ///
238
239    // myStateLabel
240    if( torrents.empty( ) )
241        string = tr( "None" );
242    else {
243        strings.clear( );
244        foreach( tor, torrents ) strings.insert( tor->activityString( ) );
245        string = strings.size()==1 ? *strings.begin() : blank;
246    }
247    myStateLabel->setText( string );
248
249    // myProgressLabel
250    if( torrents.empty( ) )
251        string = tr( "None" );
252    else {
253        double sizeWhenDone = 0;
254        double leftUntilDone = 0;
255        foreach( tor, torrents ) {
256            sizeWhenDone += tor->sizeWhenDone( );
257            leftUntilDone += tor->leftUntilDone( );
258        }
259        string = locale.toString( 100.0*((sizeWhenDone-leftUntilDone)/sizeWhenDone), 'f', 2 );
260    }
261    myProgressLabel->setText( string );
262
263    // myHaveLabel
264    int64_t haveTotal = 0;
265    int64_t haveVerified = 0;
266    int64_t verifiedPieces = 0;
267    foreach( tor, torrents ) {
268        haveTotal += tor->haveTotal( );
269        haveVerified += tor->haveVerified( );
270        verifiedPieces += tor->haveVerified( ) / tor->pieceSize( );
271    }
272    myHaveLabel->setText( tr( "%1 (%2 verified in %L3 pieces)" )
273                            .arg( Utils::sizeToString( haveTotal ) )
274                            .arg( Utils::sizeToString( haveVerified ) )
275                            .arg( verifiedPieces ) );
276
277    int64_t num = 0;
278    foreach( tor, torrents ) num += tor->downloadedEver( );
279    myDownloadedLabel->setText( Utils::sizeToString( num ) );
280
281    num = 0;
282    foreach( tor, torrents ) num += tor->uploadedEver( );
283    myUploadedLabel->setText( Utils::sizeToString( num ) );
284
285    num = 0;
286    foreach( tor, torrents ) num += tor->failedEver( );
287    myFailedLabel->setText( Utils::sizeToString( num ) );
288
289    double d = 0;
290    foreach( tor, torrents ) d += tor->ratio( );
291    myRatioLabel->setText( Utils :: ratioToString( d / n ) );
292
293    Speed speed;
294    foreach( tor, torrents ) speed += tor->swarmSpeed( );
295    mySwarmSpeedLabel->setText( Utils::speedToString( speed ) );
296
297    strings.clear( );
298    foreach( tor, torrents ) strings.insert( tor->dateAdded().toString() );
299    string = strings.size()==1 ? *strings.begin() : blank;
300    myAddedDateLabel->setText( string );
301
302    strings.clear( );
303    foreach( tor, torrents ) {
304        QDateTime dt = tor->lastActivity( );
305        strings.insert( dt.isNull() ? tr("Never") : dt.toString() );
306    }
307    string = strings.size()==1 ? *strings.begin() : blank;
308    myActivityLabel->setText( string );
309
310    if( torrents.empty( ) )
311        string = tr( "None" );
312    else {
313        strings.clear( );
314        foreach( tor, torrents ) strings.insert( tor->getError( ) );
315        string = strings.size()==1 ? *strings.begin() : blank;
316    }
317    myErrorLabel->setText( string );
318
319    ///
320    /// information tab
321    ///
322
323    // myPiecesLabel
324    int64_t pieceCount = 0;
325    int64_t pieceSize = 0;
326    foreach( tor, torrents ) {
327        pieceCount += tor->pieceCount( );
328        pieceSize += tor->pieceSize( );
329    }
330    myPiecesLabel->setText( tr( "%L1 Pieces @ %2" ).arg( pieceCount )
331                                                   .arg( Utils::sizeToString( pieceSize ) ) );
332
333    // myHashLabel
334    strings.clear( );
335    foreach( tor, torrents ) strings.insert( tor->hashString( ) );
336    string = strings.size()==1 ? *strings.begin() : blank;
337    myHashLabel->setText( string );
338
339    // myPrivacyLabel
340    strings.clear( );
341    foreach( tor, torrents )
342        strings.insert( tor->isPrivate( ) ? tr( "Private to this tracker -- PEX disabled" )
343                                          : tr( "Public torrent" ) );
344    string = strings.size()==1 ? *strings.begin() : blank;
345    myPrivacyLabel->setText( string );
346
347    // myCommentBrowser
348    strings.clear( );
349    foreach( tor, torrents ) strings.insert( tor->comment( ) );
350    string = strings.size()==1 ? *strings.begin() : blank;
351    myCommentBrowser->setText( string );
352
353    // myCreatorLabel
354    strings.clear( );
355    foreach( tor, torrents ) strings.insert( tor->creator().isEmpty() ? tr( "Unknown" ) : tor->creator() );
356    string = strings.size()==1 ? *strings.begin() : blank;
357    myCreatorLabel->setText( string );
358
359    // myDateCreatedLabel
360    strings.clear( );
361    foreach( tor, torrents ) strings.insert( tor->dateCreated().toString() );
362    string = strings.size()==1 ? *strings.begin() : blank;
363    myDateCreatedLabel->setText( string );
364
365    // myDestinationLabel
366    strings.clear( );
367    foreach( tor, torrents ) strings.insert( tor->getPath( ) );
368    string = strings.size()==1 ? *strings.begin() : blank;
369    myDestinationLabel->setText( string );
370
371    // myTorrentFileLabel
372    strings.clear( );
373    foreach( tor, torrents ) strings.insert( tor->torrentFile( ) );
374    string = strings.size()==1 ? *strings.begin() : blank;
375    myTorrentFileLabel->setText( string );
376
377    ///
378    ///  Options Tab
379    ///
380
381    if( !torrents.empty( ) )
382    {
383        int i;
384        const Torrent * baseline = *torrents.begin();
385        bool uniform;
386        bool baselineFlag;
387        int baselineInt;
388
389        // mySessionLimitCheck
390        uniform = true;
391        baselineFlag = baseline->honorsSessionLimits( );
392        foreach( tor, torrents ) if( baselineFlag != tor->honorsSessionLimits( ) ) { uniform = false; break; }
393        mySessionLimitCheck->setChecked( uniform && baselineFlag );
394
395        // mySingleDownCheck
396        uniform = true;
397        baselineFlag = baseline->downloadIsLimited( );
398        foreach( tor, torrents ) if( baselineFlag != tor->downloadIsLimited( ) ) { uniform = false; break; }
399        mySingleDownCheck->setChecked( uniform && baselineFlag );
400
401        // mySingleUpCheck
402        uniform = true;
403        baselineFlag = baseline->uploadIsLimited( );
404        foreach( tor, torrents ) if( baselineFlag != tor->uploadIsLimited( ) ) { uniform = false; break; }
405        mySingleUpCheck->setChecked( uniform && baselineFlag );
406
407        // myBandwidthPriorityCombo
408        uniform = true;
409        baselineInt = baseline->getBandwidthPriority( );
410        foreach( tor, torrents ) if ( baselineInt != tor->getBandwidthPriority( ) ) { uniform = false; break; }
411        if( uniform )
412            i = myBandwidthPriorityCombo->findData( baselineInt );
413        else
414            i = -1;
415        myBandwidthPriorityCombo->blockSignals( true );
416        myBandwidthPriorityCombo->setCurrentIndex( i );
417        myBandwidthPriorityCombo->blockSignals( false );
418
419        mySingleDownSpin->blockSignals( true );
420        mySingleDownSpin->setValue( tor->downloadLimit().kbps() );
421        mySingleDownSpin->blockSignals( false );
422
423        mySingleUpSpin->blockSignals( true );
424        mySingleUpSpin->setValue( tor->uploadLimit().kbps() );
425        mySingleUpSpin->blockSignals( false );
426
427        myPeerLimitSpin->blockSignals( true );
428        myPeerLimitSpin->setValue( tor->peerLimit() );
429        myPeerLimitSpin->blockSignals( false );
430
431        // ratio radios
432        uniform = true;
433        baselineInt = tor->seedRatioMode( );
434        foreach( tor, torrents ) if( baselineInt != tor->seedRatioMode( ) ) { uniform = false; break; }
435        if( !uniform ) {
436            mySeedGlobalRadio->setChecked( false );
437            mySeedCustomRadio->setChecked( false );
438            mySeedForeverRadio->setChecked( false );
439        } else {
440            QRadioButton * rb;
441            switch( baselineInt ) {
442                case TR_RATIOLIMIT_GLOBAL:    rb = mySeedGlobalRadio; break;
443                case TR_RATIOLIMIT_SINGLE:    rb = mySeedCustomRadio; break;
444                case TR_RATIOLIMIT_UNLIMITED: rb = mySeedForeverRadio; break;
445            }
446            rb->setChecked( true );
447        }
448
449        mySeedCustomSpin->blockSignals( true );
450        mySeedCustomSpin->setValue( tor->seedRatioLimit( ) );
451        mySeedCustomSpin->blockSignals( false );
452    }
453
454    // tracker tab
455    const time_t now( time( 0 ) );
456    myScrapeTimePrevLabel->setText( tor ? tor->lastScrapeTime().toString() : blank );
457    myScrapeResponseLabel->setText( tor ? tor->scrapeResponse() : blank );
458    myScrapeTimeNextLabel->setText( Utils :: timeToString( tor ? tor->nextScrapeTime().toTime_t() - now : 0 ) );
459    myAnnounceTimePrevLabel->setText( tor ? tor->lastScrapeTime().toString() : blank );
460    myAnnounceTimeNextLabel->setText( Utils :: timeToString( tor ? tor->nextAnnounceTime().toTime_t() - now : 0  ) );
461    myAnnounceManualLabel->setText( Utils :: timeToString( tor ? tor->manualAnnounceTime().toTime_t() - now : 0 ) );
462    myAnnounceResponseLabel->setText( tor ? tor->announceResponse( ) : blank );
463
464    // myTrackerLabel
465    strings.clear( );
466    foreach( tor, torrents ) strings.insert( QUrl(tor->announceUrl()).host() );
467    string = strings.size()==1 ? *strings.begin() : blank;
468    myTrackerLabel->setText( string );
469
470    ///
471    ///  Peers tab
472    ///
473
474    i = 0;
475    foreach( tor, torrents ) i += tor->seeders( );
476    mySeedersLabel->setText( locale.toString( i ) );
477
478    i = 0;
479    foreach( tor, torrents ) i += tor->leechers( );
480    myLeechersLabel->setText( locale.toString( i ) );
481
482    i = 0;
483    foreach( tor, torrents ) i += tor->timesCompleted( );
484    myTimesCompletedLabel->setText( locale.toString( i ) );
485
486    PeerList peers;
487    foreach( tor, torrents ) peers << tor->peers( );
488    QMap<QString,QTreeWidgetItem*> peers2;
489    QList<QTreeWidgetItem*> newItems;
490    static const QIcon myEncryptionIcon( ":/icons/encrypted.png" );
491    static const QIcon myEmptyIcon;
492    foreach( const Peer& peer, peers )
493    {
494        PeerItem * item = (PeerItem*) myPeers.value( peer.address, 0 );
495        if( item == 0 ) { // new peer has connected
496            item = new PeerItem;
497            item->setTextAlignment( COL_UP, Qt::AlignRight );
498            item->setTextAlignment( COL_DOWN, Qt::AlignRight );
499            item->setTextAlignment( COL_PERCENT, Qt::AlignRight );
500            newItems << item;
501        }
502
503        QString code;
504        if( peer.isDownloadingFrom )                           { code += 'D'; }
505        else if( peer.clientIsInterested )                     { code += 'd'; }
506        if( peer.isUploadingTo )                               { code += 'U'; }
507        else if( peer.peerIsInterested )                       { code += 'u'; }
508        if( !peer.clientIsChoked && !peer.clientIsInterested ) { code += 'K'; }
509        if( !peer.peerIsChoked && !peer.peerIsInterested )     { code += '?'; }
510        if( peer.isEncrypted )                                 { code += 'E'; }
511        if( peer.isIncoming )                                  { code += 'I'; }
512
513        item->setPeer( peer );
514        item->setStatus( code );
515
516        QString codeTip;
517        foreach( QChar ch, code ) {
518            QString txt;
519            switch( ch.toAscii() ) {
520                case 'O': txt = tr( "Optimistic unchoke" ); break;
521                case 'D': txt = tr( "Downloading from this peer" ); break;
522                case 'd': txt = tr( "We would download from this peer if they would let us" ); break;
523                case 'U': txt = tr( "Uploading to peer" ); break;
524                case 'u': txt = tr( "We would upload to this peer if they asked" ); break;
525                case 'K': txt = tr( "Peer has unchoked us, but we're not interested" ); break;
526                case '?': txt = tr( "We unchoked this peer, but they're not interested" ); break;
527                case 'E': txt = tr( "Encrypted connection" ); break;
528                case 'X': txt = tr( "Peer was discovered through Peer Exchange (PEX)" ); break;
529                case 'I': txt = tr( "Peer is an incoming connection" ); break;
530            }
531            if( !txt.isEmpty( ) )
532                codeTip += QString("%1: %2\n").arg(ch).arg(txt);
533        }
534        if( !codeTip.isEmpty() )
535            codeTip.resize( codeTip.size()-1 ); // eat the trailing linefeed
536
537        item->setIcon( COL_LOCK, peer.isEncrypted ? myEncryptionIcon : myEmptyIcon );
538        item->setToolTip( COL_LOCK, peer.isEncrypted ? tr( "Encrypted connection" ) : "" );
539        item->setText( COL_UP, peer.rateToPeer.isZero() ? "" : Utils::speedToString( peer.rateToPeer ) );
540        item->setText( COL_DOWN, peer.rateToClient.isZero() ? "" : Utils::speedToString( peer.rateToClient ) );
541        item->setText( COL_PERCENT, peer.progress > 0 ? QString( "%1%" ).arg( locale.toString((int)(peer.progress*100.0))) : "" );
542        item->setText( COL_STATUS, code );
543        item->setToolTip( COL_STATUS, codeTip );
544        item->setText( COL_ADDRESS, peer.address );
545        item->setText( COL_CLIENT, peer.clientName );
546        peers2.insert( peer.address, item );
547    }
548    myPeerTree->addTopLevelItems( newItems );
549    foreach( QString key, myPeers.keys() ) {
550        if( !peers2.contains( key ) ) { // old peer has disconnected
551            QTreeWidgetItem * item = myPeers.value( key, 0 );
552            myPeerTree->takeTopLevelItem( myPeerTree->indexOfTopLevelItem( item ) );
553            delete item;
554        }
555    }
556    myPeers = peers2;
557
558    if( single ) {
559        tor = *torrents.begin();
560        myFileTreeView->update( tor->files( ) );
561    } else { 
562        myFileTreeView->clear( );
563    }
564
565    myHavePendingRefresh = false;
566    foreach( QWidget * w, myWidgets )
567        w->setEnabled( true );
568}
569
570void
571Details :: enableWhenChecked( QCheckBox * box, QWidget * w )
572{
573    connect( box, SIGNAL(toggled(bool)), w, SLOT(setEnabled(bool)) );
574    w->setEnabled( box->isChecked( ) );
575}
576
577
578/***
579****
580***/
581
582QWidget *
583Details :: createActivityTab( )
584{
585    HIG * hig = new HIG( this );
586
587    hig->addSectionTitle( tr( "Transfer" ) );
588    hig->addRow( tr( "State:" ), myStateLabel = new SqueezeLabel );
589    hig->addRow( tr( "Progress:" ), myProgressLabel = new SqueezeLabel );
590    hig->addRow( tr( "Have:" ), myHaveLabel = new SqueezeLabel );
591    hig->addRow( tr( "Downloaded:" ), myDownloadedLabel = new SqueezeLabel );
592    hig->addRow( tr( "Uploaded:" ), myUploadedLabel = new SqueezeLabel );
593    hig->addRow( tr( "Failed DL:" ), myFailedLabel = new SqueezeLabel );
594    hig->addRow( tr( "Ratio:" ), myRatioLabel = new SqueezeLabel );
595    hig->addRow( tr( "Swarm Rate:" ), mySwarmSpeedLabel = new SqueezeLabel );
596    hig->addRow( tr( "Error:" ), myErrorLabel = new SqueezeLabel );
597    hig->addSectionDivider( );
598
599    hig->addSectionTitle( tr( "Dates" ) );
600    hig->addRow( tr( "Added on:" ), myAddedDateLabel = new SqueezeLabel );
601    hig->addRow( tr( "Last activity on:" ), myActivityLabel = new SqueezeLabel );
602    hig->finish( );
603
604    return hig;
605}
606
607/***
608****
609***/
610
611void
612Details :: onHonorsSessionLimitsToggled( bool val )
613{
614std::cerr << " honorsSessionLimits clicked to " << val << std::endl;
615    mySession.torrentSet( myIds, "honorsSessionLimits", val );
616}
617void
618Details :: onDownloadLimitedToggled( bool val )
619{
620    mySession.torrentSet( myIds, "downloadLimited", val );
621}
622void
623Details :: onDownloadLimitChanged( int val )
624{
625    mySession.torrentSet( myIds, "downloadLimit", val );
626}
627void
628Details :: onUploadLimitedToggled( bool val )
629{
630    mySession.torrentSet( myIds, "uploadLimited", val );
631}
632void
633Details :: onUploadLimitChanged( int val )
634{
635    mySession.torrentSet( myIds, "uploadLimit", val );
636}
637
638#define RATIO_KEY "seedRatioMode"
639
640void
641Details :: onSeedUntilChanged( bool b )
642{
643    if( b )
644        mySession.torrentSet( myIds, RATIO_KEY, sender()->property(RATIO_KEY).toInt() );
645}
646
647void
648Details :: onSeedRatioLimitChanged( double val )
649{
650    QSet<int> ids;
651
652    foreach( int id, myIds ) {
653        const Torrent * tor = myModel.getTorrentFromId( id );
654        if( tor && tor->seedRatioLimit( ) )
655            ids.insert( id );
656    }
657
658    if( !ids.empty( ) )
659        mySession.torrentSet( ids, "seedRatioLimit", val );
660}
661
662void
663Details :: onMaxPeersChanged( int val )
664{
665    mySession.torrentSet( myIds, "peer-limit", val );
666}
667
668void
669Details :: onBandwidthPriorityChanged( int index )
670{
671    if( index != -1 )
672    {
673        const int priority = myBandwidthPriorityCombo->itemData(index).toInt( );
674        mySession.torrentSet( myIds, "bandwidthPriority", priority );
675    }
676}
677
678QWidget *
679Details :: createOptionsTab( )
680{
681    //QWidget * l;
682    QSpinBox * s;
683    QCheckBox * c;
684    QComboBox * m;
685    QHBoxLayout * h;
686    QRadioButton * r;
687    QDoubleSpinBox * ds;
688
689    HIG * hig = new HIG( this );
690    hig->addSectionTitle( tr( "Speed" ) );
691
692    c = new QCheckBox( tr( "Honor global &limits" ) );
693    mySessionLimitCheck = c;
694    hig->addWideControl( c );
695    connect( c, SIGNAL(clicked(bool)), this, SLOT(onHonorsSessionLimitsToggled(bool)) );
696
697    c = new QCheckBox( tr( "Limit &download speed (KB/s)" ) );
698    mySingleDownCheck = c;
699    s = new QSpinBox( );
700    mySingleDownSpin = s;
701    s->setRange( 0, INT_MAX );
702    hig->addRow( c, s );
703    enableWhenChecked( c, s );
704    connect( c, SIGNAL(clicked(bool)), this, SLOT(onDownloadLimitedToggled(bool)) );
705    connect( s, SIGNAL(valueChanged(int)), this, SLOT(onDownloadLimitChanged(int)));
706
707    c = new QCheckBox( tr( "Limit &upload speed (KB/s)" ) );
708    mySingleUpCheck = c;
709    s = new QSpinBox( );
710    mySingleUpSpin = s;
711    s->setRange( 0, INT_MAX );
712    hig->addRow( c, s );
713    enableWhenChecked( c, s );
714    connect( c, SIGNAL(clicked(bool)), this, SLOT(onUploadLimitedToggled(bool)) );
715    connect( s, SIGNAL(valueChanged(int)), this, SLOT(onUploadLimitChanged(int)));
716
717    m = new QComboBox;
718    m->addItem( tr( "Low" ),    TR_PRI_LOW );
719    m->addItem( tr( "Normal" ), TR_PRI_NORMAL );
720    m->addItem( tr( "High" ),   TR_PRI_HIGH );
721    connect( m, SIGNAL(currentIndexChanged(int)), this, SLOT(onBandwidthPriorityChanged(int)));
722    hig->addRow( tr( "&Bandwidth priority:" ), m );
723    myBandwidthPriorityCombo = m;
724   
725
726    hig->addSectionDivider( );
727    hig->addSectionTitle( tr( "Seed-Until Ratio" ) );
728
729    r = new QRadioButton( tr( "Use &global setting" ) );
730    r->setProperty( RATIO_KEY, TR_RATIOLIMIT_GLOBAL );
731    connect( r, SIGNAL(clicked(bool)), this, SLOT(onSeedUntilChanged(bool)));
732    mySeedGlobalRadio = r;
733    hig->addWideControl( r );
734
735    r = new QRadioButton( tr( "Seed &regardless of ratio" ) );
736    r->setProperty( RATIO_KEY, TR_RATIOLIMIT_UNLIMITED );
737    connect( r, SIGNAL(clicked(bool)), this, SLOT(onSeedUntilChanged(bool)));
738    mySeedForeverRadio = r;
739    hig->addWideControl( r );
740
741    h = new QHBoxLayout( );
742    h->setSpacing( HIG :: PAD );
743    r = new QRadioButton( tr( "&Stop seeding when a torrent's ratio reaches" ) );
744    r->setProperty( RATIO_KEY, TR_RATIOLIMIT_SINGLE );
745    connect( r, SIGNAL(clicked(bool)), this, SLOT(onSeedUntilChanged(bool)));
746    mySeedCustomRadio = r;
747    h->addWidget( r );
748    ds = new QDoubleSpinBox( );
749    ds->setRange( 0.5, INT_MAX );
750    connect( ds, SIGNAL(valueChanged(double)), this, SLOT(onSeedRatioLimitChanged(double)));
751    mySeedCustomSpin = ds;
752    h->addWidget( ds );
753    hig->addWideControl( h );
754
755    hig->addSectionDivider( );
756    hig->addSectionTitle( tr( "Peer Connections" ) );
757
758    s = new QSpinBox( );
759    s->setRange( 1, 300 );
760    connect( s, SIGNAL(valueChanged(int)), this, SLOT(onMaxPeersChanged(int)));
761    myPeerLimitSpin = s;
762    hig->addRow( tr( "&Maximum Peers" ), s );
763
764    hig->finish( );
765
766    return hig;
767}
768
769/***
770****
771***/
772
773QWidget *
774Details :: createInfoTab( )
775{
776    HIG * hig = new HIG( );
777    hig->addSectionTitle( tr( "Details" ) );
778    hig->addRow( tr( "Pieces:" ), myPiecesLabel = new SqueezeLabel );
779    hig->addRow( tr( "Hash:" ), myHashLabel = new SqueezeLabel );
780    hig->addRow( tr( "Privacy:" ), myPrivacyLabel = new SqueezeLabel );
781    hig->addRow( tr( "Comment:" ), myCommentBrowser = new QTextBrowser );
782    hig->addSectionDivider( );
783    hig->addSectionTitle( tr( "Origins" ) );
784    hig->addRow( tr( "Creator:" ), myCreatorLabel = new SqueezeLabel );
785    hig->addRow( tr( "Date:" ), myDateCreatedLabel = new SqueezeLabel );
786    hig->addSectionDivider( );
787    hig->addSectionTitle( tr( "Origins" ) );
788    hig->addRow( tr( "Destination folder:" ), myDestinationLabel = new SqueezeLabel );
789    hig->addRow( tr( "Torrent file:" ), myTorrentFileLabel = new SqueezeLabel );
790    const int h = QFontMetrics(myCommentBrowser->font()).lineSpacing() * 4;
791    myTorrentFileLabel->setMinimumWidth( 300 );
792    myTorrentFileLabel->setSizePolicy ( QSizePolicy::Expanding, QSizePolicy::Preferred );
793
794    myCommentBrowser->setMinimumHeight( h );
795    myCommentBrowser->setMaximumHeight( h );
796
797    hig->finish( );
798    return hig;
799}
800
801/***
802****
803***/
804
805QWidget *
806Details :: createTrackerTab( )
807{
808    HIG * hig = new HIG( );
809
810    hig->addSectionTitle( tr( "Scrape" ) );
811    hig->addRow( tr( "Last scrape at:" ), myScrapeTimePrevLabel = new SqueezeLabel );
812    hig->addRow( tr( "Tracker responded:" ), myScrapeResponseLabel = new SqueezeLabel );
813    hig->addRow( tr( "Next scrape in:" ), myScrapeTimeNextLabel = new SqueezeLabel );
814    hig->addSectionDivider( );
815    hig->addSectionTitle( tr( "Announce" ) );
816    hig->addRow( tr( "Tracker:" ), myTrackerLabel = new SqueezeLabel );
817    hig->addRow( tr( "Last announce at:" ), myAnnounceTimePrevLabel = new SqueezeLabel );
818    hig->addRow( tr( "Tracker responded:" ), myAnnounceResponseLabel = new SqueezeLabel );
819    hig->addRow( tr( "Next announce in:" ), myAnnounceTimeNextLabel = new SqueezeLabel );
820    hig->addRow( tr( "Manual announce allowed in:" ), myAnnounceManualLabel = new SqueezeLabel );
821    hig->finish( );
822
823    myTrackerLabel->setScaledContents( true );
824
825    return hig;
826}
827
828/***
829****
830***/
831
832QWidget *
833Details :: createPeersTab( )
834{
835    QWidget * top = new QWidget;
836    QVBoxLayout * v = new QVBoxLayout( top );
837    v->setSpacing( HIG :: PAD_BIG );
838    v->setContentsMargins( HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG );
839
840    QStringList headers;
841    headers << QString() << tr("Up") << tr("Down") << tr("%") << tr("Status") << tr("Address") << tr("Client");
842    myPeerTree = new QTreeWidget;
843    myPeerTree->setUniformRowHeights( true );
844    myPeerTree->setHeaderLabels( headers );
845    myPeerTree->setColumnWidth( 0, 20 );
846    myPeerTree->setSortingEnabled( true );
847    myPeerTree->setRootIsDecorated( false );
848    myPeerTree->setTextElideMode( Qt::ElideRight );
849    v->addWidget( myPeerTree, 1 );
850
851    const QFontMetrics m( font( ) );
852    QSize size = m.size( 0, "1024 MB/s" );
853    myPeerTree->setColumnWidth( COL_UP, size.width( ) );
854    myPeerTree->setColumnWidth( COL_DOWN, size.width( ) );
855    size = m.size( 0, " 100% " );
856    myPeerTree->setColumnWidth( COL_PERCENT, size.width( ) );
857    size = m.size( 0, "ODUK?EXI" );
858    myPeerTree->setColumnWidth( COL_STATUS, size.width( ) );
859    size = m.size( 0, "888.888.888.888" );
860    myPeerTree->setColumnWidth( COL_ADDRESS, size.width( ) );
861    size = m.size( 0, "Some BitTorrent Client" );
862    myPeerTree->setColumnWidth( COL_CLIENT, size.width( ) );
863    //myPeerTree->sortItems( myTorrent.isDone() ? COL_UP : COL_DOWN, Qt::DescendingOrder );
864    myPeerTree->setAlternatingRowColors( true );
865
866    QHBoxLayout * h = new QHBoxLayout;
867    h->setSpacing( HIG :: PAD );
868    v->addLayout( h );
869
870    QLabel * l = new QLabel( "Seeders:" );
871    l->setStyleSheet( "font: bold" );
872    h->addWidget( l );
873    l = mySeedersLabel = new QLabel( "a" );
874    h->addWidget( l );
875    h->addStretch( 1 );
876   
877    l = new QLabel( "Leechers:" );
878    l->setStyleSheet( "font: bold" );
879    h->addWidget( l );
880    l = myLeechersLabel = new QLabel( "b" );
881    h->addWidget( l );
882    h->addStretch( 1 );
883   
884    l = new QLabel( "Times Completed:" );
885    l->setStyleSheet( "font: bold" );
886    h->addWidget( l );
887    l = myTimesCompletedLabel = new QLabel( "c" );
888    h->addWidget( l );
889
890    return top;
891}
892
893/***
894****
895***/
896
897QWidget *
898Details :: createFilesTab( )
899{
900    myFileTreeView = new FileTreeView( );
901
902    connect( myFileTreeView, SIGNAL(      priorityChanged(const QSet<int>&, int)),
903             this,           SLOT(  onFilePriorityChanged(const QSet<int>&, int)));
904
905    connect( myFileTreeView, SIGNAL(      wantedChanged(const QSet<int>&, bool)),
906             this,           SLOT(  onFileWantedChanged(const QSet<int>&, bool)));
907
908    return myFileTreeView;
909}
910
911void
912Details :: onFilePriorityChanged( const QSet<int>& indices, int priority )
913{
914    QString key;
915    switch( priority ) {
916        case TR_PRI_LOW:   key = "priority-low"; break;
917        case TR_PRI_HIGH:  key = "priority-high"; break;
918        default:           key = "priority-normal"; break;
919    }
920    mySession.torrentSet( myIds, key, indices.toList( ) );
921}
922
923void
924Details :: onFileWantedChanged( const QSet<int>& indices, bool wanted )
925{
926    QString key( wanted ? "files-wanted" : "files-unwanted" );
927    mySession.torrentSet( myIds, key, indices.toList( ) );
928}
Note: See TracBrowser for help on using the repository browser.