source: trunk/qt/details.cc @ 10936

Last change on this file since 10936 was 10936, checked in by Longinus00, 12 years ago

(qt) fix a potential crash in the tracker display

  • Property svn:keywords set to Date Rev Author Id
File size: 45.7 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 10936 2010-07-03 19:34:36Z Longinus00 $
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.length() == 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().Bps() / Formatter::speed_K );
640        mySingleDownSpin->blockSignals( false );
641
642        mySingleUpSpin->blockSignals( true );
643        mySingleUpSpin->setValue( tor->uploadLimit().Bps() / Formatter::speed_K );
644        mySingleUpSpin->blockSignals( false );
645
646        myPeerLimitSpin->blockSignals( true );
647        myPeerLimitSpin->setValue( tor->peerLimit() );
648        myPeerLimitSpin->blockSignals( false );
649
650        // ratio radios
651        uniform = true;
652        baselineInt = tor->seedRatioMode( );
653        foreach( tor, torrents ) if( baselineInt != tor->seedRatioMode( ) ) { uniform = false; break; }
654        if( !uniform ) {
655            mySeedGlobalRadio->setChecked( false );
656            mySeedCustomRadio->setChecked( false );
657            mySeedForeverRadio->setChecked( false );
658        } else {
659            QRadioButton * rb;
660            switch( baselineInt ) {
661                case TR_RATIOLIMIT_GLOBAL:    rb = mySeedGlobalRadio; break;
662                case TR_RATIOLIMIT_SINGLE:    rb = mySeedCustomRadio; break;
663                case TR_RATIOLIMIT_UNLIMITED: rb = mySeedForeverRadio; break;
664            }
665            rb->setChecked( true );
666        }
667
668        mySeedCustomSpin->blockSignals( true );
669        mySeedCustomSpin->setValue( tor->seedRatioLimit( ) );
670        mySeedCustomSpin->blockSignals( false );
671    }
672
673    // tracker tab
674    //
675    QMap<QString,QTreeWidgetItem*> trackerTiers;
676    QMap<QString,QTreeWidgetItem*> trackerItems;
677    const time_t now( time( 0 ) );
678    const bool showScrape = myPrefs.getBool( Prefs::SHOW_TRACKER_SCRAPES );
679    foreach( const Torrent * t, torrents )
680    {
681        const QString idStr( QString::number( t->id( ) ) );
682        const TrackerStatsList trackerStats = t->trackerStats( );
683
684        foreach( const TrackerStat& trackerStat, trackerStats )
685        {
686            QFont font;
687            QString str;
688            const QString tierKey( QString::number(trackerStat.tier) );
689            QTreeWidgetItem * tier = (QTreeWidgetItem*) myTrackerTiers.value( tierKey, 0 );
690
691            if( tier == 0 ) // check if has tier been created this pass
692                tier = (QTreeWidgetItem*) trackerTiers.value( tierKey, 0 );
693
694            if( tier == 0 ) // new tier
695            {
696                QFont tierFont;
697                tier = new QTreeWidgetItem( myTrackerTree );
698                myTrackerTree->addTopLevelItem( tier );
699                str = "Tier: " + QString::number( trackerStat.tier + 1 );
700                tier->setText( 0, str );
701                tierFont.setBold( true );
702                tier->setFont( 0, tierFont );
703            }
704
705            const QString key( idStr + tierKey + ":" + QString::number( trackerStat.id ) );
706            QTreeWidgetItem * item = (QTreeWidgetItem*) myTrackerItems.value( key, 0 );
707
708            if( item == 0 ) // new tracker
709            {
710                item = new QTreeWidgetItem( tier );
711                tier->addChild( item );
712                if( tier->childCount() == 1 )
713                    tier->setExpanded( true );
714            }
715            str = trackerStat.host;
716
717            if( trackerStat.isBackup )
718            {
719                font.setItalic( true );
720                if( showScrape )
721                {
722                    str += "\n";
723                    str += "Tracker will be used as a backup";
724                }
725            }
726            else
727            {
728                font.setItalic( false );
729                if( trackerStat.hasAnnounced )
730                {
731                    const QString tstr( timeToStringRounded( now - trackerStat.lastAnnounceTime ) );
732                    str += "\n";
733                    if( trackerStat.lastAnnounceSucceeded )
734                    {
735                        str += tr( "Got a list of %1 peers %2 ago" )
736                            .arg( trackerStat.lastAnnouncePeerCount )
737                            .arg( tstr );
738                    }
739                    else if( trackerStat.lastAnnounceTimedOut )
740                    {
741                        str += tr( "Peer list request timed out %1 ago; will retry" )
742                            .arg( tstr );
743                    }
744                    else
745                    {
746                        str += tr( "Got an error %1 ago" )
747                            .arg( tstr );
748                    }
749                }
750                switch( trackerStat.announceState )
751                {
752                    case TR_TRACKER_INACTIVE:
753                        if( trackerStat.hasAnnounced )
754                        {
755                            str += "\n";
756                            str += tr( "No updates scheduled" );
757                        }
758                        break;
759                    case TR_TRACKER_WAITING:
760                        {
761                            const QString tstr( timeToStringRounded( trackerStat.nextAnnounceTime - now ) );
762                            str += "\n";
763                            str += tr( "Asking for more peers in %1" )
764                                .arg( tstr );
765                        }
766                        break;
767                    case TR_TRACKER_QUEUED:
768                        str += "\n";
769                        str += tr( "Queued to ask for more peers" );
770                        break;
771                    case TR_TRACKER_ACTIVE:
772                        {
773                            const QString tstr( timeToStringRounded( now - trackerStat.lastAnnounceStartTime ) );
774                            str += "\n";
775                            str += tr( "Asking for more peers now... %1" )
776                                .arg( tstr );
777                        }
778                        break;
779                }
780                if( showScrape )
781                {
782                    if( trackerStat.hasScraped )
783                    {
784                        const QString tstr( timeToStringRounded( now - trackerStat.lastScrapeTime ) );
785                        str += "\n";
786                        if( trackerStat.lastScrapeSucceeded )
787                        {
788                            str += tr( "Tracker had %1 seeders and %2 leechers %3 ago" )
789                                .arg( trackerStat.seederCount )
790                                .arg( trackerStat.leecherCount )
791                                .arg( tstr );
792                        }
793                        else
794                        {
795                            str += tr( "Got a scrape error %1 ago" )
796                                .arg( tstr );
797                        }
798                    }
799                    switch( trackerStat.scrapeState )
800                    {
801                        case TR_TRACKER_INACTIVE:
802                            break;
803                        case TR_TRACKER_WAITING:
804                            {
805                                const QString tstr( timeToStringRounded( trackerStat.nextScrapeTime - now ) );
806                                str += "\n";
807                                str += tr( "Asking for peer counts in %1" )
808                                    .arg( tstr );
809                            }
810                            break;
811                        case TR_TRACKER_QUEUED:
812                            str += "\n";
813                            str += tr( "Queued to ask for peer counts" );
814                            break;
815                        case TR_TRACKER_ACTIVE:
816                            {
817                                const QString tstr( timeToStringRounded( now - trackerStat.lastScrapeStartTime ) );
818                                str += "\n";
819                                str += tr( "Asking for peer counts now... %1" )
820                                    .arg( tstr );
821                            }
822                            break;
823                    }
824                }
825            }
826            item->setText( 0, str );
827            item->setFont( 0, font );
828            item->setData( 0, TRACKERID, trackerStat.id );
829            item->setData( 0, TRACKERURL, trackerStat.announce );
830            item->setData( 0, TRACKERTIER, trackerStat.tier );
831            item->setData( 0, TORRENTID, t->id() );
832
833            tier->setData( 0, TRACKERID, -1 );
834            tier->setData( 0, TRACKERURL, QString() );
835            tier->setData( 0, TRACKERTIER, trackerStat.tier );
836            tier->setData( 0, TORRENTID, torrents.count() > 1 ? -1 : t->id() );
837
838            trackerTiers.insert( tierKey, tier );
839            trackerItems.insert( key, item );
840        }
841    }
842    QList<QTreeWidgetItem*> tierList = trackerTiers.values();
843    QList<QTreeWidgetItem*> itemList = trackerItems.values();
844    for( int i = 0; i < myTrackerTree->topLevelItemCount(); ++i )
845    {
846        QTreeWidgetItem * tier = myTrackerTree->topLevelItem( i );
847        for( int j = 0; j < tier->childCount(); ++j )
848        {
849            if( !itemList.contains( tier->child( j ) ) ) // tracker has disappeared
850                delete tier->takeChild( j-- );
851        }
852        if( !tierList.contains( tier ) ) // tier has disappeared
853            delete myTrackerTree->takeTopLevelItem( i-- );
854    }
855    myTrackerTiers = trackerTiers;
856    myTrackerItems = trackerItems;
857
858    ///
859    ///  Peers tab
860    ///
861
862    QMap<QString,QTreeWidgetItem*> peers2;
863    QList<QTreeWidgetItem*> newItems;
864    foreach( const Torrent * t, torrents )
865    {
866        const QString idStr( QString::number( t->id( ) ) );
867        PeerList peers = t->peers( );
868
869        foreach( const Peer& peer, peers )
870        {
871            const QString key = idStr + ":" + peer.address;
872            PeerItem * item = (PeerItem*) myPeers.value( key, 0 );
873
874            if( item == 0 ) // new peer has connected
875            {
876                static const QIcon myEncryptionIcon( ":/icons/encrypted.png" );
877                static const QIcon myEmptyIcon;
878                item = new PeerItem( peer );
879                item->setTextAlignment( COL_UP, Qt::AlignRight );
880                item->setTextAlignment( COL_DOWN, Qt::AlignRight );
881                item->setTextAlignment( COL_PERCENT, Qt::AlignRight );
882                item->setIcon( COL_LOCK, peer.isEncrypted ? myEncryptionIcon : myEmptyIcon );
883                item->setToolTip( COL_LOCK, peer.isEncrypted ? tr( "Encrypted connection" ) : "" );
884                item->setText( COL_ADDRESS, peer.address );
885                item->setText( COL_CLIENT, peer.clientName );
886                newItems << item;
887            }
888
889            const QString code = peer.flagStr;
890            item->setStatus( code );
891            item->refresh( peer );
892
893            QString codeTip;
894            foreach( QChar ch, code ) {
895                QString txt;
896                switch( ch.toAscii() ) {
897                    case 'O': txt = tr( "Optimistic unchoke" ); break;
898                    case 'D': txt = tr( "Downloading from this peer" ); break;
899                    case 'd': txt = tr( "We would download from this peer if they would let us" ); break;
900                    case 'U': txt = tr( "Uploading to peer" ); break;
901                    case 'u': txt = tr( "We would upload to this peer if they asked" ); break;
902                    case 'K': txt = tr( "Peer has unchoked us, but we're not interested" ); break;
903                    case '?': txt = tr( "We unchoked this peer, but they're not interested" ); break;
904                    case 'E': txt = tr( "Encrypted connection" ); break;
905                    case 'H': txt = tr( "Peer was discovered through DHT" ); break;
906                    case 'X': txt = tr( "Peer was discovered through Peer Exchange (PEX)" ); break;
907                    case 'I': txt = tr( "Peer is an incoming connection" ); break;
908                }
909                if( !txt.isEmpty( ) )
910                    codeTip += QString("%1: %2\n").arg(ch).arg(txt);
911            }
912
913            if( !codeTip.isEmpty() )
914                codeTip.resize( codeTip.size()-1 ); // eat the trailing linefeed
915
916            item->setText( COL_UP, peer.rateToPeer.isZero() ? "" : Formatter::speedToString( peer.rateToPeer ) );
917            item->setText( COL_DOWN, peer.rateToClient.isZero() ? "" : Formatter::speedToString( peer.rateToClient ) );
918            item->setText( COL_PERCENT, peer.progress > 0 ? QString( "%1%" ).arg( (int)( peer.progress * 100.0 ) ) : "" );
919            item->setText( COL_STATUS, code );
920            item->setToolTip( COL_STATUS, codeTip );
921
922            peers2.insert( key, item );
923        }
924    }
925    myPeerTree->addTopLevelItems( newItems );
926    foreach( QString key, myPeers.keys() ) {
927        if( !peers2.contains( key ) ) { // old peer has disconnected
928            QTreeWidgetItem * item = myPeers.value( key, 0 );
929            myPeerTree->takeTopLevelItem( myPeerTree->indexOfTopLevelItem( item ) );
930            delete item;
931        }
932    }
933    myPeers = peers2;
934
935    if( single )
936        myFileTreeView->update( torrents[0]->files( ) , myChangedTorrents );
937    else
938        myFileTreeView->clear( );
939
940    myChangedTorrents = false;
941    myHavePendingRefresh = false;
942    foreach( QWidget * w, myWidgets )
943        w->setEnabled( true );
944}
945
946void
947Details :: enableWhenChecked( QCheckBox * box, QWidget * w )
948{
949    connect( box, SIGNAL(toggled(bool)), w, SLOT(setEnabled(bool)) );
950    w->setEnabled( box->isChecked( ) );
951}
952
953
954/***
955****
956***/
957
958QWidget *
959Details :: createInfoTab( )
960{
961    HIG * hig = new HIG( this );
962
963    hig->addSectionTitle( tr( "Activity" ) );
964    hig->addRow( tr( "Torrent size:" ), mySizeLabel = new SqueezeLabel );
965    hig->addRow( tr( "Have:" ), myHaveLabel = new SqueezeLabel );
966    hig->addRow( tr( "Availability:" ), myAvailabilityLabel = new SqueezeLabel );
967    hig->addRow( tr( "Downloaded:" ), myDownloadedLabel = new SqueezeLabel );
968    hig->addRow( tr( "Uploaded:" ), myUploadedLabel = new SqueezeLabel );
969    hig->addRow( tr( "Ratio:" ), myRatioLabel = new SqueezeLabel );
970    hig->addRow( tr( "State:" ), myStateLabel = new SqueezeLabel );
971    hig->addRow( tr( "Running time:" ), myRunTimeLabel = new SqueezeLabel );
972    hig->addRow( tr( "Remaining time:" ), myETALabel = new SqueezeLabel );
973    hig->addRow( tr( "Last activity:" ), myLastActivityLabel = new SqueezeLabel );
974    hig->addRow( tr( "Error:" ), myErrorLabel = new SqueezeLabel );
975    hig->addSectionDivider( );
976
977    hig->addSectionDivider( );
978    hig->addSectionTitle( tr( "Details" ) );
979    hig->addRow( tr( "Location:" ), myLocationLabel = new SqueezeLabel );
980    hig->addRow( tr( "Hash:" ), myHashLabel = new SqueezeLabel );
981    hig->addRow( tr( "Privacy:" ), myPrivacyLabel = new SqueezeLabel );
982    hig->addRow( tr( "Origin:" ), myOriginLabel = new SqueezeLabel );
983    myOriginLabel->setMinimumWidth( 325 ); // stop long origin strings from resizing the widgit
984    hig->addRow( tr( "Comment:" ), myCommentBrowser = new QTextBrowser );
985    const int h = QFontMetrics(myCommentBrowser->font()).lineSpacing() * 4;
986    myCommentBrowser->setFixedHeight( h );
987
988    hig->finish( );
989
990    return hig;
991}
992
993/***
994****
995***/
996
997void
998Details :: onShowTrackerScrapesToggled( bool val )
999{
1000    myPrefs.set( Prefs::SHOW_TRACKER_SCRAPES, val );
1001}
1002
1003void
1004Details :: onHonorsSessionLimitsToggled( bool val )
1005{
1006    mySession.torrentSet( myIds, "honorsSessionLimits", val );
1007}
1008void
1009Details :: onDownloadLimitedToggled( bool val )
1010{
1011    mySession.torrentSet( myIds, "downloadLimited", val );
1012}
1013void
1014Details :: onDownloadLimitChanged( int val )
1015{
1016    mySession.torrentSet( myIds, "downloadLimit", val * Formatter::speed_K  );
1017}
1018void
1019Details :: onUploadLimitedToggled( bool val )
1020{
1021    mySession.torrentSet( myIds, "uploadLimited", val );
1022}
1023void
1024Details :: onUploadLimitChanged( int val )
1025{
1026    mySession.torrentSet( myIds, "uploadLimit", val * Formatter::speed_K );
1027}
1028
1029#define RATIO_KEY "seedRatioMode"
1030
1031void
1032Details :: onSeedUntilChanged( bool b )
1033{
1034    if( b )
1035        mySession.torrentSet( myIds, RATIO_KEY, sender()->property(RATIO_KEY).toInt() );
1036}
1037
1038void
1039Details :: onSeedRatioLimitChanged( double val )
1040{
1041    QSet<int> ids;
1042
1043    foreach( int id, myIds ) {
1044        const Torrent * tor = myModel.getTorrentFromId( id );
1045        if( tor && tor->seedRatioLimit( ) )
1046            ids.insert( id );
1047    }
1048
1049    if( !ids.empty( ) )
1050        mySession.torrentSet( ids, "seedRatioLimit", val );
1051}
1052
1053void
1054Details :: onMaxPeersChanged( int val )
1055{
1056    mySession.torrentSet( myIds, "peer-limit", val );
1057}
1058
1059void
1060Details :: onBandwidthPriorityChanged( int index )
1061{
1062    if( index != -1 )
1063    {
1064        const int priority = myBandwidthPriorityCombo->itemData(index).toInt( );
1065        mySession.torrentSet( myIds, "bandwidthPriority", priority );
1066    }
1067}
1068
1069void
1070Details :: onTrackerSelectionChanged( )
1071{
1072    const QList<QTreeWidgetItem*> items = myTrackerTree->selectedItems();
1073    if( items.count() == 1 )
1074        myEditTrackerButton->setEnabled( items.first()->data( 0, TRACKERID ).toInt() >= 0 );
1075    else
1076        myEditTrackerButton->setEnabled( false );
1077    myRemoveTrackerButton->setEnabled( !items.isEmpty() );
1078}
1079
1080bool
1081Details :: findTrackerByURL( const QString& url, int torId )
1082{
1083    bool duplicate = false;
1084    foreach( QTreeWidgetItem * tracker, myTrackerItems.values() )
1085    {
1086        if( tracker->data( 0, TRACKERURL ).toString() == url &&
1087          ( torId == -1 || tracker->data( 0, TORRENTID ).toInt() == torId ) )
1088        {
1089            duplicate = true;
1090            break;
1091        }
1092    }
1093    return duplicate;
1094}
1095
1096void
1097Details :: onAddTrackerPushed( )
1098{
1099    const QString urlString = QInputDialog::getText( this,
1100                                                     tr( "Add tracker announce URL " ),
1101                                                     NULL );
1102    if( !urlString.isEmpty() )
1103    {
1104        if( !findTrackerByURL( urlString, -1 ) )
1105        {
1106            QByteArray url = urlString.toUtf8();
1107            tr_benc top;
1108
1109            tr_bencInitDict( &top, 1 );
1110            tr_bencDictAddStr( &top, "announce", url );
1111
1112            mySession.torrentSet( myIds, "trackerAdd", &top );
1113        }
1114        else
1115            QMessageBox::warning( this, "Error", "Tracker already exists." );
1116    }
1117}
1118
1119void
1120Details :: onEditTrackerPushed( )
1121{
1122    const QTreeWidgetItem * item = myTrackerTree->selectedItems().first();
1123    const QString urlString = QInputDialog::getText( this,
1124                                                     tr( "Edit tracker announce URL " ),
1125                                                     NULL,
1126                                                     QLineEdit::Normal,
1127                                                     item->data( 0, TRACKERURL ).toString() );
1128    if( !urlString.isEmpty() )
1129    {
1130        const int torId = item->data( 0, TORRENTID ).toInt();
1131        if( !findTrackerByURL( urlString, torId ) )
1132        {
1133            QByteArray url = urlString.toUtf8();
1134            QSet<int> ids;
1135            tr_benc top;
1136
1137            ids << torId;
1138            tr_bencInitDict( &top, 2 );
1139            tr_bencDictAddStr( &top, "announce", item->data( 0, TRACKERURL ).toByteArray() );
1140            tr_bencDictAddStr( &top, "announce-new", url );
1141
1142            mySession.torrentSet( ids, "trackerEdit", &top );
1143        }
1144        else
1145            QMessageBox::warning( this, "Error", "Tracker already exists." );
1146    }
1147}
1148
1149void
1150Details :: removeTracker( const QTreeWidgetItem * item )
1151{
1152    QByteArray url = item->data( 0, TRACKERURL ).toByteArray();
1153    const int torId = item->data( 0, TORRENTID ).toInt();
1154    QSet<int> ids;
1155    tr_benc top;
1156
1157    ids << torId;
1158    tr_bencInitDict( &top, 1 );
1159    tr_bencDictAddStr( &top, "announce", url );
1160
1161    mySession.torrentSet( ids, "trackerRemove", &top );
1162}
1163
1164void
1165Details :: onRemoveTrackerPushed( )
1166{
1167    const QList<QTreeWidgetItem*> items = myTrackerTree->selectedItems();
1168    QSet<int> removedTiers;
1169    foreach( const QTreeWidgetItem * item, items ) {
1170        const bool isTier = item->data( 0, TRACKERID ).toInt() == -1;
1171        const int curTier = item->data( 0, TRACKERTIER ).toInt();
1172        if( isTier )
1173        {
1174            removedTiers << curTier;
1175            for( int i = 0; i < item->childCount(); ++i )
1176                removeTracker( item->child( i ) );
1177        }
1178        else if( !removedTiers.contains( curTier ) ) // skip trackers removed by clearing a tier
1179            removeTracker( item );
1180    }
1181}
1182
1183QWidget *
1184Details :: createOptionsTab( )
1185{
1186    //QWidget * l;
1187    QSpinBox * s;
1188    QCheckBox * c;
1189    QComboBox * m;
1190    QHBoxLayout * h;
1191    QRadioButton * r;
1192    QDoubleSpinBox * ds;
1193
1194    HIG * hig = new HIG( this );
1195    hig->addSectionTitle( tr( "Speed" ) );
1196
1197    c = new QCheckBox( tr( "Honor global &limits" ) );
1198    mySessionLimitCheck = c;
1199    hig->addWideControl( c );
1200    connect( c, SIGNAL(clicked(bool)), this, SLOT(onHonorsSessionLimitsToggled(bool)) );
1201
1202    c = new QCheckBox( tr( "Limit &download speed (%1):" ).arg( Formatter::speed_K_str ) );
1203    mySingleDownCheck = c;
1204    s = new QSpinBox( );
1205    mySingleDownSpin = s;
1206    s->setRange( 0, INT_MAX );
1207    hig->addRow( c, s );
1208    enableWhenChecked( c, s );
1209    connect( c, SIGNAL(clicked(bool)), this, SLOT(onDownloadLimitedToggled(bool)) );
1210    connect( s, SIGNAL(valueChanged(int)), this, SLOT(onDownloadLimitChanged(int)));
1211
1212    c = new QCheckBox( tr( "Limit &upload speed (%1):" ).arg( Formatter::speed_K_str ) );
1213    mySingleUpCheck = c;
1214    s = new QSpinBox( );
1215    mySingleUpSpin = s;
1216    s->setRange( 0, INT_MAX );
1217    hig->addRow( c, s );
1218    enableWhenChecked( c, s );
1219    connect( c, SIGNAL(clicked(bool)), this, SLOT(onUploadLimitedToggled(bool)) );
1220    connect( s, SIGNAL(valueChanged(int)), this, SLOT(onUploadLimitChanged(int)));
1221
1222    m = new QComboBox;
1223    m->addItem( tr( "High" ),   TR_PRI_HIGH );
1224    m->addItem( tr( "Normal" ), TR_PRI_NORMAL );
1225    m->addItem( tr( "Low" ),    TR_PRI_LOW );
1226    connect( m, SIGNAL(currentIndexChanged(int)), this, SLOT(onBandwidthPriorityChanged(int)));
1227    hig->addRow( tr( "Torrent &priority:" ), m );
1228    myBandwidthPriorityCombo = m;
1229
1230    hig->addSectionDivider( );
1231    hig->addSectionTitle( tr( "Seed-Until Ratio" ) );
1232
1233    r = new QRadioButton( tr( "Use &global settings" ) );
1234    r->setProperty( RATIO_KEY, TR_RATIOLIMIT_GLOBAL );
1235    connect( r, SIGNAL(clicked(bool)), this, SLOT(onSeedUntilChanged(bool)));
1236    mySeedGlobalRadio = r;
1237    hig->addWideControl( r );
1238
1239    r = new QRadioButton( tr( "Seed &regardless of ratio" ) );
1240    r->setProperty( RATIO_KEY, TR_RATIOLIMIT_UNLIMITED );
1241    connect( r, SIGNAL(clicked(bool)), this, SLOT(onSeedUntilChanged(bool)));
1242    mySeedForeverRadio = r;
1243    hig->addWideControl( r );
1244
1245    h = new QHBoxLayout( );
1246    h->setSpacing( HIG :: PAD );
1247    r = new QRadioButton( tr( "&Seed torrent until its ratio reaches:" ) );
1248    r->setProperty( RATIO_KEY, TR_RATIOLIMIT_SINGLE );
1249    connect( r, SIGNAL(clicked(bool)), this, SLOT(onSeedUntilChanged(bool)));
1250    mySeedCustomRadio = r;
1251    h->addWidget( r );
1252    ds = new QDoubleSpinBox( );
1253    ds->setRange( 0.5, INT_MAX );
1254    connect( ds, SIGNAL(valueChanged(double)), this, SLOT(onSeedRatioLimitChanged(double)));
1255    mySeedCustomSpin = ds;
1256    h->addWidget( ds );
1257    hig->addWideControl( h );
1258
1259    hig->addSectionDivider( );
1260    hig->addSectionTitle( tr( "Peer Connections" ) );
1261
1262    s = new QSpinBox( );
1263    s->setRange( 1, 300 );
1264    connect( s, SIGNAL(valueChanged(int)), this, SLOT(onMaxPeersChanged(int)));
1265    myPeerLimitSpin = s;
1266    hig->addRow( tr( "&Maximum peers:" ), s );
1267
1268    hig->finish( );
1269
1270    return hig;
1271}
1272
1273/***
1274****
1275***/
1276
1277QWidget *
1278Details :: createTrackerTab( )
1279{
1280    QCheckBox * c;
1281    QPushButton * p;
1282    QWidget * top = new QWidget;
1283    QVBoxLayout * v = new QVBoxLayout( top );
1284    QHBoxLayout * h = new QHBoxLayout();
1285    QVBoxLayout * v2 = new QVBoxLayout();
1286
1287    v->setSpacing( HIG::PAD_BIG );
1288    v->setContentsMargins( HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG );
1289
1290    h->setSpacing( HIG::PAD );
1291    h->setContentsMargins( HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL );
1292
1293    v2->setSpacing( HIG::PAD );
1294
1295    QStringList headers;
1296    headers << tr("Trackers");
1297    myTrackerTree = new QTreeWidget;
1298    myTrackerTree->setHeaderLabels( headers );
1299    myTrackerTree->setSelectionMode( QTreeWidget::ExtendedSelection );
1300    myTrackerTree->setRootIsDecorated( false );
1301    myTrackerTree->setIndentation( 2 );
1302    myTrackerTree->setItemsExpandable( false );
1303    myTrackerTree->setTextElideMode( Qt::ElideRight );
1304    myTrackerTree->setAlternatingRowColors( true );
1305    connect( myTrackerTree, SIGNAL(itemSelectionChanged()), this, SLOT(onTrackerSelectionChanged()));
1306    h->addWidget( myTrackerTree, 1 );
1307
1308    p = new QPushButton();
1309    p->setIcon( getStockIcon( "list-add", QStyle::SP_DialogOpenButton ) );
1310    p->setToolTip( "Add Tracker" );
1311    myAddTrackerButton = p;
1312    v2->addWidget( p, 1 );
1313    connect( p, SIGNAL(clicked(bool)), this, SLOT(onAddTrackerPushed()));
1314
1315    p = new QPushButton();
1316    p->setIcon( getStockIcon( "document-properties", QStyle::SP_DesktopIcon ) );
1317    p->setToolTip( "Edit Tracker" );
1318    myAddTrackerButton = p;
1319    p->setEnabled( false );
1320    myEditTrackerButton = p;
1321    v2->addWidget( p, 1 );
1322    connect( p, SIGNAL(clicked(bool)), this, SLOT(onEditTrackerPushed()));
1323
1324    p = new QPushButton();
1325    p->setIcon( getStockIcon( "list-remove", QStyle::SP_TrashIcon ) );
1326    p->setToolTip( "Remove Trackers" );
1327    p->setEnabled( false );
1328    myRemoveTrackerButton = p;
1329    v2->addWidget( p, 1 );
1330    connect( p, SIGNAL(clicked(bool)), this, SLOT(onRemoveTrackerPushed()));
1331
1332    v2->addStretch( 1 );
1333
1334    h->addLayout( v2, 1 );
1335    h->setStretch( 1, 0 );
1336
1337    v->addLayout( h, 1 );
1338
1339    c = new QCheckBox( tr( "Show &more details" ) );
1340    c->setChecked( myPrefs.getBool( Prefs::SHOW_TRACKER_SCRAPES ) );
1341    myShowTrackerScrapesCheck = c;
1342    v->addWidget( c, 1 );
1343    connect( c, SIGNAL(clicked(bool)), this, SLOT(onShowTrackerScrapesToggled(bool)) );
1344
1345    return top;
1346}
1347
1348/***
1349****
1350***/
1351
1352QWidget *
1353Details :: createPeersTab( )
1354{
1355    QWidget * top = new QWidget;
1356    QVBoxLayout * v = new QVBoxLayout( top );
1357    v->setSpacing( HIG :: PAD_BIG );
1358    v->setContentsMargins( HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG );
1359
1360    QStringList headers;
1361    headers << QString() << tr("Up") << tr("Down") << tr("%") << tr("Status") << tr("Address") << tr("Client");
1362    myPeerTree = new QTreeWidget;
1363    myPeerTree->setUniformRowHeights( true );
1364    myPeerTree->setHeaderLabels( headers );
1365    myPeerTree->setColumnWidth( 0, 20 );
1366    myPeerTree->setSortingEnabled( true );
1367    myPeerTree->sortByColumn( COL_ADDRESS, Qt::AscendingOrder );
1368    myPeerTree->setRootIsDecorated( false );
1369    myPeerTree->setTextElideMode( Qt::ElideRight );
1370    v->addWidget( myPeerTree, 1 );
1371
1372    const QFontMetrics m( font( ) );
1373    QSize size = m.size( 0, "1024 MiB/s" );
1374    myPeerTree->setColumnWidth( COL_UP, size.width( ) );
1375    myPeerTree->setColumnWidth( COL_DOWN, size.width( ) );
1376    size = m.size( 0, " 100% " );
1377    myPeerTree->setColumnWidth( COL_PERCENT, size.width( ) );
1378    size = m.size( 0, "ODUK?EXI" );
1379    myPeerTree->setColumnWidth( COL_STATUS, size.width( ) );
1380    size = m.size( 0, "888.888.888.888" );
1381    myPeerTree->setColumnWidth( COL_ADDRESS, size.width( ) );
1382    size = m.size( 0, "Some BitTorrent Client" );
1383    myPeerTree->setColumnWidth( COL_CLIENT, size.width( ) );
1384    myPeerTree->setAlternatingRowColors( true );
1385
1386    return top;
1387}
1388
1389/***
1390****
1391***/
1392
1393QWidget *
1394Details :: createFilesTab( )
1395{
1396    myFileTreeView = new FileTreeView( );
1397
1398    connect( myFileTreeView, SIGNAL(      priorityChanged(const QSet<int>&, int)),
1399             this,           SLOT(  onFilePriorityChanged(const QSet<int>&, int)));
1400
1401    connect( myFileTreeView, SIGNAL(      wantedChanged(const QSet<int>&, bool)),
1402             this,           SLOT(  onFileWantedChanged(const QSet<int>&, bool)));
1403
1404    return myFileTreeView;
1405}
1406
1407void
1408Details :: onFilePriorityChanged( const QSet<int>& indices, int priority )
1409{
1410    QString key;
1411    switch( priority ) {
1412        case TR_PRI_LOW:   key = "priority-low"; break;
1413        case TR_PRI_HIGH:  key = "priority-high"; break;
1414        default:           key = "priority-normal"; break;
1415    }
1416    mySession.torrentSet( myIds, key, indices.toList( ) );
1417}
1418
1419void
1420Details :: onFileWantedChanged( const QSet<int>& indices, bool wanted )
1421{
1422    QString key( wanted ? "files-wanted" : "files-unwanted" );
1423    mySession.torrentSet( myIds, key, indices.toList( ) );
1424}
Note: See TracBrowser for help on using the repository browser.