View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0001418 | Double Commander | Logic | public | 2016-06-13 06:08 | 2023-01-10 09:30 |
Reporter | cordylus | Assigned To | cordylus | ||
Priority | normal | Severity | minor | Reproducibility | always |
Status | closed | Resolution | fixed | ||
Projection | none | ETA | none | ||
Target Version | 1.0.0 | Fixed in Version | 1.0.0 | ||
Summary | 0001418: Natural sorting order is unconventional | ||||
Description | Numbers should go before letters but after special symbols such as ![(, like it is implemented in other software. | ||||
Tags | No tags attached. | ||||
Attached Files | bug1418.patch (480 bytes)
Index: src/udcutils.pas =================================================================== --- src/udcutils.pas (revision 7902) +++ src/udcutils.pas (working copy) @@ -988,12 +988,6 @@ if (is_digit1 and is_digit2) then break; - if (is_digit1 and not is_digit2) then - exit(-1); - - if (is_digit2 and not is_digit1) then - exit(+1); - string_result:= str_cmp(str1^, str2^); if (string_result <> 0) then exit(string_result); newnaturalsort.patch (8,718 bytes)
Index: src/udcutils.pas =================================================================== --- src/udcutils.pas (revision 8402) +++ src/udcutils.pas (working copy) @@ -189,9 +189,8 @@ procedure InsertFirstItem(sLine: String; comboBox: TCustomComboBox); {en Compares two strings taking into account the numbers. - Strings must have tailing zeros (#0). } -function StrFloatCmpW(str1, str2: PWideChar; CaseSensitivity: TCaseSensitivity): PtrInt; +function StrFloatCmp(const str1, str2: String; CaseSensitivity: TCaseSensitivity): PtrInt; function EstimateRemainingTime(StartValue, CurrentValue, EndValue: Int64; StartTime: TDateTime; CurrentTime: TDateTime; @@ -807,7 +806,7 @@ function CompareStrings(const s1, s2: String; Natural: Boolean; CaseSensitivity: TCaseSensitivity): PtrInt; inline; begin if Natural then - Result:= StrFloatCmpW(PWideChar(UTF8Decode(s1)), PWideChar(UTF8Decode(s2)), CaseSensitivity) + Result:= StrFloatCmp(s1, s2, CaseSensitivity) else begin case CaseSensitivity of @@ -863,161 +862,157 @@ Result := ord(pstr1[counter]) - ord(pstr2[counter]); end; -function StrFloatCmpW(str1, str2: PWideChar; CaseSensitivity: TCaseSensitivity): PtrInt; +const SORT_NATURAL = True; +const SORT_SPECIAL = False; + +function StrFloatCmp(const str1, str2: String; CaseSensitivity: TCaseSensitivity): PtrInt; + +type + TCategory = (cNone, cNumber, cSpecial, cString); + + TChunk = record + FullStr: String; + Str: String; + Category: TCategory; + PosStart: Integer; + PosEnd: Integer; + end; + var - is_digit1, is_digit2: boolean; - string_result: ptrint = 0; - number_result: ptrint = 0; - number1_size: ptrint = 0; - number2_size: ptrint = 0; - str_cmp: function(const s1, s2: WideString): PtrInt; + Chunk1, Chunk2: TChunk; - function is_digit(c: widechar): boolean; inline; + function Categorize(c: Char): TCategory; inline; begin - result:= (c in ['0'..'9']); + if SORT_NATURAL and (c in ['0'..'9']) then + Result := cNumber + else if SORT_SPECIAL and (c in [' '..'/', ':'..'@', '['..'`', '{'..'~']) then + Result := cSpecial + else + Result := cString; end; - function is_point(c: widechar): boolean; inline; + procedure NextChunkInit(var Chunk: TChunk); inline; begin - result:= (c in [',', '.']); + Chunk.PosStart := Chunk.PosEnd; + if Chunk.PosStart > Length(Chunk.FullStr) then + Chunk.Category := cNone + else + Chunk.Category := Categorize(Chunk.FullStr[Chunk.PosStart]); end; -begin - // Set up compare function - case CaseSensitivity of - cstNotSensitive: str_cmp:= @WideCompareText; - cstLocale: str_cmp:= @WideCompareStr; - cstCharValue: str_cmp:= @WideStrComp; - else - raise Exception.Create('Invalid CaseSensitivity parameter'); + procedure FindChunk(var Chunk: TChunk); inline; + begin + Chunk.PosEnd := Chunk.PosStart; + repeat + inc(Chunk.PosEnd); + until (Chunk.PosEnd > Length(Chunk.FullStr)) or + (Categorize(Chunk.FullStr[Chunk.PosEnd]) <> Chunk.Category); end; - while (true) do + procedure FindSameCategoryChunks; inline; begin - // compare string part - while (true) do - begin - if str1^ = #0 then - begin - if str2^ <> #0 then - exit(-1) - else - exit(0); - end; + Chunk1.PosEnd := Chunk1.PosStart; + Chunk2.PosEnd := Chunk2.PosStart; + repeat + inc(Chunk1.PosEnd); + inc(Chunk2.PosEnd); + until (Chunk1.PosEnd > Length(Chunk1.FullStr)) or + (Chunk2.PosEnd > Length(Chunk2.FullStr)) or + (Categorize(Chunk1.FullStr[Chunk1.PosEnd]) <> Chunk1.Category) or + (Categorize(Chunk2.FullStr[Chunk2.PosEnd]) <> Chunk2.Category); + end; - if str2^ = #0 then - begin - if str1^ <> #0 then - exit(+1) - else - exit(0); - end; + procedure PrepareChunk(var Chunk: TChunk); inline; + begin + Chunk.Str := Copy(Chunk.FullStr, Chunk.PosStart, Chunk.PosEnd - Chunk.PosStart); + end; - is_digit1 := is_digit(str1^); - is_digit2 := is_digit(str2^); + procedure PrepareNumberChunk(var Chunk: TChunk); inline; + begin + while (Chunk.PosStart <= Length(Chunk.FullStr)) and + (Chunk.FullStr[Chunk.PosStart] = '0') do + inc(Chunk.PosStart); + PrepareChunk(Chunk); + end; - if (is_digit1 and is_digit2) then break; +begin + Chunk1.FullStr := str1; + Chunk2.FullStr := str2; + Chunk1.PosEnd := 1; + Chunk2.PosEnd := 1; - string_result:= str_cmp(str1^, str2^); + NextChunkInit(Chunk1); + NextChunkInit(Chunk2); - if (string_result <> 0) then exit(string_result); + if (Chunk1.Category = cSpecial) and (Chunk2.Category = cSpecial) then + FindSameCategoryChunks + else + begin + FindChunk(Chunk1); + FindChunk(Chunk2); + end; - inc(str1); - inc(str2); - end; + if Chunk1.Category <> Chunk2.Category then + Chunk1.Category := cString; - // skip leading zeroes for number - while (str1^ = '0') do - inc(str1); - while (str2^ = '0') do - inc(str2); + while True do + begin - // compare number before decimal point - while (true) do - begin - is_digit1 := is_digit(str1^); - is_digit2 := is_digit(str2^); - - if (not is_digit1 and not is_digit2) then - break; - - if ((number_result = 0) and is_digit1 and is_digit2) then - begin - if (str1^ > str2^) then - number_result := +1 - else if (str1^ < str2^) then - number_result := -1 - else - number_result := 0; - end; - - if (is_digit1) then - begin - inc(str1); - inc(number1_size); - end; - - if (is_digit2) then - begin - inc(str2); - inc(number2_size); - end; - end; - - if (number1_size <> number2_size) then - exit(number1_size - number2_size); - - if (number_result <> 0) then - exit(number_result); - - // if there is a decimal point, compare number after one - if (is_point(str1^) or is_point(str2^)) then - begin - if (is_point(str1^)) then - inc(str1); - - if (is_point(str2^)) then - inc(str2); - - while (true) do - begin - is_digit1 := is_digit(str1^); - is_digit2 := is_digit(str2^); - - if (not is_digit1 and not is_digit2) then - break; - - if (is_digit1 and not is_digit2) then + case Chunk1.Category of + cString: begin - while (str1^ = '0') do - inc(str1); - - if (is_digit(str1^)) then - exit(+1) - else - break; + PrepareChunk(Chunk1); + PrepareChunk(Chunk2); + case CaseSensitivity of + cstNotSensitive: Result := WideCompareText(UTF8Decode(Chunk1.Str), UTF8Decode(Chunk2.Str)); + cstLocale: Result := WideCompareStr(UTF8Decode(Chunk1.Str), UTF8Decode(Chunk2.Str)); + cstCharValue: Result := WideStrComp(UTF8Decode(Chunk1.Str), UTF8Decode(Chunk2.Str)); + else + raise Exception.Create('Invalid CaseSensitivity parameter'); + end; + if Result <> 0 then + Exit; end; - - if (is_digit2 and not is_digit1) then + cNumber: begin - while (str2^ = '0') do - inc(str2); - - if (is_digit(str2^)) then - exit(-1) - else - break; + PrepareNumberChunk(Chunk1); + PrepareNumberChunk(Chunk2); + Result := Length(Chunk1.Str) - Length(Chunk2.Str); + if Result <> 0 then + Exit; + Result := CompareStr(Chunk1.Str, Chunk2.Str); + if Result <> 0 then + Exit; end; + cSpecial: + begin + PrepareChunk(Chunk1); + PrepareChunk(Chunk2); + Result := CompareStr(Chunk1.Str, Chunk2.Str); + if Result <> 0 then + Exit; + end; + cNone: + Exit(WideStrComp(UTF8Decode(str1), UTF8Decode(str2))); + end; - if (str1^ > str2^) then - exit(+1) - else if (str1^ < str2^) then - exit(-1); + NextChunkInit(Chunk1); + NextChunkInit(Chunk2); - inc(str1); - inc(str2); - end; + if Chunk1.Category <> Chunk2.Category then + if Chunk1.Category < Chunk2.Category then + Exit(-1) + else + Exit(1); + + if Chunk1.Category = cSpecial then + FindSameCategoryChunks + else + begin + FindChunk(Chunk1); + FindChunk(Chunk2); end; + end; end; newnaturalsort+oldnotremoved.patch (5,510 bytes)
Index: src/udcutils.pas =================================================================== --- src/udcutils.pas (revision 8402) +++ src/udcutils.pas (working copy) @@ -192,6 +192,7 @@ Strings must have tailing zeros (#0). } function StrFloatCmpW(str1, str2: PWideChar; CaseSensitivity: TCaseSensitivity): PtrInt; +function StrFloatCmp(const str1, str2: String; CaseSensitivity: TCaseSensitivity): PtrInt; function EstimateRemainingTime(StartValue, CurrentValue, EndValue: Int64; StartTime: TDateTime; CurrentTime: TDateTime; @@ -807,7 +808,8 @@ function CompareStrings(const s1, s2: String; Natural: Boolean; CaseSensitivity: TCaseSensitivity): PtrInt; inline; begin if Natural then - Result:= StrFloatCmpW(PWideChar(UTF8Decode(s1)), PWideChar(UTF8Decode(s2)), CaseSensitivity) +// Result:= StrFloatCmpW(PWideChar(UTF8Decode(s1)), PWideChar(UTF8Decode(s2)), CaseSensitivity) + Result:= StrFloatCmp(s1, s2, CaseSensitivity) else begin case CaseSensitivity of @@ -1021,6 +1023,160 @@ end; end; +const SORT_NATURAL = True; +const SORT_SPECIAL = False; + +function StrFloatCmp(const str1, str2: String; CaseSensitivity: TCaseSensitivity): PtrInt; + +type + TCategory = (cNone, cNumber, cSpecial, cString); + + TChunk = record + FullStr: String; + Str: String; + Category: TCategory; + PosStart: Integer; + PosEnd: Integer; + end; + +var + Chunk1, Chunk2: TChunk; + + function Categorize(c: Char): TCategory; inline; + begin + if SORT_NATURAL and (c in ['0'..'9']) then + Result := cNumber + else if SORT_SPECIAL and (c in [' '..'/', ':'..'@', '['..'`', '{'..'~']) then + Result := cSpecial + else + Result := cString; + end; + + procedure NextChunkInit(var Chunk: TChunk); inline; + begin + Chunk.PosStart := Chunk.PosEnd; + if Chunk.PosStart > Length(Chunk.FullStr) then + Chunk.Category := cNone + else + Chunk.Category := Categorize(Chunk.FullStr[Chunk.PosStart]); + end; + + procedure FindChunk(var Chunk: TChunk); inline; + begin + Chunk.PosEnd := Chunk.PosStart; + repeat + inc(Chunk.PosEnd); + until (Chunk.PosEnd > Length(Chunk.FullStr)) or + (Categorize(Chunk.FullStr[Chunk.PosEnd]) <> Chunk.Category); + end; + + procedure FindSameCategoryChunks; inline; + begin + Chunk1.PosEnd := Chunk1.PosStart; + Chunk2.PosEnd := Chunk2.PosStart; + repeat + inc(Chunk1.PosEnd); + inc(Chunk2.PosEnd); + until (Chunk1.PosEnd > Length(Chunk1.FullStr)) or + (Chunk2.PosEnd > Length(Chunk2.FullStr)) or + (Categorize(Chunk1.FullStr[Chunk1.PosEnd]) <> Chunk1.Category) or + (Categorize(Chunk2.FullStr[Chunk2.PosEnd]) <> Chunk2.Category); + end; + + procedure PrepareChunk(var Chunk: TChunk); inline; + begin + Chunk.Str := Copy(Chunk.FullStr, Chunk.PosStart, Chunk.PosEnd - Chunk.PosStart); + end; + + procedure PrepareNumberChunk(var Chunk: TChunk); inline; + begin + while (Chunk.PosStart <= Length(Chunk.FullStr)) and + (Chunk.FullStr[Chunk.PosStart] = '0') do + inc(Chunk.PosStart); + PrepareChunk(Chunk); + end; + +begin + Chunk1.FullStr := str1; + Chunk2.FullStr := str2; + Chunk1.PosEnd := 1; + Chunk2.PosEnd := 1; + + NextChunkInit(Chunk1); + NextChunkInit(Chunk2); + + if (Chunk1.Category = cSpecial) and (Chunk2.Category = cSpecial) then + FindSameCategoryChunks + else + begin + FindChunk(Chunk1); + FindChunk(Chunk2); + end; + + if Chunk1.Category <> Chunk2.Category then + Chunk1.Category := cString; + + while True do + begin + + case Chunk1.Category of + cString: + begin + PrepareChunk(Chunk1); + PrepareChunk(Chunk2); + case CaseSensitivity of + cstNotSensitive: Result := WideCompareText(UTF8Decode(Chunk1.Str), UTF8Decode(Chunk2.Str)); + cstLocale: Result := WideCompareStr(UTF8Decode(Chunk1.Str), UTF8Decode(Chunk2.Str)); + cstCharValue: Result := WideStrComp(UTF8Decode(Chunk1.Str), UTF8Decode(Chunk2.Str)); + else + raise Exception.Create('Invalid CaseSensitivity parameter'); + end; + if Result <> 0 then + Exit; + end; + cNumber: + begin + PrepareNumberChunk(Chunk1); + PrepareNumberChunk(Chunk2); + Result := Length(Chunk1.Str) - Length(Chunk2.Str); + if Result <> 0 then + Exit; + Result := CompareStr(Chunk1.Str, Chunk2.Str); + if Result <> 0 then + Exit; + end; + cSpecial: + begin + PrepareChunk(Chunk1); + PrepareChunk(Chunk2); + Result := CompareStr(Chunk1.Str, Chunk2.Str); + if Result <> 0 then + Exit; + end; + cNone: + Exit(WideStrComp(UTF8Decode(str1), UTF8Decode(str2))); + end; + + NextChunkInit(Chunk1); + NextChunkInit(Chunk2); + + if Chunk1.Category <> Chunk2.Category then + if Chunk1.Category < Chunk2.Category then + Exit(-1) + else + Exit(1); + + if Chunk1.Category = cSpecial then + FindSameCategoryChunks + else + begin + FindChunk(Chunk1); + FindChunk(Chunk2); + end; + + end; +end; + function EstimateRemainingTime(StartValue, CurrentValue, EndValue: Int64; StartTime: TDateTime; CurrentTime: TDateTime; out SpeedPerSecond: Int64): TDateTime; newnaturalsort-v2.patch (8,969 bytes)
Index: src/udcutils.pas =================================================================== --- src/udcutils.pas (revision 8402) +++ src/udcutils.pas (working copy) @@ -189,9 +189,8 @@ procedure InsertFirstItem(sLine: String; comboBox: TCustomComboBox); {en Compares two strings taking into account the numbers. - Strings must have tailing zeros (#0). } -function StrFloatCmpW(str1, str2: PWideChar; CaseSensitivity: TCaseSensitivity): PtrInt; +function StrFloatCmp(const str1, str2: String; CaseSensitivity: TCaseSensitivity): PtrInt; function EstimateRemainingTime(StartValue, CurrentValue, EndValue: Int64; StartTime: TDateTime; CurrentTime: TDateTime; @@ -807,7 +806,7 @@ function CompareStrings(const s1, s2: String; Natural: Boolean; CaseSensitivity: TCaseSensitivity): PtrInt; inline; begin if Natural then - Result:= StrFloatCmpW(PWideChar(UTF8Decode(s1)), PWideChar(UTF8Decode(s2)), CaseSensitivity) + Result:= StrFloatCmp(s1, s2, CaseSensitivity) else begin case CaseSensitivity of @@ -863,161 +862,162 @@ Result := ord(pstr1[counter]) - ord(pstr2[counter]); end; -function StrFloatCmpW(str1, str2: PWideChar; CaseSensitivity: TCaseSensitivity): PtrInt; +const SORT_NATURAL = True; +const SORT_SPECIAL = False; + +function StrFloatCmp(const str1, str2: String; CaseSensitivity: TCaseSensitivity): PtrInt; + +type + TCategory = (cNone, cNumber, cSpecial, cString); + + TChunk = record + FullStr: String; + Str: String; + Category: TCategory; + PosStart: Integer; + PosEnd: Integer; + end; + var - is_digit1, is_digit2: boolean; - string_result: ptrint = 0; - number_result: ptrint = 0; - number1_size: ptrint = 0; - number2_size: ptrint = 0; - str_cmp: function(const s1, s2: WideString): PtrInt; + Chunk1, Chunk2: TChunk; - function is_digit(c: widechar): boolean; inline; + function Categorize(c: Char): TCategory; inline; begin - result:= (c in ['0'..'9']); + if SORT_NATURAL and (c in ['0'..'9']) then + Result := cNumber + else if SORT_SPECIAL and (c in [' '..'/', ':'..'@', '['..'`', '{'..'~']) then + Result := cSpecial + else + Result := cString; end; - function is_point(c: widechar): boolean; inline; + procedure NextChunkInit(var Chunk: TChunk); inline; begin - result:= (c in [',', '.']); + Chunk.PosStart := Chunk.PosEnd; + if Chunk.PosStart > Length(Chunk.FullStr) then + Chunk.Category := cNone + else + Chunk.Category := Categorize(Chunk.FullStr[Chunk.PosStart]); end; -begin - // Set up compare function - case CaseSensitivity of - cstNotSensitive: str_cmp:= @WideCompareText; - cstLocale: str_cmp:= @WideCompareStr; - cstCharValue: str_cmp:= @WideStrComp; - else - raise Exception.Create('Invalid CaseSensitivity parameter'); + procedure FindChunk(var Chunk: TChunk); inline; + begin + Chunk.PosEnd := Chunk.PosStart; + repeat + inc(Chunk.PosEnd); + until (Chunk.PosEnd > Length(Chunk.FullStr)) or + (Categorize(Chunk.FullStr[Chunk.PosEnd]) <> Chunk.Category); end; - while (true) do + procedure FindSameCategoryChunks; inline; begin - // compare string part - while (true) do - begin - if str1^ = #0 then - begin - if str2^ <> #0 then - exit(-1) - else - exit(0); - end; + Chunk1.PosEnd := Chunk1.PosStart; + Chunk2.PosEnd := Chunk2.PosStart; + repeat + inc(Chunk1.PosEnd); + inc(Chunk2.PosEnd); + until (Chunk1.PosEnd > Length(Chunk1.FullStr)) or + (Chunk2.PosEnd > Length(Chunk2.FullStr)) or + (Categorize(Chunk1.FullStr[Chunk1.PosEnd]) <> Chunk1.Category) or + (Categorize(Chunk2.FullStr[Chunk2.PosEnd]) <> Chunk2.Category); + end; - if str2^ = #0 then - begin - if str1^ <> #0 then - exit(+1) - else - exit(0); - end; + procedure PrepareChunk(var Chunk: TChunk); inline; + begin + Chunk.Str := Copy(Chunk.FullStr, Chunk.PosStart, Chunk.PosEnd - Chunk.PosStart); + end; - is_digit1 := is_digit(str1^); - is_digit2 := is_digit(str2^); + procedure PrepareNumberChunk(var Chunk: TChunk); inline; + begin + while (Chunk.PosStart <= Length(Chunk.FullStr)) and + (Chunk.FullStr[Chunk.PosStart] = '0') do + inc(Chunk.PosStart); + PrepareChunk(Chunk); + end; - if (is_digit1 and is_digit2) then break; +begin + Chunk1.FullStr := str1; + Chunk2.FullStr := str2; + Chunk1.PosEnd := 1; + Chunk2.PosEnd := 1; - string_result:= str_cmp(str1^, str2^); + NextChunkInit(Chunk1); + NextChunkInit(Chunk2); - if (string_result <> 0) then exit(string_result); + if (Chunk1.Category = cSpecial) and (Chunk2.Category <> cSpecial) then + Exit(-1); + if (Chunk2.Category = cSpecial) and (Chunk1.Category <> cSpecial) then + Exit(1); - inc(str1); - inc(str2); - end; + if Chunk1.Category = cSpecial then + FindSameCategoryChunks + else + begin + FindChunk(Chunk1); + FindChunk(Chunk2); + end; - // skip leading zeroes for number - while (str1^ = '0') do - inc(str1); - while (str2^ = '0') do - inc(str2); + if (Chunk1.Category = cNumber) xor (Chunk2.Category = cNumber) then // one of them is number + Chunk1.Category := cString; // compare as strings to put numbers in a natural position - // compare number before decimal point - while (true) do - begin - is_digit1 := is_digit(str1^); - is_digit2 := is_digit(str2^); + while True do + begin - if (not is_digit1 and not is_digit2) then - break; - - if ((number_result = 0) and is_digit1 and is_digit2) then - begin - if (str1^ > str2^) then - number_result := +1 - else if (str1^ < str2^) then - number_result := -1 - else - number_result := 0; - end; - - if (is_digit1) then - begin - inc(str1); - inc(number1_size); - end; - - if (is_digit2) then - begin - inc(str2); - inc(number2_size); - end; - end; - - if (number1_size <> number2_size) then - exit(number1_size - number2_size); - - if (number_result <> 0) then - exit(number_result); - - // if there is a decimal point, compare number after one - if (is_point(str1^) or is_point(str2^)) then - begin - if (is_point(str1^)) then - inc(str1); - - if (is_point(str2^)) then - inc(str2); - - while (true) do - begin - is_digit1 := is_digit(str1^); - is_digit2 := is_digit(str2^); - - if (not is_digit1 and not is_digit2) then - break; - - if (is_digit1 and not is_digit2) then + case Chunk1.Category of + cString: begin - while (str1^ = '0') do - inc(str1); - - if (is_digit(str1^)) then - exit(+1) - else - break; + PrepareChunk(Chunk1); + PrepareChunk(Chunk2); + case CaseSensitivity of + cstNotSensitive: Result := WideCompareText(UTF8Decode(Chunk1.Str), UTF8Decode(Chunk2.Str)); + cstLocale: Result := WideCompareStr(UTF8Decode(Chunk1.Str), UTF8Decode(Chunk2.Str)); + cstCharValue: Result := WideStrComp(UTF8Decode(Chunk1.Str), UTF8Decode(Chunk2.Str)); + else + raise Exception.Create('Invalid CaseSensitivity parameter'); + end; + if Result <> 0 then + Exit; end; - - if (is_digit2 and not is_digit1) then + cNumber: begin - while (str2^ = '0') do - inc(str2); - - if (is_digit(str2^)) then - exit(-1) - else - break; + PrepareNumberChunk(Chunk1); + PrepareNumberChunk(Chunk2); + Result := Length(Chunk1.Str) - Length(Chunk2.Str); + if Result <> 0 then + Exit; + Result := CompareStr(Chunk1.Str, Chunk2.Str); + if Result <> 0 then + Exit; end; + cSpecial: + begin + PrepareChunk(Chunk1); + PrepareChunk(Chunk2); + Result := CompareStr(Chunk1.Str, Chunk2.Str); + if Result <> 0 then + Exit; + end; + cNone: + Exit(WideStrComp(UTF8Decode(str1), UTF8Decode(str2))); + end; - if (str1^ > str2^) then - exit(+1) - else if (str1^ < str2^) then - exit(-1); + NextChunkInit(Chunk1); + NextChunkInit(Chunk2); - inc(str1); - inc(str2); - end; + if Chunk1.Category <> Chunk2.Category then + if Chunk1.Category < Chunk2.Category then + Exit(-1) + else + Exit(1); + + if Chunk1.Category = cSpecial then + FindSameCategoryChunks + else + begin + FindChunk(Chunk1); + FindChunk(Chunk2); end; + end; end; newnaturalsort-v3.patch (18,877 bytes)
Index: src/foptions.pas =================================================================== --- src/foptions.pas (revision 8429) +++ src/foptions.pas (working copy) @@ -211,7 +211,7 @@ if TOptionsEditorView(Node2.Data).EditorClass.ClassName='TfrmOptionsLanguage' then result:=1 else - result:=CompareStrings(Node1.Text,Node2.Text, gSortNatural, gSortCaseSensitivity) + result:=CompareStrings(Node1.Text, Node2.Text, gSortNatural, gSortSpecial, gSortCaseSensitivity) end; end; Index: src/frames/foptionsdirectoryhotlist.pas =================================================================== --- src/frames/foptionsdirectoryhotlist.pas (revision 8430) +++ src/frames/foptionsdirectoryhotlist.pas (working copy) @@ -1781,7 +1781,7 @@ function CompareStringsFromTStringList(List: TStringList; Index1, Index2: integer): integer; begin - Result := CompareStrings(List.Strings[Index1], List.Strings[Index2], gSortNatural, gSortCaseSensitivity); + Result := CompareStrings(List.Strings[Index1], List.Strings[Index2], gSortNatural, gSortSpecial, gSortCaseSensitivity); end; { TfrmOptionsDirectoryHotlist.TryToGetCloserHotDir } @@ -1875,7 +1875,7 @@ if Result <> nil then begin - if CompareStrings(localDirToFindAPlaceFor, UTF8LowerCase(IncludeTrailingPathDelimiter(mbExpandFileName(tHotDir(Result.Data).HotDirPath))), gSortNatural, gSortCaseSensitivity) = -1 then TypeOfAddition := ACTION_INSERTHOTDIR; + if CompareStrings(localDirToFindAPlaceFor, UTF8LowerCase(IncludeTrailingPathDelimiter(mbExpandFileName(tHotDir(Result.Data).HotDirPath))), gSortNatural, gSortSpecial, gSortCaseSensitivity) = -1 then TypeOfAddition := ACTION_INSERTHOTDIR; end; finally MagickSortedList.Free; @@ -2108,7 +2108,7 @@ begin if (THotdir(Node1.Data).GroupNumber = THotDir(Node2.Data).GroupNumber) and (THotdir(Node1.Data).GroupNumber <> 0) then begin - Result := CompareStrings(THotdir(Node1.Data).HotDirName, THotDir(Node2.Data).HotDirName, gSortNatural, gSortCaseSensitivity); + Result := CompareStrings(THotdir(Node1.Data).HotDirName, THotDir(Node2.Data).HotDirName, gSortNatural, gSortSpecial, gSortCaseSensitivity); end else begin Index: src/frames/foptionsfavoritetabs.pas =================================================================== --- src/frames/foptionsfavoritetabs.pas (revision 8429) +++ src/frames/foptionsfavoritetabs.pas (working copy) @@ -262,7 +262,7 @@ function CompareStringsFromTStringList(List: TStringList; Index1, Index2: integer): integer; begin - Result := CompareStrings(List.Strings[Index1], List.Strings[Index2], gSortNatural, gSortCaseSensitivity); + Result := CompareStrings(List.Strings[Index1], List.Strings[Index2], gSortNatural, gSortSpecial, gSortCaseSensitivity); end; { TfrmOptionsFavoriteTabs.btnRenameClick } @@ -325,7 +325,7 @@ begin if (TFavoriteTabs(Node1.Data).GroupNumber = TFavoriteTabs(Node2.Data).GroupNumber) and (TFavoriteTabs(Node1.Data).GroupNumber <> 0) then begin - Result := CompareStrings(TFavoriteTabs(Node1.Data).FavoriteTabsName, TFavoriteTabs(Node2.Data).FavoriteTabsName, gSortNatural, gSortCaseSensitivity); + Result := CompareStrings(TFavoriteTabs(Node1.Data).FavoriteTabsName, TFavoriteTabs(Node2.Data).FavoriteTabsName, gSortNatural, gSortSpecial, gSortCaseSensitivity); end else begin Index: src/frames/foptionsfilesviews.pas =================================================================== --- src/frames/foptionsfilesviews.pas (revision 8429) +++ src/frames/foptionsfilesviews.pas (working copy) @@ -144,6 +144,8 @@ cbSortMethod.ItemIndex:= 0 else cbSortMethod.ItemIndex:= 1; + if gSortSpecial then + cbSortMethod.ItemIndex := cbSortMethod.ItemIndex + 2; case gSortFolderMode of sfmSortNameShowFirst: cbSortFolderMode.ItemIndex := 0; sfmSortLikeFileShowFirst: cbSortFolderMode.ItemIndex := 1; @@ -198,7 +200,8 @@ 1: gSortCaseSensitivity := cstLocale; 2: gSortCaseSensitivity := cstCharValue; end; - gSortNatural := (cbSortMethod.ItemIndex = 1); + gSortNatural := (cbSortMethod.ItemIndex in [1,3]); + gSortSpecial := (cbSortMethod.ItemIndex in [2,3]); case cbSortFolderMode.ItemIndex of 0: gSortFolderMode := sfmSortNameShowFirst; 1: gSortFolderMode := sfmSortLikeFileShowFirst; Index: src/udcutils.pas =================================================================== --- src/udcutils.pas (revision 8429) +++ src/udcutils.pas (working copy) @@ -184,14 +184,13 @@ } procedure SplitCmdLine(sCmdLine : String; var sCmd, sParams : String); {$ENDIF} -function CompareStrings(const s1, s2: String; Natural: Boolean; CaseSensitivity: TCaseSensitivity): PtrInt; +function CompareStrings(const s1, s2: String; Natural: Boolean; Special: Boolean; CaseSensitivity: TCaseSensitivity): PtrInt; procedure InsertFirstItem(sLine: String; comboBox: TCustomComboBox); {en - Compares two strings taking into account the numbers. - Strings must have tailing zeros (#0). + Compares two strings taking into account the numbers or special chararcters. } -function StrFloatCmpW(str1, str2: PWideChar; CaseSensitivity: TCaseSensitivity): PtrInt; +function StrChunkCmp(const str1, str2: String; Natural: Boolean; Special: Boolean; CaseSensitivity: TCaseSensitivity): PtrInt; function EstimateRemainingTime(StartValue, CurrentValue, EndValue: Int64; StartTime: TDateTime; CurrentTime: TDateTime; @@ -804,10 +803,10 @@ end; {$ENDIF} -function CompareStrings(const s1, s2: String; Natural: Boolean; CaseSensitivity: TCaseSensitivity): PtrInt; inline; +function CompareStrings(const s1, s2: String; Natural: Boolean; Special: Boolean; CaseSensitivity: TCaseSensitivity): PtrInt; inline; begin - if Natural then - Result:= StrFloatCmpW(PWideChar(UTF8Decode(s1)), PWideChar(UTF8Decode(s2)), CaseSensitivity) + if Natural or Special then + Result:= StrChunkCmp(s1, s2, Natural, Special, CaseSensitivity) else begin case CaseSensitivity of @@ -863,161 +862,159 @@ Result := ord(pstr1[counter]) - ord(pstr2[counter]); end; -function StrFloatCmpW(str1, str2: PWideChar; CaseSensitivity: TCaseSensitivity): PtrInt; +function StrChunkCmp(const str1, str2: String; Natural: Boolean; Special: Boolean; CaseSensitivity: TCaseSensitivity): PtrInt; + +type + TCategory = (cNone, cNumber, cSpecial, cString); + + TChunk = record + FullStr: String; + Str: String; + Category: TCategory; + PosStart: Integer; + PosEnd: Integer; + end; + var - is_digit1, is_digit2: boolean; - string_result: ptrint = 0; - number_result: ptrint = 0; - number1_size: ptrint = 0; - number2_size: ptrint = 0; - str_cmp: function(const s1, s2: WideString): PtrInt; + Chunk1, Chunk2: TChunk; - function is_digit(c: widechar): boolean; inline; + function Categorize(c: Char): TCategory; inline; begin - result:= (c in ['0'..'9']); + if Natural and (c in ['0'..'9']) then + Result := cNumber + else if Special and (c in [' '..'/', ':'..'@', '['..'`', '{'..'~']) then + Result := cSpecial + else + Result := cString; end; - function is_point(c: widechar): boolean; inline; + procedure NextChunkInit(var Chunk: TChunk); inline; begin - result:= (c in [',', '.']); + Chunk.PosStart := Chunk.PosEnd; + if Chunk.PosStart > Length(Chunk.FullStr) then + Chunk.Category := cNone + else + Chunk.Category := Categorize(Chunk.FullStr[Chunk.PosStart]); end; -begin - // Set up compare function - case CaseSensitivity of - cstNotSensitive: str_cmp:= @WideCompareText; - cstLocale: str_cmp:= @WideCompareStr; - cstCharValue: str_cmp:= @WideStrComp; - else - raise Exception.Create('Invalid CaseSensitivity parameter'); + procedure FindChunk(var Chunk: TChunk); inline; + begin + Chunk.PosEnd := Chunk.PosStart; + repeat + inc(Chunk.PosEnd); + until (Chunk.PosEnd > Length(Chunk.FullStr)) or + (Categorize(Chunk.FullStr[Chunk.PosEnd]) <> Chunk.Category); end; - while (true) do + procedure FindSameCategoryChunks; inline; begin - // compare string part - while (true) do - begin - if str1^ = #0 then - begin - if str2^ <> #0 then - exit(-1) - else - exit(0); - end; + Chunk1.PosEnd := Chunk1.PosStart; + Chunk2.PosEnd := Chunk2.PosStart; + repeat + inc(Chunk1.PosEnd); + inc(Chunk2.PosEnd); + until (Chunk1.PosEnd > Length(Chunk1.FullStr)) or + (Chunk2.PosEnd > Length(Chunk2.FullStr)) or + (Categorize(Chunk1.FullStr[Chunk1.PosEnd]) <> Chunk1.Category) or + (Categorize(Chunk2.FullStr[Chunk2.PosEnd]) <> Chunk2.Category); + end; - if str2^ = #0 then - begin - if str1^ <> #0 then - exit(+1) - else - exit(0); - end; + procedure PrepareChunk(var Chunk: TChunk); inline; + begin + Chunk.Str := Copy(Chunk.FullStr, Chunk.PosStart, Chunk.PosEnd - Chunk.PosStart); + end; - is_digit1 := is_digit(str1^); - is_digit2 := is_digit(str2^); + procedure PrepareNumberChunk(var Chunk: TChunk); inline; + begin + while (Chunk.PosStart <= Length(Chunk.FullStr)) and + (Chunk.FullStr[Chunk.PosStart] = '0') do + inc(Chunk.PosStart); + PrepareChunk(Chunk); + end; - if (is_digit1 and is_digit2) then break; +begin + Chunk1.FullStr := str1; + Chunk2.FullStr := str2; + Chunk1.PosEnd := 1; + Chunk2.PosEnd := 1; - string_result:= str_cmp(str1^, str2^); + NextChunkInit(Chunk1); + NextChunkInit(Chunk2); - if (string_result <> 0) then exit(string_result); + if (Chunk1.Category = cSpecial) and (Chunk2.Category <> cSpecial) then + Exit(-1); + if (Chunk2.Category = cSpecial) and (Chunk1.Category <> cSpecial) then + Exit(1); - inc(str1); - inc(str2); - end; + if Chunk1.Category = cSpecial then + FindSameCategoryChunks + else + begin + FindChunk(Chunk1); + FindChunk(Chunk2); + end; - // skip leading zeroes for number - while (str1^ = '0') do - inc(str1); - while (str2^ = '0') do - inc(str2); + if (Chunk1.Category = cNumber) xor (Chunk2.Category = cNumber) then // one of them is number + Chunk1.Category := cString; // compare as strings to put numbers in a natural position - // compare number before decimal point - while (true) do - begin - is_digit1 := is_digit(str1^); - is_digit2 := is_digit(str2^); + while True do + begin - if (not is_digit1 and not is_digit2) then - break; - - if ((number_result = 0) and is_digit1 and is_digit2) then - begin - if (str1^ > str2^) then - number_result := +1 - else if (str1^ < str2^) then - number_result := -1 - else - number_result := 0; - end; - - if (is_digit1) then - begin - inc(str1); - inc(number1_size); - end; - - if (is_digit2) then - begin - inc(str2); - inc(number2_size); - end; - end; - - if (number1_size <> number2_size) then - exit(number1_size - number2_size); - - if (number_result <> 0) then - exit(number_result); - - // if there is a decimal point, compare number after one - if (is_point(str1^) or is_point(str2^)) then - begin - if (is_point(str1^)) then - inc(str1); - - if (is_point(str2^)) then - inc(str2); - - while (true) do - begin - is_digit1 := is_digit(str1^); - is_digit2 := is_digit(str2^); - - if (not is_digit1 and not is_digit2) then - break; - - if (is_digit1 and not is_digit2) then + case Chunk1.Category of + cString: begin - while (str1^ = '0') do - inc(str1); - - if (is_digit(str1^)) then - exit(+1) - else - break; + PrepareChunk(Chunk1); + PrepareChunk(Chunk2); + case CaseSensitivity of + cstNotSensitive: Result := WideCompareText(UTF8Decode(Chunk1.Str), UTF8Decode(Chunk2.Str)); + cstLocale: Result := WideCompareStr(UTF8Decode(Chunk1.Str), UTF8Decode(Chunk2.Str)); + cstCharValue: Result := WideStrComp(UTF8Decode(Chunk1.Str), UTF8Decode(Chunk2.Str)); + else + raise Exception.Create('Invalid CaseSensitivity parameter'); + end; + if Result <> 0 then + Exit; end; - - if (is_digit2 and not is_digit1) then + cNumber: begin - while (str2^ = '0') do - inc(str2); - - if (is_digit(str2^)) then - exit(-1) - else - break; + PrepareNumberChunk(Chunk1); + PrepareNumberChunk(Chunk2); + Result := Length(Chunk1.Str) - Length(Chunk2.Str); + if Result <> 0 then + Exit; + Result := CompareStr(Chunk1.Str, Chunk2.Str); + if Result <> 0 then + Exit; end; + cSpecial: + begin + PrepareChunk(Chunk1); + PrepareChunk(Chunk2); + Result := CompareStr(Chunk1.Str, Chunk2.Str); + if Result <> 0 then + Exit; + end; + cNone: + Exit(WideStrComp(UTF8Decode(str1), UTF8Decode(str2))); + end; - if (str1^ > str2^) then - exit(+1) - else if (str1^ < str2^) then - exit(-1); + NextChunkInit(Chunk1); + NextChunkInit(Chunk2); - inc(str1); - inc(str2); - end; + if Chunk1.Category <> Chunk2.Category then + if Chunk1.Category < Chunk2.Category then + Exit(-1) + else + Exit(1); + + if Chunk1.Category = cSpecial then + FindSameCategoryChunks + else + begin + FindChunk(Chunk1); + FindChunk(Chunk2); end; + end; end; Index: src/ufilesorting.pas =================================================================== --- src/ufilesorting.pas (revision 8429) +++ src/ufilesorting.pas (working copy) @@ -453,12 +453,12 @@ else if (gSortFolderMode <> sfmSortNameShowFirst) then Result := 0 else - Result := CompareStrings(item1.Name, item2.Name, gSortNatural, gSortCaseSensitivity); + Result := CompareStrings(item1.Name, item2.Name, gSortNatural, gSortSpecial, gSortCaseSensitivity); end; function ICompareByName(item1, item2: TFile; bSortNegative: Boolean):Integer; begin - Result := CompareStrings(item1.Name, item2.Name, gSortNatural, gSortCaseSensitivity); + Result := CompareStrings(item1.Name, item2.Name, gSortNatural, gSortSpecial, gSortCaseSensitivity); if bSortNegative then Result := -Result; @@ -475,7 +475,7 @@ end else begin - Result := CompareStrings(item1.NameNoExt, item2.NameNoExt, gSortNatural, gSortCaseSensitivity); + Result := CompareStrings(item1.NameNoExt, item2.NameNoExt, gSortNatural, gSortSpecial, gSortCaseSensitivity); if bSortNegative then Result := -Result; @@ -484,7 +484,7 @@ function ICompareByExt(item1, item2: TFile; bSortNegative: Boolean):Integer; begin - Result := CompareStrings(item1.Extension, item2.Extension, gSortNatural, gSortCaseSensitivity); + Result := CompareStrings(item1.Extension, item2.Extension, gSortNatural, gSortSpecial, gSortCaseSensitivity); if bSortNegative then Result := -Result; @@ -552,7 +552,7 @@ function ICompareByVariant(Value1, Value2: Variant; bSortNegative: Boolean):Integer; begin if VarIsType(Value1, varString) then - Result := CompareStrings(Value1, Value2, gSortNatural, gSortCaseSensitivity) + Result := CompareStrings(Value1, Value2, gSortNatural, gSortSpecial, gSortCaseSensitivity) else if Value1 = Value2 then Exit(0) else Index: src/uglobs.pas =================================================================== --- src/uglobs.pas (revision 8429) +++ src/uglobs.pas (working copy) @@ -264,6 +264,7 @@ gRunTermParams: String; gSortCaseSensitivity: TCaseSensitivity; gSortNatural: Boolean; + gSortSpecial: Boolean; gSortFolderMode: TSortFolderMode; gNewFilesPosition: TNewFilesPosition; gUpdatedFilesPosition: TUpdatedFilesPosition; @@ -1378,6 +1379,7 @@ gLynxLike := True; gSortCaseSensitivity := cstNotSensitive; gSortNatural := False; + gSortSpecial := False; gSortFolderMode := sfmSortNameShowFirst; gNewFilesPosition := nfpSortedPosition; gUpdatedFilesPosition := ufpNoChange; @@ -2382,6 +2384,7 @@ begin gSortCaseSensitivity := TCaseSensitivity(GetValue(SubNode, 'CaseSensitivity', Integer(gSortCaseSensitivity))); gSortNatural := GetValue(SubNode, 'NaturalSorting', gSortNatural); + gSortSpecial := GetValue(SubNode, 'SpecialSorting', gSortSpecial); gSortFolderMode:= TSortFolderMode(GetValue(SubNode, 'SortFolderMode', Integer(gSortFolderMode))); gNewFilesPosition := TNewFilesPosition(GetValue(SubNode, 'NewFilesPosition', Integer(gNewFilesPosition))); gUpdatedFilesPosition := TUpdatedFilesPosition(GetValue(SubNode, 'UpdatedFilesPosition', Integer(gUpdatedFilesPosition))); @@ -2976,6 +2979,7 @@ SubNode := FindNode(Node, 'Sorting', True); SetValue(SubNode, 'CaseSensitivity', Integer(gSortCaseSensitivity)); SetValue(SubNode, 'NaturalSorting', gSortNatural); + SetValue(SubNode, 'SpecialSorting', gSortSpecial); SetValue(SubNode, 'SortFolderMode', Integer(gSortFolderMode)); SetValue(SubNode, 'NewFilesPosition', Integer(gNewFilesPosition)); SetValue(SubNode, 'UpdatedFilesPosition', Integer(gUpdatedFilesPosition)); Index: src/ulng.pas =================================================================== --- src/ulng.pas (revision 8429) +++ src/ulng.pas (working copy) @@ -749,7 +749,7 @@ rsOptPluginSortOnlyWhenByExtension = 'Sorting WCX plugins is only possible when showing plugins by extension'; rsPluginFilenameStyleList = 'With complete absolute path;Path relative to %COMMANDER_PATH%;Relative to the following'; //------------------------------- - rsOptSortMethod = 'Alphabetical, considering accents;Natural sorting: alphabetical and numbers'; + rsOptSortMethod = 'Alphabetical, considering accents;Natural sorting: alphabetical and numbers;Alphabetical with special characters sort;Natural with special characters sort'; rsOptSortCaseSens = 'not case sensitive;according to locale settings (aAbBcC);first upper then lower case (ABCabc)'; rsOptSortFolderMode = 'sort by name and show first;sort like files and show first;sort like files'; rsOptNewFilesPosition = 'at the top of the file list;after directories (if directories are sorted before files);at sorted position;at the bottom of the file list'; | ||||
Fixed in Revision | 7924,8004,9204-9206 | ||||
Operating system | |||||
Widgetset | |||||
Architecture | |||||
|
Отличный патч! Имея привычку переименовывать некоторые папки так, чтобы они были в самом верху списка в нужном порядке, в DC тяжеловато... |
|
Почему "!" идёт после "_"? |
|
Linux? |
|
Ага :) Почему-то был уверен, что вариант "Естественная сортировка: алфавитно-числовая" не зависит от локали - ошибаюсь? По-крайней мере ничего другого в голову не приходит. Используется ru_RU.UTF-8, но связи не улавливаю. |
|
Зависит, символы сравниваются системной функцией. Получается в ru_RU.UTF-8 "!" идёт после "_". |
|
В этой теме экспериментировали с локалью, чтобы добиться желаемой сортировки: https://doublecmd.sourceforge.io/forum/viewtopic.php?p=23465#p23465 |
|
Странное, однако, решение, хотя, наверное, какую-то логику использовали... А в случае полей WDX-плагинов для сортировки используется ведь всё та же системная функция и можно и не пытаться обойти? Тему вспомнил, но генерить свою локаль как-то не очень хочется :( |
|
>>>А в случае полей WDX-плагинов... Да. Теоретически можно добавить режим сортировки при котором символы с кодами < 128 сортируются по правилам ASCII, без учета локали. В таком режиме спецсимволы будут сортироваться везде одинаково. Или даже лучше: сортировать спецсимволы по коду, остальное как было. |
|
Латинские буквы по ASCII не сортируются нормально. Сейчас режим сортировки этим методом (по коду символа) используется при выборе Case sensitivity: first upper then lower case (ABCabc). По названию видно, к чему это приводит. Спецсимволы по коду и перед буквами - это уже ближе к Windows, но одинарная кавычка, например, там идет сразу после буквы (то есть, не учитывается, как подчёркивание на Linux). Причём если Sort method выставлен как Natural sorting, сортировка уже отрабатывает некорректно (отличается от системной в проводнике) - 'z идет в начале списка, а не после z. Про такое поведение под Linux писал еще в теме про подчёркивания, а теперь оказывается, есть проявление и на Windows. Дело, как я понимаю, в том, что при натуральной сортировке сравнение системной функцией происходит посимвольно, поэтому она не видит второй символ, когда сравнивает первый. Теоретически это нужно переписать на сравнение отрывками строк между цифрами, тогда должно стать как в системе. |
|
>>> Латинские буквы по ASCII не сортируются нормально Я имел ввиду другой алгоритм. Типа как в стандартной функции CompareText, она производит сравнение по коду символа без учета регистра и только для ASCII. Вроде цели сделать как в Проводнике и не было, если надо как в проводнике то можно просто использовать StrCmpLogicalW, будет один в один. TC использует её для натуральной сортировки. Еще было бы хорошо иметь метод сортировки независимый от системы, чтобы сортировало одинаково и в Windows и в Linux. Хотя бы в плане спецсимволов. |
|
>>> Почему "!" идёт после "_"? Возможно, ноги растут из Юникода, по следующей ссылке выдаёт именно такой порядок. https://ssl.icu-project.org/icu-bin/locexp?_=en&d_=en&x=col >>> Вроде цели сделать как в Проводнике и не было Тут дело не только в том, чтобы как в Проводнике (или, скорее, как в системе), а и что поведение отличается в зависимости от того, алфавитная или натуральная сортировка выбрана. Я немного покопался, WideCompareText и WideCompareStr работают через widestringmanager.CompareWideStringProc, который параметром поддерживает TCompareOptions, в которые могут входить coIgnoreCase, coDigitAsNumbers и coStringSort. Под Windows дальше работает через CompareStringW, последние опции соответствуют флагам SORT_DIGITSASNUMBERS и SORT_STRINGSORT. Их описание здесь: https://docs.microsoft.com/en-us/windows/desktop/api/stringapiset/nf-stringapiset-comparestringex https://stackoverflow.com/a/48929800 Первая - это натуральная сортировка, а вторая - как раз отключение "съедания" спецсимволов. Идеально было бы использовать как раз эту функцию с этими параметрами, но первый параметр - только Windows 7+ (на XP - ошибка) и оба, подозреваю, игнорируются под Linux (не проверял, но не вижу использования в коде). Интерфейс TCompareOptions - не изобретение FPC, а унаследован от Delphi, так что если дореализовать эти функции, патчи могут и принять. Это немного отклонение от темы про спецсимволы, скорее касается бага 0001239, ну и общего понимания особенностей сортировки. Сейчас эффект StringSort достигается только неявным образом при включении натуральной сортировки и нет возможности настраивать его отдельно. Насчет спецсимволов есть еще вариант, предложенный в теме, сделать возможность задавать список символов, которые идут в начале. >>> Я имел ввиду другой алгоритм. Типа как в стандартной функции CompareText, она производит сравнение по коду символа без учета регистра и только для ASCII. Это, наверное, поломает сортировку для языков с диакритическими знаками. |
|
> > Почему "!" идёт после "_"? > Возможно, ноги растут из Юникода, по следующей ссылке выдаёт именно такой порядок. https://ssl.icu-project.org/icu-bin/locexp?_=en&d_=en&x=col [^] Там речь вроде не о юникоде, а об английской локали. Код в юникоде как есть можно, например, тут http://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt глянуть. В голову пришло, что, может, в локалях логика порядка символов типа: "_" - часть слова ("[A-Za-z0-9_]+"), а "!" - уже натурально разделитель? Или ещё и что-то вроде частоты встречаемости учитывают? В общем, определённо не по коду. |
|
>>> Это, наверное, поломает сортировку для языков с диакритическими знаками. Верно, поэтому чуть ниже я и предложил применять особую обработку только к спецсимволам. Под Windows >= 7 можно использовать CompareWideStringProc с флагами, а вот под Linux и Windows < 7 надо добавлять поддержку недостающих флагов самим. Дело за малым, придумать алгоритм :) Либо оставить как есть, но в обоих методах сортировки вызывать CompareWideStringProc с флагом coStringSort. Тогда наверное поведение будет одинаковым для обоих методов. Это для Windows. Под Linux дополнительно придется реализовать поддержку флага coStringSort. |
|
>>> Код в юникоде как есть можно, например, тут глянуть Так там не по коду сравнивается, а в соответствии с collation rules, которые и зависят от локали. Но и если переключить на Root (убрать английскую локаль), ! останется после _. https://unicode.org/reports/tr10/#Collation_And_Code_Chart_Order The position of characters in the Unicode code charts does not specify their sort order. Базовый порядок сортировки определяется в DUCET: http://ftp.unicode.org/Public/UCA/6.0.0/allkeys.txt По первой ссылке есть еще один интересный момент: Collation order is not a stable sort. Stability is a property of a sort algorithm, not of a collation sequence. Что выражается в том, что строки могут быть равны, и в таком случае порядок зависит от алгоритма сортировки. В процессе экспериментов до этого я уже сталкивался с ситуацией, что при пересортировке (два клика по столбцу) у меня менялись файлы "the" и "The" местами (под Linux), теперь понятно, чем это объясняется. В идеале это тоже надо бы как-то контролировать на уровне алгоритма. Возможно, сортировать по коду символа, если имена файлов оказались равны через стандартную функцию сравнения. >>> Верно, поэтому чуть ниже я и предложил применять особую обработку только к спецсимволам. Да, это остаётся единственный рабочий вариант. Теперь только определить, что считать спецсимволами и нужно ли пользователю иметь возможность задавать этот список. >>> Под Linux дополнительно придется реализовать поддержку флага coStringSort. Если из текущего алгоритма натуральной сортировки убрать саму, собственно, натуральную сортировку, один этот эффект и останется. Это можно хоть на всех платформах использовать, но это дополнительная нагрузка на систему - дергать системную функцию сравнения гораздо больше раз, чем без этого. Хотя если на производительность натуральной сортировки никто не жаловался, то должно быть ОК. Но мне кажется, или где-то всё-таки был баг на тему, что в DC папка с большим количеством файлов открывается дольше, чем в других файловых менеджерах? Плюс, так будет одинаково в двух функциях, но возможность word sort (которая не string sort) теряется. Если она нужна, то алгоритм, чтобы не терять ее при натуральной сортировке, я вижу в дроблении строки на подстроки, которые будут сортироваться независимо. Но не посимвольно, а объединяя в подстроки те символы, которые должны сортироваться одной функцией. То есть, для натуральной сортировки строка ABC-DEF123GHI разбивается на ABC-DEF,123,GHI, первая и последняя части сравниваются системной функцией (исполняющей функции word sort), а номер - функцией натуральной сортировки. Если включена еще и сортировка спецсимволов, то разбивается на ABC,-,DEF,123,GHI, часть "-" передается в функцию сравнения спецсимволов, где сравнивается с другой строкой по коду, если в другой строке спецсимвол, либо возвращает меньше (располагать раньше), если с другой стороны не спецсимвол. |
|
Ещё можно попробовать переписать на паскаль реализацию CompareStringW из Wine. |
|
Посмотрел тут ради любопытства, в ASCII (первой половине) наберётся с 4е десятка небуквенных символов, включая цифры... И цифры можно сортировать по цифрам (1, 10, 2) или как числа (1, 2, 10 - по модулю, видимо) - тоже гемор выходит. А как DC видит и обрабатывает всё это добро? Как строки в UTF-8 или тупо массивы байт? P.S. Сегодня чуть мышкой не начал стучать по столу: папка оказалась, мягко говоря, не там, где очевидно ожидалась... Надо, наверное, совсем перестать запускать Thunar :)) |
|
>>>Если из текущего алгоритма натуральной сортировки убрать саму, собственно, натуральную сортировку, один этот эффект и останется. Да, но хотелось бы более оптимального алгоритма. Натуральная сортировка примерно в 2 раза медленнее обычной (в Linux). CompareStringW в Wine обращается напрямую к collation table, не знаю можно ли получить прямой доступ к системной collation table из DC. Сходу найти не получилось. >>>А как DC видит и обрабатывает всё это добро? Как строки в UTF-8 или тупо массивы байт? Изначально строка в UTF-8, для сортировки конвертируется в UTF-16 (но обрабатывается как UCS2). Дальше идет работа с 2-х байтовыми символами. |
|
Переписал натуральную сортировку, теперь соответствует TC и Проводнику, за исключением некоторых случаев, когда сам Проводник и TC не сортируют стабильно (например, "-20", "(a" иногда не на своих местах). Для дополнительной сортировки спецсимволов включить SORT_SPECIAL, вместе с этим она должна примерно соответствовать string sort, без этого остаётся системный word sort, это заметное отличие от старой натуральной сортировки. В старой сортировке ещё зачем-то обрабатывалась точка и запятая, это для каких случаев? На скорость не проверял, но должна быть быстрее предыдущей, т.к. в Юникод строки конвертируются только перед передачей другим функциям сравнения, а сама по себе она работает с обычными строками. Это возможно, поскольку для особых случаев нас интересуют только символы из ASCII-набора, а UTF-8 гарантирует несовпадение дополнительных байтов с ними, см. http://wiki.freepascal.org/UTF8_strings_and_characters#The_beauty_of_UTF-8 |
|
Пока только в форме патча на проверку. Посмотрите, нет ли каких-то неожиданностей в этом режиме. Также ожидаю, что с SORT_SPECIAL = True порядок под Linux будет соответствовать Windows в этом же режиме. Skif_off, покажете, что получается под Linux с тестовым набором? SORT_SPECIAL сначала нужно включить в патче, по умолчанию выключен. Дальше вопрос, как оформить новый режим в настройках? Заменить старую натуральную сортировку, а для спецсимволов новую галочку? Или добавить в выпадающий список разные комбинации настроек? Или вообще убрать выпадающий список и сделать две галочки "Натуральная сортировка" и "Спецсимволы в начале по ASCII"? |
|
>>> Надо, наверное, совсем перестать запускать Thunar О, и сравните, пожалуйста, с Thunar в режиме SORT_SPECIAL = False. Надеюсь, будут эквивалентны. |
|
>>>В старой сортировке ещё зачем-то обрабатывалась точка и запятая, это для каких случаев? Дробные числа Скорость можно замерить собрав DC с дефайном "timeFileView". В консоль будет время писаться. |
|
>>> Дробные числа Так и предполагал. Но я это не перереализовывал в новой версии, т.к. во-первых, отличается от системного (и вообще большинства других программ, я думаю), а во-вторых, в имени файла точка - это чаще разделитель номера версии, чем дробные числа, ИМХО. |
|
>>>Дальше вопрос, как оформить новый режим в настройках? Получается у нас теперь 4 вида сортировки: стандартная, натуральная, стандартная+спецсимволы и натуральная+спецсимволы. Наверное добавить еще 2 вида в выпадающий список. |
|
В новом патче код тот же самый, просто не удалена старая сортировка, чтобы удобно было переключаться между новой и старой путем комментирования строк 811-812. >>> Скорость можно замерить собрав DC с дефайном "timeFileView". В консоль будет время писаться. Потестировал под XP, компиляция с выключенной оптимизацией, разница в мс между Loaded files и Made sorted disp.lst: 750 - алфавитная 900 - натуральная новая 1000 - натуральная + спецсимволы 1500 - натуральная старая 500 - по коду символа (алфавитная + first upper then lower case) |
|
Патч v2 - исправлен алгоритм, чтобы сортировал спецсимволы в начале слова в начале списка также в режиме first upper then lower case |
|
Патч v2 работает странно (местами), с тире что-то не так: 1) "abc0123def" "-abc0123def" "abc123def" по идее папка "-abc0123def" должна идти за "-100". 2) в случае цифр и тире ("-20", "-100") они оказываются почти в самом верху. 3) накидал папок не мудрствуя: TC: |DC: #--01 | '--05 $--02 | ---10 %--03 | #--01 &--04 | $--02 (--06 | %--03 )--07 | &--04 ,--09 | (--06 +--08 | )--07 '--05 | ,--09 ---10 | +--08 (под Win щупал, естественная-натуральная, без учёта регистра, цифры - порядок в таблице ASCII) > Получается у нас теперь 4 вида сортировки: стандартная, натуральная, стандартная+спецсимволы и натуральная+спецсимволы. А "500 - по коду символа (алфавитная + first upper then lower case)"? |
|
>>>А "500 - по коду символа (алфавитная + first upper then lower case)"? Это стандартная. А "first upper then lower case" это уже из второго выпадающего списка "Чувствительность к регистру". С учетом выпадающего списка "Чувствительность к регистру" будет 12 видов сортировки. |
|
Приведенные Вами результаты в TC 9.0a на XP (рус) воспроизвести не удалось. Какая версия и язык Windows и какой стоит sort method в настройках TC (категория Display)? Natural sorting: by character code and numbers соответствует SORT_SPECIAL = True Natural sorting: alphabetical and numbers - SORT_SPECIAL = False В собранном у Вас DC, похоже, False (как по умолчанию в патче). Этим же вызвано неожиданное поведение сортировки с тире и апострофом - их как бы не существует, в этом и заключается word sort. Под Linux в эту категорию пропускаемых символов входит и подчеркивание, что приводит к результатам, показанным в 0001239. Это минус с точки зрения спецсимволов, но может приводить к большей интуитивности в средине строки. По приведенной выше ссылке на Stack Overflow есть примеры. Мои результаты под XP: SORT_SPECIAL = False, соответствуют указанным Вами, TC в соответствующем режиме и Проводнику: ' - # $ % & ( ) , + SORT_SPECIAL = True, соответствуют ASCII: # $ % & ' ( ) + , - Под Linux, SORT_SPECIAL = False: - , ' ( ) $ & # % + В Nemo почему-то немного отличаются: - , ' ( ) $ & % + # Тестовый набор в DC соответствует Nemo и Archive Manager, за исключением abcA011 abcA013 abca012 в Nemo (хотя в других местах регистр игнорируется) и abcA011 abca012 abcA013 в DC, а также abc123def abc0123def в Nemo и abc0123def abc123def в DC. Тем не менее, подход в DC считаю лучшим в данных ситуациях. При SORT_SPECIAL = True сортировка тестового набора в Linux и Windows совпадает. |
|
> В собранном у Вас DC, похоже, False (как по умолчанию в патче). Так и есть: слил, накатил, собрал, запустил. Win7x32 русская (форматы и расположение соотв.), ТС 8.52а, "Естественная сортировка: алфавитно-числовая". |
|
Skif_off, перепроверьте, пожалуйста, свои результаты. Протестировал TC 8.51a под Win7x32 русской, получаю тот столбец, что Вы писали для DC. Ваш результат выглядит, будто часть списка отсортирована по коду символа, а другая - естественной сортировкой. Либо у Вас каким-то образом включен string sort на уровне системы (хотя точно не уверен, что он дал бы именно такие результаты). Или, может, сборка TC кастомная? Какой порядок сортировки в Проводнике? |
|
Выяснил, что под Windows 7 отличается натуральная сортировка от XP и Linux в некоторых аспектах, а именно: -20 сортируется теперь рядом с 20, аналогично как -a рядом с a. Можно попробовать исправить, отправляя в функцию сравнения спецсимвол с добавленным в конце нулём, если за спецсимволом идёт число. Числа не на первом месте идут не первыми, а в общем порядке. Например, стало __ _1 _a вместо _1 __ _a. В начале строки числа идут в общем порядке (между спецсимволами и буквами) на всех системах. Тут проблема концептуальная: либо сортировка не будет соответствовать принятой в системе, либо даже с SORT_SPECIAL перестанет быть одинаковой на всех системах. |
|
Есть идея оставить сортировку как есть (v2), а для Windows 7+ использовать CompareStringW, используя string sort как эквивалент сортировки спецсимволов. Проще, чем опять менять сортировку и вдобавок должно стать быстрее. Разве что с first upper then lower case всё равно придётся использовать свой алгоритм и останется небольшое отличие от системы, но это уже не так важно? |
|
Патч v3 добавляет новые методы сортировки в настройки с возможностью переключения в рантайме. Алгоритм из v2, без изменений. Не придумал ничего лучше, чем назвать новые sort method как: Alphabetical with special characters sort Natural with special characters sort Глобальную переменную и опцию в конфигурационном файле взял gSortSpecial и SpecialSorting, по аналогии с gSortNatural и NaturalSorting. Но сами по себе они звучат не очень интуитивно. Есть альтернативные предложения по названиям? |
|
> Не придумал ничего лучше, чем назвать новые sort method как: Получается, что новые методы - дополнение существующих сортировкой спецсимволов и список должен будет выглядеть так ----------------------------------------- Alphabetical, considering accents Alphabetical with special characters sort Natural sorting: alphabetical and numbers Natural with special characters sort ----------------------------------------- ? > Но сами по себе они звучат не очень интуитивно. По хорошему, наверное, подобные вещи можно и нужно в справке описать? Что-то типа "Alphabetical, considering accents, symbols of punctuation and orthography" как-то длинно выходит. |
|
Я пока их добавил просто в конец: ----------------------------------------- Alphabetical, considering accents Natural sorting: alphabetical and numbers Alphabetical with special characters sort Natural with special characters sort ----------------------------------------- Но могу и передвинуть, чтобы было как у Skif_off. Будет лучше, если передвинуть? И как вообще быть с переводами? Добавить во все языковые файлы англоязычные пункты в строку перевода? Потому что если вообще не трогать, то в других языках будет не хватать этих пунктов. Либо можно вообще поудалять переводы этих строк, но эта идея мне не нравится. |
|
> Но могу и передвинуть, чтобы было как у Skif_off. Будет лучше, если передвинуть? Если можно сказать, что "Alphabetical with special characters sort" - это как "Alphabetical, considering accents" + больше внимания уделено спецсимволам, то поставить их рядом более логично, наверное. > И как вообще быть с переводами? Добавить во все языковые файлы англоязычные пункты в строку перевода? Тут строка не заменяется целиком, а обновляется: правильнее не трогать вручную, пусть строка будет автоматически помечена как fuzzy (но никто не может помешать вам потом поправить перевод на знакомые вам языки :)). |
|
> поставить их рядом более логично, наверное. ОК, в следующем патче передвину пункты и собираюсь добавить вызов системной функции сортировки для Windows 7+. > Тут строка не заменяется целиком, а обновляется: правильнее не трогать вручную Дело в том, что в данном случае реализовано так, что количество пунктов в выпадающем списке задаётся в соответствии с количеством точек с запятой в строке и поэтому должно соответствовать на всех языках. Получается, просто предоставить его самому себе - не вариант, т.к. новые пункты просто не появятся в других языках. |
|
>>>Получается, просто предоставить его самому себе - не вариант, т.к. новые пункты просто не появятся в других языках. Lazarus пометит строку как "fuzzy", такие строки выводятся на английском, соответственно с кол-вом пунктов будет нормально. |
|
Кто-нибудь пользуется newnaturalsort-v3.patch? Он под r8429, на альфу можно накатить? Вспомнилось внезапно о результатах тестирования https://doublecmd.sourceforge.io/mantisbt/view.php?id=1418#c2904 (предпочтение отдаю натуральной), не то чтобы имею дела с гигантскими каталогами, но цифры вкусные... |
|
Возможно его пора влить в trunk. |
|
Я помню об этом патче, обещаю до конца года закоммитить. |
|
Всем новой сортировки в новом году! |
|
Спасибо! Есть проблема (с локализацией, может?): в списке вижу только два старых метода. |
|
Да, я не трогал файлы локализации вообще, просто ни разу не имел с ними дела раньше, надеялся, что само подтянется. Сейчас посмотрю. |
|
Обновил языковые файлы, должно работать теперь. Alexx2000, я не поменял cbSortMethod.Items на форме frmOptionsFilesViews. Стоит ради этого изменения делать коммит или оставить как есть? На функциональность оно не влияет, как я понимаю, так как в рантайме заменяется реальным значением. |
|
Да, заменяется. На всякий случай можно поменять, чтобы и в режиме дизайна правильно было. По идее эффективнее использовать CompareUnicodeStringProc, UnicodeCompareText и UnicodeCompareStr. Уже баг нашелся: https://doublecmd.sourceforge.io/forum/viewtopic.php?t=5941&start=12 У себя не могу воспроизвести. |
Date Modified | Username | Field | Change |
---|---|---|---|
2016-06-13 06:08 | cordylus | New Issue | |
2017-12-09 22:04 | cordylus | File Added: bug1418.patch | |
2017-12-11 13:44 | Skif_off | Note Added: 0002418 | |
2017-12-17 14:06 | Alexx2000 | Assigned To | => Alexx2000 |
2017-12-17 14:06 | Alexx2000 | Status | new => resolved |
2017-12-17 14:06 | Alexx2000 | Resolution | open => fixed |
2017-12-17 14:06 | Alexx2000 | Target Version | => 0.8.2 |
2017-12-17 14:24 | Alexx2000 | Fixed in Revision | => 7924 |
2018-02-04 15:13 | Alexx2000 | Relationship added | related to 0001983 |
2018-02-23 11:48 | Alexx2000 | Fixed in Revision | 7924 => 7924,8004 |
2018-02-23 11:48 | Alexx2000 | Fixed in Version | => 0.8.2 |
2018-11-06 11:04 | Skif_off | Note Added: 0002868 | |
2018-11-06 11:54 | Alexx2000 | Note Added: 0002869 | |
2018-11-06 13:23 | Skif_off | Note Added: 0002871 | |
2018-11-06 13:25 | Skif_off | Note Edited: 0002871 | |
2018-11-06 13:25 | Skif_off | Note Edited: 0002871 | |
2018-11-06 13:32 | Alexx2000 | Note Added: 0002872 | |
2018-11-06 13:44 | cordylus | Note Added: 0002873 | |
2018-11-06 14:05 | Skif_off | Note Added: 0002874 | |
2018-11-06 14:06 | Skif_off | Note Edited: 0002874 | |
2018-11-07 09:20 | Alexx2000 | Note Added: 0002875 | |
2018-11-07 10:56 | Alexx2000 | Note Edited: 0002875 | |
2018-11-07 19:21 | cordylus | Note Added: 0002877 | |
2018-11-07 19:32 | cordylus | Note Edited: 0002877 | |
2018-11-07 19:34 | cordylus | Note Edited: 0002877 | |
2018-11-07 19:36 | cordylus | Note Edited: 0002877 | |
2018-11-07 20:17 | Alexx2000 | Note Added: 0002879 | |
2018-11-07 23:23 | cordylus | Note Added: 0002881 | |
2018-11-08 02:51 | Skif_off | Note Added: 0002882 | |
2018-11-08 08:17 | Alexx2000 | Note Added: 0002883 | |
2018-11-08 08:53 | Alexx2000 | Note Edited: 0002883 | |
2018-11-08 08:54 | Alexx2000 | Note Edited: 0002883 | |
2018-11-08 09:08 | Alexx2000 | Note Edited: 0002883 | |
2018-11-08 15:14 | cordylus | Note Added: 0002889 | |
2018-11-08 16:43 | cordylus | Note Added: 0002890 | |
2018-11-08 16:43 | cordylus | Note Edited: 0002890 | |
2018-11-10 12:14 | Skif_off | Note Added: 0002894 | |
2018-11-10 12:57 | Alexx2000 | Note Added: 0002895 | |
2018-11-10 18:14 | cordylus | File Added: newnaturalsort.patch | |
2018-11-10 18:16 | cordylus | File Added: newnaturalsort-testsuite.zip | |
2018-11-10 18:41 | cordylus | Note Added: 0002897 | |
2018-11-10 18:52 | cordylus | Note Added: 0002898 | |
2018-11-10 18:55 | cordylus | Note Edited: 0002898 | |
2018-11-10 18:55 | cordylus | Note Edited: 0002898 | |
2018-11-10 19:00 | cordylus | Note Edited: 0002897 | |
2018-11-10 19:27 | cordylus | Note Added: 0002899 | |
2018-11-10 20:01 | Alexx2000 | Note Added: 0002900 | |
2018-11-10 20:11 | Alexx2000 | Note Edited: 0002900 | |
2018-11-10 20:25 | cordylus | Note Added: 0002901 | |
2018-11-10 21:15 | cordylus | File Added: newnaturalsort+oldnotremoved.patch | |
2018-11-10 21:45 | Alexx2000 | Note Added: 0002903 | |
2018-11-10 22:01 | cordylus | Note Added: 0002904 | |
2018-11-10 22:04 | cordylus | Note Edited: 0002904 | |
2018-11-10 22:20 | cordylus | Note Edited: 0002904 | |
2018-11-11 02:47 | cordylus | File Added: newnaturalsort-v2.patch | |
2018-11-11 02:49 | cordylus | Note Added: 0002909 | |
2018-11-11 08:58 | Skif_off | Note Added: 0002910 | |
2018-11-11 10:57 | Alexx2000 | Note Added: 0002911 | |
2018-11-11 21:15 | cordylus | Note Added: 0002912 | |
2018-11-11 21:51 | cordylus | Note Edited: 0002912 | |
2018-11-11 21:58 | cordylus | Note Edited: 0002912 | |
2018-11-11 22:19 | Skif_off | Note Added: 0002913 | |
2018-11-11 22:20 | cordylus | Note Edited: 0002912 | |
2018-11-11 23:05 | cordylus | Note Added: 0002916 | |
2018-11-11 23:07 | cordylus | Note Edited: 0002916 | |
2018-11-11 23:20 | cordylus | Note Edited: 0002916 | |
2018-11-11 23:21 | cordylus | Note Edited: 0002916 | |
2018-11-12 00:37 | cordylus | Note Added: 0002917 | |
2018-11-12 01:57 | cordylus | Note Added: 0002918 | |
2018-11-12 02:02 | cordylus | Note Edited: 0002918 | |
2018-11-14 04:04 | cordylus | File Added: newnaturalsort-v3.patch | |
2018-11-14 04:13 | cordylus | Note Added: 0002922 | |
2018-11-14 09:31 | Skif_off | Note Added: 0002924 | |
2018-11-14 09:32 | Skif_off | Note Edited: 0002924 | |
2018-11-14 17:59 | cordylus | Note Added: 0002925 | |
2018-11-14 18:00 | cordylus | Note Edited: 0002925 | |
2018-11-14 21:09 | Skif_off | Note Added: 0002926 | |
2018-11-14 22:42 | cordylus | Note Added: 0002927 | |
2018-11-14 22:49 | cordylus | Note Edited: 0002927 | |
2018-11-14 23:07 | Alexx2000 | Note Added: 0002928 | |
2019-11-28 22:25 | Skif_off | Note Added: 0003326 | |
2019-11-28 23:13 | Alexx2000 | Note Added: 0003327 | |
2019-11-29 02:38 | cordylus | Note Added: 0003328 | |
2020-01-01 00:07 | cordylus | Assigned To | Alexx2000 => cordylus |
2020-01-01 00:07 | cordylus | Status | resolved => assigned |
2020-01-01 00:07 | cordylus | Fixed in Revision | 7924,8004 => 7924,8004,9204-9205 |
2020-01-01 00:07 | cordylus | Status | assigned => resolved |
2020-01-01 00:08 | cordylus | Note Added: 0003365 | |
2020-01-01 01:24 | Skif_off | Note Added: 0003366 | |
2020-01-01 01:36 | cordylus | Note Added: 0003367 | |
2020-01-01 02:35 | cordylus | Fixed in Revision | 7924,8004,9204-9205 => 7924,8004,9204-9206 |
2020-01-01 02:37 | cordylus | Note Added: 0003368 | |
2020-01-01 02:39 | cordylus | Note Edited: 0003368 | |
2020-01-08 18:26 | Alexx2000 | Note Added: 0003372 | |
2020-08-31 08:15 | Alexx2000 | Fixed in Version | 0.8.2 => 1.0.0 |
2020-08-31 08:15 | Alexx2000 | Target Version | 0.8.2 => 1.0.0 |
2023-01-10 09:29 | Alexx2000 | Relationship added | related to 0002584 |
2023-01-10 09:30 | Alexx2000 | Status | resolved => closed |