из интереса немного покопался в исходниках... Итак, собака зарыта между dolphn'ом, kdelibs, qtcore и icu (библиотека для работы с юникодом).
и так, по порядку:
1. dolphin
Здесь всё просто:
m_naturalSorting ? KStringHandler::naturalCompare(a, b, Qt::CaseSensitive)
: QString::compare(a, b, Qt::CaseSensitive);
т.е. если галочка стоит, то используется умная функция KStringHandler::naturalCompare() из kdelibs, а если нет, то обычный ламповый QString::compare(), который просто сравнивает строки посимвольно... (и, кстати говоря, обладает мелкими недочётами... например, для русского алфавита, ставит ё в конец)
2. kdelibs
int KStringHandler::naturalCompare(const QString &_a, const QString &_b, Qt::CaseSensitivity caseSensitivity)
{
// This method chops the input a and b into pieces of
// digits and non-digits (a1.05 becomes a | 1 | . | 05)
// and compares these pieces of a and b to each other
// (first with first, second with second, ...).
//
// This is based on the natural sort order code code by Martin Pool
// http://sourcefrog.net/projects/natsort/
// Martin Pool agreed to license this under LGPL or GPL.
// FIXME: Using toLower() to implement case insensitive comparison is
// sub-optimal, but is needed because we compare strings with
// localeAwareCompare(), which does not know about case sensitivity.
// A task has been filled for this in Qt Task Tracker with ID 205990.
// http://trolltech.com/developer/task-tracker/index_html?method=entry&id=205990
QString a;
QString b;
if (caseSensitivity == Qt::CaseSensitive) {
a = _a;
b = _b;
} else {
a = _a.toLower();
b = _b.toLower();
}
const QChar* currA = a.unicode(); // iterator over a
const QChar* currB = b.unicode(); // iterator over b
if (currA == currB) {
return 0;
}
while (!currA->isNull() && !currB->isNull()) {
const QChar* begSeqA = currA; // beginning of a new character sequence of a
const QChar* begSeqB = currB;
if (currA->unicode() == QChar::ObjectReplacementCharacter) {
return 1;
}
if (currB->unicode() == QChar::ObjectReplacementCharacter) {
return -1;
}
if (currA->unicode() == QChar::ReplacementCharacter) {
return 1;
}
if (currB->unicode() == QChar::ReplacementCharacter) {
return -1;
}
// find sequence of characters ending at the first non-character
while (!currA->isNull() && !currA->isDigit() && !currA->isPunct() && !currA->isSpace()) {
++currA;
}
while (!currB->isNull() && !currB->isDigit() && !currB->isPunct() && !currB->isSpace()) {
++currB;
}
// compare these sequences
const QStringRef& subA(a.midRef(begSeqA - a.unicode(), currA - begSeqA));
const QStringRef& subB(b.midRef(begSeqB - b.unicode(), currB - begSeqB));
const int cmp = QStringRef::localeAwareCompare(subA, subB);
if (cmp != 0) {
return cmp < 0 ? -1 : +1;
}
if (currA->isNull() || currB->isNull()) {
break;
}
// find sequence of characters ending at the first non-character
while ((currA->isPunct() || currA->isSpace()) && (currB->isPunct() || currB->isSpace())) {
if (*currA != *currB) {
return (*currA < *currB) ? -1 : +1;
}
++currA;
++currB;
if (currA->isNull() || currB->isNull()) {
break;
}
}
// now some digits follow...
if ((*currA == QLatin1Char('0')) || (*currB == QLatin1Char('0'))) {
// one digit-sequence starts with 0 -> assume we are in a fraction part
// do left aligned comparison (numbers are considered left aligned)
while (1) {
if (!currA->isDigit() && !currB->isDigit()) {
break;
} else if (!currA->isDigit()) {
return +1;
} else if (!currB->isDigit()) {
return -1;
} else if (*currA < *currB) {
return -1;
} else if (*currA > *currB) {
return + 1;
}
++currA;
++currB;
}
} else {
// No digit-sequence starts with 0 -> assume we are looking at some integer
// do right aligned comparison.
//
// The longest run of digits wins. That aside, the greatest
// value wins, but we can't know that it will until we've scanned
// both numbers to know that they have the same magnitude.
bool isFirstRun = true;
int weight = 0;
while (1) {
if (!currA->isDigit() && !currB->isDigit()) {
if (weight != 0) {
return weight;
}
break;
} else if (!currA->isDigit()) {
if (isFirstRun) {
return *currA < *currB ? -1 : +1;
} else {
return -1;
}
} else if (!currB->isDigit()) {
if (isFirstRun) {
return *currA < *currB ? -1 : +1;
} else {
return +1;
}
} else if ((*currA < *currB) && (weight == 0)) {
weight = -1;
} else if ((*currA > *currB) && (weight == 0)) {
weight = + 1;
}
++currA;
++currB;
isFirstRun = false;
}
}
}
if (currA->isNull() && currB->isNull()) {
return 0;
}
return currA->isNull() ? -1 : + 1;
}
KStringHandler::naturalCompare() — основное назначение этой замечательной функции это как раз «человеческая» сортировка по-номерам. Но в добавок ко всему она использует в качестве бек-энда для сравнения строк использует QStringRef::localeAwareCompare(); (что в общем правильно).
3. QtCore
Здесь интерес представляет QString::localeAwareCompare(). Вообще говоря, её реализация зависит от системы, но для unix — это strcoll() (стандартная функция libc) и относительно недавно добавленная icu (а именно ucol_strcoll()). Соответствующий коммит. Ориентировочно, он должен был войти 4.8-лохматый релиз...
4. icu
вот эта штука как раз и ставит кириллицу на первое место... ни какими переменными повлиять на её решение мне не удалось... из каких источников она берёт порядок сортировки я тоже не нашёл... но, честно говоря, я не сильно и пытался... копать разные версии я тоже не стал...
5. Вместо вывода.
Проблема эта Обще-Qt-шная. В частности наблюдается и в clementine. Виноват ли в этом icu или нет вопрос ИМХО пока открытый.
Средств борьбы я вижу три:
TODO: поискать баги... уже должны быть открыты...
В качестве бонуса небольшая тестовая программка, которая показывает как разные функции из разных библиотек по-разному сортируют строки...
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unicode/ucol.h>
#include <QtCore/QString>
#include <QtCore/QLocale>
#include <KStringHandler>
typedef int (*StrCmpFunc)(const char *s1, const char *s2);
static int strcmp_wrapper(const void *p1, const void *p2, void* cmp_func);
void compare_func(const char **strs, size_t strn, const char *label, StrCmpFunc cmp) {
int i;
qsort_r(strs, strn, sizeof(char*), strcmp_wrapper, (void *)cmp);
printf("%s:", label);
for( i=0; i<strn; i++) {
printf(" %s",strs[i]);
}
putchar('\n');
}
static int strcmp_wrapper(const void *p1, const void *p2, void* cmp_func)
{
StrCmpFunc cmp = (StrCmpFunc) cmp_func;
return cmp(* (char * const *) p1, * (char * const *) p2);
}
int ucol_strcollUTF8_wrapper(const char *s1, const char *s2) {
UErrorCode icuStatus = U_ZERO_ERROR;
UCollator *icuCollator = ucol_open("ru-RU", &icuStatus);
int rv;
if(!icuCollator) {
fprintf(stderr, "ucol_open failed: %d(%s)\n", icuStatus, u_errorName(icuStatus));
exit(EXIT_FAILURE);
}
rv = ucol_strcollUTF8(icuCollator, s1, strlen(s1), s2, strlen(s2), &icuStatus);
ucol_close(icuCollator);
return rv;
}
int QString_compare_wrapper(const char *s1, const char *s2) {
return QString::compare(QString::fromUtf8(s1), QString::fromUtf8(s2));
}
int QString_localeAwareCompare_wrapper(const char *s1, const char *s2) {
return QString::localeAwareCompare(QString::fromUtf8(s1), QString::fromUtf8(s2));
}
int KStringHandler_naturalCompare_wrapper(const char *s1, const char *s2) {
return KStringHandler::naturalCompare(QString::fromUtf8(s1), QString::fromUtf8(s2));
}
int main (int argc, char **argv) {
char cmp_sign;
if (argc < 3) {
fprintf(stderr, "Usage: %s <str1> <str2> ...\n", argv[0]);
return EXIT_FAILURE;
}
compare_func((const char **)argv+1, argc - 1, "strcmp", strcmp);
compare_func((const char **)argv+1, argc - 1, "strcoll", strcoll);
compare_func((const char **)argv+1, argc - 1, "ucol_strcoll", ucol_strcollUTF8_wrapper);
QLocale locale; // we should initialize some qt global objects
compare_func((const char **)argv+1, argc - 1, "QString::compare", QString_compare_wrapper);
compare_func((const char **)argv+1, argc - 1, "QString::localeAwareCompare", QString_localeAwareCompare_wrapper);
compare_func((const char **)argv+1, argc - 1, "KStringHandler::naturalCompare", KStringHandler_naturalCompare_wrapper);
return 0;
}
Консоль:
alexander@goblin /tmp $ g++ -I/usr/include/qt4 -I/usr/include/KDE -L/usr/lib64/qt4 -lQtCore -licui18n -lkdecore strcmper.cpp -g # сборка; пути могут отличаться
alexander@goblin /tmp $ ./a.out ё й я q w s 12 2
strcmp: 12 2 q s w й я ё
strcoll: 12 2 q s w ё й я
ucol_strcoll: 12 2 ё й я q s w
QString::compare: 12 2 q s w й я ё
QString::localeAwareCompare: 12 2 ё й я q s w
KStringHandler::naturalCompare: 2 12 ё й я q s w
95% процентов проблем находятся между клавиатурой и стулом.