diff --git a/src/kits/tracker/Model.cpp b/src/kits/tracker/Model.cpp index 3eaec89e71..344d08ac1b 100644 --- a/src/kits/tracker/Model.cpp +++ b/src/kits/tracker/Model.cpp @@ -321,7 +321,7 @@ Model::CompareFolderNamesFirst(const Model *compareModel) const } else if (resolvedCompareModel->IsDirectory()) return 1; - return strcasecmp(Name(), compareModel->Name()); + return NaturalCompare(Name(), compareModel->Name()); } diff --git a/src/kits/tracker/Utilities.h b/src/kits/tracker/Utilities.h index 1ffd0b92c9..4fb10c5bd1 100644 --- a/src/kits/tracker/Utilities.h +++ b/src/kits/tracker/Utilities.h @@ -136,6 +136,7 @@ class PoseInfo { BPoint fLocation; }; + // extends PoseInfo adding workspace support; used for desktop // poses only class ExtendedPoseInfo { @@ -176,8 +177,144 @@ class ExtendedPoseInfo { void DisallowMetaKeys(BTextView *); void DisallowFilenameKeys(BTextView *); + +inline bool +IsDigit(const char c) +{ + if ((c >= 48 && c <= 57) || c == 32) + return true; + else + return false; +} + + +//! Compares two strings naturally, as opposed to lexicographically +inline int +NaturalCompare(const char *s1, const char *s2) +{ + struct Chunk { + int32 type; + char* ascii; + // Type = 0 + int32 num; + // Type = 1 + }; + + Chunk a; + Chunk b; + + size_t len1 = strlen(s1); + size_t len2 = strlen(s2); + + char bufferA[len1 + 1]; + char bufferB[len2 + 1]; + + uint32 i = 0; + uint32 j = 0; + + while (true) { + // determine type of next chunks in each string based on first char + if (i == len1) + a.type = -1; + else if (IsDigit(s1[i])) + a.type = 1; + else + a.type = 0; + + if (j == len2) + b.type = -1; + else if (IsDigit(s2[j])) + b.type = 1; + else + b.type = 0; + + // check if we reached the end of either string + if (a.type == b.type && a.type == -1) + return 0; + if (a.type == -1) + return -1; + if (b.type == -1) + return 1; + + if (a.type != b.type) { + // different chunk types, just compare the remaining strings + return strcasecmp(&s1[i], &s2[j]); + } + + // fetch the next chunk for a + if (a.type == 0) { + // string chunk + int32 k = i; + while (!IsDigit(s1[k]) && s1[k] != 0) { + bufferA[k - i] = s1[k]; + k++; + } + bufferA[k - i] = 0; + a.ascii = bufferA; + i += k - i; + } else { + // number chunk + int32 k = i; + while (IsDigit(s1[k]) && s1[k] != 0) { + bufferA[k - i] = s1[k]; + k++; + } + bufferA[k - i] = 0; + a.ascii = bufferA; + a.num = atoi(bufferA); + i += k - i; + } + + // fetch the next chunk for b + if (b.type == 0) { + // string chunk + int32 k = j; + while (!IsDigit(s2[k]) && s2[k] != 0) { + bufferB[k - j] = s2[k]; + k++; + } + bufferB[k - j] = 0; + b.ascii = bufferB; + j += k - j; + } else { + // number chunk + int32 k = j; + while (IsDigit(s2[k]) && s2[k] != 0) { + bufferB[k - j] = s2[k]; + k++; + } + bufferB[k - j] = 0; + b.ascii = bufferB; + b.num = atoi(bufferB); + j += k - j; + } + + // compare the two chunks based on their type + if (a.type == 0) { + // string chunks + int stringCompareResult = strcasecmp(a.ascii, b.ascii); + // if the chunk strings are the same, keep using natural + // sorting for the next chunks + if (stringCompareResult != 0) + return stringCompareResult; + } else { + // number chunks + if (a.num != b.num) { + if (a.num < b.num) + return -1; + if (a.num > b.num) + return 1; + } + } + } + + return 0; +} + + bool ValidateStream(BMallocIO *, uint32, int32 version); + uint32 HashString(const char *string, uint32 seed); uint32 AttrHashString(const char *string, uint32 type); diff --git a/src/kits/tracker/WidgetAttributeText.cpp b/src/kits/tracker/WidgetAttributeText.cpp index 4b9882b490..5baaa3903f 100644 --- a/src/kits/tracker/WidgetAttributeText.cpp +++ b/src/kits/tracker/WidgetAttributeText.cpp @@ -63,7 +63,6 @@ All rights reserved. const int32 kGenericReadBufferSize = 1024; - template float TruncStringBase(BString *result, const char *str, int32 length, @@ -595,7 +594,7 @@ StringAttributeText::Compare(WidgetAttributeText &attr, BPoseView *view) if (fValueDirty) ReadValue(&fFullValueText); - return strcasecmp(fFullValueText.String(), compareTo->ValueAsText(view)); + return NaturalCompare(fFullValueText.String(), compareTo->ValueAsText(view)); } @@ -782,7 +781,7 @@ NameAttributeText::Compare(WidgetAttributeText &attr, BPoseView *view) if (NameAttributeText::sSortFolderNamesFirst) return fModel->CompareFolderNamesFirst(attr.TargetModel()); - return strcasecmp(fFullValueText.String(), compareTo->ValueAsText(view)); + return NaturalCompare(fFullValueText.String(), compareTo->ValueAsText(view)); }