mirror of
https://github.com/doublecmd/doublecmd.git
synced 2026-06-21 09:58:13 +00:00
1216 lines
35 KiB
ObjectPascal
1216 lines
35 KiB
ObjectPascal
unit uFileSorting;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, uFileFunctions, uFile, uFileProperty, uDisplayFile;
|
|
|
|
type
|
|
|
|
TSortDirection = (sdNone, sdAscending, sdDescending);
|
|
|
|
TFileSorting = record
|
|
SortFunctions: TFileFunctions;
|
|
SortDirection: TSortDirection;
|
|
end;
|
|
|
|
TFileSortings = array of TFileSorting;
|
|
|
|
{ TBaseSorter }
|
|
|
|
TBaseSorter = class
|
|
private
|
|
FSortings: TFileSortings;
|
|
|
|
{en
|
|
Checks the files list for supported properties and removes
|
|
not supported sortings. Currently treats files as if they
|
|
all had the same properties.
|
|
}
|
|
procedure CheckSupportedProperties(SupportedFileProperties: TFilePropertiesTypes);
|
|
|
|
{en
|
|
Compares two file records using file functions.
|
|
@param(ptr1
|
|
First file)
|
|
@param(ptr2
|
|
Second file)
|
|
@returns(-1 lesser
|
|
@br 0 equal
|
|
@br 1 greater)
|
|
}
|
|
class function Compare(const FileSorting: TFileSorting; File1, File2: TFile): Integer;
|
|
|
|
public
|
|
constructor Create(const Sortings: TFileSortings); reintroduce;
|
|
end;
|
|
|
|
{ TFileSorter }
|
|
|
|
TFileSorter = class(TBaseSorter)
|
|
private
|
|
FSortList: TFiles;
|
|
|
|
function MultiCompare(item1, item2: Pointer):Integer;
|
|
procedure QuickSort(FList: PPointerList; L, R : Longint);
|
|
|
|
public
|
|
{en
|
|
Creates the sorter.
|
|
@param(Files
|
|
List of files to be sorted.)
|
|
@param(FileSorting
|
|
Sorting which will be used to sort file records.)
|
|
}
|
|
constructor Create(Files: TFiles; Sortings: TFileSortings);
|
|
|
|
procedure Sort;
|
|
|
|
{en
|
|
Sorts files in FilesToSort using ASorting.
|
|
}
|
|
class procedure Sort(FilesToSort: TFiles; const ASortings: TFileSortings);
|
|
end;
|
|
|
|
{ TDisplayFileSorter }
|
|
|
|
TDisplayFileSorter = class(TBaseSorter)
|
|
private
|
|
FDisplaySortList: TDisplayFiles;
|
|
FFileToInsert: TDisplayFile;
|
|
FFilesToInsert: TDisplayFiles;
|
|
FFileIndexToResort: Integer;
|
|
FResortSingle: Boolean;
|
|
FSequentialSearch: Boolean; // Use sequential search instead of binary
|
|
|
|
protected
|
|
procedure BinaryInsertSingle(FileToInsert: TDisplayFile; List: TFPList; L, R: Longint);
|
|
procedure BinaryResortSingle(UnsortedIndex: Integer; PList: PPointerList; L, R : Longint);
|
|
function BinarySearch(DisplayFile: Pointer; PList: PPointerList; L, R: Longint; out FoundIndex: Longint): Integer;
|
|
procedure InsertSort(FilesToInsert, AlreadySortedFiles: TDisplayFiles);
|
|
procedure InsertSort(FileToInsert: TDisplayFile; AlreadySortedFiles: TDisplayFiles);
|
|
function MultiCompare(item1, item2: Pointer):Integer;
|
|
procedure QuickSort(FList: PPointerList; L, R : Longint);
|
|
{en
|
|
The single file at index IndexToResort should be repositioned in the
|
|
SortedFiles list. All other elements, except for the element at IndexToResort,
|
|
must be already sorted.
|
|
}
|
|
procedure ResortSingle(IndexToResort: Integer; SortedFiles: TDisplayFiles);
|
|
procedure SequentialInsertSingle(FileToInsert: TDisplayFile; List: TFPList);
|
|
|
|
public
|
|
constructor Create(Files: TDisplayFiles; Sortings: TFileSortings);
|
|
constructor Create(FilesToInsert, AlreadySortedFiles: TDisplayFiles;
|
|
const Sortings: TFileSortings);
|
|
constructor Create(FileToInsert: TDisplayFile; AlreadySortedFiles: TDisplayFiles;
|
|
const Sortings: TFileSortings; ASequentialSearch: Boolean = False);
|
|
constructor Create(IndexToResort: Integer; SortedFiles: TDisplayFiles;
|
|
const Sortings: TFileSortings);
|
|
|
|
procedure Sort;
|
|
|
|
class procedure InsertSort(FilesToInsert, AlreadySortedFiles: TDisplayFiles;
|
|
const ASortings: TFileSortings);
|
|
class procedure InsertSort(FileToInsert: TDisplayFile; AlreadySortedFiles: TDisplayFiles;
|
|
const ASortings: TFileSortings; ASequentialSearch: Boolean = False);
|
|
class procedure ResortSingle(IndexToResort: Integer; SortedFiles: TDisplayFiles;
|
|
const ASortings: TFileSortings);
|
|
class procedure Sort(FilesToSort: TDisplayFiles; const ASortings: TFileSortings);
|
|
end;
|
|
|
|
{en
|
|
Returns true if the file functions will sort by the given sort function.
|
|
}
|
|
function HasSortFunction(FileFunctions: TFileFunctions;
|
|
SortFunction: TFileFunction): Boolean;
|
|
function HasSortFunction(FileSortings: TFileSortings;
|
|
SortFunction: TFileFunction): Boolean;
|
|
function GetSortDirection(FileSortings: TFileSortings;
|
|
SortFunction: TFileFunction): TSortDirection;
|
|
function GetSortDirection(FileSortings: TFileSortings;
|
|
SortFunctions: TFileFunctions): TSortDirection;
|
|
{en
|
|
Adds a function to the given list of functions.
|
|
}
|
|
procedure AddSortFunction(var FileFunctions: TFileFunctions;
|
|
SortFunction: TFileFunction);
|
|
{en
|
|
Deletes a function from the given list of functions.
|
|
}
|
|
procedure DeleteSortFunction(var FileFunctions: TFileFunctions;
|
|
SortFunction: TFileFunction);
|
|
|
|
{en
|
|
Adds sorting by functions with a given sorting direction to existing sorting.
|
|
}
|
|
procedure AddSorting(var Sortings: TFileSortings;
|
|
SortFunctions: TFileFunctions;
|
|
SortDirection: TSortDirection);
|
|
|
|
procedure AddOrUpdateSorting(var Sortings: TFileSortings;
|
|
SortFunctions: TFileFunctions;
|
|
SortDirection: TSortDirection);
|
|
{en
|
|
Checks if there is a sorting by Name, NameNoExtension or Extension
|
|
and adds such sortings if there isn't.
|
|
}
|
|
procedure AddSortingByNameIfNeeded(var FileSortings: TFileSortings);
|
|
|
|
{en
|
|
Creates a deep copy of sortings.
|
|
}
|
|
function CloneSortings(const Sortings: TFileSortings): TFileSortings;
|
|
|
|
function ICompareByDirectory(item1, item2: TFile; bSortNegative: Boolean):Integer;
|
|
function ICompareByName(item1, item2: TFile; bSortNegative: Boolean):Integer;
|
|
function ICompareByNameNoExt(item1, item2: TFile; bSortNegative: Boolean):Integer;
|
|
function ICompareByExt (item1, item2: TFile; bSortNegative: Boolean):Integer;
|
|
function ICompareBySize(item1, item2: TFile; bSortNegative: Boolean):Integer;
|
|
function ICompareByDate(date1, date2: TDateTime; bSortNegative: Boolean):Integer;
|
|
function ICompareByAttr(item1, item2: TFile; bSortNegative: Boolean):Integer;
|
|
|
|
function CloneAndAddSortByNameIfNeeded(const Sortings: TFileSortings): TFileSortings;
|
|
function ReverseSortDirection(SortDirection: TSortDirection): TSortDirection;
|
|
function ReverseSortDirection(Sortings: TFileSortings): TFileSortings;
|
|
|
|
implementation
|
|
|
|
uses
|
|
Variants, DCBasicTypes, uGlobs, DCStrUtils, uDCUtils
|
|
{$IFDEF fileSortingTime}
|
|
, uDebug
|
|
{$ENDIF}
|
|
;
|
|
|
|
{$IFDEF fileSortingTime}
|
|
var
|
|
fileSortingTimer: TDateTime;
|
|
{$ENDIF}
|
|
|
|
procedure TFPListFastMove(CurIndex, NewIndex: Integer; PList: PPointerList);
|
|
var
|
|
Temp: Pointer;
|
|
begin
|
|
Temp := PList^[CurIndex];
|
|
if NewIndex > CurIndex then
|
|
System.Move(PList^[CurIndex+1], PList^[CurIndex], (NewIndex - CurIndex) * SizeOf(Pointer))
|
|
else
|
|
System.Move(PList^[NewIndex], PList^[NewIndex+1], (CurIndex - NewIndex) * SizeOf(Pointer));
|
|
PList^[NewIndex] := Temp;
|
|
end;
|
|
|
|
function HasSortFunction(FileFunctions: TFileFunctions;
|
|
SortFunction: TFileFunction): Boolean;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to Length(FileFunctions) - 1 do
|
|
begin
|
|
if SortFunction = FileFunctions[i] then
|
|
Exit(True);
|
|
end;
|
|
Result := False;
|
|
end;
|
|
|
|
function HasSortFunction(FileSortings: TFileSortings;
|
|
SortFunction: TFileFunction): Boolean;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to Length(FileSortings) - 1 do
|
|
begin
|
|
if HasSortFunction(FileSortings[i].SortFunctions, SortFunction) then
|
|
Exit(True);
|
|
end;
|
|
Result := False;
|
|
end;
|
|
|
|
function GetSortDirection(FileSortings: TFileSortings;
|
|
SortFunction: TFileFunction): TSortDirection;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to Length(FileSortings) - 1 do
|
|
begin
|
|
if HasSortFunction(FileSortings[i].SortFunctions, SortFunction) then
|
|
Exit(FileSortings[i].SortDirection);
|
|
end;
|
|
Result := sdNone;
|
|
end;
|
|
|
|
function GetSortDirection(FileSortings: TFileSortings;
|
|
SortFunctions: TFileFunctions): TSortDirection;
|
|
var
|
|
i, j: Integer;
|
|
Found: Boolean;
|
|
begin
|
|
for i := 0 to Length(FileSortings) - 1 do
|
|
begin
|
|
if Length(FileSortings[i].SortFunctions) = Length(SortFunctions) then
|
|
begin
|
|
Found := True;
|
|
for j := 0 to Length(SortFunctions) - 1 do
|
|
if FileSortings[i].SortFunctions[j] <> SortFunctions[j] then
|
|
begin
|
|
Found := False;
|
|
Break;
|
|
end;
|
|
|
|
if Found then
|
|
Exit(FileSortings[i].SortDirection);
|
|
end;
|
|
end;
|
|
Result := sdNone;
|
|
end;
|
|
|
|
procedure AddSortFunction(var FileFunctions: TFileFunctions;
|
|
SortFunction: TFileFunction);
|
|
begin
|
|
SetLength(FileFunctions, Length(FileFunctions) + 1);
|
|
FileFunctions[Length(FileFunctions) - 1] := SortFunction;
|
|
end;
|
|
|
|
procedure DeleteSorting(var FileSortings: TFileSortings; Index: Integer);
|
|
var
|
|
Len: Integer;
|
|
i: Integer;
|
|
begin
|
|
Len := Length(FileSortings);
|
|
for i := Index + 1 to Len - 1 do
|
|
FileSortings[i - 1] := FileSortings[i];
|
|
SetLength(FileSortings, Len - 1);
|
|
end;
|
|
|
|
procedure DeleteSortFunction(var FileFunctions: TFileFunctions;
|
|
SortFunction: TFileFunction);
|
|
var
|
|
Len: Integer;
|
|
i, j: Integer;
|
|
begin
|
|
for i := Low(FileFunctions) to High(FileFunctions) do
|
|
if FileFunctions[i] = SortFunction then
|
|
begin
|
|
Len := Length(FileFunctions);
|
|
for j := i + 1 to Len - 1 do
|
|
FileFunctions[j - 1] := FileFunctions[j];
|
|
SetLength(FileFunctions, Len - 1);
|
|
Break;
|
|
end;
|
|
end;
|
|
|
|
procedure AddSorting(var Sortings: TFileSortings;
|
|
SortFunctions: TFileFunctions;
|
|
SortDirection: TSortDirection);
|
|
var
|
|
SortingIndex: Integer;
|
|
begin
|
|
if Length(SortFunctions) > 0 then
|
|
begin
|
|
SortingIndex := Length(Sortings);
|
|
SetLength(Sortings, SortingIndex + 1);
|
|
Sortings[SortingIndex].SortFunctions := SortFunctions;
|
|
Sortings[SortingIndex].SortDirection := SortDirection;
|
|
end;
|
|
end;
|
|
|
|
procedure AddSorting(var FileSortings: TFileSortings;
|
|
SortFunction: TFileFunction;
|
|
SortDirection: TSortDirection);
|
|
var
|
|
SortFunctions: TFileFunctions = nil;
|
|
begin
|
|
AddSortFunction(SortFunctions, SortFunction);
|
|
AddSorting(FileSortings, SortFunctions, SortDirection);
|
|
end;
|
|
|
|
procedure AddOrUpdateSorting(var Sortings: TFileSortings;
|
|
SortFunctions: TFileFunctions;
|
|
SortDirection: TSortDirection);
|
|
var
|
|
i, j: Integer;
|
|
RemainingFunctions: TFileFunctions;
|
|
begin
|
|
if Length(SortFunctions) = 0 then
|
|
Exit;
|
|
|
|
RemainingFunctions := SortFunctions;
|
|
|
|
// Check if there is already sorting by the functions.
|
|
// If it is then reverse direction of sorting.
|
|
for i := Low(Sortings) to High(Sortings) do
|
|
begin
|
|
RemainingFunctions := SortFunctions;
|
|
|
|
for j := Low(SortFunctions) to High(SortFunctions) do
|
|
begin
|
|
if HasSortFunction(Sortings[i].SortFunctions, SortFunctions[j]) then
|
|
DeleteSortFunction(RemainingFunctions, SortFunctions[j]);
|
|
end;
|
|
|
|
if Length(RemainingFunctions) = 0 then
|
|
begin
|
|
// Sorting contains all functions - reverse direction.
|
|
Sortings[i].SortDirection := ReverseSortDirection(Sortings[i].SortDirection);
|
|
SortFunctions := nil;
|
|
Break;
|
|
end
|
|
else if Length(RemainingFunctions) < Length(SortFunctions) then
|
|
begin
|
|
// Sorting contains some but not all functions - delete this one and later add sorting with all functions.
|
|
Sortings[i].SortDirection := sdNone;
|
|
end;
|
|
end;
|
|
|
|
for i := High(Sortings) downto Low(Sortings) do
|
|
if Sortings[i].SortDirection = sdNone then
|
|
DeleteSorting(Sortings, i);
|
|
|
|
AddSorting(Sortings, SortFunctions, SortDirection);
|
|
end;
|
|
|
|
procedure AddSortingByNameIfNeeded(var FileSortings: TFileSortings);
|
|
var
|
|
bSortedByName: Boolean = False;
|
|
bSortedByExtension: Boolean = False;
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to Length(FileSortings) - 1 do
|
|
begin
|
|
if HasSortFunction(FileSortings[i].SortFunctions, fsfName) then
|
|
begin
|
|
bSortedByName := True;
|
|
bSortedByExtension := True;
|
|
Exit;
|
|
end
|
|
else if HasSortFunction(FileSortings[i].SortFunctions, fsfNameNoExtension)
|
|
then
|
|
begin
|
|
bSortedByName := True;
|
|
end
|
|
else if HasSortFunction(FileSortings[i].SortFunctions, fsfExtension)
|
|
then
|
|
begin
|
|
bSortedByExtension := True;
|
|
end;
|
|
end;
|
|
|
|
if not bSortedByName then
|
|
begin
|
|
if not bSortedByExtension then
|
|
AddSorting(FileSortings, fsfName, sdAscending)
|
|
else
|
|
AddSorting(FileSortings, fsfNameNoExtension, sdAscending);
|
|
end
|
|
else if not bSortedByExtension then
|
|
AddSorting(FileSortings, fsfExtension, sdAscending);
|
|
// else
|
|
// There is already a sorting by filename and extension.
|
|
end;
|
|
|
|
function CloneSortings(const Sortings: TFileSortings): TFileSortings;
|
|
var
|
|
i, j: Integer;
|
|
begin
|
|
SetLength(Result, Length(Sortings));
|
|
for i := 0 to Length(Sortings) - 1 do
|
|
begin
|
|
SetLength(Result[i].SortFunctions, Length(Sortings[i].SortFunctions));
|
|
for j := 0 to Length(Sortings[i].SortFunctions) - 1 do
|
|
Result[i].SortFunctions[j] := Sortings[i].SortFunctions[j];
|
|
Result[i].SortDirection := Sortings[i].SortDirection;
|
|
end;
|
|
end;
|
|
|
|
function CloneAndAddSortByNameIfNeeded(const Sortings: TFileSortings): TFileSortings;
|
|
begin
|
|
Result := CloneSortings(Sortings);
|
|
|
|
// Add automatic sorting by name and/or extension if there wasn't any.
|
|
AddSortingByNameIfNeeded(Result);
|
|
end;
|
|
|
|
function ICompareByDirectory(item1, item2: TFile; bSortNegative: Boolean):Integer;
|
|
var
|
|
IsDir1, IsDir2: Boolean;
|
|
begin
|
|
IsDir1 := item1.IsDirectory or item1.IsLinkToDirectory;
|
|
IsDir2 := item2.IsDirectory or item2.IsLinkToDirectory;
|
|
|
|
if (not IsDir1) and (not IsDir2) then
|
|
Result := 0
|
|
else if (not IsDir1) and IsDir2 then
|
|
Result := 1
|
|
else if IsDir1 and (not IsDir2) then
|
|
Result := -1
|
|
// Put '..' first.
|
|
else if item1.Name = '..' then
|
|
Result := -1
|
|
else if item2.Name = '..' then
|
|
Result := 1
|
|
else if (gSortFolderMode <> sfmSortNameShowFirst) then
|
|
Result := 0
|
|
else
|
|
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, gSortSpecial, gSortCaseSensitivity);
|
|
|
|
if bSortNegative then
|
|
Result := -Result;
|
|
end;
|
|
|
|
function ICompareByNameNoExt(item1, item2: TFile; bSortNegative: Boolean):Integer;
|
|
begin
|
|
// Don't sort directories only by name.
|
|
if item1.IsDirectory or item1.IsLinkToDirectory or
|
|
item2.IsDirectory or item2.IsLinkToDirectory then
|
|
begin
|
|
// Sort by full name.
|
|
Result := ICompareByName(item1, item2, bSortNegative);
|
|
end
|
|
else
|
|
begin
|
|
Result := CompareStrings(item1.NameNoExt, item2.NameNoExt, gSortNatural, gSortSpecial, gSortCaseSensitivity);
|
|
|
|
if bSortNegative then
|
|
Result := -Result;
|
|
end;
|
|
end;
|
|
|
|
function ICompareByExt(item1, item2: TFile; bSortNegative: Boolean):Integer;
|
|
begin
|
|
Result := CompareStrings(item1.Extension, item2.Extension, gSortNatural, gSortSpecial, gSortCaseSensitivity);
|
|
|
|
if bSortNegative then
|
|
Result := -Result;
|
|
end;
|
|
|
|
function ICompareByDate(date1, date2: TDateTime; bSortNegative: Boolean):Integer;
|
|
begin
|
|
if date1 = date2 then
|
|
Result := 0
|
|
else
|
|
begin
|
|
if date1 < date2 then
|
|
Result := -1
|
|
else
|
|
Result := +1;
|
|
|
|
if bSortNegative then
|
|
Result := -Result;
|
|
end;
|
|
end;
|
|
|
|
function ICompareByAttr(item1, item2: TFile; bSortNegative: Boolean):Integer;
|
|
var
|
|
Attr1, Attr2: TFileAttrs;
|
|
begin
|
|
Attr1 := item1.Attributes;
|
|
Attr2 := item2.Attributes;
|
|
|
|
if Attr1 = Attr2 then
|
|
Result := 0
|
|
else
|
|
begin
|
|
if Attr1 > Attr2 then
|
|
Result := -1
|
|
else
|
|
Result := +1;
|
|
|
|
if bSortNegative then
|
|
Result := -Result;
|
|
end;
|
|
end;
|
|
|
|
function ICompareBySize(item1, item2: TFile; bSortNegative: Boolean):Integer;
|
|
var
|
|
iSize1 : Int64;
|
|
iSize2 : Int64;
|
|
begin
|
|
iSize1 := item1.Size;
|
|
iSize2 := item2.Size;
|
|
|
|
if iSize1 = iSize2 then
|
|
Result := 0
|
|
else
|
|
begin
|
|
if iSize1 < iSize2 then
|
|
Result := -1
|
|
else
|
|
Result := +1;
|
|
|
|
if bSortNegative then
|
|
Result := -Result;
|
|
end;
|
|
end;
|
|
|
|
function ICompareByVariant(Value1, Value2: Variant; bSortNegative: Boolean):Integer;
|
|
begin
|
|
if VarIsType(Value1, varString) then
|
|
Result := CompareStrings(Value1, Value2, gSortNatural, gSortSpecial, gSortCaseSensitivity)
|
|
else if Value1 = Value2 then
|
|
Exit(0)
|
|
else
|
|
begin
|
|
if Value1 < Value2 then
|
|
Result := -1
|
|
else
|
|
Result := +1;
|
|
end;
|
|
if bSortNegative then
|
|
Result := -Result;
|
|
end;
|
|
|
|
function ReverseSortDirection(SortDirection: TSortDirection): TSortDirection;
|
|
begin
|
|
case SortDirection of
|
|
sdAscending:
|
|
Result := sdDescending;
|
|
sdDescending:
|
|
Result := sdAscending;
|
|
end;
|
|
end;
|
|
|
|
function ReverseSortDirection(Sortings: TFileSortings): TFileSortings;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result := CloneSortings(Sortings);
|
|
for i := 0 to Length(Result) - 1 do
|
|
Result[i].SortDirection := ReverseSortDirection(Result[i].SortDirection);
|
|
end;
|
|
|
|
{ TBaseSorter }
|
|
|
|
constructor TBaseSorter.Create(const Sortings: TFileSortings);
|
|
begin
|
|
FSortings := Sortings;
|
|
inherited Create;
|
|
end;
|
|
|
|
procedure TBaseSorter.CheckSupportedProperties(SupportedFileProperties: TFilePropertiesTypes);
|
|
var
|
|
SortingIndex: Integer;
|
|
FunctionIndex: Integer;
|
|
i: Integer;
|
|
begin
|
|
// Check if each sort function is supported.
|
|
SortingIndex := 0;
|
|
while SortingIndex < Length(FSortings) do
|
|
begin
|
|
FunctionIndex := 0;
|
|
while FunctionIndex < Length(FSortings[SortingIndex].SortFunctions) do
|
|
begin
|
|
if not (GetFilePropertyType(FSortings[SortingIndex].SortFunctions[FunctionIndex]) <= SupportedFileProperties) then
|
|
begin
|
|
for i := FunctionIndex to Length(FSortings[SortingIndex].SortFunctions) - 2 do
|
|
FSortings[SortingIndex].SortFunctions[i] := FSortings[SortingIndex].SortFunctions[i+1];
|
|
SetLength(FSortings[SortingIndex].SortFunctions, Length(FSortings[SortingIndex].SortFunctions) - 1);
|
|
end
|
|
else
|
|
Inc(FunctionIndex);
|
|
end;
|
|
|
|
if Length(FSortings[SortingIndex].SortFunctions) = 0 then
|
|
begin
|
|
for i := SortingIndex to Length(FSortings) - 2 do
|
|
FSortings[i] := FSortings[i+1];
|
|
SetLength(FSortings, Length(FSortings) - 1);
|
|
end
|
|
else
|
|
Inc(SortingIndex);
|
|
end;
|
|
end;
|
|
|
|
class function TBaseSorter.Compare(const FileSorting: TFileSorting; File1, File2: TFile): Integer;
|
|
var
|
|
i: Integer;
|
|
bNegative: Boolean;
|
|
AFileProp: TFilePropertyType;
|
|
begin
|
|
Result := 0;
|
|
|
|
case FileSorting.SortDirection of
|
|
sdAscending:
|
|
bNegative := False;
|
|
|
|
sdDescending:
|
|
bNegative := True;
|
|
|
|
else
|
|
Exit;
|
|
end;
|
|
|
|
for i := 0 to Length(FileSorting.SortFunctions) - 1 do
|
|
begin
|
|
//------------------------------------------------------
|
|
case FileSorting.SortFunctions[i] of
|
|
fsfName:
|
|
Result := ICompareByName(File1, File2, bNegative);
|
|
fsfExtension:
|
|
Result := ICompareByExt(File1, File2, bNegative);
|
|
fsfSize:
|
|
Result := ICompareBySize(File1, File2, bNegative);
|
|
fsfAttr:
|
|
Result := ICompareByAttr(File1, File2, bNegative);
|
|
fsfPath:
|
|
begin
|
|
Result := CompareStrings(File1.Path, File2.Path, gSortNatural, gSortSpecial, gSortCaseSensitivity);
|
|
if bNegative then Result := -Result;
|
|
end;
|
|
fsfGroup:
|
|
begin
|
|
Result := mbCompareText(File1.OwnerProperty.GroupStr,
|
|
File2.OwnerProperty.GroupStr);
|
|
if bNegative then
|
|
Result := -Result;
|
|
end;
|
|
fsfOwner:
|
|
begin
|
|
Result := mbCompareText(File1.OwnerProperty.OwnerStr,
|
|
File2.OwnerProperty.OwnerStr);
|
|
if bNegative then
|
|
Result := -Result;
|
|
end;
|
|
fsfModificationTime:
|
|
Result := ICompareByDate(File1.ModificationTime,
|
|
File2.ModificationTime,
|
|
bNegative);
|
|
fsfCreationTime:
|
|
Result := ICompareByDate(File1.CreationTime,
|
|
File2.CreationTime,
|
|
bNegative);
|
|
fsfLastAccessTime:
|
|
Result := ICompareByDate(File1.LastAccessTime,
|
|
File2.LastAccessTime,
|
|
bNegative);
|
|
fsfChangeTime:
|
|
Result := ICompareByDate(File1.ChangeTime,
|
|
File2.ChangeTime,
|
|
bNegative);
|
|
fsfLinkTo:
|
|
begin
|
|
Result := CompareStrings(File1.LinkProperty.LinkTo, File2.LinkProperty.LinkTo,
|
|
gSortNatural, gSortSpecial, gSortCaseSensitivity);
|
|
if bNegative then Result := -Result;
|
|
end;
|
|
fsfNameNoExtension:
|
|
Result := ICompareByNameNoExt(File1, File2, bNegative);
|
|
fsfType:
|
|
begin
|
|
Result := mbCompareText(File1.TypeProperty.Value,
|
|
File2.TypeProperty.Value);
|
|
if bNegative then Result := -Result;
|
|
end;
|
|
fsfComment:
|
|
begin
|
|
Result := mbCompareText(File1.CommentProperty.Value,
|
|
File2.CommentProperty.Value);
|
|
if bNegative then Result := -Result;
|
|
end;
|
|
// Variant properties from plugins
|
|
else if FileSorting.SortFunctions[i] in fsfVariantAll then
|
|
begin
|
|
AFileProp:= TFilePropertyType(FileSorting.SortFunctions[i]);
|
|
Result:= ICompareByVariant(TFileVariantProperty(File1.Properties[AFileProp]).Value,
|
|
TFileVariantProperty(File2.Properties[AFileProp]).Value, bNegative)
|
|
end;
|
|
end;
|
|
|
|
if Result <> 0 then
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
{ TDisplayFileSorter }
|
|
|
|
constructor TDisplayFileSorter.Create(Files: TDisplayFiles; Sortings: TFileSortings);
|
|
begin
|
|
inherited Create(Sortings);
|
|
FDisplaySortList := Files;
|
|
|
|
if Assigned(FDisplaySortList) and (FDisplaySortList.Count > 0) then
|
|
CheckSupportedProperties(FDisplaySortList[0].FSFile.SupportedProperties);
|
|
end;
|
|
|
|
constructor TDisplayFileSorter.Create(FilesToInsert, AlreadySortedFiles: TDisplayFiles; const Sortings: TFileSortings);
|
|
begin
|
|
inherited Create(Sortings);
|
|
FFilesToInsert := FilesToInsert;
|
|
FDisplaySortList := AlreadySortedFiles;
|
|
|
|
if Assigned(FDisplaySortList) and (FDisplaySortList.Count > 0) and
|
|
Assigned(FFilesToInsert) and (FFilesToInsert.Count > 0) then
|
|
begin
|
|
CheckSupportedProperties(FDisplaySortList[0].FSFile.SupportedProperties);
|
|
CheckSupportedProperties(FFilesToInsert[0].FSFile.SupportedProperties);
|
|
end;
|
|
end;
|
|
|
|
constructor TDisplayFileSorter.Create(FileToInsert: TDisplayFile; AlreadySortedFiles: TDisplayFiles; const Sortings: TFileSortings; ASequentialSearch: Boolean);
|
|
begin
|
|
inherited Create(Sortings);
|
|
FFileToInsert := FileToInsert;
|
|
FDisplaySortList := AlreadySortedFiles;
|
|
FSequentialSearch := ASequentialSearch;
|
|
|
|
if Assigned(FDisplaySortList) and (FDisplaySortList.Count > 0) and
|
|
Assigned(FFileToInsert) then
|
|
begin
|
|
CheckSupportedProperties(FDisplaySortList[0].FSFile.SupportedProperties);
|
|
CheckSupportedProperties(FFileToInsert.FSFile.SupportedProperties);
|
|
end;
|
|
end;
|
|
|
|
constructor TDisplayFileSorter.Create(IndexToResort: Integer; SortedFiles: TDisplayFiles; const Sortings: TFileSortings);
|
|
begin
|
|
inherited Create(Sortings);
|
|
FFileIndexToResort := IndexToResort;
|
|
FResortSingle := True;
|
|
FDisplaySortList := SortedFiles;
|
|
|
|
if Assigned(FDisplaySortList) and (FDisplaySortList.Count > 0) then
|
|
CheckSupportedProperties(FDisplaySortList[0].FSFile.SupportedProperties);
|
|
end;
|
|
|
|
procedure TDisplayFileSorter.Sort;
|
|
begin
|
|
{$IFDEF fileSortingTime}
|
|
fileSortingTimer := Now;
|
|
{$ENDIF}
|
|
|
|
// Restore this check when independent SortFunctions are implemented and sorting
|
|
// by directory condition (gSortFolderMode <> sfmSortLikeFile) is removed from
|
|
// the sorter and moved into Sortings.
|
|
//if Length(FSortings) > 0 then
|
|
begin
|
|
if FResortSingle and Assigned(FDisplaySortList) then
|
|
begin
|
|
ResortSingle(FFileIndexToResort, FDisplaySortList);
|
|
{$IFDEF fileSortingTime}
|
|
DCDebug('FileSorter: Resort time: ', IntToStr(DateTimeToTimeStamp(Now - fileSortingTimer).Time));
|
|
{$ENDIF}
|
|
end
|
|
else if Assigned(FFileToInsert) and Assigned(FDisplaySortList) then
|
|
begin
|
|
InsertSort(FFileToInsert, FDisplaySortList);
|
|
{$IFDEF fileSortingTime}
|
|
DCDebug('FileSorter: Insert sort time: ', IntToStr(DateTimeToTimeStamp(Now - fileSortingTimer).Time));
|
|
{$ENDIF}
|
|
end
|
|
else if Assigned(FFilesToInsert) and Assigned(FDisplaySortList) then
|
|
begin
|
|
InsertSort(FFilesToInsert, FDisplaySortList);
|
|
{$IFDEF fileSortingTime}
|
|
DCDebug('FileSorter: Insert sort time: ', IntToStr(DateTimeToTimeStamp(Now - fileSortingTimer).Time));
|
|
{$ENDIF}
|
|
end
|
|
else if Assigned(FDisplaySortList) and (FDisplaySortList.Count > 1) then
|
|
begin
|
|
QuickSort(FDisplaySortList.List.List, 0, FDisplaySortList.List.Count-1);
|
|
{$IFDEF fileSortingTime}
|
|
DCDebug('FileSorter: Sorting DisplayFiles time: ', IntToStr(DateTimeToTimeStamp(Now - fileSortingTimer).Time));
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
class procedure TDisplayFileSorter.InsertSort(FilesToInsert, AlreadySortedFiles: TDisplayFiles; const ASortings: TFileSortings);
|
|
var
|
|
FileListSorter: TDisplayFileSorter;
|
|
begin
|
|
FileListSorter := TDisplayFileSorter.Create(FilesToInsert, AlreadySortedFiles, ASortings);
|
|
try
|
|
FileListSorter.Sort;
|
|
finally
|
|
FreeAndNil(FileListSorter);
|
|
end;
|
|
end;
|
|
|
|
class procedure TDisplayFileSorter.InsertSort(FileToInsert: TDisplayFile; AlreadySortedFiles: TDisplayFiles; const ASortings: TFileSortings; ASequentialSearch: Boolean);
|
|
var
|
|
FileListSorter: TDisplayFileSorter;
|
|
begin
|
|
FileListSorter := TDisplayFileSorter.Create(FileToInsert, AlreadySortedFiles, ASortings, ASequentialSearch);
|
|
try
|
|
FileListSorter.Sort;
|
|
finally
|
|
FreeAndNil(FileListSorter);
|
|
end;
|
|
end;
|
|
|
|
class procedure TDisplayFileSorter.ResortSingle(IndexToResort: Integer; SortedFiles: TDisplayFiles; const ASortings: TFileSortings);
|
|
var
|
|
FileListSorter: TDisplayFileSorter;
|
|
begin
|
|
FileListSorter := TDisplayFileSorter.Create(IndexToResort, SortedFiles, ASortings);
|
|
try
|
|
FileListSorter.Sort;
|
|
finally
|
|
FreeAndNil(FileListSorter);
|
|
end;
|
|
end;
|
|
|
|
class procedure TDisplayFileSorter.Sort(FilesToSort: TDisplayFiles; const ASortings: TFileSortings);
|
|
var
|
|
FileListSorter: TDisplayFileSorter;
|
|
begin
|
|
FileListSorter := TDisplayFileSorter.Create(FilesToSort, ASortings);
|
|
try
|
|
FileListSorter.Sort;
|
|
finally
|
|
FreeAndNil(FileListSorter);
|
|
end;
|
|
end;
|
|
|
|
procedure TDisplayFileSorter.BinaryInsertSingle(FileToInsert: TDisplayFile; List: TFPList; L, R: Longint);
|
|
var
|
|
CompareRes: Integer;
|
|
FoundIndex: Longint;
|
|
begin
|
|
if List.Count = 0 then
|
|
FoundIndex := 0
|
|
else
|
|
begin
|
|
CompareRes := BinarySearch(FileToInsert, List.List, L, R, FoundIndex);
|
|
if CompareRes > 0 then
|
|
Inc(FoundIndex); // Insert after because it's greater than FoundIndex item.
|
|
end;
|
|
List.Insert(FoundIndex, FileToInsert);
|
|
end;
|
|
|
|
procedure TDisplayFileSorter.BinaryResortSingle(UnsortedIndex: Integer; PList: PPointerList; L, R: Longint);
|
|
var
|
|
CompareRes: Integer;
|
|
FoundIndex: Longint;
|
|
begin
|
|
CompareRes := BinarySearch(PList^[UnsortedIndex], PList, L, R, FoundIndex);
|
|
if CompareRes = 0 then
|
|
TFPListFastMove(UnsortedIndex, FoundIndex, PList)
|
|
else
|
|
begin
|
|
if UnsortedIndex < FoundIndex then
|
|
begin
|
|
if CompareRes < 0 then
|
|
Dec(FoundIndex);
|
|
end
|
|
else
|
|
begin
|
|
if CompareRes > 0 then
|
|
Inc(FoundIndex);
|
|
end;
|
|
TFPListFastMove(UnsortedIndex, FoundIndex, PList);
|
|
end;
|
|
end;
|
|
|
|
function TDisplayFileSorter.BinarySearch(
|
|
DisplayFile: Pointer;
|
|
PList: PPointerList;
|
|
L, R: Longint;
|
|
out FoundIndex: Longint): Integer;
|
|
var
|
|
I, J, K : Longint;
|
|
begin
|
|
I := L;
|
|
J := R;
|
|
repeat
|
|
K := (I + J) div 2;
|
|
Result := MultiCompare(DisplayFile, PList^[K]);
|
|
if Result < 0 then
|
|
J := K - 1
|
|
else if Result > 0 then
|
|
I := K + 1
|
|
else
|
|
Break;
|
|
until I > J;
|
|
FoundIndex := K;
|
|
end;
|
|
|
|
procedure TDisplayFileSorter.InsertSort(FilesToInsert, AlreadySortedFiles: TDisplayFiles);
|
|
var
|
|
i, j: PtrInt;
|
|
L, R, FoundIndex: Longint;
|
|
Psrc: PPointerList;
|
|
Pcur: Pointer;
|
|
SearchResult: Integer;
|
|
DestList: TFPList;
|
|
begin
|
|
if FFilesToInsert.Count > 0 then
|
|
begin
|
|
if FFilesToInsert.Count = 1 then
|
|
begin
|
|
InsertSort(FFilesToInsert[0], AlreadySortedFiles);
|
|
Exit;
|
|
end
|
|
else
|
|
begin
|
|
// First sort the files to insert of which there should be only a small number.
|
|
QuickSort(FilesToInsert.List.List, 0, FilesToInsert.List.Count-1);
|
|
end;
|
|
|
|
Psrc := FilesToInsert.List.List;
|
|
DestList := AlreadySortedFiles.List;
|
|
L := 0;
|
|
R := DestList.Count - 1;
|
|
if R < 0 then
|
|
begin
|
|
// Add remaining files at the end.
|
|
for j := 0 to FilesToInsert.Count - 1 do
|
|
DestList.Add(Psrc^[j]);
|
|
end
|
|
else
|
|
begin
|
|
FoundIndex := 0;
|
|
for i := 0 to FilesToInsert.Count - 1 do
|
|
begin
|
|
Pcur := Psrc^[i];
|
|
SearchResult := BinarySearch(Pcur, DestList.List, L, R, FoundIndex);
|
|
// Insert Pcur after FoundIndex if it was greater.
|
|
if SearchResult > 0 then
|
|
Inc(FoundIndex);
|
|
|
|
if FoundIndex > R then
|
|
begin
|
|
// Add remaining files at the end.
|
|
for j := i to FilesToInsert.Count - 1 do
|
|
DestList.Add(Psrc^[j]);
|
|
Break;
|
|
end;
|
|
|
|
DestList.Insert(FoundIndex, Pcur);
|
|
L := FoundIndex + 1; // Next time start searching from the next element after the one just inserted.
|
|
Inc(R); // Number of elements has increased so also increase right boundary.
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TDisplayFileSorter.InsertSort(FileToInsert: TDisplayFile; AlreadySortedFiles: TDisplayFiles);
|
|
begin
|
|
if FSequentialSearch then
|
|
SequentialInsertSingle(FileToInsert, AlreadySortedFiles.List)
|
|
else
|
|
BinaryInsertSingle(FileToInsert, AlreadySortedFiles.List, 0, AlreadySortedFiles.Count - 1);
|
|
end;
|
|
|
|
function TDisplayFileSorter.MultiCompare(item1, item2: Pointer): Integer;
|
|
var
|
|
i : Integer;
|
|
begin
|
|
Result := 0;
|
|
if item1 = item2 then Exit;
|
|
|
|
// Put directories first.
|
|
if (gSortFolderMode <> sfmSortLikeFile) then
|
|
begin
|
|
Result := ICompareByDirectory(TDisplayFile(item1).FSFile, TDisplayFile(item2).FSFile, False); // Ascending
|
|
if Result <> 0 then Exit;
|
|
end
|
|
else begin
|
|
// Put '..' first.
|
|
if TDisplayFile(item1).FSFile.Name = '..' then Exit(-1);
|
|
if TDisplayFile(item2).FSFile.Name = '..' then Exit(+1);
|
|
end;
|
|
|
|
for i := 0 to Length(FSortings) - 1 do
|
|
begin
|
|
Result := Compare(FSortings[i], TDisplayFile(item1).FSFile, TDisplayFile(item2).FSFile);
|
|
if Result <> 0 then Exit;
|
|
end;
|
|
end;
|
|
|
|
procedure TDisplayFileSorter.QuickSort(FList: PPointerList; L, R: Longint);
|
|
var
|
|
I, J : Longint;
|
|
P, Q : Pointer;
|
|
begin
|
|
repeat
|
|
I := L;
|
|
J := R;
|
|
P := FList^[ (L + R) div 2 ];
|
|
repeat
|
|
while MultiCompare(P, FList^[i]) > 0 do
|
|
I := I + 1;
|
|
while MultiCompare(P, FList^[J]) < 0 do
|
|
J := J - 1;
|
|
If I <= J then
|
|
begin
|
|
Q := FList^[I];
|
|
Flist^[I] := FList^[J];
|
|
FList^[J] := Q;
|
|
I := I + 1;
|
|
J := J - 1;
|
|
end;
|
|
until I > J;
|
|
if L < J then
|
|
QuickSort(FList, L, J);
|
|
L := I;
|
|
until I >= R;
|
|
end;
|
|
|
|
procedure TDisplayFileSorter.ResortSingle(IndexToResort: Integer; SortedFiles: TDisplayFiles);
|
|
var
|
|
PUnsorted: Pointer;
|
|
PSorted: PPointerList;
|
|
begin
|
|
PSorted := SortedFiles.List.List;
|
|
PUnsorted := PSorted^[IndexToResort];
|
|
// The element at IndexToResort must either be moved left or right,
|
|
// or should stay where it is.
|
|
if (IndexToResort > 0) and
|
|
(MultiCompare(PUnsorted, PSorted^[IndexToResort - 1]) < 0) then
|
|
begin
|
|
if IndexToResort = 1 then
|
|
SortedFiles.List.Exchange(IndexToResort, IndexToResort - 1)
|
|
else
|
|
BinaryResortSingle(IndexToResort, PSorted, 0, IndexToResort - 1);
|
|
end
|
|
else if (IndexToResort < SortedFiles.List.Count - 1) and
|
|
(MultiCompare(PUnsorted, PSorted^[IndexToResort + 1]) > 0) then
|
|
begin
|
|
if IndexToResort = SortedFiles.List.Count - 2 then
|
|
SortedFiles.List.Exchange(IndexToResort, IndexToResort + 1)
|
|
else
|
|
BinaryResortSingle(IndexToResort, PSorted, IndexToResort + 1, SortedFiles.List.Count - 1);
|
|
end;
|
|
end;
|
|
|
|
procedure TDisplayFileSorter.SequentialInsertSingle(FileToInsert: TDisplayFile; List: TFPList);
|
|
var
|
|
SortedIndex: PtrInt;
|
|
Pdst: PPointerList;
|
|
begin
|
|
SortedIndex := 0;
|
|
Pdst := List.List;
|
|
|
|
while (SortedIndex < List.Count) and
|
|
(MultiCompare(FileToInsert, Pdst^[SortedIndex]) > 0) do
|
|
Inc(SortedIndex);
|
|
|
|
List.Insert(SortedIndex, FileToInsert);
|
|
end;
|
|
|
|
{ TFileSorter }
|
|
|
|
constructor TFileSorter.Create(Files: TFiles; Sortings: TFileSortings);
|
|
begin
|
|
inherited Create(Sortings);
|
|
FSortList := Files;
|
|
|
|
if Assigned(FSortList) and (FSortList.Count > 0) then
|
|
CheckSupportedProperties(FSortList.Items[0].SupportedProperties);
|
|
end;
|
|
|
|
procedure TFileSorter.Sort;
|
|
begin
|
|
{$IFDEF fileSortingTime}
|
|
fileSortingTimer := Now;
|
|
{$ENDIF}
|
|
|
|
// Restore this check when independent SortFunctions are implemented and sorting
|
|
// by directory condition (gSortFolderMode <> sfmSortLikeFile) is removed from
|
|
// the sorter and moved into Sortings.
|
|
//if Length(FSortings) > 0 then
|
|
begin
|
|
if Assigned(FSortList) and (FSortList.Count > 1) then
|
|
begin
|
|
QuickSort(FSortList.List.List, 0, FSortList.List.Count-1);
|
|
{$IFDEF fileSortingTime}
|
|
DCDebug('FileSorter: Sorting FSFiles time: ', IntToStr(DateTimeToTimeStamp(Now - fileSortingTimer).Time));
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
class procedure TFileSorter.Sort(FilesToSort: TFiles; const ASortings: TFileSortings);
|
|
var
|
|
FileListSorter: TFileSorter;
|
|
begin
|
|
FileListSorter := TFileSorter.Create(FilesToSort, ASortings);
|
|
try
|
|
FileListSorter.Sort;
|
|
finally
|
|
FreeAndNil(FileListSorter);
|
|
end;
|
|
end;
|
|
|
|
{ Return Values for ICompareByxxxx function
|
|
|
|
> 0 (positive) Item1 is greater than Item2
|
|
0 Item1 is equal to Item2
|
|
< 0 (negative) Item1 is less than Item2
|
|
}
|
|
|
|
{
|
|
This function is simples support of sorting
|
|
directory (handle uglobs.gDirSortFirst)
|
|
|
|
Result is 0 if both parametres is directory and equal
|
|
or not a directory (both).
|
|
|
|
Else return +/- as ICompare****
|
|
}
|
|
|
|
function TFileSorter.MultiCompare(item1, item2: Pointer):Integer;
|
|
var
|
|
i : Integer;
|
|
begin
|
|
Result := 0;
|
|
if item1 = item2 then Exit;
|
|
|
|
// Put directories first.
|
|
if (gSortFolderMode <> sfmSortLikeFile) then
|
|
begin
|
|
Result := ICompareByDirectory(TFile(item1), TFile(item2), False); // Ascending
|
|
if Result <> 0 then Exit;
|
|
end
|
|
else begin
|
|
// Put '..' first.
|
|
if TFile(item1).Name = '..' then Exit(-1);
|
|
if TFile(item2).Name = '..' then Exit(+1);
|
|
end;
|
|
|
|
for i := 0 to Length(FSortings) - 1 do
|
|
begin
|
|
Result := Compare(FSortings[i], TFile(item1), TFile(item2));
|
|
if Result <> 0 then Exit;
|
|
end;
|
|
end;
|
|
|
|
// From FPC: lists.inc.
|
|
procedure TFileSorter.QuickSort(FList: PPointerList; L, R : Longint);
|
|
var
|
|
I, J : Longint;
|
|
P, Q : Pointer;
|
|
begin
|
|
repeat
|
|
I := L;
|
|
J := R;
|
|
P := FList^[ (L + R) div 2 ];
|
|
repeat
|
|
while MultiCompare(P, FList^[i]) > 0 do
|
|
I := I + 1;
|
|
while MultiCompare(P, FList^[J]) < 0 do
|
|
J := J - 1;
|
|
If I <= J then
|
|
begin
|
|
Q := FList^[I];
|
|
Flist^[I] := FList^[J];
|
|
FList^[J] := Q;
|
|
I := I + 1;
|
|
J := J - 1;
|
|
end;
|
|
until I > J;
|
|
if L < J then
|
|
QuickSort(FList, L, J);
|
|
L := I;
|
|
until I >= R;
|
|
end;
|
|
|
|
end.
|
|
|