diff --git a/src/core/utils/string.cpp b/src/core/utils/string.cpp index 6dffa2615..d0c05329a 100644 --- a/src/core/utils/string.cpp +++ b/src/core/utils/string.cpp @@ -45,7 +45,7 @@ std::string Utils::String::toStdString(const QString &str) } // uses lessThan comparison -bool Utils::String::naturalSort(QString left, QString right, bool &result) +bool Utils::String::naturalSort(const QString &left, const QString &right, bool &result) { // Return value indicates if functions was successful // result argument will contain actual comparison result if function was successful @@ -106,6 +106,83 @@ bool Utils::String::naturalSort(QString left, QString right, bool &result) return false; } +Utils::String::NaturalCompare::NaturalCompare() +{ +#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) +#if defined(Q_OS_WIN) + // Without ICU library, QCollator doesn't support `setNumericMode(true)` on OS older than Win7 + if(SysInfo::windowsVersion() < QSysInfo::WV_WINDOWS7) + return; +#endif + m_collator.setNumericMode(true); + m_collator.setCaseSensitivity(Qt::CaseInsensitive); +#endif +} + +bool Utils::String::NaturalCompare::operator()(const QString &l, const QString &r) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) +#if defined(Q_OS_WIN) + // Without ICU library, QCollator doesn't support `setNumericMode(true)` on OS older than Win7 + if(SysInfo::windowsVersion() < QSysInfo::WV_WINDOWS7) + return lessThan(l, r); +#endif + return (m_collator.compare(l, r) < 0); +#else + return lessThan(l, r); +#endif +} + +bool Utils::String::NaturalCompare::lessThan(const QString &left, const QString &right) +{ + // Return value `false` indicates `right` should go before `left`, otherwise, after + int posL = 0; + int posR = 0; + while (true) { + while (true) { + if (posL == left.size() || posR == right.size()) + return (left.size() < right.size()); // when a shorter string is another string's prefix, shorter string place before longer string + + QChar leftChar = left[posL].toLower(); + QChar rightChar = right[posR].toLower(); + if (leftChar == rightChar) + ; // compare next character + else if (leftChar.isDigit() && rightChar.isDigit()) + break; // Both are digits, break this loop and compare numbers + else + return leftChar < rightChar; + + ++posL; + ++posR; + } + + int startL = posL; + while ((posL < left.size()) && left[posL].isDigit()) + ++posL; +#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) + int numL = left.midRef(startL, posL - startL).toInt(); +#else + int numL = left.mid(startL, posL - startL).toInt(); +#endif + + int startR = posR; + while ((posR < right.size()) && right[posR].isDigit()) + ++posR; +#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) + int numR = right.midRef(startR, posR - startR).toInt(); +#else + int numR = right.mid(startR, posR - startR).toInt(); +#endif + + if (numL != numR) + return (numL < numR); + + // Strings + digits do match and we haven't hit string end + // Do another round + } + return false; +} + // to send numbers instead of strings with suffixes QString Utils::String::fromDouble(double n, int precision) { diff --git a/src/core/utils/string.h b/src/core/utils/string.h index d2863dc7c..36b34e979 100644 --- a/src/core/utils/string.h +++ b/src/core/utils/string.h @@ -31,6 +31,10 @@ #define UTILS_STRING_H #include +#include +#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) +#include +#endif class QString; class QByteArray; @@ -41,12 +45,25 @@ namespace Utils { QString fromStdString(const std::string &str); std::string toStdString(const QString &str); - bool naturalSort(QString left, QString right, bool &result); QString fromDouble(double n, int precision); // Implements constant-time comparison to protect against timing attacks // Taken from https://crackstation.net/hashing-security.htm bool slowEquals(const QByteArray &a, const QByteArray &b); + + bool naturalSort(const QString &left, const QString &right, bool &result); + + class NaturalCompare + { + public: + NaturalCompare(); + bool operator()(const QString &l, const QString &r); + bool lessThan(const QString &left, const QString &right); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) + private: + QCollator m_collator; +#endif + }; } } diff --git a/src/gui/rss/automatedrssdownloader.cpp b/src/gui/rss/automatedrssdownloader.cpp index d4200cf89..b51c7bec1 100644 --- a/src/gui/rss/automatedrssdownloader.cpp +++ b/src/gui/rss/automatedrssdownloader.cpp @@ -43,6 +43,7 @@ #include "guiiconprovider.h" #include "autoexpandabledialog.h" #include "core/utils/fs.h" +#include "core/utils/string.h" AutomatedRssDownloader::AutomatedRssDownloader(const QWeakPointer& manager, QWidget *parent) : QDialog(parent), @@ -262,9 +263,9 @@ void AutomatedRssDownloader::updateRuleDefinitionBox() QDateTime dateTime = rule->lastMatch(); QString lMatch; if (dateTime.isValid()) - lMatch = tr("Last match: %1 days ago").arg(dateTime.daysTo(QDateTime::currentDateTime())); + lMatch = tr("Last Match: %1 days ago").arg(dateTime.daysTo(QDateTime::currentDateTime())); else - lMatch = tr("Last match: Unknown"); + lMatch = tr("Last Match: Unknown"); ui->lblLastMatch->setText(lMatch); updateMustLineValidity(); updateMustNotLineValidity(); @@ -310,10 +311,10 @@ RssDownloadRulePtr AutomatedRssDownloader::getCurrentRule() const void AutomatedRssDownloader::initLabelCombobox() { // Load custom labels - const QStringList customLabels = Preferences::instance()->getTorrentLabels(); - foreach (const QString& label, customLabels) { - ui->comboLabel->addItem(label); - } + QStringList customLabels = Preferences::instance()->getTorrentLabels(); + std::sort(customLabels.begin(), customLabels.end(), Utils::String::NaturalCompare()); + foreach (const QString& l, customLabels) + ui->comboLabel->addItem(l); } void AutomatedRssDownloader::saveEditedRule() diff --git a/src/gui/rss/automatedrssdownloader.ui b/src/gui/rss/automatedrssdownloader.ui index 41ce6742c..54a0fd32b 100644 --- a/src/gui/rss/automatedrssdownloader.ui +++ b/src/gui/rss/automatedrssdownloader.ui @@ -7,11 +7,11 @@ 0 0 816 - 494 + 537 - Automated RSS Downloader + RSS Downloader @@ -23,7 +23,7 @@ - Enable the automated RSS downloader + Enable Automated RSS Downloader @@ -45,31 +45,12 @@ - Download rules + Download Rules - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 40 - 20 - - - - - - - 24 @@ -80,9 +61,6 @@ - - - 24 @@ -91,19 +69,6 @@ - - - - Qt::Horizontal - - - - 40 - 20 - - - - @@ -120,141 +85,78 @@ - Rule definition + Rule Definition - Use regular expressions + Use Regular Expressions - + - Must contain: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Must Contain: - - - - - - - - - - 0 - 0 - - - - - 18 - 18 - - - - - 18 - 18 - - - - - - - - - - Must not contain: + Must Not Contain: - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + Episode Filter: - - - - - - - - - - 0 - 0 - - - - - 18 - 18 - - - - - 18 - 18 - - - - - - - - + + + + + 18 + 18 + + + + + + + + + 18 + 18 + + + + + + - - - - - - - - - 0 - 0 - - - - - 18 - 18 - - - - - 18 - 18 - - - - - - - - + - - - - Episode filter: + + + + + 18 + 18 + + + + @@ -265,18 +167,21 @@ - - + + - - Assign label: + + + 0 + 0 + - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + Assign Label: - + true @@ -288,13 +193,13 @@ - Save to a different directory + Save to a Different Directory - - + + false @@ -302,31 +207,24 @@ Save to: - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + false - - - - - - false - - - - - - - false - - - ... - - - - + + + + false + + + ... + + @@ -335,23 +233,10 @@ - Ignore subsequent matches for (0 to disable) + Ignore Subsequent Matches for (0 to Disable) - - - - Qt::Horizontal - - - - 40 - 20 - - - - @@ -361,43 +246,32 @@ days - 360 + 365 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - true - - - - - - - + + + true + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + 0 + 0 + + Add Paused: @@ -405,25 +279,19 @@ - - - 0 - 0 - - - Use global setting + Use global settings - Always add paused + Always - Never add paused + Never @@ -444,7 +312,7 @@ - Apply rule to feeds: + Apply Rule to Feeds: @@ -466,7 +334,7 @@ - Matching RSS articles + Matching RSS Articles @@ -491,35 +359,19 @@ - Import... + &Import... - Export... + &Export... - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - Qt::Horizontal - QDialogButtonBox::Close