diff --git a/qt/details.cc b/qt/details.cc index 2b932e21f..246c6848a 100644 --- a/qt/details.cc +++ b/qt/details.cc @@ -25,7 +25,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -39,11 +41,13 @@ #include #include +#include #include "details.h" #include "file-tree.h" #include "hig.h" #include "prefs.h" +#include "qticonloader.h" #include "session.h" #include "squeezelabel.h" #include "torrent.h" @@ -117,6 +121,17 @@ class PeerItem: public QTreeWidgetItem **** ***/ +QIcon +Details :: getStockIcon( const QString& freedesktop_name, int fallback ) +{ + QIcon fallbackIcon; + + if( fallback > 0 ) + fallbackIcon = style()->standardIcon( QStyle::StandardPixmap( fallback ), 0, this ); + + return QtIconLoader::icon( freedesktop_name, fallbackIcon ); +} + Details :: Details( Session& session, Prefs& prefs, TorrentModel& model, QWidget * parent ): QDialog( parent, Qt::Dialog ), mySession( session ), @@ -657,30 +672,57 @@ Details :: refresh( ) // tracker tab // - QMap trackers2; - QList newItems2; + QMap trackerTiers; + QMap trackerItems; const time_t now( time( 0 ) ); - const bool showBackup = myPrefs.getBool( Prefs::SHOW_BACKUP_TRACKERS ); const bool showScrape = myPrefs.getBool( Prefs::SHOW_TRACKER_SCRAPES ); foreach( const Torrent * t, torrents ) { const QString idStr( QString::number( t->id( ) ) ); - TrackerStatsList trackerStats = t->trackerStats( ); + const TrackerStatsList trackerStats = t->trackerStats( ); foreach( const TrackerStat& trackerStat, trackerStats ) { - const QString key( idStr + ":" + QString::number(trackerStat.id) ); - QTreeWidgetItem * item = (QTreeWidgetItem*) myTrackerStats.value( key, 0 ); + QFont font; QString str; + const QString tierKey( QString::number(trackerStat.tier) ); + QTreeWidgetItem * tier = (QTreeWidgetItem*) myTrackerTiers.value( tierKey, 0 ); + + if( tier == 0 ) // new tier + { + QFont tierFont; + tier = new QTreeWidgetItem( myTrackerTree ); + myTrackerTree->addTopLevelItem( tier ); + str = "Tier: " + QString::number( trackerStat.tier + 1 ); + tier->setText( 0, str ); + tierFont.setBold( true ); + tier->setFont( 0, tierFont ); + } + + const QString key( idStr + tierKey + ":" + QString::number( trackerStat.id ) ); + QTreeWidgetItem * item = (QTreeWidgetItem*) myTrackerItems.value( key, 0 ); if( item == 0 ) // new tracker { - item = new QTreeWidgetItem( myTrackerTree ); - newItems2 << item; + item = new QTreeWidgetItem( tier ); + tier->addChild( item ); + if( tier->childCount() == 1 ) + tier->setExpanded( true ); } str = trackerStat.host; - if( showBackup || !trackerStat.isBackup) + + if( trackerStat.isBackup ) { + font.setItalic( true ); + if( showScrape ) + { + str += "\n"; + str += "Tracker will be used as a backup"; + } + } + else + { + font.setItalic( false ); if( trackerStat.hasAnnounced ) { const QString tstr( timeToStringRounded( now - trackerStat.lastAnnounceTime ) ); @@ -778,21 +820,37 @@ Details :: refresh( ) } } } - item->setText( 0, str ); + item->setFont( 0, font ); + item->setData( 0, TRACKERID, trackerStat.id ); + item->setData( 0, TRACKERURL, trackerStat.announce ); + item->setData( 0, TRACKERTIER, trackerStat.tier ); + item->setData( 0, TORRENTID, t->id() ); - trackers2.insert( key, item ); + tier->setData( 0, TRACKERID, -1 ); + tier->setData( 0, TRACKERURL, QString() ); + tier->setData( 0, TRACKERTIER, trackerStat.tier ); + tier->setData( 0, TORRENTID, torrents.count() > 1 ? -1 : t->id() ); + + trackerTiers.insert( tierKey, tier ); + trackerItems.insert( key, item ); } } - myTrackerTree->addTopLevelItems( newItems2 ); - foreach( QString key, myTrackerStats.keys() ) { - if( !trackers2.contains( key ) ) { // tracker has disappeared - QTreeWidgetItem * item = myTrackerStats.value( key, 0 ); - myTrackerTree->takeTopLevelItem( myTrackerTree->indexOfTopLevelItem( item ) ); - delete item; + QList tierList = trackerTiers.values(); + QList itemList = trackerItems.values(); + for( int i = 0; i < myTrackerTree->topLevelItemCount(); ++i ) + { + QTreeWidgetItem * tier = myTrackerTree->topLevelItem( i ); + for( int j = 0; j < tier->childCount(); ++j ) + { + if( !itemList.contains( tier->child( j ) ) ) // tracker has disappeared + delete tier->takeChild( j-- ); } + if( !tierList.contains( tier ) ) // tier has disappeared + delete myTrackerTree->takeTopLevelItem( i-- ); } - myTrackerStats = trackers2; + myTrackerTiers = trackerTiers; + myTrackerItems = trackerItems; /// /// Peers tab @@ -933,12 +991,6 @@ Details :: createInfoTab( ) **** ***/ -void -Details :: onShowBackupTrackersToggled( bool val ) -{ - myPrefs.set( Prefs::SHOW_BACKUP_TRACKERS, val ); -} - void Details :: onShowTrackerScrapesToggled( bool val ) { @@ -1011,6 +1063,115 @@ Details :: onBandwidthPriorityChanged( int index ) } } +void +Details :: onTrackerSelectionChanged( ) +{ + const QList items = myTrackerTree->selectedItems(); + if( items.count() == 1 ) + myEditTrackerButton->setEnabled( items.first()->data( 0, TRACKERID ).toInt() >= 0 ); + else + myEditTrackerButton->setEnabled( false ); + myRemoveTrackerButton->setEnabled( !items.isEmpty() ); +} + +bool +Details :: findTrackerByURL( const QString& url, int torId ) +{ + bool duplicate = false; + foreach( QTreeWidgetItem * tracker, myTrackerItems.values() ) + { + if( tracker->data( 0, TRACKERURL ).toString() == url && + ( torId == -1 || tracker->data( 0, TORRENTID ).toInt() == torId ) ) + { + duplicate = true; + break; + } + } + return duplicate; +} + +void +Details :: onAddTrackerPushed( ) +{ + const QString urlString = QInputDialog::getText( this, + tr( "Add tracker announce URL " ), + NULL ); + if( !urlString.isEmpty() ) + { + if( !findTrackerByURL( urlString, -1 ) ) + { + QByteArray url = urlString.toUtf8(); + tr_benc top; + + tr_bencInitDict( &top, 1 ); + tr_bencDictAddStr( &top, "announce", url ); + + mySession.torrentSet( myIds, "trackerAdd", &top ); + } + else + QMessageBox::warning( this, "Error", "Tracker already exists." ); + } +} + +void +Details :: onEditTrackerPushed( ) +{ + const QTreeWidgetItem * item = myTrackerTree->selectedItems().first(); + const QString urlString = QInputDialog::getText( this, + tr( "Edit tracker announce URL " ), + NULL, + QLineEdit::Normal, + item->data( 0, TRACKERURL ).toString() ); + if( !urlString.isEmpty() ) + { + const int torId = item->data( 0, TORRENTID ).toInt(); + if( !findTrackerByURL( urlString, torId ) ) + { + QByteArray url = urlString.toUtf8(); + QSet ids; + tr_benc top; + + ids << torId; + tr_bencInitDict( &top, 2 ); + tr_bencDictAddStr( &top, "announce", item->data( 0, TRACKERURL ).toByteArray() ); + tr_bencDictAddStr( &top, "announce-new", url ); + + mySession.torrentSet( ids, "trackerEdit", &top ); + } + else + QMessageBox::warning( this, "Error", "Tracker already exists." ); + } +} + +void +Details :: removeTracker( const QTreeWidgetItem * item ) +{ + QByteArray url = item->data( 0, TRACKERURL ).toByteArray(); + const int torId = item->data( 0, TORRENTID ).toInt(); + QSet ids; + tr_benc top; + + ids << torId; + tr_bencInitDict( &top, 1 ); + tr_bencDictAddStr( &top, "announce", url ); + + mySession.torrentSet( ids, "trackerRemove", &top ); +} + +void +Details :: onRemoveTrackerPushed( ) +{ + const QTreeWidgetItem * item = myTrackerTree->selectedItems().first(); + const bool isTier = item->data( 0, TRACKERID ).toInt() == -1; + if( isTier ) + { + for( int i = 0; i < item->childCount(); ++i ) + removeTracker( item->child( i ) ); + } + else + removeTracker( item ); +} + QWidget * Details :: createOptionsTab( ) { @@ -1109,21 +1270,63 @@ QWidget * Details :: createTrackerTab( ) { QCheckBox * c; + QPushButton * p; QWidget * top = new QWidget; QVBoxLayout * v = new QVBoxLayout( top ); + QHBoxLayout * h = new QHBoxLayout(); + QVBoxLayout * v2 = new QVBoxLayout(); - v->setSpacing( HIG :: PAD_BIG ); + v->setSpacing( HIG::PAD_BIG ); v->setContentsMargins( HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG ); + h->setSpacing( HIG::PAD ); + h->setContentsMargins( HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL ); + + v2->setSpacing( HIG::PAD ); + QStringList headers; headers << tr("Trackers"); myTrackerTree = new QTreeWidget; myTrackerTree->setHeaderLabels( headers ); - myTrackerTree->setSelectionMode( QTreeWidget::NoSelection ); + myTrackerTree->setSelectionMode( QTreeWidget::SingleSelection ); myTrackerTree->setRootIsDecorated( false ); + myTrackerTree->setIndentation( 2 ); + myTrackerTree->setItemsExpandable( false ); myTrackerTree->setTextElideMode( Qt::ElideRight ); myTrackerTree->setAlternatingRowColors( true ); - v->addWidget( myTrackerTree, 1 ); + connect( myTrackerTree, SIGNAL(itemSelectionChanged()), this, SLOT(onTrackerSelectionChanged())); + h->addWidget( myTrackerTree, 1 ); + + p = new QPushButton(); + p->setIcon( getStockIcon( "list-add", QStyle::SP_DialogOpenButton ) ); + p->setToolTip( "Add Tracker" ); + myAddTrackerButton = p; + v2->addWidget( p, 1 ); + connect( p, SIGNAL(clicked(bool)), this, SLOT(onAddTrackerPushed())); + + p = new QPushButton(); + p->setIcon( getStockIcon( "document-properties", QStyle::SP_DesktopIcon ) ); + p->setToolTip( "Edit Tracker" ); + myAddTrackerButton = p; + p->setEnabled( false ); + myEditTrackerButton = p; + v2->addWidget( p, 1 ); + connect( p, SIGNAL(clicked(bool)), this, SLOT(onEditTrackerPushed())); + + p = new QPushButton(); + p->setIcon( getStockIcon( "list-remove", QStyle::SP_TrashIcon ) ); + p->setToolTip( "Remove Trackers" ); + p->setEnabled( false ); + myRemoveTrackerButton = p; + v2->addWidget( p, 1 ); + connect( p, SIGNAL(clicked(bool)), this, SLOT(onRemoveTrackerPushed())); + + v2->addStretch( 1 ); + + h->addLayout( v2, 1 ); + h->setStretch( 1, 0 ); + + v->addLayout( h, 1 ); c = new QCheckBox( tr( "Show &more details" ) ); c->setChecked( myPrefs.getBool( Prefs::SHOW_TRACKER_SCRAPES ) ); @@ -1131,12 +1334,6 @@ Details :: createTrackerTab( ) v->addWidget( c, 1 ); connect( c, SIGNAL(clicked(bool)), this, SLOT(onShowTrackerScrapesToggled(bool)) ); - c = new QCheckBox( tr( "Show &backup trackers" ) ); - c->setChecked( myPrefs.getBool( Prefs::SHOW_BACKUP_TRACKERS ) ); - myShowBackupTrackersCheck = c; - v->addWidget( c, 1 ); - connect( c, SIGNAL(clicked(bool)), this, SLOT(onShowBackupTrackersToggled(bool)) ); - return top; } diff --git a/qt/details.h b/qt/details.h index 3702ffd53..3917b042e 100644 --- a/qt/details.h +++ b/qt/details.h @@ -41,6 +41,15 @@ class Details: public QDialog { Q_OBJECT + private: + enum + { + TRACKERID = Qt::UserRole, + TRACKERURL, + TRACKERTIER, + TORRENTID + }; + private slots: void onTorrentChanged( ); void onTimer( ); @@ -58,12 +67,14 @@ class Details: public QDialog QWidget * createOptionsTab( ); private: + QIcon getStockIcon( const QString& freedesktop_name, int fallback ); QString timeToStringRounded( int seconds ); QString trimToDesiredWidth( const QString& str ); void enableWhenChecked( QCheckBox *, QWidget * ); + bool findTrackerByURL( const QString& url, int torId ); + void removeTracker( const QTreeWidgetItem * item ); private: - Session& mySession; Prefs& myPrefs; TorrentModel& myModel; @@ -87,7 +98,9 @@ class Details: public QDialog QCheckBox * mySingleDownCheck; QCheckBox * mySingleUpCheck; QCheckBox * myShowTrackerScrapesCheck; - QCheckBox * myShowBackupTrackersCheck; + QPushButton * myAddTrackerButton; + QPushButton * myEditTrackerButton; + QPushButton * myRemoveTrackerButton; QSpinBox * mySingleDownSpin; QSpinBox * mySingleUpSpin; QRadioButton * mySeedGlobalRadio; @@ -115,7 +128,8 @@ class Details: public QDialog QTreeWidget * myTrackerTree; QTreeWidget * myPeerTree; - QMap myTrackerStats; + QMap myTrackerTiers; + QMap myTrackerItems; QMap myPeers; QWidgetList myWidgets; @@ -132,8 +146,11 @@ class Details: public QDialog void onUploadLimitChanged( int ); void onSeedUntilChanged( bool ); void onSeedRatioLimitChanged( double ); - void onShowBackupTrackersToggled( bool ); void onShowTrackerScrapesToggled( bool ); + void onTrackerSelectionChanged( ); + void onAddTrackerPushed( ); + void onEditTrackerPushed( ); + void onRemoveTrackerPushed( ); void onMaxPeersChanged( int ); void refresh( ); }; diff --git a/qt/prefs.cc b/qt/prefs.cc index 73b94ea6f..1ffc5f659 100644 --- a/qt/prefs.cc +++ b/qt/prefs.cc @@ -42,7 +42,6 @@ Prefs::PrefItem Prefs::myItems[] = { SORT_MODE, "sort-mode", TrTypes::SortModeType }, { SORT_REVERSED, "sort-reversed", QVariant::Bool }, { COMPACT_VIEW, "compact-view", QVariant::Bool }, - { SHOW_BACKUP_TRACKERS, "show-backup-trackers", QVariant::Bool }, { FILTERBAR, "show-filterbar", QVariant::Bool }, { STATUSBAR, "show-statusbar", QVariant::Bool }, { STATUSBAR_STATS, "statusbar-stats", QVariant::String }, @@ -244,7 +243,6 @@ Prefs :: initDefaults( tr_benc * d ) tr_bencDictAddInt( d, keyStr(BLOCKLIST_DATE), 0 ); tr_bencDictAddInt( d, keyStr(BLOCKLIST_UPDATES_ENABLED), true ); tr_bencDictAddStr( d, keyStr(OPEN_DIALOG_FOLDER), QDir::home().absolutePath().toLatin1() ); - tr_bencDictAddInt( d, keyStr(SHOW_BACKUP_TRACKERS), false ); tr_bencDictAddInt( d, keyStr(SHOW_TRACKER_SCRAPES), false ); tr_bencDictAddInt( d, keyStr(TOOLBAR), true ); tr_bencDictAddInt( d, keyStr(FILTERBAR), true ); diff --git a/qt/prefs.h b/qt/prefs.h index 01aeb5d0a..dde37f162 100644 --- a/qt/prefs.h +++ b/qt/prefs.h @@ -45,7 +45,6 @@ class Prefs: public QObject SORT_MODE, SORT_REVERSED, COMPACT_VIEW, - SHOW_BACKUP_TRACKERS, FILTERBAR, STATUSBAR, STATUSBAR_STATS, diff --git a/qt/session.cc b/qt/session.cc index 0a1c29b07..a96bd8845 100644 --- a/qt/session.cc +++ b/qt/session.cc @@ -418,6 +418,20 @@ Session :: torrentSet( const QSet& ids, const QString& key, const QList& ids, const QString& key, const tr_benc * value ) +{ + tr_benc top; + tr_bencInitDict( &top, 2 ); + tr_bencDictAddStr( &top, "method", "torrent-set" ); + tr_benc * args( tr_bencDictAddDict( &top, "arguments", 2 ) ); + addOptionalIds( args, ids ); + tr_benc * child( tr_bencDictAdd( args, key.toUtf8().constData() ) ); + memcpy( child, value, sizeof(tr_benc) ); + exec( &top ); + tr_bencFree( &top ); +} + void Session :: torrentSetLocation( const QSet& ids, const QString& location, bool doMove ) { diff --git a/qt/session.h b/qt/session.h index 4065f0bda..461852d45 100644 --- a/qt/session.h +++ b/qt/session.h @@ -100,6 +100,7 @@ class Session: public QObject void torrentSet( const QSet& ids, const QString& key, int val ); void torrentSet( const QSet& ids, const QString& key, double val ); void torrentSet( const QSet& ids, const QString& key, const QList& val ); + void torrentSet( const QSet& ids, const QString& key, const tr_benc * value ); void torrentSetLocation( const QSet& ids, const QString& path, bool doMove );