doublecmd/src/ufilefunctions.pas

664 lines
21 KiB
ObjectPascal

{
Double Commander
-------------------------------------------------------------------------
Filepanel columns implementation unit
Copyright (C) 2008-2020 Alexander Koblov (alexx2000@mail.ru)
Copyright (C) 2008 Dmitry Kolomiets (B4rr4cuda@rambler.ru)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
}
unit uFileFunctions;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Menus, uFile, uFileProperty, uFileSource;
type
TFileFunction = (fsfName = 0,
fsfExtension = 1,
fsfSize = 2,
fsfAttr = 3,
fsfPath = 4,
fsfGroup = 5,
fsfOwner = 6,
fsfModificationTime = 7,
fsfCreationTime = 8,
fsfLastAccessTime = 9,
fsfChangeTime = 10,
fsfLinkTo = 11,
fsfNameNoExtension = 12,
fsfType = 13,
fsfComment = 14,
fsfCompressedSize = 15,
fsfInvalid = 16,
fsfVariant = Ord(fpVariant),
fsfMaximum = Ord(fpMaximum));
TFileFunctions = array of TFileFunction;
const
fsfVariantAll = [fsfVariant..fsfMaximum];
const TFileFunctionStrings: array [Low(TFileFunction)..fsfInvalid] of string
= ('GETFILENAME',
'GETFILEEXT',
'GETFILESIZE',
'GETFILEATTR',
'GETFILEPATH',
'GETFILEGROUP',
'GETFILEOWNER',
'GETFILETIME',
'GETFILECREATIONTIME',
'GETFILELASTACCESSTIME',
'GETFILECHANGETIME',
'GETFILELINKTO',
'GETFILENAMENOEXT',
'GETFILETYPE',
'GETFILECOMMENT',
'GETFILECOMPRESSEDSIZE',
'' // fsfInvalid
);
function FormatFileFunction(FuncS: string; AFile: TFile; const AFileSource: IFileSource; RetrieveProperties: Boolean = False): string;
function FormatFileFunctions(FuncS: String; AFile: TFile; const AFileSource: IFileSource): String;
function GetVariantFileProperty(const FuncS: String; AFile: TFile; const AFileSource: IFileSource): Variant;
function GetFileFunctionByName(FuncS: string): TFileFunction;
function GetFilePropertyType(FileFunction: TFileFunction): TFilePropertiesTypes; inline;
procedure FillContentFieldMenu(MenuOrMenuItem: TObject; OnMenuItemClick: TNotifyEvent; const FileSystem: String = '');
procedure FillFileFuncList;
const
sFuncTypeDC = 'DC';
sFuncTypePlugin = 'PLUGIN';
var
FileFunctionsStr: TStringList;
implementation
uses
StrUtils, WdxPlugin, uWdxModule, uGlobs, uLng, uDefaultFilePropertyFormatter,
uFileSourceProperty, uWfxPluginFileSource, uWfxModule, uColumns, DCFileAttributes,
DCStrUtils, DCBasicTypes, Variants, uDCUtils, uTypes;
const
ATTR_OCTAL = 'OCTAL';
//***Note: Number of elements in "FILE_SIZE" should normally fit with the number of element in "TFileSizeFormat" we have in "uTypes" unit.:
FILE_SIZE: array[0..11] of String = ('FLOAT', 'BYTE', 'KILO', 'MEGA', 'GIGA', 'TERA', 'PERSFLOAT', 'PERSBYTE', 'PERSKILO', 'PERSMEGA', 'PERSGIGA', 'PERSTERA');
// Which file properties must be supported for each file function to work.
const TFileFunctionToProperty: array [Low(TFileFunction)..fsfInvalid] of TFilePropertiesTypes
= ([fpName],
[fpName],
[fpSize],
[fpAttributes],
[] { path },
[fpOwner],
[fpOwner],
[fpModificationTime],
[fpCreationTime],
[fpLastAccessTime],
[fpChangeTime],
[fpLink],
[fpName],
[fpType],
[fpComment],
[fpCompressedSize],
[] { invalid });
//Return type (Script or DC or Plugin etc)
function GetModType(str: String): String;
begin
if pos('(', Str) > 0 then
Result := Copy(Str, 1, pos('(', Str) - 1)
else
Result := EmptyStr;
end;
//Return name in (). (SriptName or PluginName etc)
function GetModName(str: String): String;
var
s: String;
begin
s := str;
if pos('(', S) > 0 then
Delete(s, 1, pos('(', S))
else
Exit(EmptyStr);
if pos(')', s) > 0 then
Result := Copy(s, 1, pos(')', s) - 1);
end;
//Return function name (DCFunction,PluginFunction etc)
function GetModFunctionName(str: String): String;
var
s: String;
begin
s := str;
if pos('.', S) > 0 then
Delete(s, 1, pos('.', S))
else
Exit(EmptyStr);
if pos('{', S) > 0 then
Result := Copy(s, 1, pos('{', S) - 1);
end;
// Return function parameters
function GetModFunctionParams(str: String): String;
var
I: Integer;
S: String;
begin
S := str;
I := pos('{', S);
if I < 0 then
Exit(EmptyStr);
Delete(S, 1, I);
I := pos('}', S);
if I < 0 then
Exit(EmptyStr);
Result := Copy(S, 1, I - 1);
end;
function FormatFileFunction(FuncS: string; AFile: TFile;
const AFileSource: IFileSource; RetrieveProperties: Boolean): string;
var
AIndex: Integer;
AValue: Variant;
FileFunction: TFileFunction;
AType, AFunc, AParam: String;
AFileProperty: TFileVariantProperty;
FilePropertiesNeeded: TFilePropertiesTypes;
ADisplayName: String;
ADisplayNameNoExtension: String;
ADisplayExtension: String;
procedure prepareDisplayFileName;
begin
ADisplayName := AFileSource.GetDisplayFileName(AFile);
if ADisplayName = EmptyStr then begin
ADisplayName := AFile.Name;
ADisplayNameNoExtension := AFile.NameNoExt;
ADisplayExtension := AFile.Extension;
end else begin
TFile.SplitIntoNameAndExtension(ADisplayName, ADisplayNameNoExtension, ADisplayExtension);
end;
end;
begin
Result := EmptyStr;
//---------------------
AType := upcase(GetModType(FuncS));
AFunc := upcase(GetModFunctionName(FuncS));
AParam := upcase(GetModFunctionParams(FuncS));
//---------------------
//Internal doublecmd function
//------------------------------------------------------
if AType = sFuncTypeDC then
begin
AIndex:= FileFunctionsStr.IndexOfName(AFunc);
if AIndex < 0 then Exit;
FileFunction:= TFileFunction(AIndex);
// Retrieve additional properties if needed
if RetrieveProperties then
begin
FilePropertiesNeeded:= TFileFunctionToProperty[FileFunction];
if aFileSource.CanRetrieveProperties(AFile, FilePropertiesNeeded) then
aFileSource.RetrieveProperties(AFile, FilePropertiesNeeded, []);
end;
prepareDisplayFileName;
case FileFunction of
fsfName:
begin
// Show square brackets around directories
if gDirBrackets and (AFile.IsDirectory or
AFile.IsLinkToDirectory) then
Result := gFolderPrefix + ADisplayName + gFolderPostfix
else
Result := ADisplayName;
end;
fsfExtension:
begin
Result := ADisplayExtension;
end;
fsfSize:
begin
if (AFile.IsDirectory or AFile.IsLinkToDirectory) and
((not (fpSize in AFile.SupportedProperties)) or (AFile.Size = 0))
then begin
if AFile.IsLinkToDirectory then
Result := rsAbbrevDisplayLink
else
Result := rsAbbrevDisplayDir;
end
else if fpSize in AFile.SupportedProperties then
begin
if AFile.IsDirectory and (AFile.Size < 0) then
begin
case AFile.Size of
FOLDER_SIZE_ZERO: Result := '0';
FOLDER_SIZE_WAIT: Result := '??';
FOLDER_SIZE_CALC: Result := '--';
FOLDER_SIZE_ERRO: Result := '0?';
end;
end
else if AFile.SizeProperty.IsValid then
begin
if Length(AParam) = 0 then
Result := AFile.Properties[fpSize].Format(DefaultFilePropertyFormatter)
else begin
for AIndex:= 0 to High(FILE_SIZE) do
begin
if AParam = FILE_SIZE[AIndex] then
begin
Result := cnvFormatFileSize(AFile.Size, TFileSizeFormat(AIndex), gFileSizeDigits);
Break;
end;
end;
end;
end;
end;
end;
fsfAttr:
if fpAttributes in AFile.SupportedProperties then
begin
if (AFile.Properties[fpAttributes] is TUnixFileAttributesProperty) and (AParam = ATTR_OCTAL) then
Result := FormatUnixModeOctal(AFile.Attributes)
else
Result := AFile.Properties[fpAttributes].Format(DefaultFilePropertyFormatter);
end;
fsfPath:
Result := AFile.Path;
fsfGroup:
if fpOwner in AFile.SupportedProperties then
Result := AFile.OwnerProperty.GroupStr;
fsfOwner:
if fpOwner in AFile.SupportedProperties then
Result := AFile.OwnerProperty.OwnerStr;
fsfModificationTime:
if fpModificationTime in AFile.SupportedProperties then
begin
if AFile.ModificationTimeProperty.IsValid then
begin
if Length(AParam) > 0 then
Result := SysUtils.FormatDateTime(AParam, AFile.ModificationTime)
else
Result := AFile.Properties[fpModificationTime].Format(DefaultFilePropertyFormatter);
end;
end;
fsfCreationTime:
if fpCreationTime in AFile.SupportedProperties then
begin
if Length(AParam) > 0 then
Result := SysUtils.FormatDateTime(AParam, AFile.CreationTime)
else
Result := AFile.Properties[fpCreationTime].Format(DefaultFilePropertyFormatter);
end;
fsfLastAccessTime:
if fpLastAccessTime in AFile.SupportedProperties then
begin
if Length(AParam) > 0 then
Result := SysUtils.FormatDateTime(AParam, AFile.LastAccessTime)
else
Result := AFile.Properties[fpLastAccessTime].Format(DefaultFilePropertyFormatter);
end;
fsfChangeTime:
if fpChangeTime in AFile.SupportedProperties then
begin
if Length(AParam) > 0 then
Result := SysUtils.FormatDateTime(AParam, AFile.ChangeTime)
else
Result := AFile.Properties[fpChangeTime].Format(DefaultFilePropertyFormatter);
end;
fsfLinkTo:
if fpLink in AFile.SupportedProperties then
Result := AFile.LinkProperty.LinkTo;
fsfNameNoExtension:
begin
// Show square brackets around directories
if gDirBrackets and (AFile.IsDirectory or
AFile.IsLinkToDirectory) then
Result := gFolderPrefix + ADisplayNameNoExtension + gFolderPostfix
else
Result := ADisplayNameNoExtension;
end;
fsfType:
if fpType in AFile.SupportedProperties then
Result := AFile.TypeProperty.Format(DefaultFilePropertyFormatter);
fsfComment:
if fpComment in AFile.SupportedProperties then
Result := AFile.CommentProperty.Format(DefaultFilePropertyFormatter);
fsfCompressedSize:
begin
if (AFile.IsDirectory or AFile.IsLinkToDirectory) and
((not (fpCompressedSize in AFile.SupportedProperties)) or (AFile.CompressedSize = 0))
then begin
if AFile.IsLinkToDirectory then
Result := rsAbbrevDisplayLink
else
Result := rsAbbrevDisplayDir;
end
else if fpCompressedSize in AFile.SupportedProperties then
Result := AFile.Properties[fpCompressedSize].Format(DefaultFilePropertyFormatter);
end;
end;
end
//------------------------------------------------------
//Plugin function
//------------------------------------------------------
else if AType = sFuncTypePlugin then
begin
// Retrieve additional properties if needed
if RetrieveProperties then
begin
AValue:= GetVariantFileProperty(FuncS, AFile, AFileSource);
if not VarIsBool(AValue) then
Result := AValue
else if AValue then
Result := rsSimpleWordTrue
else
Result := rsSimpleWordFalse;
end
else begin
for AIndex:= 0 to High(AFile.VariantProperties) do
begin
AFileProperty:= TFileVariantProperty(AFile.VariantProperties[AIndex]);
if Assigned(AFileProperty) and SameText(FuncS, AFileProperty.Name) then
begin
Result:= AFileProperty.Format(DefaultFilePropertyFormatter);
Break;
end;
end;
end;
end;
//------------------------------------------------------
end;
function FormatFileFunctions(FuncS: String; AFile: TFile; const AFileSource: IFileSource): String;
var
P: Integer;
begin
Result:= EmptyStr;
while True do
begin
P := Pos('[', FuncS);
if P = 0 then
Break
else if P > 1 then
Result:= Result + Copy(FuncS, 1, P - 1);
Delete(FuncS, 1, P);
P := Pos(']', FuncS);
if P = 0 then
Break
else if P > 1 then
Result:= Result + FormatFileFunction(Copy(FuncS, 1, P - 1), AFile, AFileSource, True);
Delete(FuncS, 1, P);
end;
if Length(FuncS) <> 0 then
Result:= Result + FuncS;
end;
function GetFileFunctionByName(FuncS: String): TFileFunction;
var
AIndex: Integer;
AType, AFunc: String;
begin
AType := UpCase(GetModType(FuncS));
AFunc := UpCase(GetModFunctionName(FuncS));
// Only internal DC functions.
if AType = sFuncTypeDC then
begin
AIndex := FileFunctionsStr.IndexOfName(AFunc);
if AIndex >= 0 then Exit(TFileFunction(AIndex));
end
else if AType = sFuncTypePlugin then Exit(fsfVariant);
Result := fsfInvalid;
end;
function GetVariantFileProperty(const FuncS: String; AFile: TFile;
const AFileSource: IFileSource): Variant;
var
AType, AName, AFunc, AParam: String;
begin
Result := Unassigned;
//---------------------
AType := upcase(GetModType(FuncS));
AName := upcase(GetModName(FuncS));
AFunc := upcase(GetModFunctionName(FuncS));
AParam := upcase(GetModFunctionParams(FuncS));
//------------------------------------------------------
//Plugin function
//------------------------------------------------------
if AType = sFuncTypePlugin then
begin
if AFileSource.IsClass(TWfxPluginFileSource) then
begin
with AFileSource as IWfxPluginFileSource do
begin
if WfxModule.ContentPlugin and WfxModule.FileParamVSDetectStr(AFile) then
begin
Result := WfxModule.CallContentGetValueV(AFile.FullPath, AFunc, AParam, 0);
end;
end;
end
else if fspDirectAccess in AFileSource.Properties then
begin
if not gWdxPlugins.IsLoaded(AName) then
if not gWdxPlugins.LoadModule(AName) then
Exit;
if gWdxPlugins.GetWdxModule(AName).FileParamVSDetectStr(AFile) then
begin
Result := gWdxPlugins.GetWdxModule(AName).CallContentGetValueV(
AFile.FullPath, AFunc, AParam, 0);
end;
end;
end;
//------------------------------------------------------
end;
function GetFilePropertyType(FileFunction: TFileFunction): TFilePropertiesTypes;
begin
if FileFunction >= fsfVariant then
Result:= [TFilePropertyType(FileFunction)]
else begin
Result:= TFileFunctionToProperty[FileFunction];
end;
end;
procedure AddModule(MenuItem: TMenuItem; OnMenuItemClick: TNotifyEvent; Module: TWDXModule);
var
J, K: Integer;
MI, MI2: TMenuItem;
WdxField: TWdxField;
begin
MI:= TMenuItem.Create(MenuItem);
MI.Caption:= Module.Name;
MenuItem.Add(MI);
// Load fields list
for J:= 0 to Module.FieldList.Count - 1 do
begin
WdxField:= TWdxField(Module.FieldList.Objects[J]);
if not (WdxField.FType in [ft_fulltext, ft_fulltextw]) then
begin
MI:= TMenuItem.Create(MenuItem);
MI.Tag:= 1;
MI.Caption:= WdxField.LName;
MI.Hint:= Module.FieldList[J];
MenuItem.Items[MenuItem.Count - 1].Add(MI);
if WdxField.FType <> ft_multiplechoice then
begin
for K:= 0 to High(WdxField.FUnits) do
begin
MI2:=TMenuItem.Create(MenuItem);
MI2.Tag:= 2;
MI2.Caption:= WdxField.LUnits[K];
MI2.Hint:= WdxField.FUnits[K];
MI2.OnClick:= OnMenuItemClick;
MI.Add(MI2);
end;
end;
if MI.Count = 0 then MI.OnClick:= OnMenuItemClick;
end;
end;
end;
procedure FillContentFieldMenu(MenuOrMenuItem: TObject; OnMenuItemClick: TNotifyEvent; const FileSystem: String);
var
I, J: Integer;
MI, MI2, localMenuItem: TMenuItem;
Module: TWDXModule;
FileSize: TDynamicStringArray;
begin
if MenuOrMenuItem.ClassType = TPopupMenu then
localMenuItem := TPopupMenu(MenuOrMenuItem).Items
else
localMenuItem := TMenuItem(MenuOrMenuItem);
localMenuItem.Clear;
// DC commands
MI:= TMenuItem.Create(localMenuItem);
MI.Caption:= 'DC';
localMenuItem.Add(MI);
for I:= 0 to FileFunctionsStr.Count - 1 do
begin
MI:= TMenuItem.Create(localMenuItem);
MI.Tag:= 0;
MI.Hint:= FileFunctionsStr.Names[I];
MI.Caption:= FileFunctionsStr.ValueFromIndex[I] + ' (' + MI.Hint + ')';
localMenuItem.Items[0].Add(MI);
// Special case for attributes
if TFileFunctionStrings[fsfAttr] = FileFunctionsStr.Names[I] then
begin
// String attributes
MI2:= TMenuItem.Create(localMenuItem);
MI2.Tag:= 3;
MI2.Hint:= '';
MI2.Caption:= rsMnuContentDefault;
MI2.OnClick:= OnMenuItemClick;
MI.Add(MI2);
// Octal attributes
MI2:= TMenuItem.Create(localMenuItem);
MI2.Tag:= 3;
MI2.Hint:= ATTR_OCTAL;
MI2.Caption:= rsMnuContentOctal;
MI2.OnClick:= OnMenuItemClick;
MI.Add(MI2);
end;
// Special case for size
if TFileFunctionStrings[fsfSize] = FileFunctionsStr.Names[I] then
begin
// Default format
MI2:= TMenuItem.Create(localMenuItem);
MI2.Tag:= 3;
MI2.Hint:= '';
MI2.Caption:= rsMnuContentDefault;
MI2.OnClick:= OnMenuItemClick;
MI.Add(MI2);
FileSize:= SplitString(rsOptFileSizeFloat + ';' + rsLegacyOperationByteSuffixLetter + ';' + rsLegacyDisplaySizeSingleLetterKilo + ';' + rsLegacyDisplaySizeSingleLetterMega + ';' + rsLegacyDisplaySizeSingleLetterGiga + ';' + rsLegacyDisplaySizeSingleLetterTera + ';' + rsOptPersonalizedFileSizeFormat, ';');
for J:= 0 to High(FILE_SIZE) do
begin
MI2:= TMenuItem.Create(localMenuItem);
MI2.Tag:= 3;
MI2.Hint:= FILE_SIZE[J];
MI2.Caption:= FileSize[J];
MI2.OnClick:= OnMenuItemClick;
MI.Add(MI2);
end;
end;
if MI.Count = 0 then MI.OnClick:= OnMenuItemClick;
end;
// Plugins
if (FileSystem = EmptyStr) or SameText(FileSystem, FS_GENERAL) then
begin
MI:= TMenuItem.Create(localMenuItem);
MI.Caption:= rsOptionsEditorPlugins;
localMenuItem.Add(MI);
for I:= 0 to gWdxPlugins.Count - 1 do
begin
Module:= gWdxPlugins.GetWdxModule(I);
if not (Module.IsLoaded or Module.LoadModule) then
Continue;
AddModule(MI, OnMenuItemClick, Module);
end;
end
else begin
I:= gWFXPlugins.IndexOfName(FileSystem);
if (I >= 0) then begin
Module:= gWFXPlugins.LoadModule(gWFXPlugins.FileName[I]);
if Assigned(Module) and TWfxModule(Module).ContentPlugin then
AddModule(localMenuItem, OnMenuItemClick, Module);
end;
end;
end;
procedure FillFileFuncList;
begin
with FileFunctionsStr do
begin
Add(TFileFunctionStrings[fsfName] + '=' + rsFuncName);
Add(TFileFunctionStrings[fsfExtension] + '=' + rsFuncExt);
Add(TFileFunctionStrings[fsfSize] + '=' + rsFuncSize);
Add(TFileFunctionStrings[fsfAttr] + '=' + rsFuncAttr);
Add(TFileFunctionStrings[fsfPath] + '=' + rsFuncPath);
Add(TFileFunctionStrings[fsfGroup] + '=' + rsFuncGroup);
Add(TFileFunctionStrings[fsfOwner] + '=' + rsFuncOwner);
Add(TFileFunctionStrings[fsfModificationTime] + '=' + rsFuncMTime);
Add(TFileFunctionStrings[fsfCreationTime] + '=' + rsFuncCTime);
Add(TFileFunctionStrings[fsfLastAccessTime] + '=' + rsFuncATime);
Add(TFileFunctionStrings[fsfChangeTime] + '=' + rsFuncHTime);
Add(TFileFunctionStrings[fsfLinkTo] + '=' + rsFuncLinkTo);
Add(TFileFunctionStrings[fsfNameNoExtension] + '=' + rsFuncNameNoExt);
Add(TFileFunctionStrings[fsfType] + '=' + rsFuncType);
Add(TFileFunctionStrings[fsfComment] + '=' + rsFuncComment);
Add(TFileFunctionStrings[fsfCompressedSize] + '=' + rsFuncCompressedSize);
end;
end;
initialization
FileFunctionsStr := TStringList.Create;
finalization
FreeAndNil(FileFunctionsStr);
end.