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 c92ff2578..0898ca482 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), @@ -308,10 +309,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()