diff --git a/src/base/utils/misc.cpp b/src/base/utils/misc.cpp index 0a58db693..e822656bc 100644 --- a/src/base/utils/misc.cpp +++ b/src/base/utils/misc.cpp @@ -86,7 +86,9 @@ static struct { const char *source; const char *comment; } units[] = { QT_TRANSLATE_NOOP3("misc", "KiB", "kibibytes (1024 bytes)"), QT_TRANSLATE_NOOP3("misc", "MiB", "mebibytes (1024 kibibytes)"), QT_TRANSLATE_NOOP3("misc", "GiB", "gibibytes (1024 mibibytes)"), - QT_TRANSLATE_NOOP3("misc", "TiB", "tebibytes (1024 gibibytes)") + QT_TRANSLATE_NOOP3("misc", "TiB", "tebibytes (1024 gibibytes)"), + QT_TRANSLATE_NOOP3("misc", "PiB", "pebibytes (1024 tebibytes)"), + QT_TRANSLATE_NOOP3("misc", "EiB", "exbibytes (1024 pebibytes)") }; #ifndef DISABLE_GUI @@ -318,30 +320,58 @@ QString Utils::Misc::pythonVersionComplete() { return version; } -// return best userfriendly storage unit (B, KiB, MiB, GiB, TiB) +QString Utils::Misc::unitString(Utils::Misc::SizeUnit unit) +{ + return QCoreApplication::translate("misc", + units[static_cast(unit)].source, units[static_cast(unit)].comment); +} + +// return best userfriendly storage unit (B, KiB, MiB, GiB, TiB, ...) // use Binary prefix standards from IEC 60027-2 // see http://en.wikipedia.org/wiki/Kilobyte // value must be given in bytes // to send numbers instead of strings with suffixes -QString Utils::Misc::friendlyUnit(qreal val, bool is_speed) +bool Utils::Misc::friendlyUnit(qint64 sizeInBytes, qreal &val, Utils::Misc::SizeUnit &unit) { - if (val < 0) - return QCoreApplication::translate("misc", "Unknown", "Unknown (size)"); + if (sizeInBytes < 0) return false; + int i = 0; - while(val >= 1024. && i < 4) { - val /= 1024.; + qreal rawVal = static_cast(sizeInBytes); + + while ((rawVal >= 1024.) && (i <= static_cast(SizeUnit::ExbiByte))) { + rawVal /= 1024.; ++i; } + val = rawVal; + unit = static_cast(i); + return true; +} + +QString Utils::Misc::friendlyUnit(qint64 bytesValue, bool isSpeed) +{ + SizeUnit unit; + qreal friendlyVal; + if (!friendlyUnit(bytesValue, friendlyVal, unit)) { + return QCoreApplication::translate("misc", "Unknown", "Unknown (size)"); + } QString ret; - if (i == 0) - ret = QString::number((long)val) + " " + QCoreApplication::translate("misc", units[0].source, units[0].comment); + if (unit == SizeUnit::Byte) + ret = QString::number(bytesValue) + " " + unitString(unit); else - ret = Utils::String::fromDouble(val, 1) + " " + QCoreApplication::translate("misc", units[i].source, units[i].comment); - if (is_speed) + ret = Utils::String::fromDouble(friendlyVal, 1) + " " + unitString(unit); + if (isSpeed) ret += QCoreApplication::translate("misc", "/s", "per second"); return ret; } +qlonglong Utils::Misc::sizeInBytes(qreal size, Utils::Misc::SizeUnit unit) +{ + for (int i = 0; i < static_cast(unit); ++i) { + size *= 1024; + } + return size; +} + bool Utils::Misc::isPreviewable(const QString& extension) { static QSet multimedia_extensions; diff --git a/src/base/utils/misc.h b/src/base/utils/misc.h index 4831dc23d..f942fa860 100644 --- a/src/base/utils/misc.h +++ b/src/base/utils/misc.h @@ -48,6 +48,22 @@ namespace Utils { namespace Misc { + // use binary prefix standards from IEC 60027-2 + // see http://en.wikipedia.org/wiki/Kilobyte + enum class SizeUnit + { + Byte, // 1024^0, + KibiByte, // 1024^1, + MebiByte, // 1024^2, + GibiByte, // 1024^3, + TebiByte, // 1024^4, + PebiByte, // 1024^5, + ExbiByte // 1024^6, + // int64 is used for sizes and thus the next units can not be handled + // ZebiByte, // 1024^7, + // YobiByte, // 1024^8 + }; + QString parseHtmlLinks(const QString &raw_text); bool isUrl(const QString &s); @@ -64,11 +80,15 @@ namespace Utils int pythonVersion(); QString pythonExecutable(); QString pythonVersionComplete(); - // return best userfriendly storage unit (B, KiB, MiB, GiB, TiB) - // use Binary prefix standards from IEC 60027-2 - // see http://en.wikipedia.org/wiki/Kilobyte + + QString unitString(SizeUnit unit); + + // return best user friendly storage unit (B, KiB, MiB, GiB, TiB) // value must be given in bytes - QString friendlyUnit(qreal val, bool is_speed = false); + bool friendlyUnit(qint64 sizeInBytes, qreal& val, SizeUnit& unit); + QString friendlyUnit(qint64 bytesValue, bool isSpeed = false); + qint64 sizeInBytes(qreal size, SizeUnit unit); + bool isPreviewable(const QString& extension); // Take a number of seconds and return an user-friendly diff --git a/src/gui/gui.pri b/src/gui/gui.pri index c92e27571..2f8e602fb 100644 --- a/src/gui/gui.pri +++ b/src/gui/gui.pri @@ -113,6 +113,7 @@ FORMS += \ $$PWD/torrentcreatordlg.ui \ $$PWD/search/searchwidget.ui \ $$PWD/search/pluginselectdlg.ui \ - $$PWD/search/pluginsourcedlg.ui + $$PWD/search/pluginsourcedlg.ui \ + $$PWD/search/searchtab.ui RESOURCES += $$PWD/about.qrc diff --git a/src/gui/search/searchlistdelegate.cpp b/src/gui/search/searchlistdelegate.cpp index 95250d99d..a8ae79dca 100644 --- a/src/gui/search/searchlistdelegate.cpp +++ b/src/gui/search/searchlistdelegate.cpp @@ -56,7 +56,7 @@ void SearchListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op QItemDelegate::drawBackground(painter, opt, index); QItemDelegate::drawDisplay(painter, opt, option.rect, (index.data().toLongLong() >= 0) ? index.data().toString() : tr("Unknown")); break; - case SearchSortModel::LEECHS: + case SearchSortModel::LEECHES: QItemDelegate::drawBackground(painter, opt, index); QItemDelegate::drawDisplay(painter, opt, option.rect, (index.data().toLongLong() >= 0) ? index.data().toString() : tr("Unknown")); break; diff --git a/src/gui/search/searchsortmodel.cpp b/src/gui/search/searchsortmodel.cpp index 4245fe389..22cc20fd6 100644 --- a/src/gui/search/searchsortmodel.cpp +++ b/src/gui/search/searchsortmodel.cpp @@ -29,26 +29,136 @@ #include "searchsortmodel.h" SearchSortModel::SearchSortModel(QObject *parent) - : QSortFilterProxyModel(parent) + : base(parent) + , m_isNameFilterEnabled(false) + , m_minSeeds(0) + , m_maxSeeds(-1) + , m_minLeeches(0) + , m_maxLeeches(-1) + , m_minSize(0) + , m_maxSize(-1) { } +void SearchSortModel::enableNameFilter(bool enable) +{ + m_isNameFilterEnabled = enable; +} + +void SearchSortModel::setNameFilter(const QString &searchTerm) +{ + m_searchTerm = searchTerm; + if (searchTerm.length() > 2 + && searchTerm.startsWith(QLatin1Char('"')) && searchTerm.endsWith(QLatin1Char('"'))) { + m_searchTermWords = QStringList(m_searchTerm.mid(1, m_searchTerm.length() - 2)); + } + else { + m_searchTermWords = searchTerm.split(QLatin1Char(' '), QString::SkipEmptyParts); + } +} + +void SearchSortModel::setSizeFilter(qint64 minSize, qint64 maxSize) +{ + m_minSize = std::max(static_cast(0), minSize); + m_maxSize = std::max(static_cast(-1), maxSize); +} + +void SearchSortModel::setSeedsFilter(int minSeeds, int maxSeeds) +{ + m_minSeeds = std::max(0, minSeeds); + m_maxSeeds = std::max(-1, maxSeeds); +} + +void SearchSortModel::setLeechesFilter(int minLeeches, int maxLeeches) +{ + m_minLeeches = std::max(0, minLeeches); + m_maxLeeches = std::max(-1, maxLeeches); +} + +bool SearchSortModel::isNameFilterEnabled() const +{ + return m_isNameFilterEnabled; +} + +QString SearchSortModel::searchTerm() const +{ + return m_searchTerm; +} + +int SearchSortModel::minSeeds() const +{ + return m_minSeeds; +} + +int SearchSortModel::maxSeeds() const +{ + return m_maxSeeds; +} + +qint64 SearchSortModel::minSize() const +{ + return m_minSize; +} + +qint64 SearchSortModel::maxSize() const +{ + return m_maxSize; +} + bool SearchSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { if ((sortColumn() == NAME) || (sortColumn() == ENGINE_URL)) { QVariant vL = sourceModel()->data(left); QVariant vR = sourceModel()->data(right); if (!(vL.isValid() && vR.isValid())) - return QSortFilterProxyModel::lessThan(left, right); + return base::lessThan(left, right); Q_ASSERT(vL.isValid()); Q_ASSERT(vR.isValid()); bool res = false; if (Utils::String::naturalSort(vL.toString(), vR.toString(), res)) return res; - - return QSortFilterProxyModel::lessThan(left, right); } - return QSortFilterProxyModel::lessThan(left, right); + return base::lessThan(left, right); +} + +bool SearchSortModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + const QAbstractItemModel* const sourceModel = this->sourceModel(); + if (m_isNameFilterEnabled && !m_searchTerm.isEmpty()) { + QString name = sourceModel->data(sourceModel->index(sourceRow, NAME, sourceParent)).toString(); + for (const QString& word: m_searchTermWords) { + int i = name.indexOf(word, 0, Qt::CaseInsensitive); + if (i == -1) { + return false; + } + } + } + + if (m_minSize > 0 || m_maxSize >= 0) { + qlonglong size = sourceModel->data(sourceModel->index(sourceRow, SIZE, sourceParent)).toLongLong(); + if ((m_minSize > 0 && size < m_minSize) + || (m_maxSize > 0 && size > m_maxSize)) { + return false; + } + } + + if (m_minSeeds > 0 || m_maxSeeds >= 0) { + int seeds = sourceModel->data(sourceModel->index(sourceRow, SEEDS, sourceParent)).toInt(); + if ((m_minSeeds > 0 && seeds < m_minSeeds) + || (m_maxSeeds > 0 && seeds > m_maxSeeds)) { + return false; + } + } + + if (m_minLeeches > 0 || m_maxLeeches >= 0) { + int leeches = sourceModel->data(sourceModel->index(sourceRow, LEECHES, sourceParent)).toInt(); + if ((m_minLeeches > 0 && leeches < m_minLeeches) + || (m_maxLeeches > 0 && leeches > m_maxLeeches)) { + return false; + } + } + + return base::filterAcceptsRow(sourceRow, sourceParent); } diff --git a/src/gui/search/searchsortmodel.h b/src/gui/search/searchsortmodel.h index 6f85fa449..e3da49de8 100644 --- a/src/gui/search/searchsortmodel.h +++ b/src/gui/search/searchsortmodel.h @@ -30,17 +30,20 @@ #define SEARCHSORTMODEL_H #include +#include #include "base/utils/string.h" class SearchSortModel: public QSortFilterProxyModel { + using base = QSortFilterProxyModel; + public: enum SearchColumn { NAME, SIZE, SEEDS, - LEECHS, + LEECHES, ENGINE_URL, DL_LINK, DESC_LINK, @@ -49,8 +52,45 @@ public: explicit SearchSortModel(QObject *parent = 0); + void enableNameFilter(bool enabled); + void setNameFilter(const QString& searchTerm = QString()); + + //! \brief Sets parameters for filtering by size + //! \param minSize minimal size in bytes + //! \param maxSize maximal size in bytes, negative value to disable filtering + void setSizeFilter(qint64 minSize, qint64 maxSize); + + //! \brief Sets parameters for filtering by seeds number + //! \param minSeeds minimal number of seeders + //! \param maxSeeds maximal number of seeders, negative value to disable filtering + void setSeedsFilter(int minSeeds, int maxSeeds); + + //! \brief Sets parameters for filtering by leeches number + //! \param minLeeches minimal number of leechers + //! \param maxLeeches maximal number of leechers, negative value to disable filtering + void setLeechesFilter(int minLeeches, int maxLeeches); + + bool isNameFilterEnabled() const; + + QString searchTerm() const; + + int minSeeds() const; + int maxSeeds() const; + + qint64 minSize() const; + qint64 maxSize() const; + protected: - virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + +private: + bool m_isNameFilterEnabled; + QString m_searchTerm; + QStringList m_searchTermWords; + int m_minSeeds, m_maxSeeds; + int m_minLeeches, m_maxLeeches; + qint64 m_minSize, m_maxSize; }; #endif // SEARCHSORTMODEL_H diff --git a/src/gui/search/searchtab.cpp b/src/gui/search/searchtab.cpp index 8717924ea..c01140041 100644 --- a/src/gui/search/searchtab.cpp +++ b/src/gui/search/searchtab.cpp @@ -29,6 +29,7 @@ */ #include +#include #include #include #include @@ -41,16 +42,28 @@ #include "base/utils/misc.h" #include "base/preferences.h" +#include "base/settingsstorage.h" +#include "guiiconprovider.h" #include "searchsortmodel.h" #include "searchlistdelegate.h" #include "searchwidget.h" #include "searchtab.h" +namespace +{ +#define SETTINGS_KEY(name) "Search/" name + + const QString KEY_FILTER_MODE_SETTING_NAME = SETTINGS_KEY("FilteringMode"); +} + SearchTab::SearchTab(SearchWidget *parent) : QWidget(parent) , m_parent(parent) { - m_box = new QVBoxLayout(this); + setupUi(this); + retranslateUi(this); + + m_box = static_cast(this->layout()); m_resultsLbl = new QLabel(this); m_resultsBrowser = new QTreeView(this); #ifdef QBT_USES_QT5 @@ -65,14 +78,12 @@ SearchTab::SearchTab(SearchWidget *parent) m_box->addWidget(m_resultsLbl); m_box->addWidget(m_resultsBrowser); - setLayout(m_box); - // Set Search results list model m_searchListModel = new QStandardItemModel(0, SearchSortModel::NB_SEARCH_COLUMNS, this); m_searchListModel->setHeaderData(SearchSortModel::NAME, Qt::Horizontal, tr("Name", "i.e: file name")); m_searchListModel->setHeaderData(SearchSortModel::SIZE, Qt::Horizontal, tr("Size", "i.e: file size")); m_searchListModel->setHeaderData(SearchSortModel::SEEDS, Qt::Horizontal, tr("Seeders", "i.e: Number of full sources")); - m_searchListModel->setHeaderData(SearchSortModel::LEECHS, Qt::Horizontal, tr("Leechers", "i.e: Number of partial sources")); + m_searchListModel->setHeaderData(SearchSortModel::LEECHES, Qt::Horizontal, tr("Leechers", "i.e: Number of partial sources")); m_searchListModel->setHeaderData(SearchSortModel::ENGINE_URL, Qt::Horizontal, tr("Search engine")); m_proxyModel = new SearchSortModel(this); @@ -99,6 +110,22 @@ SearchTab::SearchTab(SearchWidget *parent) // Sort by Seeds m_resultsBrowser->sortByColumn(SearchSortModel::SEEDS, Qt::DescendingOrder); + + fillFilterComboBoxes(); + + updateFilter(); + + connect(filterMode, SIGNAL(currentIndexChanged(int)), this, SLOT(updateFilter())); + connect(minSeeds, SIGNAL(editingFinished()), this, SLOT(updateFilter())); + connect(minSeeds, SIGNAL(valueChanged(int)), this, SLOT(updateFilter())); + connect(maxSeeds, SIGNAL(editingFinished()), this, SLOT(updateFilter())); + connect(maxSeeds, SIGNAL(valueChanged(int)), this, SLOT(updateFilter())); + connect(minSize, SIGNAL(editingFinished()), this, SLOT(updateFilter())); + connect(minSize, SIGNAL(valueChanged(double)), this, SLOT(updateFilter())); + connect(maxSize, SIGNAL(editingFinished()), this, SLOT(updateFilter())); + connect(maxSize, SIGNAL(valueChanged(double)), this, SLOT(updateFilter())); + connect(minSizeUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(updateFilter())); + connect(maxSizeUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(updateFilter())); } void SearchTab::downloadSelectedItem(const QModelIndex &index) @@ -123,24 +150,18 @@ bool SearchTab::loadColWidthResultsList() return false; unsigned int listSize = widthList.size(); - for (unsigned int i = 0; i < listSize; ++i) { + for (unsigned int i = 0; i < listSize; ++i) m_resultsBrowser->header()->resizeSection(i, widthList.at(i).toInt()); - } return true; } -QLabel* SearchTab::getCurrentLabel() const -{ - return m_resultsLbl; -} - QTreeView* SearchTab::getCurrentTreeView() const { return m_resultsBrowser; } -QSortFilterProxyModel* SearchTab::getCurrentSearchListProxy() const +SearchSortModel* SearchTab::getCurrentSearchListProxy() const { return m_proxyModel; } @@ -154,19 +175,128 @@ QStandardItemModel* SearchTab::getCurrentSearchListModel() const void SearchTab::setRowColor(int row, QString color) { m_proxyModel->setDynamicSortFilter(false); - for (int i = 0; i < m_proxyModel->columnCount(); ++i) { + for (int i = 0; i < m_proxyModel->columnCount(); ++i) m_proxyModel->setData(m_proxyModel->index(row, i), QVariant(QColor(color)), Qt::ForegroundRole); - } m_proxyModel->setDynamicSortFilter(true); } -QString SearchTab::status() const +SearchTab::Status SearchTab::status() const { return m_status; } -void SearchTab::setStatus(const QString &value) +void SearchTab::setStatus(Status value) { m_status = value; + setStatusTip(statusText(value)); + const int thisTabIndex = m_parent->searchTabs()->indexOf(this); + m_parent->searchTabs()->setTabToolTip(thisTabIndex, statusTip()); + m_parent->searchTabs()->setTabIcon(thisTabIndex, GuiIconProvider::instance()->getIcon(statusIconName(value))); +} + +void SearchTab::updateResultsCount() +{ + const int totalResults = getCurrentSearchListModel() ? getCurrentSearchListModel()->rowCount(QModelIndex()) : 0; + const int filteredResults = getCurrentSearchListProxy() ? getCurrentSearchListProxy()->rowCount(QModelIndex()) : totalResults; + m_resultsLbl->setText(tr("Results (showing %1 out of %2):", "i.e: Search results") + .arg(filteredResults).arg(totalResults)); +} + +void SearchTab::updateFilter() +{ + using Utils::Misc::SizeUnit; + SearchSortModel* filterModel = getCurrentSearchListProxy(); + filterModel->enableNameFilter(filteringMode() == OnlyNames); + // we update size and seeds filter parameters in the model even if they are disabled + // because we need to read them from the model when search tabs switch + filterModel->setSeedsFilter(minSeeds->value(), maxSeeds->value()); + filterModel->setSizeFilter( + sizeInBytes(minSize->value(), static_cast(minSizeUnit->currentIndex())), + sizeInBytes(maxSize->value(), static_cast(maxSizeUnit->currentIndex()))); + + SettingsStorage::instance()->storeValue(KEY_FILTER_MODE_SETTING_NAME, + filterMode->itemData(filterMode->currentIndex())); + filterModel->invalidate(); + updateResultsCount(); +} + +void SearchTab::fillFilterComboBoxes() +{ + using Utils::Misc::SizeUnit; + QStringList unitStrings; + unitStrings.append(unitString(SizeUnit::Byte)); + unitStrings.append(unitString(SizeUnit::KibiByte)); + unitStrings.append(unitString(SizeUnit::MebiByte)); + unitStrings.append(unitString(SizeUnit::GibiByte)); + unitStrings.append(unitString(SizeUnit::TebiByte)); + unitStrings.append(unitString(SizeUnit::PebiByte)); + unitStrings.append(unitString(SizeUnit::ExbiByte)); + + minSizeUnit->clear(); + maxSizeUnit->clear(); + minSizeUnit->addItems(unitStrings); + maxSizeUnit->addItems(unitStrings); + + minSize->setValue(0); + minSizeUnit->setCurrentIndex(static_cast(SizeUnit::MebiByte)); + + maxSize->setValue(-1); + maxSizeUnit->setCurrentIndex(static_cast(SizeUnit::TebiByte)); + + filterMode->clear(); + + QMetaEnum nameFilteringModeEnum = + this->metaObject()->enumerator(this->metaObject()->indexOfEnumerator("NameFilteringMode")); + + filterMode->addItem(tr("Torrent names only"), nameFilteringModeEnum.valueToKey(OnlyNames)); + filterMode->addItem(tr("Everywhere"), nameFilteringModeEnum.valueToKey(Everywhere)); + + QVariant selectedMode = SettingsStorage::instance()->loadValue( + KEY_FILTER_MODE_SETTING_NAME, nameFilteringModeEnum.valueToKey(OnlyNames)); + int index = filterMode->findData(selectedMode); + filterMode->setCurrentIndex(index == -1 ? 0 : index); +} + +QString SearchTab::statusText(SearchTab::Status st) +{ + switch (st) { + case Status::Ongoing: + return tr("Searching..."); + case Status::Finished: + return tr("Search has finished"); + case Status::Aborted: + return tr("Search aborted"); + case Status::Error: + return tr("An error occurred during search..."); + case Status::NoResults: + return tr("Search returned no results"); + default: + return QString(); + } +} + +QString SearchTab::statusIconName(SearchTab::Status st) +{ + switch (st) { + case Status::Ongoing: + return QLatin1String("task-ongoing"); + case Status::Finished: + return QLatin1String("task-complete"); + case Status::Aborted: + return QLatin1String("task-reject"); + case Status::Error: + return QLatin1String("task-attention"); + case Status::NoResults: + return QLatin1String("task-attention"); + default: + return QString(); + } +} + +SearchTab::NameFilteringMode SearchTab::filteringMode() const +{ + QMetaEnum metaEnum = + this->metaObject()->enumerator(this->metaObject()->indexOfEnumerator("NameFilteringMode")); + return static_cast(metaEnum.keyToValue(filterMode->itemData(filterMode->currentIndex()).toByteArray())); } diff --git a/src/gui/search/searchtab.h b/src/gui/search/searchtab.h index 5d92afeb5..cc341d90a 100644 --- a/src/gui/search/searchtab.h +++ b/src/gui/search/searchtab.h @@ -31,42 +31,71 @@ #ifndef SEARCHTAB_H #define SEARCHTAB_H -#include +#include // I don't know why is not enought for Qt's 4.8.7 moc +#include "ui_searchtab.h" + +#define ENGINE_URL_COLUMN 4 +#define URL_COLUMN 5 class QLabel; +class QModelIndex; class QTreeView; class QHeaderView; class QStandardItemModel; -class QSortFilterProxyModel; -class QModelIndex; class QVBoxLayout; class SearchSortModel; class SearchListDelegate; class SearchWidget; -class SearchTab: public QWidget +class SearchTab: public QWidget, private Ui::SearchTab { Q_OBJECT public: - explicit SearchTab(SearchWidget *m_parent); - QLabel* getCurrentLabel() const; + enum NameFilteringMode + { + Everywhere, + OnlyNames + }; + + Q_ENUMS(NameFilteringMode) + + explicit SearchTab(SearchWidget *parent); + QStandardItemModel* getCurrentSearchListModel() const; - QSortFilterProxyModel* getCurrentSearchListProxy() const; + SearchSortModel* getCurrentSearchListProxy() const; QTreeView* getCurrentTreeView() const; QHeaderView* header() const; - QString status() const; bool loadColWidthResultsList(); void setRowColor(int row, QString color); - void setStatus(const QString &value); + + enum class Status + { + Ongoing, + Finished, + Error, + Aborted, + NoResults + }; + + void setStatus(Status value); + Status status() const; + + void updateResultsCount(); private slots: void downloadSelectedItem(const QModelIndex &index); + void updateFilter(); private: + void fillFilterComboBoxes(); + NameFilteringMode filteringMode() const; + static QString statusText(Status st); + static QString statusIconName(Status st); + QVBoxLayout *m_box; QLabel *m_resultsLbl; QTreeView *m_resultsBrowser; @@ -74,8 +103,9 @@ private: SearchSortModel *m_proxyModel; SearchListDelegate *m_searchDelegate; SearchWidget *m_parent; - QString m_status; + Status m_status; }; -#endif // SEARCHTAB_H +Q_DECLARE_METATYPE(SearchTab::NameFilteringMode) +#endif // SEARCHTAB_H diff --git a/src/gui/search/searchtab.ui b/src/gui/search/searchtab.ui new file mode 100644 index 000000000..b4e76c0b5 --- /dev/null +++ b/src/gui/search/searchtab.ui @@ -0,0 +1,240 @@ + + + SearchTab + + + + 0 + 0 + 1216 + 364 + + + + Form + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::NoFocus + + + Search in: + + + filterMode + + + + + + + <html><head/><body><p>Some search engines search in torrent description and in torrent file names too. Whether such results will be shown in the list below is controlled by this mode.</p><p><span style=" font-weight:600;">Everywhere </span>disables filtering and shows everyhing returned by the search engines.</p><p><span style=" font-weight:600;">Torrent names only</span> shows only torrents whose names match the search query.</p></body></html> + + + + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 12 + 20 + + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Set minimal and maximal allowed number of seeders</p></body></html> + + + Seeds: + + + + + + + <html><head/><body><p>Minimal number of seeds</p></body></html> + + + 0 + + + 1000 + + + 0 + + + + + + + + 0 + 0 + + + + to + + + + + + + <html><head/><body><p>Maximal number of seeds</p></body></html> + + + + + + + + + -1 + + + 1000 + + + -1 + + + + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 12 + 20 + + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Set minimal and maximal allowed size of a torrent</p></body></html> + + + Size: + + + + + + + + + <html><head/><body><p>Minimal torrent size</p></body></html> + + + 0.000000000000000 + + + 1000.000000000000000 + + + 0.000000000000000 + + + + + + + QComboBox::AdjustToContents + + + + + + + + + to + + + + + + + + + <html><head/><body><p>Maximal torrent size</p></body></html> + + + + + + -1.000000000000000 + + + 1000.000000000000000 + + + 1000.000000000000000 + + + + + + + -1 + + + QComboBox::AdjustToContents + + + + + + + + + + + + diff --git a/src/gui/search/searchwidget.cpp b/src/gui/search/searchwidget.cpp index 876bfcddb..63008b11d 100644 --- a/src/gui/search/searchwidget.cpp +++ b/src/gui/search/searchwidget.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #ifdef Q_OS_WIN @@ -74,7 +75,6 @@ SearchWidget::SearchWidget(MainWindow *mainWindow) , m_mainWindow(mainWindow) , m_isNewQueryString(false) , m_noSearchResults(true) - , m_nbSearchResults(0) { setupUi(this); @@ -82,6 +82,26 @@ SearchWidget::SearchWidget(MainWindow *mainWindow) searchBarLayout->insertWidget(0, m_searchPattern); connect(m_searchPattern, SIGNAL(returnPressed()), searchButton, SLOT(click())); + QString searchPatternHint; + QTextStream stream(&searchPatternHint, QIODevice::WriteOnly); + + stream << "

" + << tr("A phrase to search for.") << "
" + << tr("Spaces in a search term may be protected by double quotes.") + << "

" + << tr("Example:", "Search phrase example") + << "
" + << tr("foo bar: search for foo and bar", + "Search phrase example, illustrates quotes usage, a pair of " + "space delimited words, individal words are highlighted") + << "
" + << tr(""foo bar": search for foo bar", + "Search phrase example, illustrates quotes usage, double quoted" + "pair of space delimited words, the whole pair is highlighted") + << "

" << flush; + + m_searchPattern->setToolTip(searchPatternHint); + // Icons searchButton->setIcon(GuiIconProvider::instance()->getIcon("edit-find")); downloadButton->setIcon(GuiIconProvider::instance()->getIcon("download")); @@ -158,7 +178,6 @@ void SearchWidget::tab_changed(int t) goToDescBtn->setEnabled(false); copyURLBtn->setEnabled(false); } - searchStatus->setText(m_currentSearchTab->status()); } } @@ -187,6 +206,11 @@ void SearchWidget::giveFocusToSearchInput() m_searchPattern->setFocus(); } +QTabWidget *SearchWidget::searchTabs() const +{ + return tabWidget; +} + // Function called when we click on search button void SearchWidget::on_searchButton_clicked() { @@ -222,6 +246,7 @@ void SearchWidget::on_searchButton_clicked() tabName.replace(QRegExp("&{1}"), "&&"); tabWidget->addTab(m_currentSearchTab, tabName); tabWidget->setCurrentWidget(m_currentSearchTab); + m_currentSearchTab->getCurrentSearchListProxy()->setNameFilter(pattern); QStringList plugins; if (selectedPlugin() == "all") plugins = m_searchEngine->allPlugins(); @@ -233,10 +258,9 @@ void SearchWidget::on_searchButton_clicked() // Update SearchEngine widgets m_noSearchResults = true; - m_nbSearchResults = 0; // Changing the text of the current label - m_activeSearchTab->getCurrentLabel()->setText(tr("Results (%1):", "i.e: Search results").arg(0)); + m_activeSearchTab->updateResultsCount(); // Launch search m_searchEngine->startSearch(pattern, selectedCategory(), plugins); @@ -268,9 +292,7 @@ void SearchWidget::downloadTorrent(QString url) void SearchWidget::searchStarted() { // Update SearchEngine widgets - m_activeSearchTab->setStatus(tr("Searching...")); - searchStatus->setText(m_currentSearchTab->status()); - searchStatus->repaint(); + m_activeSearchTab->setStatus(SearchTab::Status::Ongoing); searchButton->setText(tr("Stop")); } @@ -285,13 +307,12 @@ void SearchWidget::searchFinished(bool cancelled) if (m_activeSearchTab.isNull()) return; // The active tab was closed if (cancelled) - m_activeSearchTab->setStatus(tr("Search aborted")); + m_activeSearchTab->setStatus(SearchTab::Status::Aborted); else if (m_noSearchResults) - m_activeSearchTab->setStatus(tr("Search returned no results")); + m_activeSearchTab->setStatus(SearchTab::Status::NoResults); else - m_activeSearchTab->setStatus(tr("Search has finished")); + m_activeSearchTab->setStatus(SearchTab::Status::Finished); - searchStatus->setText(m_currentSearchTab->status()); m_activeSearchTab = 0; searchButton->setText(tr("Search")); } @@ -304,9 +325,9 @@ void SearchWidget::searchFailed() if (m_activeSearchTab.isNull()) return; // The active tab was closed #ifdef Q_OS_WIN - m_activeSearchTab->setStatus(tr("Search aborted")); + m_activeSearchTab->setStatus(SearchTab::Status::Aborted); #else - m_activeSearchTab->setStatus(tr("An error occurred during search...")); + m_activeSearchTab->setStatus(SearchTab::Status::Error); #endif } @@ -331,14 +352,13 @@ void SearchWidget::appendSearchResults(const QList &results) curModel->setData(curModel->index(row, SearchSortModel::NAME), result.fileName); // Name curModel->setData(curModel->index(row, SearchSortModel::SIZE), result.fileSize); // Size curModel->setData(curModel->index(row, SearchSortModel::SEEDS), result.nbSeeders); // Seeders - curModel->setData(curModel->index(row, SearchSortModel::LEECHS), result.nbLeechers); // Leechers + curModel->setData(curModel->index(row, SearchSortModel::LEECHES), result.nbLeechers); // Leechers curModel->setData(curModel->index(row, SearchSortModel::ENGINE_URL), result.siteUrl); // Search site URL curModel->setData(curModel->index(row, SearchSortModel::DESC_LINK), result.descrLink); // Description Link } m_noSearchResults = false; - m_nbSearchResults += results.size(); - m_activeSearchTab->getCurrentLabel()->setText(tr("Results (%1):", "i.e: Search results").arg(m_nbSearchResults)); + m_activeSearchTab->updateResultsCount(); // Enable clear & download buttons downloadButton->setEnabled(true); @@ -361,7 +381,6 @@ void SearchWidget::closeTab(int index) if (!m_allTabs.size()) { downloadButton->setEnabled(false); goToDescBtn->setEnabled(false); - searchStatus->setText(tr("Stopped")); copyURLBtn->setEnabled(false); } } diff --git a/src/gui/search/searchwidget.h b/src/gui/search/searchwidget.h index f52742e9e..0098b4b7c 100644 --- a/src/gui/search/searchwidget.h +++ b/src/gui/search/searchwidget.h @@ -55,6 +55,8 @@ public: void downloadTorrent(QString url); void giveFocusToSearchInput(); + QTabWidget* searchTabs() const; + private slots: // Search slots void tab_changed(int); //to prevent the use of the download button when the tab is empty @@ -89,7 +91,6 @@ private: bool m_isNewQueryString; bool m_noSearchResults; QByteArray m_searchResultLineTruncated; - unsigned long m_nbSearchResults; }; #endif // SEARCHWIDGET_H diff --git a/src/gui/search/searchwidget.ui b/src/gui/search/searchwidget.ui index a9c543c5a..e3d539135 100644 --- a/src/gui/search/searchwidget.ui +++ b/src/gui/search/searchwidget.ui @@ -6,14 +6,14 @@ 0 0 - 820 - 453 + 1382 + 669 Search - + @@ -32,67 +32,14 @@ - - - - - - 16777215 - 35 - - - - - 75 - true - - - - Status: - - - - - - - - 200 - 0 - - - - - 16777215 - 35 - - - - - true - - - - Stopped - - - - - - - Qt::Horizontal - - - - 188 - 21 - - - - - - - - + + + + 0 + 1 + + + diff --git a/src/icons.qrc b/src/icons.qrc index b93a209cf..e4ce1a97d 100644 --- a/src/icons.qrc +++ b/src/icons.qrc @@ -306,6 +306,9 @@ icons/oxygen/services.png icons/oxygen/tab-close.png icons/oxygen/task-attention.png + icons/oxygen/task-complete.png + icons/oxygen/task-ongoing.png + icons/oxygen/task-reject.png icons/oxygen/text-plain.png icons/oxygen/tools-report-bug.png icons/oxygen/unavailable.png diff --git a/src/icons/oxygen/task-complete.png b/src/icons/oxygen/task-complete.png new file mode 100644 index 000000000..5ea69d2dd Binary files /dev/null and b/src/icons/oxygen/task-complete.png differ diff --git a/src/icons/oxygen/task-ongoing.png b/src/icons/oxygen/task-ongoing.png new file mode 100644 index 000000000..de6a6fce4 Binary files /dev/null and b/src/icons/oxygen/task-ongoing.png differ diff --git a/src/icons/oxygen/task-reject.png b/src/icons/oxygen/task-reject.png new file mode 100644 index 000000000..9006b077d Binary files /dev/null and b/src/icons/oxygen/task-reject.png differ