View Issue Details

IDProjectCategoryView StatusLast Update
0001418Double CommanderLogicpublic2023-01-10 09:30
Reportercordylus Assigned Tocordylus  
PrioritynormalSeverityminorReproducibilityalways
Status closedResolutionfixed 
ProjectionnoneETAnone 
Target Version1.0.0Fixed in Version1.0.0 
Summary0001418: Natural sorting order is unconventional
DescriptionNumbers should go before letters but after special symbols such as ![(, like it is implemented in other software.
TagsNo 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);
bug1418.patch (480 bytes)   
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.patch (8,718 bytes)   
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-v2.patch (8,969 bytes)   
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';
newnaturalsort-v3.patch (18,877 bytes)   
Fixed in Revision7924,8004,9204-9206
Operating system
Widgetset
Architecture

Relationships

related to 0001983 feedback Sorting order is different on Windows and Linux (Ubuntu) 
related to 0002584 closed Numerical sorting in file panel fails for my music files 

Activities

Skif_off

2017-12-11 13:44

reporter   ~0002418

Отличный патч! Имея привычку переименовывать некоторые папки так, чтобы они были в самом верху списка в нужном порядке, в DC тяжеловато...

Skif_off

2018-11-06 11:04

reporter   ~0002868

Почему "!" идёт после "_"?

Alexx2000

2018-11-06 11:54

administrator   ~0002869

Linux?

Skif_off

2018-11-06 13:23

reporter   ~0002871

Last edited: 2018-11-06 13:25

Ага :) Почему-то был уверен, что вариант "Естественная сортировка: алфавитно-числовая" не зависит от локали - ошибаюсь? По-крайней мере ничего другого в голову не приходит.
Используется ru_RU.UTF-8, но связи не улавливаю.

Alexx2000

2018-11-06 13:32

administrator   ~0002872

Зависит, символы сравниваются системной функцией. Получается в ru_RU.UTF-8 "!" идёт после "_".

cordylus

2018-11-06 13:44

developer   ~0002873

В этой теме экспериментировали с локалью, чтобы добиться желаемой сортировки: https://doublecmd.sourceforge.io/forum/viewtopic.php?p=23465#p23465

Skif_off

2018-11-06 14:05

reporter   ~0002874

Last edited: 2018-11-06 14:06

Странное, однако, решение, хотя, наверное, какую-то логику использовали...

А в случае полей WDX-плагинов для сортировки используется ведь всё та же системная функция и можно и не пытаться обойти?

Тему вспомнил, но генерить свою локаль как-то не очень хочется :(

Alexx2000

2018-11-07 09:20

administrator   ~0002875

Last edited: 2018-11-07 10:56

>>>А в случае полей WDX-плагинов...

Да.


Теоретически можно добавить режим сортировки при котором символы с кодами < 128 сортируются по правилам ASCII, без учета локали. В таком режиме спецсимволы будут сортироваться везде одинаково.

Или даже лучше: сортировать спецсимволы по коду, остальное как было.

cordylus

2018-11-07 19:21

developer   ~0002877

Last edited: 2018-11-07 19:36

Латинские буквы по ASCII не сортируются нормально. Сейчас режим сортировки этим методом (по коду символа) используется при выборе Case sensitivity: first upper then lower case (ABCabc). По названию видно, к чему это приводит.

Спецсимволы по коду и перед буквами - это уже ближе к Windows, но одинарная кавычка, например, там идет сразу после буквы (то есть, не учитывается, как подчёркивание на Linux). Причём если Sort method выставлен как Natural sorting, сортировка уже отрабатывает некорректно (отличается от системной в проводнике) - 'z идет в начале списка, а не после z. Про такое поведение под Linux писал еще в теме про подчёркивания, а теперь оказывается, есть проявление и на Windows. Дело, как я понимаю, в том, что при натуральной сортировке сравнение системной функцией происходит посимвольно, поэтому она не видит второй символ, когда сравнивает первый. Теоретически это нужно переписать на сравнение отрывками строк между цифрами, тогда должно стать как в системе.

Alexx2000

2018-11-07 20:17

administrator   ~0002879

>>> Латинские буквы по ASCII не сортируются нормально

Я имел ввиду другой алгоритм. Типа как в стандартной функции CompareText, она производит сравнение по коду символа без учета регистра и только для ASCII.


Вроде цели сделать как в Проводнике и не было, если надо как в проводнике то можно просто использовать StrCmpLogicalW, будет один в один. TC использует её для натуральной сортировки.

Еще было бы хорошо иметь метод сортировки независимый от системы, чтобы сортировало одинаково и в Windows и в Linux. Хотя бы в плане спецсимволов.

cordylus

2018-11-07 23:23

developer   ~0002881

>>> Почему "!" идёт после "_"?

Возможно, ноги растут из Юникода, по следующей ссылке выдаёт именно такой порядок.
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.

Это, наверное, поломает сортировку для языков с диакритическими знаками.

Skif_off

2018-11-08 02:51

reporter   ~0002882

> > Почему "!" идёт после "_"?

> Возможно, ноги растут из Юникода, по следующей ссылке выдаёт именно такой порядок.
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_]+"), а "!" - уже натурально разделитель? Или ещё и что-то вроде частоты встречаемости учитывают? В общем, определённо не по коду.

Alexx2000

2018-11-08 08:17

administrator   ~0002883

Last edited: 2018-11-08 09:08

>>> Это, наверное, поломает сортировку для языков с диакритическими знаками.

Верно, поэтому чуть ниже я и предложил применять особую обработку только к спецсимволам.


Под Windows >= 7 можно использовать CompareWideStringProc с флагами, а вот под Linux и Windows < 7 надо добавлять поддержку недостающих флагов самим. Дело за малым, придумать алгоритм :)

Либо оставить как есть, но в обоих методах сортировки вызывать CompareWideStringProc с флагом coStringSort. Тогда наверное поведение будет одинаковым для обоих методов. Это для Windows. Под Linux дополнительно придется реализовать поддержку флага coStringSort.

cordylus

2018-11-08 15:14

developer   ~0002889

>>> Код в юникоде как есть можно, например, тут глянуть

Так там не по коду сравнивается, а в соответствии с 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, часть "-" передается в функцию сравнения спецсимволов, где сравнивается с другой строкой по коду, если в другой строке спецсимвол, либо возвращает меньше (располагать раньше), если с другой стороны не спецсимвол.

cordylus

2018-11-08 16:43

developer   ~0002890

Last edited: 2018-11-08 16:43

Ещё можно попробовать переписать на паскаль реализацию CompareStringW из Wine.

Skif_off

2018-11-10 12:14

reporter   ~0002894

Посмотрел тут ради любопытства, в ASCII (первой половине) наберётся с 4е десятка небуквенных символов, включая цифры... И цифры можно сортировать по цифрам (1, 10, 2) или как числа (1, 2, 10 - по модулю, видимо) - тоже гемор выходит.

А как DC видит и обрабатывает всё это добро? Как строки в UTF-8 или тупо массивы байт?

P.S. Сегодня чуть мышкой не начал стучать по столу: папка оказалась, мягко говоря, не там, где очевидно ожидалась... Надо, наверное, совсем перестать запускать Thunar :))

Alexx2000

2018-11-10 12:57

administrator   ~0002895

>>>Если из текущего алгоритма натуральной сортировки убрать саму, собственно, натуральную сортировку, один этот эффект и останется.

Да, но хотелось бы более оптимального алгоритма. Натуральная сортировка примерно в 2 раза медленнее обычной (в Linux).

CompareStringW в Wine обращается напрямую к collation table, не знаю можно ли получить прямой доступ к системной collation table из DC. Сходу найти не получилось.

>>>А как DC видит и обрабатывает всё это добро? Как строки в UTF-8 или тупо массивы байт?

Изначально строка в UTF-8, для сортировки конвертируется в UTF-16 (но обрабатывается как UCS2). Дальше идет работа с 2-х байтовыми символами.

cordylus

2018-11-10 18:41

developer   ~0002897

Last edited: 2018-11-10 19:00

Переписал натуральную сортировку, теперь соответствует 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

cordylus

2018-11-10 18:52

developer   ~0002898

Last edited: 2018-11-10 18:55

Пока только в форме патча на проверку. Посмотрите, нет ли каких-то неожиданностей в этом режиме.

Также ожидаю, что с SORT_SPECIAL = True порядок под Linux будет соответствовать Windows в этом же режиме. Skif_off, покажете, что получается под Linux с тестовым набором? SORT_SPECIAL сначала нужно включить в патче, по умолчанию выключен.

Дальше вопрос, как оформить новый режим в настройках? Заменить старую натуральную сортировку, а для спецсимволов новую галочку? Или добавить в выпадающий список разные комбинации настроек? Или вообще убрать выпадающий список и сделать две галочки "Натуральная сортировка" и "Спецсимволы в начале по ASCII"?

cordylus

2018-11-10 19:27

developer   ~0002899

>>> Надо, наверное, совсем перестать запускать Thunar

О, и сравните, пожалуйста, с Thunar в режиме SORT_SPECIAL = False. Надеюсь, будут эквивалентны.

Alexx2000

2018-11-10 20:01

administrator   ~0002900

Last edited: 2018-11-10 20:11

>>>В старой сортировке ещё зачем-то обрабатывалась точка и запятая, это для каких случаев?

Дробные числа


Скорость можно замерить собрав DC с дефайном "timeFileView". В консоль будет время писаться.

cordylus

2018-11-10 20:25

developer   ~0002901

>>> Дробные числа

Так и предполагал. Но я это не перереализовывал в новой версии, т.к. во-первых, отличается от системного (и вообще большинства других программ, я думаю), а во-вторых, в имени файла точка - это чаще разделитель номера версии, чем дробные числа, ИМХО.

Alexx2000

2018-11-10 21:45

administrator   ~0002903

>>>Дальше вопрос, как оформить новый режим в настройках?

Получается у нас теперь 4 вида сортировки: стандартная, натуральная, стандартная+спецсимволы и натуральная+спецсимволы.

Наверное добавить еще 2 вида в выпадающий список.

cordylus

2018-11-10 22:01

developer   ~0002904

Last edited: 2018-11-10 22:20

В новом патче код тот же самый, просто не удалена старая сортировка, чтобы удобно было переключаться между новой и старой путем комментирования строк 811-812.

>>> Скорость можно замерить собрав DC с дефайном "timeFileView". В консоль будет время писаться.

Потестировал под XP, компиляция с выключенной оптимизацией, разница в мс между Loaded files и Made sorted disp.lst:
750 - алфавитная
900 - натуральная новая
1000 - натуральная + спецсимволы
1500 - натуральная старая
500 - по коду символа (алфавитная + first upper then lower case)

cordylus

2018-11-11 02:49

developer   ~0002909

Патч v2 - исправлен алгоритм, чтобы сортировал спецсимволы в начале слова в начале списка также в режиме first upper then lower case

Skif_off

2018-11-11 08:58

reporter   ~0002910

Патч 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)"?

Alexx2000

2018-11-11 10:57

administrator   ~0002911

>>>А "500 - по коду символа (алфавитная + first upper then lower case)"?

Это стандартная. А "first upper then lower case" это уже из второго выпадающего списка "Чувствительность к регистру".

С учетом выпадающего списка "Чувствительность к регистру" будет 12 видов сортировки.

cordylus

2018-11-11 21:15

developer   ~0002912

Last edited: 2018-11-11 22:20

Приведенные Вами результаты в 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 совпадает.

Skif_off

2018-11-11 22:19

reporter   ~0002913

> В собранном у Вас DC, похоже, False (как по умолчанию в патче).

Так и есть: слил, накатил, собрал, запустил.
Win7x32 русская (форматы и расположение соотв.), ТС 8.52а, "Естественная сортировка: алфавитно-числовая".

cordylus

2018-11-11 23:05

developer   ~0002916

Last edited: 2018-11-11 23:21

Skif_off, перепроверьте, пожалуйста, свои результаты. Протестировал TC 8.51a под Win7x32 русской, получаю тот столбец, что Вы писали для DC. Ваш результат выглядит, будто часть списка отсортирована по коду символа, а другая - естественной сортировкой.

Либо у Вас каким-то образом включен string sort на уровне системы (хотя точно не уверен, что он дал бы именно такие результаты). Или, может, сборка TC кастомная?

Какой порядок сортировки в Проводнике?

cordylus

2018-11-12 00:37

developer   ~0002917

Выяснил, что под Windows 7 отличается натуральная сортировка от XP и Linux в некоторых аспектах, а именно:

-20 сортируется теперь рядом с 20, аналогично как -a рядом с a. Можно попробовать исправить, отправляя в функцию сравнения спецсимвол с добавленным в конце нулём, если за спецсимволом идёт число.

Числа не на первом месте идут не первыми, а в общем порядке. Например, стало __ _1 _a вместо _1 __ _a. В начале строки числа идут в общем порядке (между спецсимволами и буквами) на всех системах.
Тут проблема концептуальная: либо сортировка не будет соответствовать принятой в системе, либо даже с SORT_SPECIAL перестанет быть одинаковой на всех системах.

cordylus

2018-11-12 01:57

developer   ~0002918

Last edited: 2018-11-12 02:02

Есть идея оставить сортировку как есть (v2), а для Windows 7+ использовать CompareStringW, используя string sort как эквивалент сортировки спецсимволов. Проще, чем опять менять сортировку и вдобавок должно стать быстрее. Разве что с first upper then lower case всё равно придётся использовать свой алгоритм и останется небольшое отличие от системы, но это уже не так важно?

cordylus

2018-11-14 04:13

developer   ~0002922

Патч v3 добавляет новые методы сортировки в настройки с возможностью переключения в рантайме. Алгоритм из v2, без изменений.

Не придумал ничего лучше, чем назвать новые sort method как:
Alphabetical with special characters sort
Natural with special characters sort

Глобальную переменную и опцию в конфигурационном файле взял gSortSpecial и SpecialSorting, по аналогии с gSortNatural и NaturalSorting. Но сами по себе они звучат не очень интуитивно.

Есть альтернативные предложения по названиям?

Skif_off

2018-11-14 09:31

reporter   ~0002924

Last edited: 2018-11-14 09:32

> Не придумал ничего лучше, чем назвать новые 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" как-то длинно выходит.

cordylus

2018-11-14 17:59

developer   ~0002925

Last edited: 2018-11-14 18:00

Я пока их добавил просто в конец:
-----------------------------------------
Alphabetical, considering accents
Natural sorting: alphabetical and numbers
Alphabetical with special characters sort
Natural with special characters sort
-----------------------------------------
Но могу и передвинуть, чтобы было как у Skif_off. Будет лучше, если передвинуть?

И как вообще быть с переводами? Добавить во все языковые файлы англоязычные пункты в строку перевода? Потому что если вообще не трогать, то в других языках будет не хватать этих пунктов. Либо можно вообще поудалять переводы этих строк, но эта идея мне не нравится.

Skif_off

2018-11-14 21:09

reporter   ~0002926

> Но могу и передвинуть, чтобы было как у Skif_off. Будет лучше, если передвинуть?

Если можно сказать, что "Alphabetical with special characters sort" - это как "Alphabetical, considering accents" + больше внимания уделено спецсимволам, то поставить их рядом более логично, наверное.


> И как вообще быть с переводами? Добавить во все языковые файлы англоязычные пункты в строку перевода?

Тут строка не заменяется целиком, а обновляется: правильнее не трогать вручную, пусть строка будет автоматически помечена как fuzzy (но никто не может помешать вам потом поправить перевод на знакомые вам языки :)).

cordylus

2018-11-14 22:42

developer   ~0002927

Last edited: 2018-11-14 22:49

> поставить их рядом более логично, наверное.

ОК, в следующем патче передвину пункты и собираюсь добавить вызов системной функции сортировки для Windows 7+.

> Тут строка не заменяется целиком, а обновляется: правильнее не трогать вручную

Дело в том, что в данном случае реализовано так, что количество пунктов в выпадающем списке задаётся в соответствии с количеством точек с запятой в строке и поэтому должно соответствовать на всех языках. Получается, просто предоставить его самому себе - не вариант, т.к. новые пункты просто не появятся в других языках.

Alexx2000

2018-11-14 23:07

administrator   ~0002928

>>>Получается, просто предоставить его самому себе - не вариант, т.к. новые пункты просто не появятся в других языках.

Lazarus пометит строку как "fuzzy", такие строки выводятся на английском, соответственно с кол-вом пунктов будет нормально.

Skif_off

2019-11-28 22:25

reporter   ~0003326

Кто-нибудь пользуется newnaturalsort-v3.patch? Он под r8429, на альфу можно накатить?

Вспомнилось внезапно о результатах тестирования https://doublecmd.sourceforge.io/mantisbt/view.php?id=1418#c2904 (предпочтение отдаю натуральной), не то чтобы имею дела с гигантскими каталогами, но цифры вкусные...

Alexx2000

2019-11-28 23:13

administrator   ~0003327

Возможно его пора влить в trunk.

cordylus

2019-11-29 02:38

developer   ~0003328

Я помню об этом патче, обещаю до конца года закоммитить.

cordylus

2020-01-01 00:08

developer   ~0003365

Всем новой сортировки в новом году!

Skif_off

2020-01-01 01:24

reporter   ~0003366

Спасибо!

Есть проблема (с локализацией, может?): в списке вижу только два старых метода.

cordylus

2020-01-01 01:36

developer   ~0003367

Да, я не трогал файлы локализации вообще, просто ни разу не имел с ними дела раньше, надеялся, что само подтянется. Сейчас посмотрю.

cordylus

2020-01-01 02:37

developer   ~0003368

Last edited: 2020-01-01 02:39

Обновил языковые файлы, должно работать теперь.

Alexx2000, я не поменял cbSortMethod.Items на форме frmOptionsFilesViews. Стоит ради этого изменения делать коммит или оставить как есть? На функциональность оно не влияет, как я понимаю, так как в рантайме заменяется реальным значением.

Alexx2000

2020-01-08 18:26

administrator   ~0003372

Да, заменяется. На всякий случай можно поменять, чтобы и в режиме дизайна правильно было.

По идее эффективнее использовать CompareUnicodeStringProc, UnicodeCompareText и UnicodeCompareStr.

Уже баг нашелся: https://doublecmd.sourceforge.io/forum/viewtopic.php?t=5941&start=12

У себя не могу воспроизвести.

Issue History

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