source: trunk/qt/details.cc @ 13810

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

(trunk) #1220 'change top folder names' -- add file-renaming to the Qt client

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