From d17f21af45c39dad792e3d1e96e549d33a8bb179 Mon Sep 17 00:00:00 2001 From: buinsky Date: Thu, 29 Jan 2015 16:42:04 +0300 Subject: [PATCH 01/17] Queueing flag relocation in sync/maindata response --- src/webui/btjson.cpp | 13 ++++++++---- src/webui/www/public/scripts/client.js | 28 ++++++++++++++------------ 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/webui/btjson.cpp b/src/webui/btjson.cpp index 2f5cf515d..7d4acc167 100644 --- a/src/webui/btjson.cpp +++ b/src/webui/btjson.cpp @@ -144,6 +144,9 @@ static const char KEY_TRANSFER_UPRATELIMIT[] = "up_rate_limit"; static const char KEY_TRANSFER_DHT_NODES[] = "dht_nodes"; static const char KEY_TRANSFER_CONNECTION_STATUS[] = "connection_status"; +// Sync main data keys +static const char KEY_SYNC_MAINDATA_QUEUEING[] = "queueing"; + static const char KEY_FULL_UPDATE[] = "full_update"; static const char KEY_RESPONSE_ID[] = "rid"; static const char KEY_SUFFIX_REMOVED[] = "_removed"; @@ -274,8 +277,7 @@ QByteArray btjson::getTorrents(QString filter, QString label, * - "torrents_removed": a list of hashes of removed torrents * - "labels": list of labels * - "labels_removed": list of removed labels - * - "queueing": priority system usage flag - * - "server_state": map contains information about the status of the server + * - "server_state": map contains information about the state of the server * The keys of the 'torrents' dictionary are hashes of torrents. * Each value of the 'torrents' dictionary contains map. The map can contain following keys: * - "name": Torrent name @@ -302,6 +304,7 @@ QByteArray btjson::getTorrents(QString filter, QString label, * - "up_info_data: bytes uploaded * - "up_info_speed: upload speed * - "up_rate_limit: upload speed limit + * - "queueing": priority system usage flag */ QByteArray btjson::getSyncMainData(int acceptedResponseId, QVariantMap &lastData, QVariantMap &lastAcceptedData) { @@ -321,14 +324,16 @@ QByteArray btjson::getSyncMainData(int acceptedResponseId, QVariantMap &lastData } data["torrents"] = torrents; - data["queueing"] = QBtSession::instance()->isQueueingEnabled(); QVariantList labels; foreach (QString s, Preferences::instance()->getTorrentLabels()) labels << s; data["labels"] = labels; - data["server_state"] = getTranserInfoMap(); + + QVariantMap serverState = getTranserInfoMap(); + serverState[KEY_SYNC_MAINDATA_QUEUEING] = QBtSession::instance()->isQueueingEnabled(); + data["server_state"] = serverState; return json::toJson(generateSyncData(acceptedResponseId, data, lastAcceptedData, lastData)); } diff --git a/src/webui/www/public/scripts/client.js b/src/webui/www/public/scripts/client.js index 9f3d07cc7..8f34665a3 100644 --- a/src/webui/www/public/scripts/client.js +++ b/src/webui/www/public/scripts/client.js @@ -27,6 +27,7 @@ myTable = new dynamicTable(); var updatePropertiesPanel = function(){}; var updateMainData = function(){}; var alternativeSpeedLimits = false; +var queueing_enabled = true; selected_filter = getLocalStorageItem('selected_filter', 'all'); selected_label = null; @@ -153,19 +154,6 @@ window.addEvent('load', function () { myTable.rows.erase(); if (response['rid']) syncMainDataLastResponseId = response['rid']; - if ('queueing' in response) { - var queueing_enabled = response['queueing']; - myTable.columns['priority'].force_hide = !queueing_enabled; - myTable.updateColumn('priority'); - if (queueing_enabled) { - $('queueingButtons').removeClass('invisible'); - $('queueingMenuItems').removeClass('invisible'); - } - else { - $('queueingButtons').addClass('invisible'); - $('queueingMenuItems').addClass('invisible'); - } - } if (response['torrents']) for (var key in response['torrents']) { response['torrents'][key]['hash'] = key; @@ -220,6 +208,20 @@ window.addEvent('load', function () { $('connectionStatus').src = 'images/skin/firewalled.png'; else $('connectionStatus').src = 'images/skin/disconnected.png'; + + if (queueing_enabled != serverState.queueing) { + queueing_enabled = serverState.queueing; + myTable.columns['priority'].force_hide = !queueing_enabled; + myTable.updateColumn('priority'); + if (queueing_enabled) { + $('queueingButtons').removeClass('invisible'); + $('queueingMenuItems').removeClass('invisible'); + } + else { + $('queueingButtons').addClass('invisible'); + $('queueingMenuItems').addClass('invisible'); + } + } }; var updateAltSpeedIcon = function(enabled) { From bed0784a2dc18737662ee2bad85037fee0faecfa Mon Sep 17 00:00:00 2001 From: buinsky Date: Thu, 29 Jan 2015 16:51:39 +0300 Subject: [PATCH 02/17] Follow project coding style. Issue #2192. --- src/webui/jsonutils.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/webui/jsonutils.h b/src/webui/jsonutils.h index 27d4ac7a9..97702d88d 100644 --- a/src/webui/jsonutils.h +++ b/src/webui/jsonutils.h @@ -42,23 +42,23 @@ namespace json { -inline QByteArray toJson(const QVariant& var) -{ + inline QByteArray toJson(const QVariant& var) + { #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - return QJsonDocument::fromVariant(var).toJson(); + return QJsonDocument::fromVariant(var).toJson(); #else - return QJson::Serializer().serialize(var); + return QJson::Serializer().serialize(var); #endif -} + } -inline QVariant fromJson(const QString& json) -{ + inline QVariant fromJson(const QString& json) + { #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - return QJsonDocument::fromJson(json.toUtf8()).toVariant(); + return QJsonDocument::fromJson(json.toUtf8()).toVariant(); #else - return QJson::Parser().parse(json.toUtf8()); + return QJson::Parser().parse(json.toUtf8()); #endif -} + } } From ba86d16e78588d75662bd074cb3fc84b8f006d97 Mon Sep 17 00:00:00 2001 From: buinsky Date: Thu, 29 Jan 2015 17:04:11 +0300 Subject: [PATCH 03/17] Use compact style of JSON data --- src/webui/jsonutils.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/webui/jsonutils.h b/src/webui/jsonutils.h index 97702d88d..b8619dc66 100644 --- a/src/webui/jsonutils.h +++ b/src/webui/jsonutils.h @@ -45,9 +45,11 @@ namespace json { inline QByteArray toJson(const QVariant& var) { #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - return QJsonDocument::fromVariant(var).toJson(); + return QJsonDocument::fromVariant(var).toJson(QJsonDocument::Compact); #else - return QJson::Serializer().serialize(var); + QJson::Serializer serializer; + serializer.setIndentMode(QJson::IndentCompact); + return serializer.serialize(var); #endif } From 1d5e52fc556a9a0cef32eb8df77c259d0843cb2c Mon Sep 17 00:00:00 2001 From: buinsky Date: Thu, 29 Jan 2015 17:13:48 +0300 Subject: [PATCH 04/17] Fix friendlyDuration function --- src/webui/www/public/scripts/misc.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webui/www/public/scripts/misc.js b/src/webui/www/public/scripts/misc.js index 7087f8281..645353127 100644 --- a/src/webui/www/public/scripts/misc.js +++ b/src/webui/www/public/scripts/misc.js @@ -38,11 +38,11 @@ function friendlyDuration(seconds) { if (minutes < 60) return "QBT_TR(%1m)QBT_TR".replace("%1", parseInt(minutes)); var hours = minutes / 60; - minutes = minutes - hours * 60; + minutes = minutes % 60; if (hours < 24) return "QBT_TR(%1h %2m)QBT_TR".replace("%1", parseInt(hours)).replace("%2", parseInt(minutes)) var days = hours / 24; - hours = hours - days * 24; + hours = hours % 24; if (days < 100) return "QBT_TR(%1d %2h)QBT_TR".replace("%1", parseInt(days)).replace("%2", parseInt(hours)) return "∞"; From 18b036545cbd8ac05538e957b796b0ef3060230d Mon Sep 17 00:00:00 2001 From: buinsky Date: Thu, 29 Jan 2015 17:39:17 +0300 Subject: [PATCH 05/17] Fix 'Resume all' & 'Pause all' actions --- src/webui/www/public/scripts/mocha-init.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webui/www/public/scripts/mocha-init.js b/src/webui/www/public/scripts/mocha-init.js index 491b12a37..8f38dc073 100644 --- a/src/webui/www/public/scripts/mocha-init.js +++ b/src/webui/www/public/scripts/mocha-init.js @@ -274,7 +274,7 @@ initializeWindows = function() { } }; - ['pause', 'resume', 'recheck'].each(function(item) { + ['pauseAll', 'resumeAll', 'pause', 'resume', 'recheck'].each(function(item) { addClickEvent(item, function(e) { new Event(e).stop(); var h = myTable.selectedIds(); From 135599acc4e46c39ae8c4bd9a35898d6ad765df8 Mon Sep 17 00:00:00 2001 From: buinsky Date: Thu, 29 Jan 2015 17:48:39 +0300 Subject: [PATCH 06/17] Center vertically text in progress bar --- src/webui/www/public/scripts/progressbar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webui/www/public/scripts/progressbar.js b/src/webui/www/public/scripts/progressbar.js index f4a959950..32fee1fcf 100644 --- a/src/webui/www/public/scripts/progressbar.js +++ b/src/webui/www/public/scripts/progressbar.js @@ -37,7 +37,7 @@ var ProgressBar = new Class({ 'text-align': 'center', 'left': 0, 'top': 0, - 'line-height': vals.height - 2 + 'line-height': vals.height } }); obj.vals.light = new Element('div', { @@ -52,7 +52,7 @@ var ProgressBar = new Class({ 'text-align': 'center', 'left': 0, 'top': 0, - 'line-height': vals.height - 2 + 'line-height': vals.height } }); obj.appendChild(obj.vals.dark); From 0f56440decb5979cba4a6fac38d0bb467436bd47 Mon Sep 17 00:00:00 2001 From: buinsky Date: Thu, 29 Jan 2015 19:56:14 +0300 Subject: [PATCH 07/17] Fix alternative speed limits toggle behavior --- src/webui/btjson.cpp | 2 ++ src/webui/www/public/scripts/client.js | 15 +++++---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/webui/btjson.cpp b/src/webui/btjson.cpp index 7d4acc167..d5deb38cf 100644 --- a/src/webui/btjson.cpp +++ b/src/webui/btjson.cpp @@ -146,6 +146,7 @@ static const char KEY_TRANSFER_CONNECTION_STATUS[] = "connection_status"; // Sync main data keys static const char KEY_SYNC_MAINDATA_QUEUEING[] = "queueing"; +static const char KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS[] = "use_alt_speed_limits"; static const char KEY_FULL_UPDATE[] = "full_update"; static const char KEY_RESPONSE_ID[] = "rid"; @@ -333,6 +334,7 @@ QByteArray btjson::getSyncMainData(int acceptedResponseId, QVariantMap &lastData QVariantMap serverState = getTranserInfoMap(); serverState[KEY_SYNC_MAINDATA_QUEUEING] = QBtSession::instance()->isQueueingEnabled(); + serverState[KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS] = Preferences::instance()->isAltBandwidthEnabled(); data["server_state"] = serverState; return json::toJson(generateSyncData(acceptedResponseId, data, lastAcceptedData, lastData)); diff --git a/src/webui/www/public/scripts/client.js b/src/webui/www/public/scripts/client.js index 8f34665a3..4f84ea7ed 100644 --- a/src/webui/www/public/scripts/client.js +++ b/src/webui/www/public/scripts/client.js @@ -222,6 +222,11 @@ window.addEvent('load', function () { $('queueingMenuItems').addClass('invisible'); } } + + if (alternativeSpeedLimits != serverState.use_alt_speed_limits) { + alternativeSpeedLimits = serverState.use_alt_speed_limits; + updateAltSpeedIcon(alternativeSpeedLimits); + } }; var updateAltSpeedIcon = function(enabled) { @@ -231,16 +236,6 @@ window.addEvent('load', function () { $('alternativeSpeedLimits').src = "images/slow_off.png" } - // Determine whether the alternative speed limits are enabled or not - new Request({url: 'command/alternativeSpeedLimitsEnabled', - method: 'get', - onSuccess : function (isEnabled) { - alternativeSpeedLimits = !!parseInt(isEnabled); - if (alternativeSpeedLimits) - $('alternativeSpeedLimits').src = "images/slow.png" - } - }).send(); - $('alternativeSpeedLimits').addEvent('click', function() { // Change icon immediately to give some feedback updateAltSpeedIcon(!alternativeSpeedLimits); From f5f9e206fb0c6cb0eb54dc15977753da85062fed Mon Sep 17 00:00:00 2001 From: buinsky Date: Thu, 29 Jan 2015 20:30:54 +0300 Subject: [PATCH 08/17] Add 'Resumed' filter --- src/webui/qtorrentfilter.cpp | 12 ++++++++++++ src/webui/qtorrentfilter.h | 2 ++ src/webui/webapplication.cpp | 2 +- src/webui/www/public/filters.html | 1 + src/webui/www/public/scripts/client.js | 1 + src/webui/www/public/scripts/dynamicTable.js | 4 ++++ 6 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/webui/qtorrentfilter.cpp b/src/webui/qtorrentfilter.cpp index 46504fab5..2c4ac2f6c 100644 --- a/src/webui/qtorrentfilter.cpp +++ b/src/webui/qtorrentfilter.cpp @@ -39,6 +39,8 @@ QTorrentFilter::QTorrentFilter(QString filter, QString label) type_ = Completed; else if (filter == "paused") type_ = Paused; + else if (filter == "resumed") + type_ = Resumed; else if (filter == "active") type_ = Active; else if (filter == "inactive") @@ -57,6 +59,8 @@ bool QTorrentFilter::apply(const QTorrentHandle& h) const return isTorrentCompleted(h); case Paused: return isTorrentPaused(h); + case Resumed: + return isTorrentResumed(h); case Active: return isTorrentActive(h); case Inactive: @@ -98,6 +102,14 @@ bool QTorrentFilter::isTorrentPaused(const QTorrentHandle &h) const || state == QTorrentState::Error; } +bool QTorrentFilter::isTorrentResumed(const QTorrentHandle &h) const +{ + const QTorrentState state = h.torrentState(); + + return state != QTorrentState::PausedUploading + && state != QTorrentState::PausedDownloading; +} + bool QTorrentFilter::isTorrentActive(const QTorrentHandle &h) const { const QTorrentState state = h.torrentState(); diff --git a/src/webui/qtorrentfilter.h b/src/webui/qtorrentfilter.h index 052c9ac6a..10e2bf2c0 100644 --- a/src/webui/qtorrentfilter.h +++ b/src/webui/qtorrentfilter.h @@ -40,6 +40,7 @@ public: Downloading, Completed, Paused, + Resumed, Active, Inactive }; @@ -55,6 +56,7 @@ private: bool isTorrentDownloading(const QTorrentHandle &h) const; bool isTorrentCompleted(const QTorrentHandle &h) const; bool isTorrentPaused(const QTorrentHandle &h) const; + bool isTorrentResumed(const QTorrentHandle &h) const; bool isTorrentActive(const QTorrentHandle &h) const; bool isTorrentInactive(const QTorrentHandle &h) const; bool torrentHasLabel(const QTorrentHandle &h) const; diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index f7140e96c..79fcfb0b0 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -212,7 +212,7 @@ void WebApplication::action_public_images() } // GET params: -// - filter (string): all, downloading, completed, paused, active, inactive +// - filter (string): all, downloading, completed, paused, resumed, active, inactive // - label (string): torrent label for filtering by it (empty string means "unlabeled"; no "label" param presented means "any label") // - sort (string): name of column for sorting by its value // - reverse (bool): enable reverse sorting diff --git a/src/webui/www/public/filters.html b/src/webui/www/public/filters.html index ecc3461c3..1270f4fef 100644 --- a/src/webui/www/public/filters.html +++ b/src/webui/www/public/filters.html @@ -2,6 +2,7 @@
  • QBT_TR(All)QBT_TR
  • QBT_TR(Downloading)QBT_TR
  • QBT_TR(Completed)QBT_TR
  • +
  • QBT_TR(Resumed)QBT_TR
  • QBT_TR(Paused)QBT_TR
  • QBT_TR(Active)QBT_TR
  • QBT_TR(Inactive)QBT_TR
  • diff --git a/src/webui/www/public/scripts/client.js b/src/webui/www/public/scripts/client.js index 4f84ea7ed..a468f4d96 100644 --- a/src/webui/www/public/scripts/client.js +++ b/src/webui/www/public/scripts/client.js @@ -96,6 +96,7 @@ window.addEvent('load', function () { $("downloading_filter").removeClass("selectedFilter"); $("completed_filter").removeClass("selectedFilter"); $("paused_filter").removeClass("selectedFilter"); + $("resumed_filter").removeClass("selectedFilter"); $("active_filter").removeClass("selectedFilter"); $("inactive_filter").removeClass("selectedFilter"); $(f + "_filter").addClass("selectedFilter"); diff --git a/src/webui/www/public/scripts/dynamicTable.js b/src/webui/www/public/scripts/dynamicTable.js index 9667ac371..018164d52 100644 --- a/src/webui/www/public/scripts/dynamicTable.js +++ b/src/webui/www/public/scripts/dynamicTable.js @@ -261,6 +261,10 @@ var dynamicTable = new Class({ if (!~state.indexOf('paused')) return false; break; + case 'resumed': + if (~state.indexOf('paused')) + return false; + break; case 'active': if ((state != 'uploading') && (state != 'downloading')) return false; From 623797a6c4400dd40042806d45d8928c74e1c326 Mon Sep 17 00:00:00 2001 From: buinsky Date: Thu, 29 Jan 2015 20:35:57 +0300 Subject: [PATCH 09/17] Follow project coding style. Issue #2192. --- src/gui/transferlistfilterswidget.cpp | 782 +++++++++++++------------- src/gui/transferlistfilterswidget.h | 89 +-- 2 files changed, 450 insertions(+), 421 deletions(-) diff --git a/src/gui/transferlistfilterswidget.cpp b/src/gui/transferlistfilterswidget.cpp index efb51a607..fa6f1dfce 100644 --- a/src/gui/transferlistfilterswidget.cpp +++ b/src/gui/transferlistfilterswidget.cpp @@ -48,332 +48,353 @@ #include "autoexpandabledialog.h" #include "torrentfilterenum.h" -LabelFiltersList::LabelFiltersList(QWidget *parent): QListWidget(parent) { - itemHover = 0; - // Accept drop - setAcceptDrops(true); - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - setStyleSheet("QListWidget { background: transparent; border: 0 }"); -#if defined(Q_OS_MAC) - setAttribute(Qt::WA_MacShowFocusRect, false); -#endif -} - -void LabelFiltersList::addItem(QListWidgetItem *it) { - Q_ASSERT(count() >= 2); - for (int i=2; itext().localeAwareCompare(it->text()) >= 0) { - insertItem(i, it); - return; - } - } - QListWidget::addItem(it); -} - -QString LabelFiltersList::labelFromRow(int row) const { - Q_ASSERT(row > 1); - const QString &label = item(row)->text(); - QStringList parts = label.split(" "); - Q_ASSERT(parts.size() >= 2); - parts.removeLast(); // Remove trailing number - return parts.join(" "); -} - -int LabelFiltersList::rowFromLabel(QString label) const { - Q_ASSERT(!label.isEmpty()); - for (int i=2; ipos()) && row(itemAt(event->pos())) > 0) { - if (itemHover) { - if (itemHover != itemAt(event->pos())) { - setItemHover(false); - itemHover = itemAt(event->pos()); - setItemHover(true); - } - } else { - itemHover = itemAt(event->pos()); - setItemHover(true); - } - event->acceptProposedAction(); - } else { - if (itemHover) - setItemHover(false); - event->ignore(); - } -} - -void LabelFiltersList::dropEvent(QDropEvent *event) { - qDebug("Drop Event in labels list"); - if (itemAt(event->pos())) { - emit torrentDropped(row(itemAt(event->pos()))); - } - event->ignore(); - setItemHover(false); - // Select current item again - currentItem()->setSelected(true); -} - -void LabelFiltersList::dragLeaveEvent(QDragLeaveEvent*) { - if (itemHover) - setItemHover(false); - // Select current item again - currentItem()->setSelected(true); -} - -void LabelFiltersList::setItemHover(bool hover) { - Q_ASSERT(itemHover); - if (hover) { - itemHover->setData(Qt::DecorationRole, IconProvider::instance()->getIcon("folder-documents.png")); - itemHover->setSelected(true); - //setCurrentItem(itemHover); - } else { - itemHover->setData(Qt::DecorationRole, IconProvider::instance()->getIcon("inode-directory.png")); - //itemHover->setSelected(false); +LabelFiltersList::LabelFiltersList(QWidget *parent): QListWidget(parent) +{ itemHover = 0; - } -} - -StatusFiltersWidget::StatusFiltersWidget(QWidget *parent) : QListWidget(parent), m_shown(false) { - setUniformItemSizes(true); - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - // Height is fixed (sizeHint().height() is used) - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - setStyleSheet("QListWidget { background: transparent; border: 0 }"); + // Accept drop + setAcceptDrops(true); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + setStyleSheet("QListWidget { background: transparent; border: 0 }"); #if defined(Q_OS_MAC) - setAttribute(Qt::WA_MacShowFocusRect, false); + setAttribute(Qt::WA_MacShowFocusRect, false); #endif } -QSize StatusFiltersWidget::sizeHint() const { - QSize size = QListWidget::sizeHint(); - // Height should be exactly the height of the content - size.setHeight(contentsSize().height() + 2 * frameWidth()+6); - return size; +void LabelFiltersList::addItem(QListWidgetItem *it) +{ + Q_ASSERT(count() >= 2); + for (int i = 2; itext().localeAwareCompare(it->text()) >= 0) { + insertItem(i, it); + return; + } + } + QListWidget::addItem(it); } -TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferListWidget *transferList): QFrame(parent), transferList(transferList), nb_labeled(0), nb_torrents(0) { - // Construct lists - vLayout = new QVBoxLayout(); - vLayout->setContentsMargins(0, 4, 0, 0); - QFont font; - font.setBold(true); - font.setCapitalization(QFont::SmallCaps); - QLabel *torrentsLabel = new QLabel(tr("Torrents")); - torrentsLabel->setIndent(2); - torrentsLabel->setFont(font); - vLayout->addWidget(torrentsLabel); - statusFilters = new StatusFiltersWidget(this); - vLayout->addWidget(statusFilters); - QLabel *labelsLabel = new QLabel(tr("Labels")); - labelsLabel->setIndent(2); - labelsLabel->setFont(font); - vLayout->addWidget(labelsLabel); - labelFilters = new LabelFiltersList(this); - vLayout->addWidget(labelFilters); - setLayout(vLayout); - labelFilters->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - statusFilters->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - statusFilters->setSpacing(0); - setContentsMargins(0,0,0,0); - vLayout->setSpacing(2); - // Add status filters - QListWidgetItem *all = new QListWidgetItem(statusFilters); - all->setData(Qt::DisplayRole, QVariant(tr("All") + " (0)")); - all->setData(Qt::DecorationRole, QIcon(":/icons/skin/filterall.png")); - QListWidgetItem *downloading = new QListWidgetItem(statusFilters); - downloading->setData(Qt::DisplayRole, QVariant(tr("Downloading") + " (0)")); - downloading->setData(Qt::DecorationRole, QIcon(":/icons/skin/downloading.png")); - QListWidgetItem *completed = new QListWidgetItem(statusFilters); - completed->setData(Qt::DisplayRole, QVariant(tr("Completed") + " (0)")); - completed->setData(Qt::DecorationRole, QIcon(":/icons/skin/uploading.png")); - QListWidgetItem *paused = new QListWidgetItem(statusFilters); - paused->setData(Qt::DisplayRole, QVariant(tr("Paused") + " (0)")); - paused->setData(Qt::DecorationRole, QIcon(":/icons/skin/paused.png")); - QListWidgetItem *resumed = new QListWidgetItem(statusFilters); - resumed->setData(Qt::DisplayRole, QVariant(tr("Resumed") + " (0)")); - resumed->setData(Qt::DecorationRole, QIcon(":/icons/skin/resumed.png")); - QListWidgetItem *active = new QListWidgetItem(statusFilters); - active->setData(Qt::DisplayRole, QVariant(tr("Active") + " (0)")); - active->setData(Qt::DecorationRole, QIcon(":/icons/skin/filteractive.png")); - QListWidgetItem *inactive = new QListWidgetItem(statusFilters); - inactive->setData(Qt::DisplayRole, QVariant(tr("Inactive") + " (0)")); - inactive->setData(Qt::DecorationRole, QIcon(":/icons/skin/filterinactive.png")); - - // SIGNAL/SLOT - connect(statusFilters, SIGNAL(currentRowChanged(int)), transferList, SLOT(applyStatusFilter(int))); - connect(transferList->getSourceModel(), SIGNAL(modelRefreshed()), SLOT(updateTorrentNumbers())); - connect(transferList->getSourceModel(), SIGNAL(torrentAdded(TorrentModelItem*)), SLOT(handleNewTorrent(TorrentModelItem*))); - connect(labelFilters, SIGNAL(currentRowChanged(int)), this, SLOT(applyLabelFilter(int))); - connect(labelFilters, SIGNAL(torrentDropped(int)), this, SLOT(torrentDropped(int))); - connect(transferList->getSourceModel(), SIGNAL(torrentAboutToBeRemoved(TorrentModelItem*)), SLOT(torrentAboutToBeDeleted(TorrentModelItem*))); - connect(transferList->getSourceModel(), SIGNAL(torrentChangedLabel(TorrentModelItem*,QString,QString)), SLOT(torrentChangedLabel(TorrentModelItem*, QString, QString))); - - // Add Label filters - QListWidgetItem *allLabels = new QListWidgetItem(labelFilters); - allLabels->setData(Qt::DisplayRole, QVariant(tr("All labels") + " (0)")); - allLabels->setData(Qt::DecorationRole, IconProvider::instance()->getIcon("inode-directory")); - QListWidgetItem *noLabel = new QListWidgetItem(labelFilters); - noLabel->setData(Qt::DisplayRole, QVariant(tr("Unlabeled") + " (0)")); - noLabel->setData(Qt::DecorationRole, IconProvider::instance()->getIcon("inode-directory")); - - // Load settings - loadSettings(); - - labelFilters->setCurrentRow(0); - //labelFilters->selectionModel()->select(labelFilters->model()->index(0,0), QItemSelectionModel::Select); - - // Label menu - labelFilters->setContextMenuPolicy(Qt::CustomContextMenu); - connect(labelFilters, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showLabelMenu(QPoint))); +QString LabelFiltersList::labelFromRow(int row) const +{ + Q_ASSERT(row > 1); + const QString &label = item(row)->text(); + QStringList parts = label.split(" "); + Q_ASSERT(parts.size() >= 2); + parts.removeLast(); // Remove trailing number + return parts.join(" "); } -TransferListFiltersWidget::~TransferListFiltersWidget() { - saveSettings(); - delete statusFilters; - delete labelFilters; - delete vLayout; +int LabelFiltersList::rowFromLabel(QString label) const +{ + Q_ASSERT(!label.isEmpty()); + for (int i = 2; ipos()) && row(itemAt(event->pos())) > 0) { + if (itemHover) { + if (itemHover != itemAt(event->pos())) { + setItemHover(false); + itemHover = itemAt(event->pos()); + setItemHover(true); + } + } + else { + itemHover = itemAt(event->pos()); + setItemHover(true); + } + event->acceptProposedAction(); + } + else { + if (itemHover) + setItemHover(false); + event->ignore(); + } } -void TransferListFiltersWidget::saveSettings() const { - Preferences* const pref = Preferences::instance(); - pref->setTransSelFilter(statusFilters->currentRow()); - pref->setTorrentLabels(customLabels.keys()); +void LabelFiltersList::dropEvent(QDropEvent *event) +{ + qDebug("Drop Event in labels list"); + if (itemAt(event->pos())) + emit torrentDropped(row(itemAt(event->pos()))); + event->ignore(); + setItemHover(false); + // Select current item again + currentItem()->setSelected(true); } -void TransferListFiltersWidget::loadSettings() { - statusFilters->setCurrentRow(Preferences::instance()->getTransSelFilter()); - const QStringList label_list = Preferences::instance()->getTorrentLabels(); - foreach (const QString &label, label_list) { - customLabels.insert(label, 0); - qDebug("Creating label QListWidgetItem: %s", qPrintable(label)); +void LabelFiltersList::dragLeaveEvent(QDragLeaveEvent*) +{ + if (itemHover) + setItemHover(false); + // Select current item again + currentItem()->setSelected(true); +} + +void LabelFiltersList::setItemHover(bool hover) +{ + Q_ASSERT(itemHover); + if (hover) { + itemHover->setData(Qt::DecorationRole, IconProvider::instance()->getIcon("folder-documents.png")); + itemHover->setSelected(true); + //setCurrentItem(itemHover); + } + else { + itemHover->setData(Qt::DecorationRole, IconProvider::instance()->getIcon("inode-directory.png")); + //itemHover->setSelected(false); + itemHover = 0; + } +} + +StatusFiltersWidget::StatusFiltersWidget(QWidget *parent): QListWidget(parent), m_shown(false) +{ + setUniformItemSizes(true); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + // Height is fixed (sizeHint().height() is used) + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + setStyleSheet("QListWidget { background: transparent; border: 0 }"); +#if defined(Q_OS_MAC) + setAttribute(Qt::WA_MacShowFocusRect, false); +#endif +} + +QSize StatusFiltersWidget::sizeHint() const +{ + QSize size = QListWidget::sizeHint(); + // Height should be exactly the height of the content + size.setHeight(contentsSize().height() + 2 * frameWidth() + 6); + return size; +} + +TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferListWidget *transferList): QFrame(parent), transferList(transferList), nb_labeled(0), nb_torrents(0) +{ + // Construct lists + vLayout = new QVBoxLayout(); + vLayout->setContentsMargins(0, 4, 0, 0); + QFont font; + font.setBold(true); + font.setCapitalization(QFont::SmallCaps); + QLabel *torrentsLabel = new QLabel(tr("Torrents")); + torrentsLabel->setIndent(2); + torrentsLabel->setFont(font); + vLayout->addWidget(torrentsLabel); + statusFilters = new StatusFiltersWidget(this); + vLayout->addWidget(statusFilters); + QLabel *labelsLabel = new QLabel(tr("Labels")); + labelsLabel->setIndent(2); + labelsLabel->setFont(font); + vLayout->addWidget(labelsLabel); + labelFilters = new LabelFiltersList(this); + vLayout->addWidget(labelFilters); + setLayout(vLayout); + labelFilters->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + statusFilters->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + statusFilters->setSpacing(0); + setContentsMargins(0,0,0,0); + vLayout->setSpacing(2); + // Add status filters + QListWidgetItem *all = new QListWidgetItem(statusFilters); + all->setData(Qt::DisplayRole, QVariant(tr("All") + " (0)")); + all->setData(Qt::DecorationRole, QIcon(":/Icons/skin/filterall.png")); + QListWidgetItem *downloading = new QListWidgetItem(statusFilters); + downloading->setData(Qt::DisplayRole, QVariant(tr("Downloading") + " (0)")); + downloading->setData(Qt::DecorationRole, QIcon(":/Icons/skin/downloading.png")); + QListWidgetItem *completed = new QListWidgetItem(statusFilters); + completed->setData(Qt::DisplayRole, QVariant(tr("Completed") + " (0)")); + completed->setData(Qt::DecorationRole, QIcon(":/Icons/skin/uploading.png")); + QListWidgetItem *paused = new QListWidgetItem(statusFilters); + paused->setData(Qt::DisplayRole, QVariant(tr("Paused") + " (0)")); + paused->setData(Qt::DecorationRole, QIcon(":/Icons/skin/paused.png")); + QListWidgetItem *resumed = new QListWidgetItem(statusFilters); + resumed->setData(Qt::DisplayRole, QVariant(tr("Resumed") + " (0)")); + resumed->setData(Qt::DecorationRole, QIcon(":/Icons/skin/resumed.png")); + QListWidgetItem *active = new QListWidgetItem(statusFilters); + active->setData(Qt::DisplayRole, QVariant(tr("Active") + " (0)")); + active->setData(Qt::DecorationRole, QIcon(":/Icons/skin/filteractive.png")); + QListWidgetItem *inactive = new QListWidgetItem(statusFilters); + inactive->setData(Qt::DisplayRole, QVariant(tr("Inactive") + " (0)")); + inactive->setData(Qt::DecorationRole, QIcon(":/Icons/skin/filterinactive.png")); + + // SIGNAL/SLOT + connect(statusFilters, SIGNAL(currentRowChanged(int)), transferList, SLOT(applyStatusFilter(int))); + connect(transferList->getSourceModel(), SIGNAL(modelRefreshed()), SLOT(updateTorrentNumbers())); + connect(transferList->getSourceModel(), SIGNAL(torrentAdded(TorrentModelItem*)), SLOT(handleNewTorrent(TorrentModelItem*))); + connect(labelFilters, SIGNAL(currentRowChanged(int)), this, SLOT(applyLabelFilter(int))); + connect(labelFilters, SIGNAL(torrentDropped(int)), this, SLOT(torrentDropped(int))); + connect(transferList->getSourceModel(), SIGNAL(torrentAboutToBeRemoved(TorrentModelItem*)), SLOT(torrentAboutToBeDeleted(TorrentModelItem*))); + connect(transferList->getSourceModel(), SIGNAL(torrentChangedLabel(TorrentModelItem*,QString,QString)), SLOT(torrentChangedLabel(TorrentModelItem*, QString, QString))); + + // Add Label filters + QListWidgetItem *allLabels = new QListWidgetItem(labelFilters); + allLabels->setData(Qt::DisplayRole, QVariant(tr("All labels") + " (0)")); + allLabels->setData(Qt::DecorationRole, IconProvider::instance()->getIcon("inode-directory")); + QListWidgetItem *noLabel = new QListWidgetItem(labelFilters); + noLabel->setData(Qt::DisplayRole, QVariant(tr("Unlabeled") + " (0)")); + noLabel->setData(Qt::DecorationRole, IconProvider::instance()->getIcon("inode-directory")); + + // Load settings + loadSettings(); + + labelFilters->setCurrentRow(0); + //labelFilters->selectionModel()->select(labelFilters->model()->index(0,0), QItemSelectionModel::Select); + + // Label menu + labelFilters->setContextMenuPolicy(Qt::CustomContextMenu); + connect(labelFilters, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showLabelMenu(QPoint))); +} + +TransferListFiltersWidget::~TransferListFiltersWidget() +{ + saveSettings(); + delete statusFilters; + delete labelFilters; + delete vLayout; +} + +StatusFiltersWidget* TransferListFiltersWidget::getStatusFilters() const +{ + return statusFilters; +} + +void TransferListFiltersWidget::saveSettings() const +{ + Preferences* const pref = Preferences::instance(); + pref->setTransSelFilter(statusFilters->currentRow()); + pref->setTorrentLabels(customLabels.keys()); +} + +void TransferListFiltersWidget::loadSettings() +{ + statusFilters->setCurrentRow(Preferences::instance()->getTransSelFilter()); + const QStringList label_list = Preferences::instance()->getTorrentLabels(); + foreach (const QString &label, label_list) { + customLabels.insert(label, 0); + qDebug("Creating label QListWidgetItem: %s", qPrintable(label)); + QListWidgetItem *newLabel = new QListWidgetItem(); + newLabel->setText(label + " (0)"); + newLabel->setData(Qt::DecorationRole, IconProvider::instance()->getIcon("inode-directory")); + labelFilters->addItem(newLabel); + } +} + +void TransferListFiltersWidget::updateTorrentNumbers() +{ + const TorrentStatusReport report = transferList->getSourceModel()->getTorrentStatusReport(); + statusFilters->item(TorrentFilter::ALL)->setData(Qt::DisplayRole, QVariant(tr("All") + " (" + QString::number(report.nb_active + report.nb_inactive) + ")")); + statusFilters->item(TorrentFilter::DOWNLOADING)->setData(Qt::DisplayRole, QVariant(tr("Downloading") + " (" + QString::number(report.nb_downloading) + ")")); + statusFilters->item(TorrentFilter::COMPLETED)->setData(Qt::DisplayRole, QVariant(tr("Completed") + " (" + QString::number(report.nb_seeding) + ")")); + statusFilters->item(TorrentFilter::PAUSED)->setData(Qt::DisplayRole, QVariant(tr("Paused") + " (" + QString::number(report.nb_paused) + ")")); + statusFilters->item(TorrentFilter::RESUMED)->setData(Qt::DisplayRole, QVariant(tr("Resumed") + " (" + QString::number(report.nb_active + report.nb_inactive - report.nb_paused) + ")")); + statusFilters->item(TorrentFilter::ACTIVE)->setData(Qt::DisplayRole, QVariant(tr("Active") + " (" + QString::number(report.nb_active) + ")")); + statusFilters->item(TorrentFilter::INACTIVE)->setData(Qt::DisplayRole, QVariant(tr("Inactive") + " (" + QString::number(report.nb_inactive) + ")")); +} + +void TransferListFiltersWidget::torrentDropped(int row) +{ + Q_ASSERT(row > 0); + if (row == 1) + transferList->setSelectionLabel(""); + else + transferList->setSelectionLabel(labelFilters->labelFromRow(row)); +} + +void TransferListFiltersWidget::addLabel(QString& label) +{ + label = fsutils::toValidFileSystemName(label.trimmed()); + if (label.isEmpty() || customLabels.contains(label)) return; QListWidgetItem *newLabel = new QListWidgetItem(); newLabel->setText(label + " (0)"); newLabel->setData(Qt::DecorationRole, IconProvider::instance()->getIcon("inode-directory")); labelFilters->addItem(newLabel); - } + customLabels.insert(label, 0); + Preferences::instance()->addTorrentLabel(label); } -void TransferListFiltersWidget::updateTorrentNumbers() { - const TorrentStatusReport report = transferList->getSourceModel()->getTorrentStatusReport(); - statusFilters->item(TorrentFilter::ALL)->setData(Qt::DisplayRole, QVariant(tr("All")+" ("+QString::number(report.nb_active+report.nb_inactive)+")")); - statusFilters->item(TorrentFilter::DOWNLOADING)->setData(Qt::DisplayRole, QVariant(tr("Downloading")+" ("+QString::number(report.nb_downloading)+")")); - statusFilters->item(TorrentFilter::COMPLETED)->setData(Qt::DisplayRole, QVariant(tr("Completed")+" ("+QString::number(report.nb_seeding)+")")); - statusFilters->item(TorrentFilter::PAUSED)->setData(Qt::DisplayRole, QVariant(tr("Paused")+" ("+QString::number(report.nb_paused)+")")); - statusFilters->item(TorrentFilter::RESUMED)->setData(Qt::DisplayRole, QVariant(tr("Resumed")+" ("+QString::number(report.nb_active+report.nb_inactive-report.nb_paused)+")")); - statusFilters->item(TorrentFilter::ACTIVE)->setData(Qt::DisplayRole, QVariant(tr("Active")+" ("+QString::number(report.nb_active)+")")); - statusFilters->item(TorrentFilter::INACTIVE)->setData(Qt::DisplayRole, QVariant(tr("Inactive")+" ("+QString::number(report.nb_inactive)+")")); -} - -void TransferListFiltersWidget::torrentDropped(int row) { - Q_ASSERT(row > 0); - if (row == 1) { - transferList->setSelectionLabel(""); - } else { - transferList->setSelectionLabel(labelFilters->labelFromRow(row)); - } -} - -void TransferListFiltersWidget::addLabel(QString& label) { - label = fsutils::toValidFileSystemName(label.trimmed()); - if (label.isEmpty() || customLabels.contains(label)) return; - QListWidgetItem *newLabel = new QListWidgetItem(); - newLabel->setText(label + " (0)"); - newLabel->setData(Qt::DecorationRole, IconProvider::instance()->getIcon("inode-directory")); - labelFilters->addItem(newLabel); - customLabels.insert(label, 0); - Preferences::instance()->addTorrentLabel(label); -} - -void TransferListFiltersWidget::showLabelMenu(QPoint) { - QMenu labelMenu(labelFilters); - QAction *addAct = labelMenu.addAction(IconProvider::instance()->getIcon("list-add"), tr("Add label...")); - QAction *removeAct = 0; - QAction *removeUnusedAct = 0; - if (!labelFilters->selectedItems().empty() && labelFilters->row(labelFilters->selectedItems().first()) > 1) - removeAct = labelMenu.addAction(IconProvider::instance()->getIcon("list-remove"), tr("Remove label")); - else - removeUnusedAct = labelMenu.addAction(IconProvider::instance()->getIcon("list-remove"), tr("Remove unused labels")); - labelMenu.addSeparator(); - QAction *startAct = labelMenu.addAction(IconProvider::instance()->getIcon("media-playback-start"), tr("Resume torrents")); - QAction *pauseAct = labelMenu.addAction(IconProvider::instance()->getIcon("media-playback-pause"), tr("Pause torrents")); - QAction *deleteTorrentsAct = labelMenu.addAction(IconProvider::instance()->getIcon("edit-delete"), tr("Delete torrents")); - QAction *act = 0; - act = labelMenu.exec(QCursor::pos()); - if (act) { - if (act == removeAct) { - removeSelectedLabel(); - return; - } - if (act == removeUnusedAct) { - removeUnusedLabels(); - return; - } - if (act == deleteTorrentsAct) { - transferList->deleteVisibleTorrents(); - return; - } - if (act == startAct) { - transferList->startVisibleTorrents(); - return; - } - if (act == pauseAct) { - transferList->pauseVisibleTorrents(); - return; - } - if (act == addAct) { - bool ok; - QString label = ""; - bool invalid; - do { - invalid = false; - label = AutoExpandableDialog::getText(this, tr("New Label"), tr("Label:"), QLineEdit::Normal, label, &ok); - if (ok && !label.isEmpty()) { - if (fsutils::isValidFileSystemName(label)) { - addLabel(label); - } else { - QMessageBox::warning(this, tr("Invalid label name"), tr("Please don't use any special characters in the label name.")); - invalid = true; - } +void TransferListFiltersWidget::showLabelMenu(QPoint) +{ + QMenu labelMenu(labelFilters); + QAction *addAct = labelMenu.addAction(IconProvider::instance()->getIcon("list-add"), tr("Add label...")); + QAction *removeAct = 0; + QAction *removeUnusedAct = 0; + if (!labelFilters->selectedItems().empty() && labelFilters->row(labelFilters->selectedItems().first()) > 1) + removeAct = labelMenu.addAction(IconProvider::instance()->getIcon("list-remove"), tr("Remove label")); + else + removeUnusedAct = labelMenu.addAction(IconProvider::instance()->getIcon("list-remove"), tr("Remove unused labels")); + labelMenu.addSeparator(); + QAction *startAct = labelMenu.addAction(IconProvider::instance()->getIcon("media-playback-start"), tr("Resume torrents")); + QAction *pauseAct = labelMenu.addAction(IconProvider::instance()->getIcon("media-playback-pause"), tr("Pause torrents")); + QAction *deleteTorrentsAct = labelMenu.addAction(IconProvider::instance()->getIcon("edit-delete"), tr("Delete torrents")); + QAction *act = 0; + act = labelMenu.exec(QCursor::pos()); + if (act) { + if (act == removeAct) { + removeSelectedLabel(); + return; + } + if (act == removeUnusedAct) { + removeUnusedLabels(); + return; + } + if (act == deleteTorrentsAct) { + transferList->deleteVisibleTorrents(); + return; + } + if (act == startAct) { + transferList->startVisibleTorrents(); + return; + } + if (act == pauseAct) { + transferList->pauseVisibleTorrents(); + return; + } + if (act == addAct) { + bool ok; + QString label = ""; + bool invalid; + do { + invalid = false; + label = AutoExpandableDialog::getText(this, tr("New Label"), tr("Label:"), QLineEdit::Normal, label, &ok); + if (ok && !label.isEmpty()) { + if (fsutils::isValidFileSystemName(label)) { + addLabel(label); + } + else { + QMessageBox::warning(this, tr("Invalid label name"), tr("Please don't use any special characters in the label name.")); + invalid = true; + } + } + } while (invalid); + return; } - } while(invalid); - return; } - } } -void TransferListFiltersWidget::removeSelectedLabel() { - const int row = labelFilters->row(labelFilters->selectedItems().first()); - Q_ASSERT(row > 1); - const QString &label = labelFilters->labelFromRow(row); - Q_ASSERT(customLabels.contains(label)); - customLabels.remove(label); - transferList->removeLabelFromRows(label); - // Select first label - labelFilters->setCurrentItem(labelFilters->item(0)); - labelFilters->selectionModel()->select(labelFilters->model()->index(0,0), QItemSelectionModel::Select); - applyLabelFilter(0); - // Un display filter - delete labelFilters->takeItem(row); - // Save custom labels to remember it was deleted - Preferences::instance()->removeTorrentLabel(label); +void TransferListFiltersWidget::removeSelectedLabel() +{ + const int row = labelFilters->row(labelFilters->selectedItems().first()); + Q_ASSERT(row > 1); + const QString &label = labelFilters->labelFromRow(row); + Q_ASSERT(customLabels.contains(label)); + customLabels.remove(label); + transferList->removeLabelFromRows(label); + // Select first label + labelFilters->setCurrentItem(labelFilters->item(0)); + labelFilters->selectionModel()->select(labelFilters->model()->index(0,0), QItemSelectionModel::Select); + applyLabelFilter(0); + // Un display filter + delete labelFilters->takeItem(row); + // Save custom labels to remember it was deleted + Preferences::instance()->removeTorrentLabel(label); } -void TransferListFiltersWidget::removeUnusedLabels() { +void TransferListFiltersWidget::removeUnusedLabels() +{ QStringList unusedLabels; QHash::const_iterator i; - for (i = customLabels.begin(); i != customLabels.end(); ++i) { + for (i = customLabels.begin(); i != customLabels.end(); ++i) if (i.value() == 0) unusedLabels << i.key(); - } foreach (const QString &label, unusedLabels) { customLabels.remove(label); delete labelFilters->takeItem(labelFilters->rowFromLabel(label)); @@ -381,95 +402,100 @@ void TransferListFiltersWidget::removeUnusedLabels() { } } -void TransferListFiltersWidget::applyLabelFilter(int row) { - switch(row) { - case 0: - transferList->applyLabelFilterAll(); - break; - case 1: - transferList->applyLabelFilter(QString()); - break; - default: - transferList->applyLabelFilter(labelFilters->labelFromRow(row)); - } -} - -void TransferListFiltersWidget::torrentChangedLabel(TorrentModelItem *torrentItem, QString old_label, QString new_label) { - Q_UNUSED(torrentItem); - qDebug("Torrent label changed from %s to %s", qPrintable(old_label), qPrintable(new_label)); - if (!old_label.isEmpty()) { - if (customLabels.contains(old_label)) { - const int new_count = customLabels.value(old_label, 0) - 1; - Q_ASSERT(new_count >= 0); - customLabels.insert(old_label, new_count); - const int row = labelFilters->rowFromLabel(old_label); - Q_ASSERT(row >= 2); - labelFilters->item(row)->setText(old_label + " ("+ QString::number(new_count) +")"); +void TransferListFiltersWidget::applyLabelFilter(int row) +{ + switch (row) { + case 0: + transferList->applyLabelFilterAll(); + break; + case 1: + transferList->applyLabelFilter(QString()); + break; + default: + transferList->applyLabelFilter(labelFilters->labelFromRow(row)); } - --nb_labeled; - } - if (!new_label.isEmpty()) { - if (!customLabels.contains(new_label)) - addLabel(new_label); - const int new_count = customLabels.value(new_label, 0) + 1; - Q_ASSERT(new_count >= 1); - customLabels.insert(new_label, new_count); - const int row = labelFilters->rowFromLabel(new_label); - Q_ASSERT(row >= 2); - labelFilters->item(row)->setText(new_label + " ("+ QString::number(new_count) +")"); - ++nb_labeled; - } - updateStickyLabelCounters(); } -void TransferListFiltersWidget::handleNewTorrent(TorrentModelItem* torrentItem) { - QString label = torrentItem->data(TorrentModelItem::TR_LABEL).toString(); - qDebug("New torrent was added with label: %s", qPrintable(label)); - if (!label.isEmpty()) { - if (!customLabels.contains(label)) { - addLabel(label); - // addLabel may have changed the label, update the model accordingly. - torrentItem->setData(TorrentModelItem::TR_LABEL, label); +void TransferListFiltersWidget::torrentChangedLabel(TorrentModelItem *torrentItem, QString old_label, QString new_label) +{ + Q_UNUSED(torrentItem); + qDebug("Torrent label changed from %s to %s", qPrintable(old_label), qPrintable(new_label)); + if (!old_label.isEmpty()) { + if (customLabels.contains(old_label)) { + const int new_count = customLabels.value(old_label, 0) - 1; + Q_ASSERT(new_count >= 0); + customLabels.insert(old_label, new_count); + const int row = labelFilters->rowFromLabel(old_label); + Q_ASSERT(row >= 2); + labelFilters->item(row)->setText(old_label + " (" + QString::number(new_count) + ")"); + } + --nb_labeled; } - // Update label counter - Q_ASSERT(customLabels.contains(label)); - const int new_count = customLabels.value(label, 0) + 1; - customLabels.insert(label, new_count); - const int row = labelFilters->rowFromLabel(label); - qDebug("torrentAdded, Row: %d", row); - Q_ASSERT(row >= 2); - Q_ASSERT(labelFilters->item(row)); - labelFilters->item(row)->setText(label + " ("+ QString::number(new_count) +")"); - ++nb_labeled; - } - ++nb_torrents; - Q_ASSERT(nb_torrents >= 0); - Q_ASSERT(nb_labeled >= 0); - Q_ASSERT(nb_labeled <= nb_torrents); - updateStickyLabelCounters(); + if (!new_label.isEmpty()) { + if (!customLabels.contains(new_label)) + addLabel(new_label); + const int new_count = customLabels.value(new_label, 0) + 1; + Q_ASSERT(new_count >= 1); + customLabels.insert(new_label, new_count); + const int row = labelFilters->rowFromLabel(new_label); + Q_ASSERT(row >= 2); + labelFilters->item(row)->setText(new_label + " (" + QString::number(new_count) + ")"); + ++nb_labeled; + } + updateStickyLabelCounters(); } -void TransferListFiltersWidget::torrentAboutToBeDeleted(TorrentModelItem* torrentItem) { - Q_ASSERT(torrentItem); - QString label = torrentItem->data(TorrentModelItem::TR_LABEL).toString(); - if (!label.isEmpty()) { - // Update label counter - const int new_count = customLabels.value(label, 0) - 1; - customLabels.insert(label, new_count); - const int row = labelFilters->rowFromLabel(label); - Q_ASSERT(row >= 2); - labelFilters->item(row)->setText(label + " ("+ QString::number(new_count) +")"); - --nb_labeled; - } - --nb_torrents; - qDebug("nb_torrents: %d, nb_labeled: %d", nb_torrents, nb_labeled); - Q_ASSERT(nb_torrents >= 0); - Q_ASSERT(nb_labeled >= 0); - Q_ASSERT(nb_labeled <= nb_torrents); - updateStickyLabelCounters(); +void TransferListFiltersWidget::handleNewTorrent(TorrentModelItem* torrentItem) +{ + QString label = torrentItem->data(TorrentModelItem::TR_LABEL).toString(); + qDebug("New torrent was added with label: %s", qPrintable(label)); + if (!label.isEmpty()) { + if (!customLabels.contains(label)) { + addLabel(label); + // addLabel may have changed the label, update the model accordingly. + torrentItem->setData(TorrentModelItem::TR_LABEL, label); + } + // Update label counter + Q_ASSERT(customLabels.contains(label)); + const int new_count = customLabels.value(label, 0) + 1; + customLabels.insert(label, new_count); + const int row = labelFilters->rowFromLabel(label); + qDebug("torrentAdded, Row: %d", row); + Q_ASSERT(row >= 2); + Q_ASSERT(labelFilters->item(row)); + labelFilters->item(row)->setText(label + " (" + QString::number(new_count) + ")"); + ++nb_labeled; + } + ++nb_torrents; + Q_ASSERT(nb_torrents >= 0); + Q_ASSERT(nb_labeled >= 0); + Q_ASSERT(nb_labeled <= nb_torrents); + updateStickyLabelCounters(); } -void TransferListFiltersWidget::updateStickyLabelCounters() { - labelFilters->item(0)->setText(tr("All labels") + " ("+QString::number(nb_torrents)+")"); - labelFilters->item(1)->setText(tr("Unlabeled") + " ("+QString::number(nb_torrents-nb_labeled)+")"); +void TransferListFiltersWidget::torrentAboutToBeDeleted(TorrentModelItem* torrentItem) +{ + Q_ASSERT(torrentItem); + QString label = torrentItem->data(TorrentModelItem::TR_LABEL).toString(); + if (!label.isEmpty()) { + // Update label counter + const int new_count = customLabels.value(label, 0) - 1; + customLabels.insert(label, new_count); + const int row = labelFilters->rowFromLabel(label); + Q_ASSERT(row >= 2); + labelFilters->item(row)->setText(label + " (" + QString::number(new_count) + ")"); + --nb_labeled; + } + --nb_torrents; + qDebug("nb_torrents: %d, nb_labeled: %d", nb_torrents, nb_labeled); + Q_ASSERT(nb_torrents >= 0); + Q_ASSERT(nb_labeled >= 0); + Q_ASSERT(nb_labeled <= nb_torrents); + updateStickyLabelCounters(); +} + +void TransferListFiltersWidget::updateStickyLabelCounters() +{ + labelFilters->item(0)->setText(tr("All labels") + " (" + QString::number(nb_torrents) + ")"); + labelFilters->item(1)->setText(tr("Unlabeled") + " (" + QString::number(nb_torrents - nb_labeled) + ")"); } diff --git a/src/gui/transferlistfilterswidget.h b/src/gui/transferlistfilterswidget.h index 7bea61df7..3e78ed9f9 100644 --- a/src/gui/transferlistfilterswidget.h +++ b/src/gui/transferlistfilterswidget.h @@ -43,77 +43,80 @@ QT_END_NAMESPACE class TransferListWidget; class TorrentModelItem; -class LabelFiltersList: public QListWidget { - Q_OBJECT +class LabelFiltersList: public QListWidget +{ + Q_OBJECT private: - QListWidgetItem *itemHover; + QListWidgetItem * itemHover; public: - LabelFiltersList(QWidget *parent); + LabelFiltersList(QWidget *parent); - // Redefine addItem() to make sure the list stays sorted - void addItem(QListWidgetItem *it); + // Redefine addItem() to make sure the list stays sorted + void addItem(QListWidgetItem *it); - QString labelFromRow(int row) const; - int rowFromLabel(QString label) const; + QString labelFromRow(int row) const; + int rowFromLabel(QString label) const; signals: - void torrentDropped(int label_row); + void torrentDropped(int label_row); protected: - void dragMoveEvent(QDragMoveEvent *event); - void dropEvent(QDropEvent *event); - void dragLeaveEvent(QDragLeaveEvent*); - void setItemHover(bool hover); + void dragMoveEvent(QDragMoveEvent *event); + void dropEvent(QDropEvent *event); + void dragLeaveEvent(QDragLeaveEvent*); + void setItemHover(bool hover); }; -class StatusFiltersWidget : public QListWidget { - Q_OBJECT +class StatusFiltersWidget: public QListWidget +{ + Q_OBJECT public: - StatusFiltersWidget(QWidget *parent); + StatusFiltersWidget(QWidget *parent); protected: - QSize sizeHint() const; + QSize sizeHint() const; private: - bool m_shown; + bool m_shown; }; -class TransferListFiltersWidget: public QFrame { - Q_OBJECT +class TransferListFiltersWidget: public QFrame +{ + Q_OBJECT private: - QHash customLabels; - StatusFiltersWidget* statusFilters; - LabelFiltersList* labelFilters; - QVBoxLayout* vLayout; - TransferListWidget *transferList; - int nb_labeled; - int nb_torrents; + QHash customLabels; + StatusFiltersWidget* statusFilters; + LabelFiltersList* labelFilters; + QVBoxLayout* vLayout; + TransferListWidget *transferList; + int nb_labeled; + int nb_torrents; public: - TransferListFiltersWidget(QWidget *parent, TransferListWidget *transferList); - ~TransferListFiltersWidget(); + TransferListFiltersWidget(QWidget *parent, TransferListWidget *transferList); + ~TransferListFiltersWidget(); - StatusFiltersWidget* getStatusFilters() const; + StatusFiltersWidget* getStatusFilters() const; - void saveSettings() const; - void loadSettings(); + void saveSettings() const; + void loadSettings(); protected slots: - void updateTorrentNumbers(); - void torrentDropped(int row); - void addLabel(QString& label); - void showLabelMenu(QPoint); - void removeSelectedLabel(); - void removeUnusedLabels(); - void applyLabelFilter(int row); - void torrentChangedLabel(TorrentModelItem *torrentItem, QString old_label, QString new_label); - void handleNewTorrent(TorrentModelItem* torrentItem); - void torrentAboutToBeDeleted(TorrentModelItem* torrentItem); - void updateStickyLabelCounters(); + void updateTorrentNumbers(); + void torrentDropped(int row); + void addLabel(QString& label); + void showLabelMenu(QPoint); + void removeSelectedLabel(); + void removeUnusedLabels(); + void applyLabelFilter(int row); + void torrentChangedLabel(TorrentModelItem *torrentItem, QString old_label, QString new_label); + void handleNewTorrent(TorrentModelItem* torrentItem); + void torrentAboutToBeDeleted(TorrentModelItem* torrentItem); + void updateStickyLabelCounters(); }; #endif // TRANSFERLISTFILTERSWIDGET_H From 28b976bdb8829181e4e5a8ac15a290f9e26a2b48 Mon Sep 17 00:00:00 2001 From: buinsky Date: Thu, 29 Jan 2015 20:42:09 +0300 Subject: [PATCH 10/17] Place 'Resumed' filter before 'Paused' --- src/gui/torrentfilterenum.h | 2 +- src/gui/transferlistfilterswidget.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/torrentfilterenum.h b/src/gui/torrentfilterenum.h index 0776db413..cfa7ff138 100644 --- a/src/gui/torrentfilterenum.h +++ b/src/gui/torrentfilterenum.h @@ -32,6 +32,6 @@ #define TORRENTFILTERENUM_H namespace TorrentFilter { -enum TorrentFilter {ALL, DOWNLOADING, COMPLETED, PAUSED, RESUMED, ACTIVE, INACTIVE}; + enum TorrentFilter {ALL, DOWNLOADING, COMPLETED, RESUMED, PAUSED, ACTIVE, INACTIVE}; } #endif // TORRENTFILTERENUM_H diff --git a/src/gui/transferlistfilterswidget.cpp b/src/gui/transferlistfilterswidget.cpp index fa6f1dfce..f173e2a20 100644 --- a/src/gui/transferlistfilterswidget.cpp +++ b/src/gui/transferlistfilterswidget.cpp @@ -203,12 +203,12 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi QListWidgetItem *completed = new QListWidgetItem(statusFilters); completed->setData(Qt::DisplayRole, QVariant(tr("Completed") + " (0)")); completed->setData(Qt::DecorationRole, QIcon(":/Icons/skin/uploading.png")); - QListWidgetItem *paused = new QListWidgetItem(statusFilters); - paused->setData(Qt::DisplayRole, QVariant(tr("Paused") + " (0)")); - paused->setData(Qt::DecorationRole, QIcon(":/Icons/skin/paused.png")); QListWidgetItem *resumed = new QListWidgetItem(statusFilters); resumed->setData(Qt::DisplayRole, QVariant(tr("Resumed") + " (0)")); resumed->setData(Qt::DecorationRole, QIcon(":/Icons/skin/resumed.png")); + QListWidgetItem *paused = new QListWidgetItem(statusFilters); + paused->setData(Qt::DisplayRole, QVariant(tr("Paused") + " (0)")); + paused->setData(Qt::DecorationRole, QIcon(":/Icons/skin/paused.png")); QListWidgetItem *active = new QListWidgetItem(statusFilters); active->setData(Qt::DisplayRole, QVariant(tr("Active") + " (0)")); active->setData(Qt::DecorationRole, QIcon(":/Icons/skin/filteractive.png")); From 0fee2216d0c816c14f3829e35356808f0d5f1299 Mon Sep 17 00:00:00 2001 From: buinsky Date: Thu, 29 Jan 2015 22:29:28 +0300 Subject: [PATCH 11/17] Add shortcut key for deleting selected torrents Added processing of pressing 'Delete' key on keyboard. --- src/webui/www/public/scripts/client.js | 19 ++++++++++++++----- src/webui/www/public/scripts/mocha-init.js | 2 ++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/webui/www/public/scripts/client.js b/src/webui/www/public/scripts/client.js index a468f4d96..92c11e755 100644 --- a/src/webui/www/public/scripts/client.js +++ b/src/webui/www/public/scripts/client.js @@ -352,11 +352,20 @@ window.addEvent('load', function () { function closeWindows() { MochaUI.closeAll(); -} +}; -window.addEvent('keydown', function (event) { - if (event.key == 'a' && event.control) { - event.stop(); - myTable.selectAll(); +var keyboardEvents = new Keyboard({ + defaultEventType: 'keydown', + events: { + 'ctrl+a': function(event) { + myTable.selectAll(); + event.preventDefault(); + }, + 'delete': function(event) { + deleteFN(); + event.preventDefault(); + } } }); + +keyboardEvents.activate(); diff --git a/src/webui/www/public/scripts/mocha-init.js b/src/webui/www/public/scripts/mocha-init.js index 8f38dc073..c5af9a377 100644 --- a/src/webui/www/public/scripts/mocha-init.js +++ b/src/webui/www/public/scripts/mocha-init.js @@ -29,6 +29,8 @@ function getLocalStorageItem(name, defaultVal) { return val; } +var deleteFN = function() {}; + initializeWindows = function() { function addClickEvent(el, fn) { From c4332fe9a9a81cf2dd00c0bef40406520580e16e Mon Sep 17 00:00:00 2001 From: buinsky Date: Fri, 30 Jan 2015 00:16:28 +0300 Subject: [PATCH 12/17] Add processing double click on torrent Starting/pausing torrent by double-clicking on it. --- src/webui/www/public/scripts/dynamicTable.js | 11 +++++++++++ src/webui/www/public/scripts/mocha-init.js | 2 ++ 2 files changed, 13 insertions(+) diff --git a/src/webui/www/public/scripts/dynamicTable.js b/src/webui/www/public/scripts/dynamicTable.js index 018164d52..c54152bdb 100644 --- a/src/webui/www/public/scripts/dynamicTable.js +++ b/src/webui/www/public/scripts/dynamicTable.js @@ -356,6 +356,17 @@ var dynamicTable = new Class({ myTable.selectRow(this.hash); return true; }); + tr.addEvent('dblclick', function (e) { + e.stop(); + myTable.selectRow(this.hash); + var row = myTable.rows.get(this.hash); + var state = row['full_data'].state; + if (~state.indexOf('paused')) + startFN(); + else + pauseFN(); + return true; + }); tr.addEvent('click', function (e) { e.stop(); if (e.control) { diff --git a/src/webui/www/public/scripts/mocha-init.js b/src/webui/www/public/scripts/mocha-init.js index c5af9a377..d14dbb669 100644 --- a/src/webui/www/public/scripts/mocha-init.js +++ b/src/webui/www/public/scripts/mocha-init.js @@ -30,6 +30,8 @@ function getLocalStorageItem(name, defaultVal) { } var deleteFN = function() {}; +var startFN = function() {}; +var pauseFN = function() {}; initializeWindows = function() { From 8931eec5b0f39aa5526e601c68d7e311846bf324 Mon Sep 17 00:00:00 2001 From: buinsky Date: Fri, 30 Jan 2015 01:01:41 +0300 Subject: [PATCH 13/17] Use server refresh interval Use torrents table refresh interval from server in web-client. --- src/webui/btjson.cpp | 4 ++++ src/webui/www/public/scripts/client.js | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/webui/btjson.cpp b/src/webui/btjson.cpp index d5deb38cf..e70ba5ffc 100644 --- a/src/webui/btjson.cpp +++ b/src/webui/btjson.cpp @@ -147,6 +147,7 @@ static const char KEY_TRANSFER_CONNECTION_STATUS[] = "connection_status"; // Sync main data keys static const char KEY_SYNC_MAINDATA_QUEUEING[] = "queueing"; static const char KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS[] = "use_alt_speed_limits"; +static const char KEY_SYNC_MAINDATA_REFRESH_INTERVAL[] = "refresh_interval"; static const char KEY_FULL_UPDATE[] = "full_update"; static const char KEY_RESPONSE_ID[] = "rid"; @@ -306,6 +307,7 @@ QByteArray btjson::getTorrents(QString filter, QString label, * - "up_info_speed: upload speed * - "up_rate_limit: upload speed limit * - "queueing": priority system usage flag + * - "refresh_interval": torrents table refresh interval */ QByteArray btjson::getSyncMainData(int acceptedResponseId, QVariantMap &lastData, QVariantMap &lastAcceptedData) { @@ -335,6 +337,7 @@ QByteArray btjson::getSyncMainData(int acceptedResponseId, QVariantMap &lastData QVariantMap serverState = getTranserInfoMap(); serverState[KEY_SYNC_MAINDATA_QUEUEING] = QBtSession::instance()->isQueueingEnabled(); serverState[KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS] = Preferences::instance()->isAltBandwidthEnabled(); + serverState[KEY_SYNC_MAINDATA_REFRESH_INTERVAL] = Preferences::instance()->getRefreshInterval(); data["server_state"] = serverState; return json::toJson(generateSyncData(acceptedResponseId, data, lastAcceptedData, lastData)); @@ -610,6 +613,7 @@ void processMap(QVariantMap prevData, QVariantMap data, QVariantMap &syncData) case QVariant::Bool: case QVariant::Double: case QVariant::ULongLong: + case QVariant::UInt: if (prevData[key] != data[key]) syncData[key] = data[key]; break; diff --git a/src/webui/www/public/scripts/client.js b/src/webui/www/public/scripts/client.js index 92c11e755..7ad60e2a6 100644 --- a/src/webui/www/public/scripts/client.js +++ b/src/webui/www/public/scripts/client.js @@ -28,6 +28,7 @@ var updatePropertiesPanel = function(){}; var updateMainData = function(){}; var alternativeSpeedLimits = false; var queueing_enabled = true; +var syncMainDataTimerPeriod = 1500; selected_filter = getLocalStorageItem('selected_filter', 'all'); selected_label = null; @@ -174,7 +175,7 @@ window.addEvent('load', function () { } } clearTimeout(syncMainDataTimer); - syncMainDataTimer = syncMainData.delay(1500); + syncMainDataTimer = syncMainData.delay(syncMainDataTimerPeriod); } }).send(); }; @@ -228,6 +229,10 @@ window.addEvent('load', function () { alternativeSpeedLimits = serverState.use_alt_speed_limits; updateAltSpeedIcon(alternativeSpeedLimits); } + + syncMainDataTimerPeriod = serverState.refresh_interval; + if (syncMainDataTimerPeriod < 500) + syncMainDataTimerPeriod = 500; }; var updateAltSpeedIcon = function(enabled) { From 28f573e9a81a8dcce3831c7914da8e77e6666483 Mon Sep 17 00:00:00 2001 From: buinsky Date: Fri, 30 Jan 2015 09:04:25 -0500 Subject: [PATCH 14/17] Fix processHash function Fix processHash function to not use QVariantHash as result because of QJsonDocument doesn't support QVariantHash variables in Qt 5. --- src/webui/btjson.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/webui/btjson.cpp b/src/webui/btjson.cpp index e70ba5ffc..4f7f3b5de 100644 --- a/src/webui/btjson.cpp +++ b/src/webui/btjson.cpp @@ -156,7 +156,7 @@ static const char KEY_SUFFIX_REMOVED[] = "_removed"; QVariantMap getTranserInfoMap(); QVariantMap toMap(const QTorrentHandle& h); void processMap(QVariantMap prevData, QVariantMap data, QVariantMap &syncData); -void processHash(QVariantHash prevData, QVariantHash data, QVariantHash &syncData, QVariantList &removedItems); +void processHash(QVariantHash prevData, QVariantHash data, QVariantMap &syncData, QVariantList &removedItems); void processList(QVariantList prevData, QVariantList data, QVariantList &syncData, QVariantList &removedItems); QVariantMap generateSyncData(int acceptedResponseId, QVariantMap data, QVariantMap &lastAcceptedData, QVariantMap &lastData); @@ -589,10 +589,10 @@ void processMap(QVariantMap prevData, QVariantMap data, QVariantMap &syncData) } break; case QVariant::Hash: { - QVariantHash hash; - processHash(prevData[key].toHash(), data[key].toHash(), hash, removedItems); - if (!hash.isEmpty()) - syncData[key] = hash; + QVariantMap map; + processHash(prevData[key].toHash(), data[key].toHash(), map, removedItems); + if (!map.isEmpty()) + syncData[key] = map; if (!removedItems.isEmpty()) syncData[key + KEY_SUFFIX_REMOVED] = removedItems; } @@ -626,7 +626,7 @@ void processMap(QVariantMap prevData, QVariantMap data, QVariantMap &syncData) // Compare two lists of structures (prevData, data) and calculate difference (syncData, removedItems). // Structures encoded as map. // Lists are encoded as hash table (indexed by structure key value) to improve ease of searching for removed items. -void processHash(QVariantHash prevData, QVariantHash data, QVariantHash &syncData, QVariantList &removedItems) +void processHash(QVariantHash prevData, QVariantHash data, QVariantMap &syncData, QVariantList &removedItems) { // initialize output variables syncData.clear(); @@ -634,7 +634,8 @@ void processHash(QVariantHash prevData, QVariantHash data, QVariantHash &syncDat if (prevData.isEmpty()) { // If list was empty before, then difference is a whole new list. - syncData = data; + foreach (QString key, data.keys()) + syncData[key] = data[key]; } else { foreach (QString key, data.keys()) { From facab1681a312037d4f836617f2f6bfd80c3dbe2 Mon Sep 17 00:00:00 2001 From: buinsky Date: Fri, 30 Jan 2015 15:58:27 -0500 Subject: [PATCH 15/17] Add ability to set super seeding mode for torrents --- src/webui/btjson.cpp | 2 ++ src/webui/webapplication.cpp | 16 ++++++++++++++++ src/webui/webapplication.h | 1 + src/webui/www/private/index.html | 1 + src/webui/www/public/scripts/contextmenu.js | 11 +++++++++++ src/webui/www/public/scripts/mocha-init.js | 15 +++++++++++++++ src/webui/www/public/transferlist.html | 3 +++ 7 files changed, 49 insertions(+) diff --git a/src/webui/btjson.cpp b/src/webui/btjson.cpp index 4f7f3b5de..699bf5165 100644 --- a/src/webui/btjson.cpp +++ b/src/webui/btjson.cpp @@ -102,6 +102,7 @@ static const char KEY_TORRENT_STATE[] = "state"; static const char KEY_TORRENT_SEQUENTIAL_DOWNLOAD[] = "seq_dl"; static const char KEY_TORRENT_FIRST_LAST_PIECE_PRIO[] = "f_l_piece_prio"; static const char KEY_TORRENT_LABEL[] = "label"; +static const char KEY_TORRENT_SUPER_SEEDING[] = "super_seeding"; // Tracker keys static const char KEY_TRACKER_URL[] = "url"; @@ -565,6 +566,7 @@ QVariantMap toMap(const QTorrentHandle& h) if (h.has_metadata()) ret[KEY_TORRENT_FIRST_LAST_PIECE_PRIO] = h.first_last_piece_first(); ret[KEY_TORRENT_LABEL] = TorrentPersistentData::instance()->getLabel(h.hash()); + ret[KEY_TORRENT_SUPER_SEEDING] = status.super_seeding; return ret; } diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index 79fcfb0b0..545e1ef52 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -102,6 +102,7 @@ QMap > WebApplication::initialize ADD_ACTION(command, toggleAlternativeSpeedLimits); ADD_ACTION(command, toggleSequentialDownload); ADD_ACTION(command, toggleFirstLastPiecePrio); + ADD_ACTION(command, setSuperSeeding); ADD_ACTION(command, delete); ADD_ACTION(command, deletePerm); ADD_ACTION(command, increasePrio); @@ -541,6 +542,21 @@ void WebApplication::action_command_toggleFirstLastPiecePrio() } } +void WebApplication::action_command_setSuperSeeding() +{ + CHECK_URI(0); + CHECK_PARAMETERS("hashes" << "value"); + bool value = request().posts["value"] == "true"; + QStringList hashes = request().posts["hashes"].split("|"); + foreach (const QString &hash, hashes) { + try { + QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); + h.super_seeding(value); + } + catch(invalid_handle&) {} + } +} + void WebApplication::action_command_delete() { CHECK_URI(0); diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index b4b6b9b3f..4b2e31b6d 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -76,6 +76,7 @@ private: void action_command_toggleAlternativeSpeedLimits(); void action_command_toggleSequentialDownload(); void action_command_toggleFirstLastPiecePrio(); + void action_command_setSuperSeeding(); void action_command_delete(); void action_command_deletePerm(); void action_command_increasePrio(); diff --git a/src/webui/www/private/index.html b/src/webui/www/private/index.html index e60fa79e5..2f0edbf0f 100644 --- a/src/webui/www/private/index.html +++ b/src/webui/www/private/index.html @@ -111,6 +111,7 @@
  • QBT_TR(Limit upload rate...)QBT_TR QBT_TR(Limit upload rate...)QBT_TR
  • QBT_TR(Download in sequential order)QBT_TR QBT_TR(Download in sequential order)QBT_TR
  • QBT_TR(Download first and last piece first)QBT_TR QBT_TR(Download first and last piece first)QBT_TR
  • +
  • QBT_TR(Super seeding mode)QBT_TR QBT_TR(Super seeding mode)QBT_TR
  • QBT_TR(Force recheck)QBT_TR QBT_TR(Force recheck)QBT_TR
  • diff --git a/src/webui/www/public/scripts/contextmenu.js b/src/webui/www/public/scripts/contextmenu.js index 2aaff877d..72e60f5e7 100644 --- a/src/webui/www/public/scripts/contextmenu.js +++ b/src/webui/www/public/scripts/contextmenu.js @@ -136,6 +136,7 @@ var ContextMenu = new Class({ all_are_downloaded = true; all_are_paused = true; there_are_paused = false; + all_are_super_seeding = true; var h = myTable.selectedIds(); h.each(function(item, index){ @@ -153,6 +154,8 @@ var ContextMenu = new Class({ if (data['progress'] != 1.0) // not downloaded all_are_downloaded = false; + else if (data['super_seeding'] != true) + all_are_super_seeding = false; state = data['state']; if ((state != 'pausedUP') && (state != 'pausedDL')) @@ -174,6 +177,8 @@ var ContextMenu = new Class({ if (all_are_downloaded) { this.hideItem('SequentialDownload'); this.hideItem('FirstLastPiecePrio'); + this.showItem('SuperSeeding'); + this.setItemChecked('SuperSeeding', all_are_super_seeding); } else { if (!show_seq_dl && show_f_l_piece_prio) this.menu.getElement('a[href$=FirstLastPiecePrio]').parentNode.addClass('separator'); @@ -192,6 +197,8 @@ var ContextMenu = new Class({ this.setItemChecked('SequentialDownload', all_are_seq_dl); this.setItemChecked('FirstLastPiecePrio', all_are_f_l_piece_prio); + + this.hideItem('SuperSeeding'); } if (all_are_paused) { @@ -234,6 +241,10 @@ var ContextMenu = new Class({ return this; }, + getItemChecked: function(item) { + return '0' != this.menu.getElement('a[href$=' + item + ']').firstChild.style.opacity; + }, + //hide an item hideItem: function(item) { this.menu.getElement('a[href$=' + item + ']').parentNode.addClass('invisible'); diff --git a/src/webui/www/public/scripts/mocha-init.js b/src/webui/www/public/scripts/mocha-init.js index d14dbb669..b09dc4fc9 100644 --- a/src/webui/www/public/scripts/mocha-init.js +++ b/src/webui/www/public/scripts/mocha-init.js @@ -166,6 +166,21 @@ initializeWindows = function() { } }; + setSuperSeedingFN = function(val) { + var h = myTable.selectedIds(); + if (h.length) { + new Request({ + url: 'command/setSuperSeeding', + method: 'post', + data: { + value: val, + hashes: h.join("|") + } + }).send(); + updateMainData(); + } + }; + globalDownloadLimitFN = function() { new MochaUI.Window({ id: 'downloadLimitPage', diff --git a/src/webui/www/public/transferlist.html b/src/webui/www/public/transferlist.html index d12e2027a..b65dcd96a 100644 --- a/src/webui/www/public/transferlist.html +++ b/src/webui/www/public/transferlist.html @@ -51,6 +51,9 @@ }, FirstLastPiecePrio : function (element, ref) { toggleFirstLastPiecePrioFN(); + }, + SuperSeeding : function (element, ref) { + setSuperSeedingFN(!ref.getItemChecked('SuperSeeding')); } }, offsets : { From cf55751fbeada0a05cc4d216b6393ce2f086c5c0 Mon Sep 17 00:00:00 2001 From: buinsky Date: Sun, 1 Feb 2015 12:08:45 -0500 Subject: [PATCH 16/17] Group setting torrents upload limit --- src/webui/btjson.cpp | 15 ++++++++++ src/webui/btjson.h | 1 + src/webui/webapplication.cpp | 33 +++++++++++---------- src/webui/webapplication.h | 4 +-- src/webui/www/public/scripts/mocha-init.js | 4 +-- src/webui/www/public/scripts/parametrics.js | 22 +++++++++----- src/webui/www/public/uploadlimit.html | 10 +++---- 7 files changed, 56 insertions(+), 33 deletions(-) diff --git a/src/webui/btjson.cpp b/src/webui/btjson.cpp index 699bf5165..e02423efb 100644 --- a/src/webui/btjson.cpp +++ b/src/webui/btjson.cpp @@ -539,6 +539,21 @@ QVariantMap getTranserInfoMap() return map; } +QByteArray btjson::getTorrentsRatesLimits(QStringList &hashes, bool downloadLimits) +{ + QVariantMap map; + + foreach (const QString &hash, hashes) { + int limit = -1; + QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); + if (h.is_valid()) + limit = downloadLimits ? h.download_limit() : h.upload_limit(); + map[hash] = limit; + } + + return json::toJson(map); +} + QVariantMap toMap(const QTorrentHandle& h) { libtorrent::torrent_status status = h.status(torrent_handle::query_accurate_download_counters); diff --git a/src/webui/btjson.h b/src/webui/btjson.h index 2d5f7e8eb..fb9faf434 100644 --- a/src/webui/btjson.h +++ b/src/webui/btjson.h @@ -52,6 +52,7 @@ public: static QByteArray getPropertiesForTorrent(const QString& hash); static QByteArray getFilesForTorrent(const QString& hash); static QByteArray getTransferInfo(); + static QByteArray getTorrentsRatesLimits(QStringList& hashes, bool downloadLimits); }; // class btjson #endif // BTJSON_H diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index 545e1ef52..c2d55d5f9 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -94,9 +94,9 @@ QMap > WebApplication::initialize ADD_ACTION(command, getGlobalDlLimit); ADD_ACTION(command, setGlobalUpLimit); ADD_ACTION(command, setGlobalDlLimit); - ADD_ACTION(command, getTorrentUpLimit); + ADD_ACTION(command, getTorrentsUpLimit); ADD_ACTION(command, getTorrentDlLimit); - ADD_ACTION(command, setTorrentUpLimit); + ADD_ACTION(command, setTorrentsUpLimit); ADD_ACTION(command, setTorrentDlLimit); ADD_ACTION(command, alternativeSpeedLimitsEnabled); ADD_ACTION(command, toggleAlternativeSpeedLimits); @@ -454,15 +454,12 @@ void WebApplication::action_command_setGlobalDlLimit() Preferences::instance()->setGlobalDownloadLimit(limit / 1024.); } -void WebApplication::action_command_getTorrentUpLimit() +void WebApplication::action_command_getTorrentsUpLimit() { CHECK_URI(0); - CHECK_PARAMETERS("hash"); - QString hash = request().posts["hash"]; - QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); - - if (h.is_valid()) - print(QByteArray::number(h.upload_limit())); + CHECK_PARAMETERS("hashes"); + QStringList hashes = request().posts["hashes"].split("|"); + print(btjson::getTorrentsRatesLimits(hashes, false), Http::CONTENT_TYPE_JS); } void WebApplication::action_command_getTorrentDlLimit() @@ -476,17 +473,21 @@ void WebApplication::action_command_getTorrentDlLimit() print(QByteArray::number(h.download_limit())); } -void WebApplication::action_command_setTorrentUpLimit() +void WebApplication::action_command_setTorrentsUpLimit() { CHECK_URI(0); - CHECK_PARAMETERS("hash" << "limit"); - QString hash = request().posts["hash"]; - qlonglong limit = request().posts["limit"].toLongLong(); - if (limit == 0) limit = -1; - QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); + CHECK_PARAMETERS("hashes" << "limit"); - if (h.is_valid()) + qlonglong limit = request().posts["limit"].toLongLong(); + if (limit == 0) + limit = -1; + + QStringList hashes = request().posts["hashes"].split("|"); + foreach (const QString &hash, hashes) { + QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); + if (h.is_valid()) h.set_upload_limit(limit); + } } void WebApplication::action_command_setTorrentDlLimit() diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 4b2e31b6d..96b4be35c 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -68,9 +68,9 @@ private: void action_command_getGlobalDlLimit(); void action_command_setGlobalUpLimit(); void action_command_setGlobalDlLimit(); - void action_command_getTorrentUpLimit(); + void action_command_getTorrentsUpLimit(); void action_command_getTorrentDlLimit(); - void action_command_setTorrentUpLimit(); + void action_command_setTorrentsUpLimit(); void action_command_setTorrentDlLimit(); void action_command_alternativeSpeedLimitsEnabled(); void action_command_toggleAlternativeSpeedLimits(); diff --git a/src/webui/www/public/scripts/mocha-init.js b/src/webui/www/public/scripts/mocha-init.js index b09dc4fc9..050be0eb3 100644 --- a/src/webui/www/public/scripts/mocha-init.js +++ b/src/webui/www/public/scripts/mocha-init.js @@ -107,7 +107,7 @@ initializeWindows = function() { id: 'uploadLimitPage', title: "QBT_TR(Global Upload Speed Limit)QBT_TR", loadMethod: 'iframe', - contentURL: 'uploadlimit.html?hash=global', + contentURL: 'uploadlimit.html?hashes=global', scrollbars: false, resizable: false, maximizable: false, @@ -126,7 +126,7 @@ initializeWindows = function() { id: 'uploadLimitPage', title: "QBT_TR(Torrent Upload Speed Limiting)QBT_TR", loadMethod: 'iframe', - contentURL: 'uploadlimit.html?hash=' + hash, + contentURL: 'uploadlimit.html?hashes=' + h.join("|"), scrollbars: false, resizable: false, maximizable: false, diff --git a/src/webui/www/public/scripts/parametrics.js b/src/webui/www/public/scripts/parametrics.js index 07b7ca379..f87d0ec14 100644 --- a/src/webui/www/public/scripts/parametrics.js +++ b/src/webui/www/public/scripts/parametrics.js @@ -14,7 +14,7 @@ Requires: */ MochaUI.extend({ - addUpLimitSlider: function(hash) { + addUpLimitSlider: function(hashes) { if ($('uplimitSliderarea')) { var windowOptions = MochaUI.Windows.windowOptions; var sliderFirst = true; @@ -31,15 +31,15 @@ MochaUI.extend({ maximum = tmp / 1024. } else { - if (hash == "global") + if (hashes[0] == "global") maximum = 10000; else maximum = 1000; } } - // Get torrent upload limit + // Get torrents upload limit // And create slider - if (hash == 'global') { + if (hashes[0] == 'global') { var up_limit = maximum; if (up_limit < 0) up_limit = 0; maximum = 10000; @@ -69,15 +69,21 @@ MochaUI.extend({ } } else { - var req = new Request({ - url: 'command/getTorrentUpLimit', + var req = new Request.JSON({ + url: 'command/getTorrentsUpLimit', + noCache : true, method: 'post', data: { - hash: hash + hashes: hashes.join('|') }, onSuccess: function(data) { if (data) { - var up_limit = data.toInt(); + var up_limit = data[hashes[0]]; + for(var key in data) + if (up_limit != data[key]) { + up_limit = 0; + break; + } if (up_limit < 0) up_limit = 0; var mochaSlide = new Slider($('uplimitSliderarea'), $('uplimitSliderknob'), { steps: maximum, diff --git a/src/webui/www/public/uploadlimit.html b/src/webui/www/public/uploadlimit.html index 4b63183b3..733ce393c 100644 --- a/src/webui/www/public/uploadlimit.html +++ b/src/webui/www/public/uploadlimit.html @@ -20,10 +20,10 @@
    From 902db2b9382f58a4d1f39d77600424c0223a3a81 Mon Sep 17 00:00:00 2001 From: buinsky Date: Sun, 1 Feb 2015 12:45:37 -0500 Subject: [PATCH 17/17] Group setting torrents download limit --- src/webui/webapplication.cpp | 33 +++++++++++---------- src/webui/webapplication.h | 4 +-- src/webui/www/public/downloadlimit.html | 10 +++---- src/webui/www/public/scripts/mocha-init.js | 4 +-- src/webui/www/public/scripts/parametrics.js | 22 +++++++++----- 5 files changed, 40 insertions(+), 33 deletions(-) diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index c2d55d5f9..ed2d5b36c 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -95,9 +95,9 @@ QMap > WebApplication::initialize ADD_ACTION(command, setGlobalUpLimit); ADD_ACTION(command, setGlobalDlLimit); ADD_ACTION(command, getTorrentsUpLimit); - ADD_ACTION(command, getTorrentDlLimit); + ADD_ACTION(command, getTorrentsDlLimit); ADD_ACTION(command, setTorrentsUpLimit); - ADD_ACTION(command, setTorrentDlLimit); + ADD_ACTION(command, setTorrentsDlLimit); ADD_ACTION(command, alternativeSpeedLimitsEnabled); ADD_ACTION(command, toggleAlternativeSpeedLimits); ADD_ACTION(command, toggleSequentialDownload); @@ -462,15 +462,12 @@ void WebApplication::action_command_getTorrentsUpLimit() print(btjson::getTorrentsRatesLimits(hashes, false), Http::CONTENT_TYPE_JS); } -void WebApplication::action_command_getTorrentDlLimit() +void WebApplication::action_command_getTorrentsDlLimit() { CHECK_URI(0); - CHECK_PARAMETERS("hash"); - QString hash = request().posts["hash"]; - QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); - - if (h.is_valid()) - print(QByteArray::number(h.download_limit())); + CHECK_PARAMETERS("hashes"); + QStringList hashes = request().posts["hashes"].split("|"); + print(btjson::getTorrentsRatesLimits(hashes, true), Http::CONTENT_TYPE_JS); } void WebApplication::action_command_setTorrentsUpLimit() @@ -490,17 +487,21 @@ void WebApplication::action_command_setTorrentsUpLimit() } } -void WebApplication::action_command_setTorrentDlLimit() +void WebApplication::action_command_setTorrentsDlLimit() { CHECK_URI(0); - CHECK_PARAMETERS("hash" << "limit"); - QString hash = request().posts["hash"]; - qlonglong limit = request().posts["limit"].toLongLong(); - if (limit == 0) limit = -1; - QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); + CHECK_PARAMETERS("hashes" << "limit"); - if (h.is_valid()) + qlonglong limit = request().posts["limit"].toLongLong(); + if (limit == 0) + limit = -1; + + QStringList hashes = request().posts["hashes"].split("|"); + foreach (const QString &hash, hashes) { + QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); + if (h.is_valid()) h.set_download_limit(limit); + } } void WebApplication::action_command_toggleAlternativeSpeedLimits() diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 96b4be35c..3100cb454 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -69,9 +69,9 @@ private: void action_command_setGlobalUpLimit(); void action_command_setGlobalDlLimit(); void action_command_getTorrentsUpLimit(); - void action_command_getTorrentDlLimit(); + void action_command_getTorrentsDlLimit(); void action_command_setTorrentsUpLimit(); - void action_command_setTorrentDlLimit(); + void action_command_setTorrentsDlLimit(); void action_command_alternativeSpeedLimitsEnabled(); void action_command_toggleAlternativeSpeedLimits(); void action_command_toggleSequentialDownload(); diff --git a/src/webui/www/public/downloadlimit.html b/src/webui/www/public/downloadlimit.html index ca1e521ab..43a54d97c 100644 --- a/src/webui/www/public/downloadlimit.html +++ b/src/webui/www/public/downloadlimit.html @@ -20,10 +20,10 @@
    diff --git a/src/webui/www/public/scripts/mocha-init.js b/src/webui/www/public/scripts/mocha-init.js index 050be0eb3..81b746f96 100644 --- a/src/webui/www/public/scripts/mocha-init.js +++ b/src/webui/www/public/scripts/mocha-init.js @@ -186,7 +186,7 @@ initializeWindows = function() { id: 'downloadLimitPage', title: "QBT_TR(Global Download Speed Limit)QBT_TR", loadMethod: 'iframe', - contentURL: 'downloadlimit.html?hash=global', + contentURL: 'downloadlimit.html?hashes=global', scrollbars: false, resizable: false, maximizable: false, @@ -205,7 +205,7 @@ initializeWindows = function() { id: 'downloadLimitPage', title: "QBT_TR(Torrent Download Speed Limiting)QBT_TR", loadMethod: 'iframe', - contentURL: 'downloadlimit.html?hash=' + hash, + contentURL: 'downloadlimit.html?hashes=' + h.join("|"), scrollbars: false, resizable: false, maximizable: false, diff --git a/src/webui/www/public/scripts/parametrics.js b/src/webui/www/public/scripts/parametrics.js index f87d0ec14..cda3272f5 100644 --- a/src/webui/www/public/scripts/parametrics.js +++ b/src/webui/www/public/scripts/parametrics.js @@ -118,7 +118,7 @@ MochaUI.extend({ } }, - addDlLimitSlider: function(hash) { + addDlLimitSlider: function(hashes) { if ($('dllimitSliderarea')) { var windowOptions = MochaUI.Windows.windowOptions; var sliderFirst = true; @@ -135,15 +135,15 @@ MochaUI.extend({ maximum = tmp / 1024. } else { - if (hash == "global") + if (hashes[0] == "global") maximum = 10000; else maximum = 1000; } } - // Get torrent download limit + // Get torrents download limit // And create slider - if (hash == "global") { + if (hashes[0] == 'global') { var dl_limit = maximum; if (dl_limit < 0) dl_limit = 0; maximum = 10000; @@ -173,15 +173,21 @@ MochaUI.extend({ } } else { - var req = new Request({ - url: 'command/getTorrentDlLimit', + var req = new Request.JSON({ + url: 'command/getTorrentsDlLimit', + noCache : true, method: 'post', data: { - hash: hash + hashes: hashes.join('|') }, onSuccess: function(data) { if (data) { - var dl_limit = data.toInt(); + var dl_limit = data[hashes[0]]; + for(var key in data) + if (dl_limit != data[key]) { + dl_limit = 0; + break; + } if (dl_limit < 0) dl_limit = 0; var mochaSlide = new Slider($('dllimitSliderarea'), $('dllimitSliderknob'), { steps: maximum,