doublecmd/src/ufilesorting.pas
Alexander Koblov ddcfc127f0
FIX: Compiling
2024-10-22 08:55:27 +03:00

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.