Simplify natural sort classes interface

Now the comparison function/class should be constructed before usage.
This change also make it easier to plug in into various containers which
will require a compare function type (such as std::set).
This commit is contained in:
Chocobo1 2021-04-05 13:02:28 +08:00
parent 5045fa6dcd
commit a64bb1a990
No known key found for this signature in database
GPG key ID: 210D9C873253A68C
24 changed files with 235 additions and 177 deletions

View file

@ -31,9 +31,7 @@
#include <cmath>
#include <QCollator>
#include <QLocale>
#include <QtGlobal>
#include <QVector>
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
@ -42,137 +40,6 @@
#include <QRegExp>
#endif
#if defined(Q_OS_MACOS) || defined(__MINGW32__)
#define QBT_USES_QTHREADSTORAGE
#include <QThreadStorage>
#endif
namespace
{
class NaturalCompare
{
public:
explicit NaturalCompare(const Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive)
: m_caseSensitivity(caseSensitivity)
{
#ifdef Q_OS_WIN
// Without ICU library, QCollator uses the native API on Windows 7+. But that API
// sorts older versions of μTorrent differently than the newer ones because the
// 'μ' character is encoded differently and the native API can't cope with that.
// So default to using our custom natural sorting algorithm instead.
// See #5238 and #5240
// Without ICU library, QCollator doesn't support `setNumericMode(true)` on an OS older than Win7
#else
m_collator.setNumericMode(true);
m_collator.setCaseSensitivity(caseSensitivity);
#endif
}
int operator()(const QString &left, const QString &right) const
{
#ifdef Q_OS_WIN
return compare(left, right);
#else
return m_collator.compare(left, right);
#endif
}
private:
int compare(const QString &left, const QString &right) const
{
// Return value <0: `left` is smaller than `right`
// Return value >0: `left` is greater than `right`
// Return value =0: both strings are equal
int posL = 0;
int posR = 0;
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
const QChar leftChar = (m_caseSensitivity == Qt::CaseSensitive) ? left[posL] : left[posL].toLower();
const QChar rightChar = (m_caseSensitivity == Qt::CaseSensitive) ? right[posR] : right[posR].toLower();
// Compare only non-digits.
// Numbers should be compared as a whole
// otherwise the string->int conversion can yield a wrong value
if ((leftChar == rightChar) && !leftChar.isDigit())
{
// compare next character
++posL;
++posR;
}
else if (leftChar.isDigit() && rightChar.isDigit())
{
// Both are digits, compare the numbers
const auto numberView = [](const QString &str, int &pos) -> QStringRef
{
const int start = pos;
while ((pos < str.size()) && str[pos].isDigit())
++pos;
return str.midRef(start, (pos - start));
};
const QStringRef numViewL = numberView(left, posL);
const QStringRef numViewR = numberView(right, posR);
if (numViewL.length() != numViewR.length())
return (numViewL.length() - numViewR.length());
// both string/view has the same length
for (int i = 0; i < numViewL.length(); ++i)
{
const QChar numL = numViewL[i];
const QChar numR = numViewR[i];
if (numL != numR)
return (numL.unicode() - numR.unicode());
}
// String + digits do match and we haven't hit the end of both strings
// then continue to consume the remainings
}
else
{
return (leftChar.unicode() - rightChar.unicode());
}
}
}
QCollator m_collator;
const Qt::CaseSensitivity m_caseSensitivity;
};
}
int Utils::String::naturalCompare(const QString &left, const QString &right, const Qt::CaseSensitivity caseSensitivity)
{
// provide a single `NaturalCompare` instance for easy use
// https://doc.qt.io/qt-5/threads-reentrancy.html
if (caseSensitivity == Qt::CaseSensitive)
{
#ifdef QBT_USES_QTHREADSTORAGE
static QThreadStorage<NaturalCompare> nCmp;
if (!nCmp.hasLocalData())
nCmp.setLocalData(NaturalCompare(Qt::CaseSensitive));
return (nCmp.localData())(left, right);
#else
thread_local NaturalCompare nCmp(Qt::CaseSensitive);
return nCmp(left, right);
#endif
}
#ifdef QBT_USES_QTHREADSTORAGE
static QThreadStorage<NaturalCompare> nCmp;
if (!nCmp.hasLocalData())
nCmp.setLocalData(NaturalCompare(Qt::CaseInsensitive));
return (nCmp.localData())(left, right);
#else
thread_local NaturalCompare nCmp(Qt::CaseInsensitive);
return nCmp(left, right);
#endif
}
// to send numbers instead of strings with suffixes
QString Utils::String::fromDouble(const double n, const int precision)
{