source: trunk/qt/details.cc @ 13779

Last change on this file since 13779 was 13779, checked in by jordan, 8 years ago

(qt) #5213 "Incomplete update of details.cc" from rb07

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