source: trunk/qt/details.cc @ 11047

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

(trunk qt) #2560 "idle seeding time limit" -- Qt support

  • Property svn:keywords set to Date Rev Author Id
File size: 46.3 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 11047 2010-07-24 04:14:43Z 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 <QInputDialog>
29#include <QLabel>
30#include <QMessageBox>
31#include <QPushButton>
32#include <QRadioButton>
33#include <QResizeEvent>
34#include <QSpinBox>
35#include <QStyle>
36#include <QTabWidget>
37#include <QTextBrowser>
38#include <QTreeView>
39#include <QTreeWidget>
40#include <QTreeWidgetItem>
41#include <QVBoxLayout>
42
43#include <libtransmission/transmission.h>
44#include <libtransmission/bencode.h>
45
46#include "details.h"
47#include "file-tree.h"
48#include "formatter.h"
49#include "hig.h"
50#include "prefs.h"
51#include "qticonloader.h"
52#include "session.h"
53#include "squeezelabel.h"
54#include "torrent.h"
55#include "torrent-model.h"
56
57class Prefs;
58class Session;
59
60/****
61*****
62****/
63
64namespace
65{
66    const int REFRESH_INTERVAL_MSEC = 4000;
67
68    enum // peer columns
69    {
70        COL_LOCK,
71        COL_UP,
72        COL_DOWN,
73        COL_PERCENT,
74        COL_STATUS,
75        COL_ADDRESS,
76        COL_CLIENT,
77        N_COLUMNS
78    };
79}
80
81/***
82****
83***/
84
85class PeerItem: public QTreeWidgetItem
86{
87        Peer peer;
88        QString collatedAddress;
89        QString status;
90
91    public:
92        virtual ~PeerItem( ) { }
93        PeerItem( const Peer& p ) {
94            peer = p;
95            int q[4];
96            if( sscanf( p.address.toUtf8().constData(), "%d.%d.%d.%d", q+0, q+1, q+2, q+3 ) == 4 )
97                collatedAddress.sprintf( "%03d.%03d.%03d.%03d", q[0], q[1], q[2], q[3] );
98            else
99                collatedAddress = p.address;
100        }
101    public:
102        void refresh( const Peer& p ) { peer = p; }
103        void setStatus( const QString& s ) { status = s; }
104        virtual bool operator< ( const QTreeWidgetItem & other ) const {
105            const PeerItem * i = 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 < i->peer.rateToPeer;
110                case COL_DOWN: return peer.rateToClient < i->peer.rateToClient;
111                case COL_PERCENT: return peer.progress < i->peer.progress;
112                case COL_STATUS: return status < i->status;
113                case COL_CLIENT: return peer.clientName < i->peer.clientName;
114                case COL_LOCK: return peer.isEncrypted && !i->peer.isEncrypted;
115                default: return collatedAddress < i->collatedAddress;
116            }
117        }
118};
119
120/***
121****
122***/
123
124QIcon
125Details :: getStockIcon( const QString& freedesktop_name, int fallback )
126{
127    QIcon fallbackIcon;
128
129    if( fallback > 0 )
130        fallbackIcon = style()->standardIcon( QStyle::StandardPixmap( fallback ), 0, this );
131
132    return QtIconLoader::icon( freedesktop_name, fallbackIcon );
133}
134
135Details :: Details( Session& session, Prefs& prefs, TorrentModel& model, QWidget * parent ):
136    QDialog( parent, Qt::Dialog ),
137    mySession( session ),
138    myPrefs( prefs ),
139    myModel( model ),
140    myChangedTorrents( false ),
141    myHavePendingRefresh( false )
142{
143    QVBoxLayout * layout = new QVBoxLayout( this );
144
145    setWindowTitle( tr( "Torrent Properties" ) );
146
147    QTabWidget * t = new QTabWidget( this );
148    QWidget * w;
149    t->addTab( w = createInfoTab( ),      tr( "Information" ) );
150    myWidgets << w;
151    t->addTab( w = createPeersTab( ),     tr( "Peers" ) );
152    myWidgets << w;
153    t->addTab( w = createTrackerTab( ),   tr( "Tracker" ) );
154    myWidgets << w;
155    t->addTab( w = createFilesTab( ),     tr( "Files" ) );
156    myWidgets << w;
157    t->addTab( w = createOptionsTab( ),   tr( "Options" ) );
158    myWidgets << w;
159    layout->addWidget( t );
160
161    QDialogButtonBox * buttons = new QDialogButtonBox( QDialogButtonBox::Close, Qt::Horizontal, this );
162    connect( buttons, SIGNAL(rejected()), this, SLOT(close()));
163    layout->addWidget( buttons );
164    QWidget::setAttribute( Qt::WA_DeleteOnClose, true );
165
166    connect( &myTimer, SIGNAL(timeout()), this, SLOT(onTimer()));
167
168    onTimer( );
169    myTimer.setSingleShot( false );
170    myTimer.start( REFRESH_INTERVAL_MSEC );
171}
172
173Details :: ~Details( )
174{
175}
176
177void
178Details :: setIds( const QSet<int>& ids )
179{
180    if( ids == myIds )
181        return;
182
183    myChangedTorrents = true;
184
185    // stop listening to the old torrents
186    foreach( int id, myIds ) {
187        const Torrent * tor = myModel.getTorrentFromId( id );
188        if( tor )
189            disconnect( tor, SIGNAL(torrentChanged(int)), this, SLOT(onTorrentChanged()) );
190    }
191
192    myFileTreeView->clear( );
193
194    myIds = ids;
195
196    // listen to the new torrents
197    foreach( int id, myIds ) {
198        const Torrent * tor = myModel.getTorrentFromId( id );
199        if( tor )
200            connect( tor, SIGNAL(torrentChanged(int)), this, SLOT(onTorrentChanged()) );
201    }
202
203    foreach( QWidget * w, myWidgets )
204        w->setEnabled( false );
205
206    onTimer( );
207}
208
209/***
210****
211***/
212
213QString
214Details :: timeToStringRounded( int seconds )
215{
216    if( seconds > 60 ) seconds -= ( seconds % 60 );
217    return Formatter::timeToString ( seconds );
218}
219
220void
221Details :: onTimer( )
222{
223    if( !myIds.empty( ) )
224    {
225        QSet<int> infos;
226        foreach( int id, myIds ) {
227            const Torrent * tor = myModel.getTorrentFromId( id );
228            if( tor->isMagnet() )
229                infos.insert( tor->id() );
230        }
231        if( !infos.isEmpty() )
232            mySession.initTorrents( infos );
233        mySession.refreshExtraStats( myIds );
234    }
235}
236
237void
238Details :: onTorrentChanged( )
239{
240    if( !myHavePendingRefresh ) {
241        myHavePendingRefresh = true;
242        QTimer::singleShot( 100, this, SLOT(refresh()));
243    }
244}
245
246
247void
248Details :: refresh( )
249{
250    const int n = myIds.size( );
251    const bool single = n == 1;
252    const QString blank;
253    const QFontMetrics fm( fontMetrics( ) );
254    QList<const Torrent*> torrents;
255    QString string;
256    const QString none = tr( "None" );
257    const QString mixed = tr( "Mixed" );
258    const QString unknown = tr( "Unknown" );
259
260    // build a list of torrents
261    foreach( int id, myIds ) {
262        const Torrent * tor = myModel.getTorrentFromId( id );
263        if( tor )
264            torrents << tor;
265    }
266
267    ///
268    ///  activity tab
269    ///
270
271    // myStateLabel
272    if( torrents.empty( ) )
273        string = none;
274    else {
275        bool isMixed = false;
276        bool allPaused = true;
277        bool allFinished = true;
278        const tr_torrent_activity activity = torrents[0]->getActivity( );
279        foreach( const Torrent * t, torrents ) {
280            if( activity != t->getActivity( ) )
281                isMixed = true;
282            if( activity != TR_STATUS_STOPPED )
283                allPaused = allFinished = false;
284            if( !t->isFinished( ) )
285                allFinished = false;
286        }
287        if( isMixed )
288            string = mixed;
289        else if( allFinished )
290            string = tr( "Finished" );
291        else if( allPaused )
292            string = tr( "Paused" );
293        else
294            string = torrents[0]->activityString( );
295    }
296    myStateLabel->setText( string );
297    const QString stateString = string;
298
299    // myHaveLabel
300    double sizeWhenDone = 0;
301    double leftUntilDone = 0;
302    double available = 0;
303    int64_t haveTotal = 0;
304    int64_t haveVerified = 0;
305    int64_t haveUnverified = 0;
306    int64_t verifiedPieces = 0;
307    if( torrents.empty( ) )
308        string = none;
309    else {
310        foreach( const Torrent * t, torrents ) {
311            if( t->hasMetadata( ) ) {
312                haveTotal += t->haveTotal( );
313                haveUnverified += t->haveUnverified( );
314                const uint64_t v = t->haveVerified( );
315                haveVerified += v;
316                verifiedPieces += v / t->pieceSize( );
317                sizeWhenDone += t->sizeWhenDone( );
318                leftUntilDone += t->leftUntilDone( );
319                available += t->sizeWhenDone() - t->leftUntilDone() + t->desiredAvailable();
320            }
321        }
322        if( !haveVerified && !haveUnverified )
323            string = none;
324        else {
325            const double d = 100.0 * ( sizeWhenDone ? ( sizeWhenDone - leftUntilDone ) / sizeWhenDone : 1 );
326            QString pct = Formatter::percentToString( d );
327            if( !haveUnverified )
328                string = tr( "%1 (%2%)" )
329                             .arg( Formatter::sizeToString( haveVerified + haveUnverified ) )
330                             .arg( pct );
331            else
332                string = tr( "%1 (%2%); %3 Unverified" )
333                             .arg( Formatter::sizeToString( haveVerified + haveUnverified ) )
334                             .arg( pct )
335                             .arg( Formatter::sizeToString( haveUnverified ) );
336        }
337    }
338    myHaveLabel->setText( string );
339
340    // myAvailabilityLabel
341    if( torrents.empty( ) )
342        string = none;
343    else {
344        if( sizeWhenDone == 0 )
345            string = none;
346        else
347            string = QString( "%1%" ).arg( Formatter::percentToString( ( 100.0 * available ) / sizeWhenDone ) );
348    }
349    myAvailabilityLabel->setText( string );
350
351    // myDownloadedLabel
352    uint64_t d = 0, f = 0;
353    if( torrents.empty( ) )
354        string = none;
355    else {
356        foreach( const Torrent * t, torrents ) {
357            d += t->downloadedEver( );
358            f += t->failedEver( );
359        }
360        const QString dstr = Formatter::sizeToString( d );
361        const QString fstr = Formatter::sizeToString( f );
362        if( f )
363            string = tr( "%1 (+%2 corrupt)" ).arg( dstr ).arg( fstr );
364        else
365            string = dstr;
366    }
367    myDownloadedLabel->setText( string );
368
369    uint64_t u = 0;
370    if( torrents.empty( ) )
371        string = none;
372    else {
373        foreach( const Torrent * t, torrents ) u += t->uploadedEver( );
374        string = QString( Formatter::sizeToString( u ) );
375    }
376    myUploadedLabel->setText( string );
377
378    if( torrents.empty( ) )
379        string = none;
380    else if( torrents.count() == 1 )
381        string = Formatter::ratioToString( torrents.first()->ratio() );
382    else {
383        bool isMixed = false;
384        int ratioType = torrents.first()->ratio();
385        if( ratioType > 0 ) ratioType = 0;
386        foreach( const Torrent *t, torrents )
387        {
388            if( ratioType != ( t->ratio() >= 0 ? 0 : t->ratio() ) )
389            {
390                isMixed = true;
391                break;
392            }
393        }
394        if( isMixed )
395            string = mixed;
396        else if( ratioType < 0 )
397            string = Formatter::ratioToString( ratioType );
398        else
399            string = Formatter::ratioToString( (double)u / d );
400    }
401    myRatioLabel->setText( string );
402
403    const QDateTime qdt_now = QDateTime::currentDateTime( );
404
405    // myRunTimeLabel
406    if( torrents.empty( ) )
407        string = none;
408    else {
409        bool allPaused = true;
410        QDateTime baseline = torrents[0]->lastStarted( );
411        foreach( const Torrent * t, torrents ) {
412            if( baseline != t->lastStarted( ) )
413                baseline = QDateTime( );
414            if( !t->isPaused( ) )
415                allPaused = false;
416        }
417        if( allPaused )
418            string = stateString; // paused || finished
419        else if( baseline.isNull( ) )
420            string = mixed;
421        else
422            string = Formatter::timeToString( baseline.secsTo( qdt_now ) );
423    }
424    myRunTimeLabel->setText( string );
425
426
427    // myETALabel
428    string.clear( );
429    if( torrents.empty( ) )
430        string = none;
431    else {
432        int baseline = torrents[0]->getETA( );
433        foreach( const Torrent * t, torrents ) {
434            if( baseline != t->getETA( ) ) {
435                string = mixed;
436                break;
437            }
438        }
439        if( string.isEmpty( ) ) {
440            if( baseline < 0 )
441                string = tr( "Unknown" );
442            else
443                string = Formatter::timeToString( baseline );
444       }
445    }
446    myETALabel->setText( string );
447
448
449    // myLastActivityLabel
450    if( torrents.empty( ) )
451        string = none;
452    else {
453        QDateTime latest = torrents[0]->lastActivity( );
454        foreach( const Torrent * t, torrents ) {
455            const QDateTime dt = t->lastActivity( );
456            if( latest < dt )
457                latest = dt;
458        }
459        const int seconds = latest.secsTo( qdt_now );
460        if( seconds < 5 )
461            string = tr( "Active now" );
462        else
463            string = tr( "%1 ago" ).arg( Formatter::timeToString( seconds ) );
464    }
465    myLastActivityLabel->setText( string );
466
467
468    if( torrents.empty( ) )
469        string = none;
470    else {
471        string = torrents[0]->getError( );
472        foreach( const Torrent * t, torrents ) {
473            if( string != t->getError( ) ) {
474                string = mixed;
475                break;
476            }
477        }
478    }
479    if( string.isEmpty( ) )
480        string = none;
481    myErrorLabel->setText( string );
482
483
484    ///
485    /// information tab
486    ///
487
488    // mySizeLabel
489    if( torrents.empty( ) )
490        string = none;
491    else {
492        int pieces = 0;
493        uint64_t size = 0;
494        uint32_t pieceSize = torrents[0]->pieceSize( );
495        foreach( const Torrent * t, torrents ) {
496            pieces += t->pieceCount( );
497            size += t->totalSize( );
498            if( pieceSize != t->pieceSize( ) )
499                pieceSize = 0;
500        }
501        if( !size )
502            string = none;
503        else if( pieceSize > 0 )
504            string = tr( "%1 (%Ln pieces @ %2)", "", pieces )
505                     .arg( Formatter::sizeToString( size ) )
506                     .arg( Formatter::memToString( pieceSize ) );
507        else
508            string = tr( "%1 (%Ln pieces)", "", pieces )
509                     .arg( Formatter::sizeToString( size ) );
510    }
511    mySizeLabel->setText( string );
512
513    // myHashLabel
514    if( torrents.empty( ) )
515        string = none;
516    else {
517        string = torrents[0]->hashString( );
518        foreach( const Torrent * t, torrents ) {
519            if( string != t->hashString( ) ) {
520                string = mixed;
521                break;
522            }
523        }
524    }
525    myHashLabel->setText( string );
526
527    // myPrivacyLabel
528    if( torrents.empty( ) )
529        string = none;
530    else {
531        bool b = torrents[0]->isPrivate( );
532        string = b ? tr( "Private to this tracker -- DHT and PEX disabled" )
533                   : tr( "Public torrent" );
534        foreach( const Torrent * t, torrents ) {
535            if( b != t->isPrivate( ) ) {
536                string = mixed;
537                break;
538            }
539        }
540    }
541    myPrivacyLabel->setText( string );
542
543    // myCommentBrowser
544    if( torrents.empty( ) )
545        string = none;
546    else {
547        string = torrents[0]->comment( );
548        foreach( const Torrent * t, torrents ) {
549            if( string != t->comment( ) ) {
550                string = mixed;
551                break;
552            }
553        }
554    }
555    myCommentBrowser->setText( string );
556    myCommentBrowser->setMaximumHeight( QWIDGETSIZE_MAX );
557
558    // myOriginLabel
559    if( torrents.empty( ) )
560        string = none;
561    else {
562        bool mixed_creator=false, mixed_date=false;
563        const QString creator = torrents[0]->creator();
564        const QString date = torrents[0]->dateCreated().toString();
565        foreach( const Torrent * t, torrents ) {
566            mixed_creator |= ( creator != t->creator() );
567            mixed_date |=  ( date != t->dateCreated().toString() );
568        }
569        if( mixed_creator && mixed_date )
570            string = mixed;
571        else if( mixed_date )
572            string = tr( "Created by %1" ).arg( creator );
573        else if( mixed_creator || creator.isEmpty( ) )
574            string = tr( "Created on %1" ).arg( date );
575        else
576            string = tr( "Created by %1 on %2" ).arg( creator ).arg( date );
577    }
578    myOriginLabel->setText( string );
579
580    // myLocationLabel
581    if( torrents.empty( ) )
582        string = none;
583    else {
584        string = torrents[0]->getPath( );
585        foreach( const Torrent * t, torrents ) {
586            if( string != t->getPath( ) ) {
587                string = mixed;
588                break;
589            }
590        }
591    }
592    myLocationLabel->setText( string );
593
594
595    ///
596    ///  Options Tab
597    ///
598
599    if( myChangedTorrents && !torrents.empty( ) )
600    {
601        int i;
602        const Torrent * baseline = *torrents.begin();
603        const Torrent * tor;
604        bool uniform;
605        bool baselineFlag;
606        int baselineInt;
607
608        // mySessionLimitCheck
609        uniform = true;
610        baselineFlag = baseline->honorsSessionLimits( );
611        foreach( tor, torrents ) if( baselineFlag != tor->honorsSessionLimits( ) ) { uniform = false; break; }
612        mySessionLimitCheck->setChecked( uniform && baselineFlag );
613
614        // mySingleDownCheck
615        uniform = true;
616        baselineFlag = baseline->downloadIsLimited( );
617        foreach( tor, torrents ) if( baselineFlag != tor->downloadIsLimited( ) ) { uniform = false; break; }
618        mySingleDownCheck->setChecked( uniform && baselineFlag );
619
620        // mySingleUpCheck
621        uniform = true;
622        baselineFlag = baseline->uploadIsLimited( );
623        foreach( tor, torrents ) if( baselineFlag != tor->uploadIsLimited( ) ) { uniform = false; break; }
624        mySingleUpCheck->setChecked( uniform && baselineFlag );
625
626        // myBandwidthPriorityCombo
627        uniform = true;
628        baselineInt = baseline->getBandwidthPriority( );
629        foreach( tor, torrents ) if ( baselineInt != tor->getBandwidthPriority( ) ) { uniform = false; break; }
630        if( uniform )
631            i = myBandwidthPriorityCombo->findData( baselineInt );
632        else
633            i = -1;
634        myBandwidthPriorityCombo->blockSignals( true );
635        myBandwidthPriorityCombo->setCurrentIndex( i );
636        myBandwidthPriorityCombo->blockSignals( false );
637
638        mySingleDownSpin->blockSignals( true );
639        mySingleDownSpin->setValue( tor->downloadLimit().KBps() );
640        mySingleDownSpin->blockSignals( false );
641
642        mySingleUpSpin->blockSignals( true );
643        mySingleUpSpin->setValue( tor->uploadLimit().KBps() );
644        mySingleUpSpin->blockSignals( false );
645
646        myPeerLimitSpin->blockSignals( true );
647        myPeerLimitSpin->setValue( tor->peerLimit() );
648        myPeerLimitSpin->blockSignals( false );
649    }
650
651    {
652        const Torrent * tor;
653
654        // ratio
655        bool uniform = true;
656        int baselineInt = torrents[0]->seedRatioMode( );
657        foreach( tor, torrents ) if( baselineInt != tor->seedRatioMode( ) ) { uniform = false; break; }
658
659        myRatioCombo->blockSignals( true );
660        myRatioCombo->setCurrentIndex( uniform ? myRatioCombo->findData( baselineInt ) : -1 );
661        myRatioSpin->setVisible( uniform && ( baselineInt == TR_RATIOLIMIT_SINGLE ) );
662        myRatioCombo->blockSignals( false );
663
664        myRatioSpin->blockSignals( true );
665        myRatioSpin->setValue( tor->seedRatioLimit( ) );
666        myRatioSpin->blockSignals( false );
667
668        // idle
669        uniform = true;
670        baselineInt = torrents[0]->seedIdleMode( );
671        foreach( tor, torrents ) if( baselineInt != tor->seedIdleMode( ) ) { uniform = false; break; }
672
673        myIdleCombo->blockSignals( true );
674        myIdleCombo->setCurrentIndex( uniform ? myIdleCombo->findData( baselineInt ) : -1 );
675        myIdleSpin->setVisible( uniform && ( baselineInt == TR_RATIOLIMIT_SINGLE ) );
676        myIdleCombo->blockSignals( false );
677
678        myIdleSpin->blockSignals( true );
679        myIdleSpin->setValue( tor->seedIdleLimit( ) );
680        myIdleSpin->blockSignals( false );
681    }
682
683    // tracker tab
684    //
685    QMap<QString,QTreeWidgetItem*> trackerTiers;
686    QMap<QString,QTreeWidgetItem*> trackerItems;
687    const time_t now( time( 0 ) );
688    const bool showScrape = myPrefs.getBool( Prefs::SHOW_TRACKER_SCRAPES );
689    foreach( const Torrent * t, torrents )
690    {
691        const QString idStr( QString::number( t->id( ) ) );
692        const TrackerStatsList trackerStats = t->trackerStats( );
693
694        foreach( const TrackerStat& trackerStat, trackerStats )
695        {
696            QFont font;
697            QString str;
698            const QString tierKey( QString::number(trackerStat.tier) );
699            QTreeWidgetItem * tier = (QTreeWidgetItem*) myTrackerTiers.value( tierKey, 0 );
700
701            if( tier == 0 ) // check if has tier been created this pass
702                tier = (QTreeWidgetItem*) trackerTiers.value( tierKey, 0 );
703
704            if( tier == 0 ) // new tier
705            {
706                QFont tierFont;
707                tier = new QTreeWidgetItem( myTrackerTree );
708                myTrackerTree->addTopLevelItem( tier );
709                str = "Tier: " + QString::number( trackerStat.tier + 1 );
710                tier->setText( 0, str );
711                tierFont.setBold( true );
712                tier->setFont( 0, tierFont );
713            }
714
715            const QString key( idStr + tierKey + ":" + QString::number( trackerStat.id ) );
716            QTreeWidgetItem * item = (QTreeWidgetItem*) myTrackerItems.value( key, 0 );
717
718            if( item == 0 ) // new tracker
719            {
720                item = new QTreeWidgetItem( tier );
721                tier->addChild( item );
722                if( tier->childCount() == 1 )
723                    tier->setExpanded( true );
724            }
725            str = trackerStat.host;
726
727            if( trackerStat.isBackup )
728            {
729                font.setItalic( true );
730                if( showScrape )
731                {
732                    str += "\n";
733                    str += "Tracker will be used as a backup";
734                }
735            }
736            else
737            {
738                font.setItalic( false );
739                if( trackerStat.hasAnnounced )
740                {
741                    const QString tstr( timeToStringRounded( now - trackerStat.lastAnnounceTime ) );
742                    str += "\n";
743                    if( trackerStat.lastAnnounceSucceeded )
744                    {
745                        str += tr( "Got a list of %1 peers %2 ago" )
746                            .arg( trackerStat.lastAnnouncePeerCount )
747                            .arg( tstr );
748                    }
749                    else if( trackerStat.lastAnnounceTimedOut )
750                    {
751                        str += tr( "Peer list request timed out %1 ago; will retry" )
752                            .arg( tstr );
753                    }
754                    else
755                    {
756                        str += tr( "Got an error %1 ago" )
757                            .arg( tstr );
758                    }
759                }
760                switch( trackerStat.announceState )
761                {
762                    case TR_TRACKER_INACTIVE:
763                        if( trackerStat.hasAnnounced )
764                        {
765                            str += "\n";
766                            str += tr( "No updates scheduled" );
767                        }
768                        break;
769                    case TR_TRACKER_WAITING:
770                        {
771                            const QString tstr( timeToStringRounded( trackerStat.nextAnnounceTime - now ) );
772                            str += "\n";
773                            str += tr( "Asking for more peers in %1" )
774                                .arg( tstr );
775                        }
776                        break;
777                    case TR_TRACKER_QUEUED:
778                        str += "\n";
779                        str += tr( "Queued to ask for more peers" );
780                        break;
781                    case TR_TRACKER_ACTIVE:
782                        {
783                            const QString tstr( timeToStringRounded( now - trackerStat.lastAnnounceStartTime ) );
784                            str += "\n";
785                            str += tr( "Asking for more peers now... %1" )
786                                .arg( tstr );
787                        }
788                        break;
789                }
790                if( showScrape )
791                {
792                    if( trackerStat.hasScraped )
793                    {
794                        const QString tstr( timeToStringRounded( now - trackerStat.lastScrapeTime ) );
795                        str += "\n";
796                        if( trackerStat.lastScrapeSucceeded )
797                        {
798                            str += tr( "Tracker had %1 seeders and %2 leechers %3 ago" )
799                                .arg( trackerStat.seederCount )
800                                .arg( trackerStat.leecherCount )
801                                .arg( tstr );
802                        }
803                        else
804                        {
805                            str += tr( "Got a scrape error %1 ago" )
806                                .arg( tstr );
807                        }
808                    }
809                    switch( trackerStat.scrapeState )
810                    {
811                        case TR_TRACKER_INACTIVE:
812                            break;
813                        case TR_TRACKER_WAITING:
814                            {
815                                const QString tstr( timeToStringRounded( trackerStat.nextScrapeTime - now ) );
816                                str += "\n";
817                                str += tr( "Asking for peer counts in %1" )
818                                    .arg( tstr );
819                            }
820                            break;
821                        case TR_TRACKER_QUEUED:
822                            str += "\n";
823                            str += tr( "Queued to ask for peer counts" );
824                            break;
825                        case TR_TRACKER_ACTIVE:
826                            {
827                                const QString tstr( timeToStringRounded( now - trackerStat.lastScrapeStartTime ) );
828                                str += "\n";
829                                str += tr( "Asking for peer counts now... %1" )
830                                    .arg( tstr );
831                            }
832                            break;
833                    }
834                }
835            }
836            item->setText( 0, str );
837            item->setFont( 0, font );
838            item->setData( 0, TRACKERID, trackerStat.id );
839            item->setData( 0, TRACKERURL, trackerStat.announce );
840            item->setData( 0, TRACKERTIER, trackerStat.tier );
841            item->setData( 0, TORRENTID, t->id() );
842
843            tier->setData( 0, TRACKERID, -1 );
844            tier->setData( 0, TRACKERURL, QString() );
845            tier->setData( 0, TRACKERTIER, trackerStat.tier );
846            tier->setData( 0, TORRENTID, torrents.count() > 1 ? -1 : t->id() );
847
848            trackerTiers.insert( tierKey, tier );
849            trackerItems.insert( key, item );
850        }
851    }
852    QList<QTreeWidgetItem*> tierList = trackerTiers.values();
853    QList<QTreeWidgetItem*> itemList = trackerItems.values();
854    for( int i = 0; i < myTrackerTree->topLevelItemCount(); ++i )
855    {
856        QTreeWidgetItem * tier = myTrackerTree->topLevelItem( i );
857        for( int j = 0; j < tier->childCount(); ++j )
858        {
859            if( !itemList.contains( tier->child( j ) ) ) // tracker has disappeared
860                delete tier->takeChild( j-- );
861        }
862        if( !tierList.contains( tier ) ) // tier has disappeared
863            delete myTrackerTree->takeTopLevelItem( i-- );
864    }
865    myTrackerTiers = trackerTiers;
866    myTrackerItems = trackerItems;
867
868    ///
869    ///  Peers tab
870    ///
871
872    QMap<QString,QTreeWidgetItem*> peers2;
873    QList<QTreeWidgetItem*> newItems;
874    foreach( const Torrent * t, torrents )
875    {
876        const QString idStr( QString::number( t->id( ) ) );
877        PeerList peers = t->peers( );
878
879        foreach( const Peer& peer, peers )
880        {
881            const QString key = idStr + ":" + peer.address;
882            PeerItem * item = (PeerItem*) myPeers.value( key, 0 );
883
884            if( item == 0 ) // new peer has connected
885            {
886                static const QIcon myEncryptionIcon( ":/icons/encrypted.png" );
887                static const QIcon myEmptyIcon;
888                item = new PeerItem( peer );
889                item->setTextAlignment( COL_UP, Qt::AlignRight );
890                item->setTextAlignment( COL_DOWN, Qt::AlignRight );
891                item->setTextAlignment( COL_PERCENT, Qt::AlignRight );
892                item->setIcon( COL_LOCK, peer.isEncrypted ? myEncryptionIcon : myEmptyIcon );
893                item->setToolTip( COL_LOCK, peer.isEncrypted ? tr( "Encrypted connection" ) : "" );
894                item->setText( COL_ADDRESS, peer.address );
895                item->setText( COL_CLIENT, peer.clientName );
896                newItems << item;
897            }
898
899            const QString code = peer.flagStr;
900            item->setStatus( code );
901            item->refresh( peer );
902
903            QString codeTip;
904            foreach( QChar ch, code ) {
905                QString txt;
906                switch( ch.toAscii() ) {
907                    case 'O': txt = tr( "Optimistic unchoke" ); break;
908                    case 'D': txt = tr( "Downloading from this peer" ); break;
909                    case 'd': txt = tr( "We would download from this peer if they would let us" ); break;
910                    case 'U': txt = tr( "Uploading to peer" ); break;
911                    case 'u': txt = tr( "We would upload to this peer if they asked" ); break;
912                    case 'K': txt = tr( "Peer has unchoked us, but we're not interested" ); break;
913                    case '?': txt = tr( "We unchoked this peer, but they're not interested" ); break;
914                    case 'E': txt = tr( "Encrypted connection" ); break;
915                    case 'H': txt = tr( "Peer was discovered through DHT" ); break;
916                    case 'X': txt = tr( "Peer was discovered through Peer Exchange (PEX)" ); break;
917                    case 'I': txt = tr( "Peer is an incoming connection" ); break;
918                }
919                if( !txt.isEmpty( ) )
920                    codeTip += QString("%1: %2\n").arg(ch).arg(txt);
921            }
922
923            if( !codeTip.isEmpty() )
924                codeTip.resize( codeTip.size()-1 ); // eat the trailing linefeed
925
926            item->setText( COL_UP, peer.rateToPeer.isZero() ? "" : Formatter::speedToString( peer.rateToPeer ) );
927            item->setText( COL_DOWN, peer.rateToClient.isZero() ? "" : Formatter::speedToString( peer.rateToClient ) );
928            item->setText( COL_PERCENT, peer.progress > 0 ? QString( "%1%" ).arg( (int)( peer.progress * 100.0 ) ) : "" );
929            item->setText( COL_STATUS, code );
930            item->setToolTip( COL_STATUS, codeTip );
931
932            peers2.insert( key, item );
933        }
934    }
935    myPeerTree->addTopLevelItems( newItems );
936    foreach( QString key, myPeers.keys() ) {
937        if( !peers2.contains( key ) ) { // old peer has disconnected
938            QTreeWidgetItem * item = myPeers.value( key, 0 );
939            myPeerTree->takeTopLevelItem( myPeerTree->indexOfTopLevelItem( item ) );
940            delete item;
941        }
942    }
943    myPeers = peers2;
944
945    if( single )
946        myFileTreeView->update( torrents[0]->files( ) , myChangedTorrents );
947    else
948        myFileTreeView->clear( );
949
950    myChangedTorrents = false;
951    myHavePendingRefresh = false;
952    foreach( QWidget * w, myWidgets )
953        w->setEnabled( true );
954}
955
956void
957Details :: enableWhenChecked( QCheckBox * box, QWidget * w )
958{
959    connect( box, SIGNAL(toggled(bool)), w, SLOT(setEnabled(bool)) );
960    w->setEnabled( box->isChecked( ) );
961}
962
963
964/***
965****
966***/
967
968QWidget *
969Details :: createInfoTab( )
970{
971    HIG * hig = new HIG( this );
972
973    hig->addSectionTitle( tr( "Activity" ) );
974    hig->addRow( tr( "Torrent size:" ), mySizeLabel = new SqueezeLabel );
975    hig->addRow( tr( "Have:" ), myHaveLabel = new SqueezeLabel );
976    hig->addRow( tr( "Availability:" ), myAvailabilityLabel = new SqueezeLabel );
977    hig->addRow( tr( "Downloaded:" ), myDownloadedLabel = new SqueezeLabel );
978    hig->addRow( tr( "Uploaded:" ), myUploadedLabel = new SqueezeLabel );
979    hig->addRow( tr( "Ratio:" ), myRatioLabel = new SqueezeLabel );
980    hig->addRow( tr( "State:" ), myStateLabel = new SqueezeLabel );
981    hig->addRow( tr( "Running time:" ), myRunTimeLabel = new SqueezeLabel );
982    hig->addRow( tr( "Remaining time:" ), myETALabel = new SqueezeLabel );
983    hig->addRow( tr( "Last activity:" ), myLastActivityLabel = new SqueezeLabel );
984    hig->addRow( tr( "Error:" ), myErrorLabel = new SqueezeLabel );
985    hig->addSectionDivider( );
986
987    hig->addSectionDivider( );
988    hig->addSectionTitle( tr( "Details" ) );
989    hig->addRow( tr( "Location:" ), myLocationLabel = new SqueezeLabel );
990    hig->addRow( tr( "Hash:" ), myHashLabel = new SqueezeLabel );
991    hig->addRow( tr( "Privacy:" ), myPrivacyLabel = new SqueezeLabel );
992    hig->addRow( tr( "Origin:" ), myOriginLabel = new SqueezeLabel );
993    myOriginLabel->setMinimumWidth( 325 ); // stop long origin strings from resizing the widgit
994    hig->addRow( tr( "Comment:" ), myCommentBrowser = new QTextBrowser );
995    const int h = QFontMetrics(myCommentBrowser->font()).lineSpacing() * 4;
996    myCommentBrowser->setFixedHeight( h );
997
998    hig->finish( );
999
1000    return hig;
1001}
1002
1003/***
1004****
1005***/
1006
1007void
1008Details :: onShowTrackerScrapesToggled( bool val )
1009{
1010    myPrefs.set( Prefs::SHOW_TRACKER_SCRAPES, val );
1011}
1012
1013void
1014Details :: onHonorsSessionLimitsToggled( bool val )
1015{
1016    mySession.torrentSet( myIds, "honorsSessionLimits", val );
1017}
1018void
1019Details :: onDownloadLimitedToggled( bool val )
1020{
1021    mySession.torrentSet( myIds, "downloadLimited", val );
1022}
1023void
1024Details :: onDownloadLimitChanged( int val )
1025{
1026    mySession.torrentSet( myIds, "downloadLimit", val );
1027}
1028void
1029Details :: onUploadLimitedToggled( bool val )
1030{
1031    mySession.torrentSet( myIds, "uploadLimited", val );
1032}
1033void
1034Details :: onUploadLimitChanged( int val )
1035{
1036    mySession.torrentSet( myIds, "uploadLimit", val );
1037}
1038
1039void
1040Details :: onIdleModeChanged( int index )
1041{
1042    const int val = myIdleCombo->itemData( index ).toInt( );
1043    mySession.torrentSet( myIds, "seedIdleMode", val );
1044}
1045
1046void
1047Details :: onIdleLimitChanged( int val )
1048{
1049    mySession.torrentSet( myIds, "seedIdleLimit", val );
1050}
1051
1052void
1053Details :: onRatioModeChanged( int index )
1054{
1055    const int val = myRatioCombo->itemData( index ).toInt( );
1056    mySession.torrentSet( myIds, "seedRatioMode", val );
1057}
1058
1059void
1060Details :: onRatioLimitChanged( double val )
1061{
1062    mySession.torrentSet( myIds, "seedRatioLimit", val );
1063}
1064
1065void
1066Details :: onMaxPeersChanged( int val )
1067{
1068    mySession.torrentSet( myIds, "peer-limit", val );
1069}
1070
1071void
1072Details :: onBandwidthPriorityChanged( int index )
1073{
1074    if( index != -1 )
1075    {
1076        const int priority = myBandwidthPriorityCombo->itemData(index).toInt( );
1077        mySession.torrentSet( myIds, "bandwidthPriority", priority );
1078    }
1079}
1080
1081void
1082Details :: onTrackerSelectionChanged( )
1083{
1084    const QList<QTreeWidgetItem*> items = myTrackerTree->selectedItems();
1085    if( items.count() == 1 )
1086        myEditTrackerButton->setEnabled( items.first()->data( 0, TRACKERID ).toInt() >= 0 );
1087    else
1088        myEditTrackerButton->setEnabled( false );
1089    myRemoveTrackerButton->setEnabled( !items.isEmpty() );
1090}
1091
1092bool
1093Details :: findTrackerByURL( const QString& url, int torId )
1094{
1095    bool duplicate = false;
1096    foreach( QTreeWidgetItem * tracker, myTrackerItems.values() )
1097    {
1098        if( tracker->data( 0, TRACKERURL ).toString() == url &&
1099          ( torId == -1 || tracker->data( 0, TORRENTID ).toInt() == torId ) )
1100        {
1101            duplicate = true;
1102            break;
1103        }
1104    }
1105    return duplicate;
1106}
1107
1108void
1109Details :: onAddTrackerPushed( )
1110{
1111    const QString urlString = QInputDialog::getText( this,
1112                                                     tr( "Add tracker announce URL " ),
1113                                                     NULL );
1114    if( !urlString.isEmpty() )
1115    {
1116        if( !findTrackerByURL( urlString, -1 ) )
1117        {
1118            QByteArray url = urlString.toUtf8();
1119            tr_benc top;
1120
1121            tr_bencInitDict( &top, 1 );
1122            tr_bencDictAddStr( &top, "announce", url );
1123
1124            mySession.torrentSet( myIds, "trackerAdd", &top );
1125        }
1126        else
1127            QMessageBox::warning( this, "Error", "Tracker already exists." );
1128    }
1129}
1130
1131void
1132Details :: onEditTrackerPushed( )
1133{
1134    const QTreeWidgetItem * item = myTrackerTree->selectedItems().first();
1135    const QString urlString = QInputDialog::getText( this,
1136                                                     tr( "Edit tracker announce URL " ),
1137                                                     NULL,
1138                                                     QLineEdit::Normal,
1139                                                     item->data( 0, TRACKERURL ).toString() );
1140    if( !urlString.isEmpty() )
1141    {
1142        const int torId = item->data( 0, TORRENTID ).toInt();
1143        if( !findTrackerByURL( urlString, torId ) )
1144        {
1145            QByteArray url = urlString.toUtf8();
1146            QSet<int> ids;
1147            tr_benc top;
1148
1149            ids << torId;
1150            tr_bencInitDict( &top, 2 );
1151            tr_bencDictAddStr( &top, "announce", item->data( 0, TRACKERURL ).toByteArray() );
1152            tr_bencDictAddStr( &top, "announce-new", url );
1153
1154            mySession.torrentSet( ids, "trackerEdit", &top );
1155        }
1156        else
1157            QMessageBox::warning( this, "Error", "Tracker already exists." );
1158    }
1159}
1160
1161void
1162Details :: removeTracker( const QTreeWidgetItem * item )
1163{
1164    QByteArray url = item->data( 0, TRACKERURL ).toByteArray();
1165    const int torId = item->data( 0, TORRENTID ).toInt();
1166    QSet<int> ids;
1167    tr_benc top;
1168
1169    ids << torId;
1170    tr_bencInitDict( &top, 1 );
1171    tr_bencDictAddStr( &top, "announce", url );
1172
1173    mySession.torrentSet( ids, "trackerRemove", &top );
1174}
1175
1176void
1177Details :: onRemoveTrackerPushed( )
1178{
1179    const QList<QTreeWidgetItem*> items = myTrackerTree->selectedItems();
1180    QSet<int> removedTiers;
1181    foreach( const QTreeWidgetItem * item, items ) {
1182        const bool isTier = item->data( 0, TRACKERID ).toInt() == -1;
1183        const int curTier = item->data( 0, TRACKERTIER ).toInt();
1184        if( isTier )
1185        {
1186            removedTiers << curTier;
1187            for( int i = 0; i < item->childCount(); ++i )
1188                removeTracker( item->child( i ) );
1189        }
1190        else if( !removedTiers.contains( curTier ) ) // skip trackers removed by clearing a tier
1191            removeTracker( item );
1192    }
1193}
1194
1195QWidget *
1196Details :: createOptionsTab( )
1197{
1198    QSpinBox * s;
1199    QCheckBox * c;
1200    QComboBox * m;
1201    QHBoxLayout * h;
1202    QDoubleSpinBox * ds;
1203    const QString speed_K_str = Formatter::unitStr( Formatter::SPEED, Formatter::KB );
1204
1205    HIG * hig = new HIG( this );
1206    hig->addSectionTitle( tr( "Speed" ) );
1207
1208    c = new QCheckBox( tr( "Honor global &limits" ) );
1209    mySessionLimitCheck = c;
1210    hig->addWideControl( c );
1211    connect( c, SIGNAL(clicked(bool)), this, SLOT(onHonorsSessionLimitsToggled(bool)) );
1212
1213    c = new QCheckBox( tr( "Limit &download speed (%1):" ).arg( speed_K_str ) );
1214    mySingleDownCheck = c;
1215    s = new QSpinBox( );
1216    mySingleDownSpin = s;
1217    s->setRange( 0, INT_MAX );
1218    hig->addRow( c, s );
1219    enableWhenChecked( c, s );
1220    connect( c, SIGNAL(clicked(bool)), this, SLOT(onDownloadLimitedToggled(bool)) );
1221    connect( s, SIGNAL(valueChanged(int)), this, SLOT(onDownloadLimitChanged(int)));
1222
1223    c = new QCheckBox( tr( "Limit &upload speed (%1):" ).arg( speed_K_str ) );
1224    mySingleUpCheck = c;
1225    s = new QSpinBox( );
1226    mySingleUpSpin = s;
1227    s->setRange( 0, INT_MAX );
1228    hig->addRow( c, s );
1229    enableWhenChecked( c, s );
1230    connect( c, SIGNAL(clicked(bool)), this, SLOT(onUploadLimitedToggled(bool)) );
1231    connect( s, SIGNAL(valueChanged(int)), this, SLOT(onUploadLimitChanged(int)));
1232
1233    m = new QComboBox;
1234    m->addItem( tr( "High" ),   TR_PRI_HIGH );
1235    m->addItem( tr( "Normal" ), TR_PRI_NORMAL );
1236    m->addItem( tr( "Low" ),    TR_PRI_LOW );
1237    connect( m, SIGNAL(currentIndexChanged(int)), this, SLOT(onBandwidthPriorityChanged(int)));
1238    hig->addRow( tr( "Torrent &priority:" ), m );
1239    myBandwidthPriorityCombo = m;
1240
1241    hig->addSectionDivider( );
1242    hig->addSectionTitle( tr( "Seeding Limits" ) );
1243
1244    h = new QHBoxLayout( );
1245    h->setSpacing( HIG :: PAD );
1246    m = new QComboBox;
1247    m->addItem( tr( "Use Global Settings" ),      TR_RATIOLIMIT_GLOBAL );
1248    m->addItem( tr( "Seed regardless of ratio" ), TR_RATIOLIMIT_UNLIMITED );
1249    m->addItem( tr( "Stop seeding at ratio:" ),   TR_RATIOLIMIT_SINGLE );
1250    connect( m, SIGNAL(currentIndexChanged(int)), this, SLOT(onRatioModeChanged(int)));
1251    h->addWidget( myRatioCombo = m );
1252    ds = new QDoubleSpinBox( );
1253    ds->setRange( 0.5, INT_MAX );
1254    connect( ds, SIGNAL(valueChanged(double)), this, SLOT(onRatioLimitChanged(double)));
1255    h->addWidget( myRatioSpin = ds );
1256    hig->addRow( tr( "&Ratio:" ), h, m );
1257
1258    h = new QHBoxLayout( );
1259    h->setSpacing( HIG :: PAD );
1260    m = new QComboBox;
1261    m->addItem( tr( "Use Global Settings" ),                 TR_IDLELIMIT_GLOBAL );
1262    m->addItem( tr( "Seed regardless of activity" ),         TR_IDLELIMIT_UNLIMITED );
1263    m->addItem( tr( "Stop seeding if idle for N minutes:" ), TR_IDLELIMIT_SINGLE );
1264    connect( m, SIGNAL(currentIndexChanged(int)), this, SLOT(onIdleModeChanged(int)));
1265    h->addWidget( myIdleCombo = m );
1266    s = new QSpinBox( );
1267    s->setRange( 1, 9999 );
1268    connect( s, SIGNAL(valueChanged(int)), this, SLOT(onIdleLimitChanged(int)));
1269    h->addWidget( myIdleSpin = s );
1270    hig->addRow( tr( "&Idle:" ), h, m );
1271
1272
1273    hig->addSectionDivider( );
1274    hig->addSectionTitle( tr( "Peer Connections" ) );
1275
1276    s = new QSpinBox( );
1277    s->setRange( 1, 300 );
1278    connect( s, SIGNAL(valueChanged(int)), this, SLOT(onMaxPeersChanged(int)));
1279    myPeerLimitSpin = s;
1280    hig->addRow( tr( "&Maximum peers:" ), s );
1281
1282    hig->finish( );
1283
1284    return hig;
1285}
1286
1287/***
1288****
1289***/
1290
1291QWidget *
1292Details :: createTrackerTab( )
1293{
1294    QCheckBox * c;
1295    QPushButton * p;
1296    QWidget * top = new QWidget;
1297    QVBoxLayout * v = new QVBoxLayout( top );
1298    QHBoxLayout * h = new QHBoxLayout();
1299    QVBoxLayout * v2 = new QVBoxLayout();
1300
1301    v->setSpacing( HIG::PAD_BIG );
1302    v->setContentsMargins( HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG );
1303
1304    h->setSpacing( HIG::PAD );
1305    h->setContentsMargins( HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL );
1306
1307    v2->setSpacing( HIG::PAD );
1308
1309    QStringList headers;
1310    headers << tr("Trackers");
1311    myTrackerTree = new QTreeWidget;
1312    myTrackerTree->setHeaderLabels( headers );
1313    myTrackerTree->setSelectionMode( QTreeWidget::ExtendedSelection );
1314    myTrackerTree->setRootIsDecorated( false );
1315    myTrackerTree->setIndentation( 2 );
1316    myTrackerTree->setItemsExpandable( false );
1317    myTrackerTree->setTextElideMode( Qt::ElideRight );
1318    myTrackerTree->setAlternatingRowColors( true );
1319    connect( myTrackerTree, SIGNAL(itemSelectionChanged()), this, SLOT(onTrackerSelectionChanged()));
1320    h->addWidget( myTrackerTree, 1 );
1321
1322    p = new QPushButton();
1323    p->setIcon( getStockIcon( "list-add", QStyle::SP_DialogOpenButton ) );
1324    p->setToolTip( "Add Tracker" );
1325    myAddTrackerButton = p;
1326    v2->addWidget( p, 1 );
1327    connect( p, SIGNAL(clicked(bool)), this, SLOT(onAddTrackerPushed()));
1328
1329    p = new QPushButton();
1330    p->setIcon( getStockIcon( "document-properties", QStyle::SP_DesktopIcon ) );
1331    p->setToolTip( "Edit Tracker" );
1332    myAddTrackerButton = p;
1333    p->setEnabled( false );
1334    myEditTrackerButton = p;
1335    v2->addWidget( p, 1 );
1336    connect( p, SIGNAL(clicked(bool)), this, SLOT(onEditTrackerPushed()));
1337
1338    p = new QPushButton();
1339    p->setIcon( getStockIcon( "list-remove", QStyle::SP_TrashIcon ) );
1340    p->setToolTip( "Remove Trackers" );
1341    p->setEnabled( false );
1342    myRemoveTrackerButton = p;
1343    v2->addWidget( p, 1 );
1344    connect( p, SIGNAL(clicked(bool)), this, SLOT(onRemoveTrackerPushed()));
1345
1346    v2->addStretch( 1 );
1347
1348    h->addLayout( v2, 1 );
1349    h->setStretch( 1, 0 );
1350
1351    v->addLayout( h, 1 );
1352
1353    c = new QCheckBox( tr( "Show &more details" ) );
1354    c->setChecked( myPrefs.getBool( Prefs::SHOW_TRACKER_SCRAPES ) );
1355    myShowTrackerScrapesCheck = c;
1356    v->addWidget( c, 1 );
1357    connect( c, SIGNAL(clicked(bool)), this, SLOT(onShowTrackerScrapesToggled(bool)) );
1358
1359    return top;
1360}
1361
1362/***
1363****
1364***/
1365
1366QWidget *
1367Details :: createPeersTab( )
1368{
1369    QWidget * top = new QWidget;
1370    QVBoxLayout * v = new QVBoxLayout( top );
1371    v->setSpacing( HIG :: PAD_BIG );
1372    v->setContentsMargins( HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG );
1373
1374    QStringList headers;
1375    headers << QString() << tr("Up") << tr("Down") << tr("%") << tr("Status") << tr("Address") << tr("Client");
1376    myPeerTree = new QTreeWidget;
1377    myPeerTree->setUniformRowHeights( true );
1378    myPeerTree->setHeaderLabels( headers );
1379    myPeerTree->setColumnWidth( 0, 20 );
1380    myPeerTree->setSortingEnabled( true );
1381    myPeerTree->sortByColumn( COL_ADDRESS, Qt::AscendingOrder );
1382    myPeerTree->setRootIsDecorated( false );
1383    myPeerTree->setTextElideMode( Qt::ElideRight );
1384    v->addWidget( myPeerTree, 1 );
1385
1386    const QFontMetrics m( font( ) );
1387    QSize size = m.size( 0, "1024 MiB/s" );
1388    myPeerTree->setColumnWidth( COL_UP, size.width( ) );
1389    myPeerTree->setColumnWidth( COL_DOWN, size.width( ) );
1390    size = m.size( 0, " 100% " );
1391    myPeerTree->setColumnWidth( COL_PERCENT, size.width( ) );
1392    size = m.size( 0, "ODUK?EXI" );
1393    myPeerTree->setColumnWidth( COL_STATUS, size.width( ) );
1394    size = m.size( 0, "888.888.888.888" );
1395    myPeerTree->setColumnWidth( COL_ADDRESS, size.width( ) );
1396    size = m.size( 0, "Some BitTorrent Client" );
1397    myPeerTree->setColumnWidth( COL_CLIENT, size.width( ) );
1398    myPeerTree->setAlternatingRowColors( true );
1399
1400    return top;
1401}
1402
1403/***
1404****
1405***/
1406
1407QWidget *
1408Details :: createFilesTab( )
1409{
1410    myFileTreeView = new FileTreeView( );
1411
1412    connect( myFileTreeView, SIGNAL(      priorityChanged(const QSet<int>&, int)),
1413             this,           SLOT(  onFilePriorityChanged(const QSet<int>&, int)));
1414
1415    connect( myFileTreeView, SIGNAL(      wantedChanged(const QSet<int>&, bool)),
1416             this,           SLOT(  onFileWantedChanged(const QSet<int>&, bool)));
1417
1418    return myFileTreeView;
1419}
1420
1421void
1422Details :: onFilePriorityChanged( const QSet<int>& indices, int priority )
1423{
1424    QString key;
1425    switch( priority ) {
1426        case TR_PRI_LOW:   key = "priority-low"; break;
1427        case TR_PRI_HIGH:  key = "priority-high"; break;
1428        default:           key = "priority-normal"; break;
1429    }
1430    mySession.torrentSet( myIds, key, indices.toList( ) );
1431}
1432
1433void
1434Details :: onFileWantedChanged( const QSet<int>& indices, bool wanted )
1435{
1436    QString key( wanted ? "files-wanted" : "files-unwanted" );
1437    mySession.torrentSet( myIds, key, indices.toList( ) );
1438}
Note: See TracBrowser for help on using the repository browser.