source: trunk/qt/details.cc @ 8255

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

(trunk qt) handle multiple torrent selection in the properties dialog

File size: 29.3 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->setValue( tor->seedRatioLimit( ) );
450    }
451
452    // tracker tab
453    const time_t now( time( 0 ) );
454    myScrapeTimePrevLabel->setText( tor ? tor->lastScrapeTime().toString() : blank );
455    myScrapeResponseLabel->setText( tor ? tor->scrapeResponse() : blank );
456    myScrapeTimeNextLabel->setText( Utils :: timeToString( tor ? tor->nextScrapeTime().toTime_t() - now : 0 ) );
457    myAnnounceTimePrevLabel->setText( tor ? tor->lastScrapeTime().toString() : blank );
458    myAnnounceTimeNextLabel->setText( Utils :: timeToString( tor ? tor->nextAnnounceTime().toTime_t() - now : 0  ) );
459    myAnnounceManualLabel->setText( Utils :: timeToString( tor ? tor->manualAnnounceTime().toTime_t() - now : 0 ) );
460    myAnnounceResponseLabel->setText( tor ? tor->announceResponse( ) : blank );
461
462    // myTrackerLabel
463    strings.clear( );
464    foreach( tor, torrents ) strings.insert( QUrl(tor->announceUrl()).host() );
465    string = strings.size()==1 ? *strings.begin() : blank;
466    myTrackerLabel->setText( string );
467
468    ///
469    ///  Peers tab
470    ///
471
472    i = 0;
473    foreach( tor, torrents ) i += tor->seeders( );
474    mySeedersLabel->setText( locale.toString( i ) );
475
476    i = 0;
477    foreach( tor, torrents ) i += tor->leechers( );
478    myLeechersLabel->setText( locale.toString( i ) );
479
480    i = 0;
481    foreach( tor, torrents ) i += tor->timesCompleted( );
482    myTimesCompletedLabel->setText( locale.toString( i ) );
483
484    PeerList peers;
485    foreach( tor, torrents ) peers << tor->peers( );
486    QMap<QString,QTreeWidgetItem*> peers2;
487    QList<QTreeWidgetItem*> newItems;
488    static const QIcon myEncryptionIcon( ":/icons/encrypted.png" );
489    static const QIcon myEmptyIcon;
490    foreach( const Peer& peer, peers )
491    {
492        PeerItem * item = (PeerItem*) myPeers.value( peer.address, 0 );
493        if( item == 0 ) { // new peer has connected
494            item = new PeerItem;
495            item->setTextAlignment( COL_UP, Qt::AlignRight );
496            item->setTextAlignment( COL_DOWN, Qt::AlignRight );
497            item->setTextAlignment( COL_PERCENT, Qt::AlignRight );
498            newItems << item;
499        }
500
501        QString code;
502        if( peer.isDownloadingFrom )                           { code += 'D'; }
503        else if( peer.clientIsInterested )                     { code += 'd'; }
504        if( peer.isUploadingTo )                               { code += 'U'; }
505        else if( peer.peerIsInterested )                       { code += 'u'; }
506        if( !peer.clientIsChoked && !peer.clientIsInterested ) { code += 'K'; }
507        if( !peer.peerIsChoked && !peer.peerIsInterested )     { code += '?'; }
508        if( peer.isEncrypted )                                 { code += 'E'; }
509        if( peer.isIncoming )                                  { code += 'I'; }
510
511        item->setPeer( peer );
512        item->setStatus( code );
513
514        QString codeTip;
515        foreach( QChar ch, code ) {
516            QString txt;
517            switch( ch.toAscii() ) {
518                case 'O': txt = tr( "Optimistic unchoke" ); break;
519                case 'D': txt = tr( "Downloading from this peer" ); break;
520                case 'd': txt = tr( "We would download from this peer if they would let us" ); break;
521                case 'U': txt = tr( "Uploading to peer" ); break;
522                case 'u': txt = tr( "We would upload to this peer if they asked" ); break;
523                case 'K': txt = tr( "Peer has unchoked us, but we're not interested" ); break;
524                case '?': txt = tr( "We unchoked this peer, but they're not interested" ); break;
525                case 'E': txt = tr( "Encrypted connection" ); break;
526                case 'X': txt = tr( "Peer was discovered through Peer Exchange (PEX)" ); break;
527                case 'I': txt = tr( "Peer is an incoming connection" ); break;
528            }
529            if( !txt.isEmpty( ) )
530                codeTip += QString("%1: %2\n").arg(ch).arg(txt);
531        }
532        if( !codeTip.isEmpty() )
533            codeTip.resize( codeTip.size()-1 ); // eat the trailing linefeed
534
535        item->setIcon( COL_LOCK, peer.isEncrypted ? myEncryptionIcon : myEmptyIcon );
536        item->setToolTip( COL_LOCK, peer.isEncrypted ? tr( "Encrypted connection" ) : "" );
537        item->setText( COL_UP, peer.rateToPeer.isZero() ? "" : Utils::speedToString( peer.rateToPeer ) );
538        item->setText( COL_DOWN, peer.rateToClient.isZero() ? "" : Utils::speedToString( peer.rateToClient ) );
539        item->setText( COL_PERCENT, peer.progress > 0 ? QString( "%1%" ).arg( locale.toString((int)(peer.progress*100.0))) : "" );
540        item->setText( COL_STATUS, code );
541        item->setToolTip( COL_STATUS, codeTip );
542        item->setText( COL_ADDRESS, peer.address );
543        item->setText( COL_CLIENT, peer.clientName );
544        peers2.insert( peer.address, item );
545    }
546    myPeerTree->addTopLevelItems( newItems );
547    foreach( QString key, myPeers.keys() ) {
548        if( !peers2.contains( key ) ) { // old peer has disconnected
549            QTreeWidgetItem * item = myPeers.value( key, 0 );
550            myPeerTree->takeTopLevelItem( myPeerTree->indexOfTopLevelItem( item ) );
551            delete item;
552        }
553    }
554    myPeers = peers2;
555
556    if( single ) {
557        tor = *torrents.begin();
558        myFileTreeView->update( tor->files( ) );
559    } else { 
560        myFileTreeView->clear( );
561    }
562
563    myHavePendingRefresh = false;
564    foreach( QWidget * w, myWidgets )
565        w->setEnabled( true );
566}
567
568void
569Details :: enableWhenChecked( QCheckBox * box, QWidget * w )
570{
571    connect( box, SIGNAL(toggled(bool)), w, SLOT(setEnabled(bool)) );
572    w->setEnabled( box->isChecked( ) );
573}
574
575
576/***
577****
578***/
579
580QWidget *
581Details :: createActivityTab( )
582{
583    HIG * hig = new HIG( this );
584
585    hig->addSectionTitle( tr( "Transfer" ) );
586    hig->addRow( tr( "State:" ), myStateLabel = new SqueezeLabel );
587    hig->addRow( tr( "Progress:" ), myProgressLabel = new SqueezeLabel );
588    hig->addRow( tr( "Have:" ), myHaveLabel = new SqueezeLabel );
589    hig->addRow( tr( "Downloaded:" ), myDownloadedLabel = new SqueezeLabel );
590    hig->addRow( tr( "Uploaded:" ), myUploadedLabel = new SqueezeLabel );
591    hig->addRow( tr( "Failed DL:" ), myFailedLabel = new SqueezeLabel );
592    hig->addRow( tr( "Ratio:" ), myRatioLabel = new SqueezeLabel );
593    hig->addRow( tr( "Swarm Rate:" ), mySwarmSpeedLabel = new SqueezeLabel );
594    hig->addRow( tr( "Error:" ), myErrorLabel = new SqueezeLabel );
595    hig->addSectionDivider( );
596
597    hig->addSectionTitle( tr( "Dates" ) );
598    hig->addRow( tr( "Added on:" ), myAddedDateLabel = new SqueezeLabel );
599    hig->addRow( tr( "Last activity on:" ), myActivityLabel = new SqueezeLabel );
600    hig->finish( );
601
602    return hig;
603}
604
605/***
606****
607***/
608
609void
610Details :: onHonorsSessionLimitsToggled( bool val )
611{
612std::cerr << " honorsSessionLimits clicked to " << val << std::endl;
613    mySession.torrentSet( myIds, "honorsSessionLimits", val );
614}
615void
616Details :: onDownloadLimitedToggled( bool val )
617{
618    mySession.torrentSet( myIds, "downloadLimited", val );
619}
620void
621Details :: onDownloadLimitChanged( int val )
622{
623    mySession.torrentSet( myIds, "downloadLimit", val );
624}
625void
626Details :: onUploadLimitedToggled( bool val )
627{
628    mySession.torrentSet( myIds, "uploadLimited", val );
629}
630void
631Details :: onUploadLimitChanged( int val )
632{
633    mySession.torrentSet( myIds, "uploadLimit", val );
634}
635
636#define RATIO_KEY "seedRatioMode"
637
638void
639Details :: onSeedUntilChanged( bool b )
640{
641    if( b )
642        mySession.torrentSet( myIds, RATIO_KEY, sender()->property(RATIO_KEY).toInt() );
643}
644
645void
646Details :: onSeedRatioLimitChanged( double val )
647{
648    mySession.torrentSet( myIds, "seedRatioLimit", val );
649}
650
651void
652Details :: onMaxPeersChanged( int val )
653{
654    mySession.torrentSet( myIds, "peer-limit", val );
655}
656
657void
658Details :: onBandwidthPriorityChanged( int index )
659{
660    if( index != -1 )
661    {
662        const int priority = myBandwidthPriorityCombo->itemData(index).toInt( );
663        mySession.torrentSet( myIds, "bandwidthPriority", priority );
664    }
665}
666
667QWidget *
668Details :: createOptionsTab( )
669{
670    //QWidget * l;
671    QSpinBox * s;
672    QCheckBox * c;
673    QComboBox * m;
674    QHBoxLayout * h;
675    QRadioButton * r;
676    QDoubleSpinBox * ds;
677
678    HIG * hig = new HIG( this );
679    hig->addSectionTitle( tr( "Speed" ) );
680
681    c = new QCheckBox( tr( "Honor global &limits" ) );
682    mySessionLimitCheck = c;
683    hig->addWideControl( c );
684    connect( c, SIGNAL(clicked(bool)), this, SLOT(onHonorsSessionLimitsToggled(bool)) );
685
686    c = new QCheckBox( tr( "Limit &download speed (KB/s)" ) );
687    mySingleDownCheck = c;
688    s = new QSpinBox( );
689    mySingleDownSpin = s;
690    s->setRange( 0, INT_MAX );
691    hig->addRow( c, s );
692    enableWhenChecked( c, s );
693    connect( c, SIGNAL(clicked(bool)), this, SLOT(onDownloadLimitedToggled(bool)) );
694    connect( s, SIGNAL(valueChanged(int)), this, SLOT(onDownloadLimitChanged(int)));
695
696    c = new QCheckBox( tr( "Limit &upload speed (KB/s)" ) );
697    mySingleUpCheck = c;
698    s = new QSpinBox( );
699    mySingleUpSpin = s;
700    s->setRange( 0, INT_MAX );
701    hig->addRow( c, s );
702    enableWhenChecked( c, s );
703    connect( c, SIGNAL(clicked(bool)), this, SLOT(onUploadLimitedToggled(bool)) );
704    connect( s, SIGNAL(valueChanged(int)), this, SLOT(onUploadLimitChanged(int)));
705
706    m = new QComboBox;
707    m->addItem( tr( "Low" ),    TR_PRI_LOW );
708    m->addItem( tr( "Normal" ), TR_PRI_NORMAL );
709    m->addItem( tr( "High" ),   TR_PRI_HIGH );
710    connect( m, SIGNAL(currentIndexChanged(int)), this, SLOT(onBandwidthPriorityChanged(int)));
711    hig->addRow( tr( "Bandwidth priority:" ), m );
712    myBandwidthPriorityCombo = m;
713   
714
715    hig->addSectionDivider( );
716    hig->addSectionTitle( tr( "Seed-Until Ratio" ) );
717
718    r = new QRadioButton( tr( "Use &global setting" ) );
719    r->setProperty( RATIO_KEY, TR_RATIOLIMIT_GLOBAL );
720    connect( r, SIGNAL(clicked(bool)), this, SLOT(onSeedUntilChanged(bool)));
721    mySeedGlobalRadio = r;
722    hig->addWideControl( r );
723
724    r = new QRadioButton( tr( "Seed &regardless of ratio" ) );
725    r->setProperty( RATIO_KEY, TR_RATIOLIMIT_UNLIMITED );
726    connect( r, SIGNAL(clicked(bool)), this, SLOT(onSeedUntilChanged(bool)));
727    mySeedForeverRadio = r;
728    hig->addWideControl( r );
729
730    h = new QHBoxLayout( );
731    h->setSpacing( HIG :: PAD );
732    r = new QRadioButton( tr( "&Stop seeding when a torrent's ratio reaches" ) );
733    r->setProperty( RATIO_KEY, TR_RATIOLIMIT_SINGLE );
734    connect( r, SIGNAL(clicked(bool)), this, SLOT(onSeedUntilChanged(bool)));
735    mySeedCustomRadio = r;
736    h->addWidget( r );
737    ds = new QDoubleSpinBox( );
738    ds->setRange( 0.5, INT_MAX );
739    connect( ds, SIGNAL(valueChanged(double)), this, SLOT(onSeedRatioLimitChanged(double)));
740    mySeedCustomSpin = ds;
741    h->addWidget( ds );
742    hig->addWideControl( h );
743
744    hig->addSectionDivider( );
745    hig->addSectionTitle( tr( "Peer Connections" ) );
746
747    s = new QSpinBox( );
748    s->setRange( 1, 300 );
749    connect( s, SIGNAL(valueChanged(int)), this, SLOT(onMaxPeersChanged(int)));
750    myPeerLimitSpin = s;
751    hig->addRow( tr( "&Maximum Peers" ), s );
752
753    hig->finish( );
754
755    return hig;
756}
757
758/***
759****
760***/
761
762QWidget *
763Details :: createInfoTab( )
764{
765    HIG * hig = new HIG( );
766    hig->addSectionTitle( tr( "Details" ) );
767    hig->addRow( tr( "Pieces:" ), myPiecesLabel = new SqueezeLabel );
768    hig->addRow( tr( "Hash:" ), myHashLabel = new SqueezeLabel );
769    hig->addRow( tr( "Privacy:" ), myPrivacyLabel = new SqueezeLabel );
770    hig->addRow( tr( "Comment:" ), myCommentBrowser = new QTextBrowser );
771    hig->addSectionDivider( );
772    hig->addSectionTitle( tr( "Origins" ) );
773    hig->addRow( tr( "Creator:" ), myCreatorLabel = new SqueezeLabel );
774    hig->addRow( tr( "Date:" ), myDateCreatedLabel = new SqueezeLabel );
775    hig->addSectionDivider( );
776    hig->addSectionTitle( tr( "Origins" ) );
777    hig->addRow( tr( "Destination folder:" ), myDestinationLabel = new SqueezeLabel );
778    hig->addRow( tr( "Torrent file:" ), myTorrentFileLabel = new SqueezeLabel );
779    const int h = QFontMetrics(myCommentBrowser->font()).lineSpacing() * 4;
780    myTorrentFileLabel->setMinimumWidth( 300 );
781    myTorrentFileLabel->setSizePolicy ( QSizePolicy::Expanding, QSizePolicy::Preferred );
782
783    myCommentBrowser->setMinimumHeight( h );
784    myCommentBrowser->setMaximumHeight( h );
785
786    hig->finish( );
787    return hig;
788}
789
790/***
791****
792***/
793
794QWidget *
795Details :: createTrackerTab( )
796{
797    HIG * hig = new HIG( );
798
799    hig->addSectionTitle( tr( "Scrape" ) );
800    hig->addRow( tr( "Last scrape at:" ), myScrapeTimePrevLabel = new SqueezeLabel );
801    hig->addRow( tr( "Tracker responded:" ), myScrapeResponseLabel = new SqueezeLabel );
802    hig->addRow( tr( "Next scrape in:" ), myScrapeTimeNextLabel = new SqueezeLabel );
803    hig->addSectionDivider( );
804    hig->addSectionTitle( tr( "Announce" ) );
805    hig->addRow( tr( "Tracker:" ), myTrackerLabel = new SqueezeLabel );
806    hig->addRow( tr( "Last announce at:" ), myAnnounceTimePrevLabel = new SqueezeLabel );
807    hig->addRow( tr( "Tracker responded:" ), myAnnounceResponseLabel = new SqueezeLabel );
808    hig->addRow( tr( "Next announce in:" ), myAnnounceTimeNextLabel = new SqueezeLabel );
809    hig->addRow( tr( "Manual announce allowed in:" ), myAnnounceManualLabel = new SqueezeLabel );
810    hig->finish( );
811
812    myTrackerLabel->setScaledContents( true );
813
814    return hig;
815}
816
817/***
818****
819***/
820
821QWidget *
822Details :: createPeersTab( )
823{
824    QWidget * top = new QWidget;
825    QVBoxLayout * v = new QVBoxLayout( top );
826    v->setSpacing( HIG :: PAD_BIG );
827    v->setContentsMargins( HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG );
828
829    QStringList headers;
830    headers << QString() << tr("Up") << tr("Down") << tr("%") << tr("Status") << tr("Address") << tr("Client");
831    myPeerTree = new QTreeWidget;
832    myPeerTree->setUniformRowHeights( true );
833    myPeerTree->setHeaderLabels( headers );
834    myPeerTree->setColumnWidth( 0, 20 );
835    myPeerTree->setSortingEnabled( true );
836    myPeerTree->setRootIsDecorated( false );
837    myPeerTree->setTextElideMode( Qt::ElideRight );
838    v->addWidget( myPeerTree, 1 );
839
840    const QFontMetrics m( font( ) );
841    QSize size = m.size( 0, "1024 MB/s" );
842    myPeerTree->setColumnWidth( COL_UP, size.width( ) );
843    myPeerTree->setColumnWidth( COL_DOWN, size.width( ) );
844    size = m.size( 0, " 100% " );
845    myPeerTree->setColumnWidth( COL_PERCENT, size.width( ) );
846    size = m.size( 0, "ODUK?EXI" );
847    myPeerTree->setColumnWidth( COL_STATUS, size.width( ) );
848    size = m.size( 0, "888.888.888.888" );
849    myPeerTree->setColumnWidth( COL_ADDRESS, size.width( ) );
850    size = m.size( 0, "Some BitTorrent Client" );
851    myPeerTree->setColumnWidth( COL_CLIENT, size.width( ) );
852    //myPeerTree->sortItems( myTorrent.isDone() ? COL_UP : COL_DOWN, Qt::DescendingOrder );
853    myPeerTree->setAlternatingRowColors( true );
854
855    QHBoxLayout * h = new QHBoxLayout;
856    h->setSpacing( HIG :: PAD );
857    v->addLayout( h );
858
859    QLabel * l = new QLabel( "Seeders:" );
860    l->setStyleSheet( "font: bold" );
861    h->addWidget( l );
862    l = mySeedersLabel = new QLabel( "a" );
863    h->addWidget( l );
864    h->addStretch( 1 );
865   
866    l = new QLabel( "Leechers:" );
867    l->setStyleSheet( "font: bold" );
868    h->addWidget( l );
869    l = myLeechersLabel = new QLabel( "b" );
870    h->addWidget( l );
871    h->addStretch( 1 );
872   
873    l = new QLabel( "Times Completed:" );
874    l->setStyleSheet( "font: bold" );
875    h->addWidget( l );
876    l = myTimesCompletedLabel = new QLabel( "c" );
877    h->addWidget( l );
878
879    return top;
880}
881
882/***
883****
884***/
885
886QWidget *
887Details :: createFilesTab( )
888{
889    myFileTreeView = new FileTreeView( );
890
891    connect( myFileTreeView, SIGNAL(      priorityChanged(const QSet<int>&, int)),
892             this,           SLOT(  onFilePriorityChanged(const QSet<int>&, int)));
893
894    connect( myFileTreeView, SIGNAL(      wantedChanged(const QSet<int>&, bool)),
895             this,           SLOT(  onFileWantedChanged(const QSet<int>&, bool)));
896
897    return myFileTreeView;
898}
899
900void
901Details :: onFilePriorityChanged( const QSet<int>& indices, int priority )
902{
903    QString key;
904    switch( priority ) {
905        case TR_PRI_LOW:   key = "priority-low"; break;
906        case TR_PRI_HIGH:  key = "priority-high"; break;
907        default:           key = "priority-normal"; break;
908    }
909    mySession.torrentSet( myIds, key, indices.toList( ) );
910}
911
912void
913Details :: onFileWantedChanged( const QSet<int>& indices, bool wanted )
914{
915    QString key( wanted ? "files-wanted" : "files-unwanted" );
916    mySession.torrentSet( myIds, key, indices.toList( ) );
917}
Note: See TracBrowser for help on using the repository browser.