doublecmd/src/uformcommands.pas
Denis Bisson 3c8068c942 ADD: New internal command "cm_AddNewSearch" to generate a new instance of the "Find files" dialog window. This way, more than one search may be done at the same time.
ADD: When closing any "Find files" dialog window, if at least one search has been done, that window will remain in memory. If no search has been done, it will be cleared from memory.
ADD: New internal command "cm_ViewSearches" to show a list of "Find files" dialog window and select one of them to activate it. This way, it possible to do a second search without losing the first one and to be able to reactive it.
ADD: New internal command "cm_DeleteSearches" to delete all the "Find files" dialog windows we may have in memory. If you want to delete just one, activate it via the "cm_ViewSearches" internal command and then clear it from memory from there.
ADD: New internal command "cm_ConfigSearches" to jump directly to the configuration of options related with searches.
ADD: New 32x32 icons for the new internal commands "cm_AddNewSearch", "cm_ViewSearches", "cm_DeleteSearches" and "cm_ConfigSearches". For the 16x16, the last three mentionned were added since for the "cm_AddNewSearch" it was already there, added by mistake too early but now it's used.
ADD: There is now a dedicated page for the option of the search instead of being inside the "File operations" page.
UPD: On new installation, in the "Find files" dialog window, the "ENTER" key is not a shortcut anymore to launch a search. Too many persons get used that pressing enter in the result page was making us to jump to the file location. Code were already in place to have pertinent action on default button so pressing "ENTER" do the pertinent expected job.
ADD: Possibility to have a menu bar in the "Find files" dialog window. This is mainly to help the user at first to see all the possibility. There is an option to hide it and probably experienced user after a while will hide it to gain some 20 extra pixels vertically.
ADD: Reserve, if not already used, the keyboard shortcut "Ctrl+Shift+F7" for the new internal command "cm_AddNewSearch".
UPD: Inside the "Find files" unit, re-arrange the code so the buttons will use TAction to do something instead of the opposite where the TAction were pressing button.
UPD: Language files have been updated.
2016-12-01 02:23:28 +00:00

664 lines
23 KiB
ObjectPascal

{
Double Commander
-------------------------------------------------------------------------
Implements custom commands for a component
Copyright (C) 2011-2012 Przemyslaw Nagay (cobines@gmail.com)
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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
}
unit uFormCommands;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, StringHashList, ActnList, Menus;
type
TCommandFuncResult = (cfrSuccess, cfrDisabled, cfrNotFound);
TCommandFunc = procedure(const Params: array of string) of object;
TCommandCaptionType = (cctShort, cctLong);
TCommandCategorySortOrder = (ccsLegacy, ccsAlphabetical);
TCommandSortOrder = (csLegacy, csAlphabetical);
(*
The commands are 'user' functions which can be assigned to toolbar
button, hotkey, menu item, executed by scripts, etc.
Only published functions and procedures can by found by MethodAddress.
How to set up a form to handle hotkeys:
1. Specify that the form class implements IFormCommands (class (TForm, IFormCommands)).
2. Add private FCommands: TFormCommands that will implement the interface.
3. Add property that will specify that FCommands implements the interface in place of the form.
property Commands: TFormCommands read FCommands{$IF FPC_FULLVERSION >= 020501} implements IFormCommands{$ENDIF};
For FPC < 2.5.1 "implements" does not work correctly so the form must
implement the interface itself. For example see fViewer.
{$IF FPC_FULLVERSION < 020501}
// "implements" does not work in FPC < 2.5.1
function ExecuteCommand(Command: string; Param: String=''): TCommandFuncResult;
function GetCommandCaption(Command: String; CaptionType: TCommandCaptionType): String;
procedure GetCommandsList(List: TStrings);
{$ENDIF}
4. Make sure a default constructor Create(TheOwner: TComponent) is present which
will create the FCommands on demand when it is needed to read the hotkeys
when the form is not currently created.
5. Register the form and action list in HotkeyManager somewhere in constructor:
const
HotkeysCategory = <unique_name>;
HMForm := HotMan.Register(Self, HotkeysCategory);
HMForm.RegisterActionList(actionList);
And unregister in destructor:
HotMan.UnRegister(Self);
6. Register form as commands form so that it is displayed in Options:
initialization
TFormCommands.RegisterCommandsForm(Tfrm..., HotkeysCategory, @rsHotkeyCategory...);
*)
{ IFormCommands }
{$interfaces corba}
// If a form/object implements this interface then it can execute custom
// commands with parameters.
IFormCommands = interface
['{0464B1C0-BA98-4258-A286-F0F726FF66C4}']
function ExecuteCommand(Command: String; const Params: array of string): TCommandFuncResult;
function GetCommandCaption(Command: String; CaptionType: TCommandCaptionType = cctShort): String;
procedure GetCommandsList(List: TStrings);
procedure GetCommandCategoriesList(List: TStringList; CommandCategorySortOrder:TCommandCategorySortOrder);
procedure GetCommandsListForACommandCategory(List: TStringList; sCategoryName:String; CommandSortOrder: TCommandSortOrder);
procedure ExtractCommandFields(ItemInList: string; var sCategory:string; var sCommand: string; var sHint: string; var sHotKey: string; var FlagCategoryTitle: boolean);
end;
{$interfaces default}
// Used to filter out commands. If this function returns True the command
// is not included in the commands list.
TCommandFilterFunc = function (Command: String): Boolean of object;
TCommandRec = record
Address: Pointer; //<en Address of the command function in the class.
Action: TAction; //<en If a TAction is assigned to a named action it is cached here.
end;
PCommandRec = ^TCommandRec;
{en
Stores association between method name and its address.
StringHashList is used for this purpose, which may be faster
than linear scanning by using MethodAddress on the given object.
}
{ TFormCommands }
TFormCommands = class(TComponent, IFormCommands)
private
FFilterFunc: TCommandFilterFunc;
FInstanceObject: TObject;
FMethods: TStringHashList;
FTranslatableCommandCategory: TStringList;
class procedure GetMethodsList(Instance: TObject; MethodsList: TStringHashList; ActionList: TActionList);
public
{en
Creates methods list.
@param(TheOwner
Object of which we want the list of methods.
It will also be the owner component.)
@param(ActionList
Optional. If contains actions corresponding to commands names
(but prefixed with "act" instead of "cm_") then actions hints or
captions will be used as descriptions for the commands.)
}
constructor Create(TheOwner: TComponent; ActionList: TActionList = nil); reintroduce;
destructor Destroy; override;
function ExecuteCommand(Command: string; const Params: array of string): TCommandFuncResult;
{en
Enables/disables command.
@param(CommandName
Name of the command. Include prefix if exists, like 'cm_'.)
@param(Enable
Whether to enable or disable the command.)
}
procedure EnableCommand(Command: String; Enable: Boolean);
function GetCommandCaption(Command: String; CaptionType: TCommandCaptionType): String;
function GetCommandName(Index: Integer): String;
function GetCommandRec(Command: String): PCommandRec;
procedure GetCommandsList(List: TStrings);
procedure GetCommandCategoriesList(List: TStringList; CommandCategorySortOrder:TCommandCategorySortOrder);
procedure GetCommandsListForACommandCategory(List: TStringList; sCategoryName:String; CommandSortOrder: TCommandSortOrder);
procedure ExtractCommandFields(ItemInList: string; var sCategory: string; var sCommand: string; var sHint: string; var sHotKey: string; var FlagCategoryTitle: boolean);
class procedure GetCategoriesList(List: TStrings; Translated: TStrings);
class function GetCommandsForm(CategoryName: String): TComponentClass;
class procedure RegisterCommandsForm(AClass: TClass; CategoryName: String; TranslatedName: PResStringRec);
property FilterFunc: TCommandFilterFunc read FFilterFunc write FFilterFunc;
end;
function GetDefaultParam(const Params: array of String): String;
{en
Searches for parameters starting with "Key=" and sets Value to the
the rest of the parameter string (Key=Value).
If the key is not found it sets Value to empty string and returns @false.
@returns(@true if the key was found, @false if it was not found)
}
function GetParamValue(const Params: array of String; Key: String; out Value: String): Boolean;
function GetParamValue(const Param: String; Key: String; out Value: String): Boolean;
function GetParamBoolValue(const Param: String; Key: String; out BoolValue: Boolean): Boolean;
{en
If StrValue matches any value that can be translated into boolean then
it returns @true and sets Value appropriately. Otherwise returns @false.
}
function GetBoolValue(StrValue: string; out BoolValue: Boolean): Boolean;
function CloneMainAction(AMainAction:TAction; ATargetActionList:TActionList; AMenuToInsert:TMenuItem=nil; APositionToInsert:integer=-1):TAction;
implementation
uses
uGlobs, uHotkeyManager, DCStrUtils, uLng;
type
TCommandsFormRec = record
AClass: TComponentClass;
Name: String;
TranslatedName: PResStringRec; // Until FPC 2.7.1 resource strings translation
// is not applied after assining so pointer is used here.
// It is OK because the address doesn't change after translation.
end;
var
CommandsForms: array of TCommandsFormRec;
constructor TFormCommands.Create(TheOwner: TComponent; ActionList: TActionList);
begin
inherited Create(TheOwner);
FInstanceObject := TheOwner;
FMethods := TStringHashList.Create(False); // False = not case-sensitive
GetMethodsList(FInstanceObject, FMethods, ActionList);
FTranslatableCommandCategory:=TStringList.Create;
ParseLineToList(rsCmdCategoryListInOrder, FTranslatableCommandCategory);
end;
destructor TFormCommands.Destroy;
var
Index: Integer;
begin
for Index := 0 to FMethods.Count - 1 do
Dispose(PCommandRec(FMethods.List[Index]^.Data));
FreeAndNil(FMethods);
FTranslatableCommandCategory.Free;
inherited;
end;
function TFormCommands.ExecuteCommand(Command: String; const Params: array of string): TCommandFuncResult;
var
Method: TMethod;
CommandRec: PCommandRec;
begin
CommandRec := GetCommandRec(Command);
if Assigned(CommandRec) then
begin
if Assigned(CommandRec^.Action) and not CommandRec^.Action.Enabled then
Result := cfrDisabled
else
begin
Method.Code := CommandRec^.Address; // address of method
Method.Data := FInstanceObject; // pointer to instance
TCommandFunc(Method)(Params);
Result := cfrSuccess;
end;
end
else
Result := cfrNotFound;
end;
procedure TFormCommands.EnableCommand(Command: String; Enable: Boolean);
var
CommandRec: PCommandRec;
begin
CommandRec := GetCommandRec(Command);
if Assigned(CommandRec) then
begin
if Assigned(CommandRec^.Action) then
CommandRec^.Action.Enabled := Enable;
end
else
raise Exception.Create('Invalid command name: ' + Command);
end;
function TFormCommands.GetCommandCaption(Command: String; CaptionType: TCommandCaptionType): String;
var
CommandRec: PCommandRec;
begin
CommandRec := GetCommandRec(Command);
if Assigned(CommandRec) and Assigned(CommandRec^.Action) then
begin
if (CaptionType = cctLong) and (CommandRec^.Action.Hint <> EmptyStr) then
Result := CommandRec^.Action.Hint
else
Result := StringReplace(CommandRec^.Action.Caption, '&', '', [rfReplaceAll]);
end
else
Result:= '';
end;
function TFormCommands.GetCommandName(Index: Integer): String;
begin
if (Index >= 0) and (Index < FMethods.Count) then
Result := FMethods.List[Index]^.Key
else
raise ERangeError.Create('Invalid command index');
end;
function TFormCommands.GetCommandRec(Command: String): PCommandRec;
var
Index: Integer;
begin
Index := FMethods.Find(Command);
if Index = -1 then
Result := nil
else
Result := PCommandRec(FMethods.List[Index]^.Data);
end;
procedure TFormCommands.GetCommandsList(List: TStrings);
var
Index: Integer;
Command: String;
begin
List.Clear;
List.BeginUpdate;
try
for Index := 0 to FMethods.Count - 1 do
begin
Command := FMethods.List[Index]^.Key;
if not (Assigned(FilterFunc) and FilterFunc(Command)) then
List.Add(Command);
end;
finally
List.EndUpdate;
end;
end;
procedure TFormCommands.GetCommandCategoriesList(List: TStringList; CommandCategorySortOrder:TCommandCategorySortOrder);
var
Index: Integer;
Command, Category: String;
begin
List.Clear;
List.BeginUpdate;
try
for Index := 0 to FMethods.Count - 1 do
begin
Command := FMethods.List[Index]^.Key;
if not (Assigned(FilterFunc) and FilterFunc(Command)) then
begin
if TCommandRec(FMethods.List[Index]^.Data^).Action.Tag <> 0 then
begin
case CommandCategorySortOrder of
ccsLegacy: Category:=Format('%2.2d',[TCommandRec(FMethods.List[Index]^.Data^).Action.Tag])+FTranslatableCommandCategory.Strings[TCommandRec(FMethods.List[Index]^.Data^).Action.Tag];
else Category:=FTranslatableCommandCategory.Strings[TCommandRec(FMethods.List[Index]^.Data^).Action.Tag];
end;
if List.IndexOf(Category)=-1 then
List.Add(Category);
end;
end;
end;
List.Sort;
if CommandCategorySortOrder=ccsLegacy then
for Index:=0 to pred(List.count) do List.Strings[Index]:=RightStr(List.Strings[Index],length(List.Strings[Index])-2);
List.Insert(0,'('+rsSimpleWordAll+')');
finally
List.EndUpdate;
end;
end;
{ TFormCommands.GetCommandsListForACommandCategory }
{
Routine is in fact going through all the commands present in the main form.
They will store them into a list passed in parameter "List".
Each item of the list will be a string with information separate between pipe "|" symbol.
These info will be command name|shortcut|hint|category number.
For example: cm_ChangeDirToHome|Ctrl+Alt+H|Change directory to home|14
While building the list, if the wanted sort method is "csLegacy", each item will preceeded with category index on two digt and by command index on three digits.
This is to help to sort the element. We'll simply sort calling "TStringList.Sort" since the beginning of the string have the legacy reference order.
For example: 14106cm_ChangeDirToHome|Ctrl+Alt+H|Change directory to home|14
At the end, when exiting, these 5 digits which help to sort will simply be removed.
ALSO, the routine has the parameter "sCategoryName" to determine the command from which category should be in the list OR if all the commands from ALL the catagory must be returned.
When the commands from ALL the category are requested, category header will be inserted in the returned list.
These command will have the prefix for the command index set to '000' to make sure it appear at the beginning of the category command name.
For these category identifier, the other fields are empty so that's why pipe are following with nothing between.
Example: 14000Navigation||||
No special "class" has been created for all this. It seem simple like that.
}
procedure TFormCommands.GetCommandsListForACommandCategory(List: TStringList; sCategoryName:String; CommandSortOrder: TCommandSortOrder);
var
Index, iHotKey, iControl: Integer;
Command, Category, sHotKey, LocalHint, HeaderSortedHelper, HeaderCategorySortedHelper: String;
HMForm: THMForm;
HMControl: THMControl;
hotkey: THotkey;
begin
List.Clear;
List.BeginUpdate;
try
HeaderSortedHelper:='';
HMForm := HotMan.Forms.Find('main');
for Index := 0 to FMethods.Count - 1 do
begin
Command := FMethods.List[Index]^.Key;
if not (Assigned(FilterFunc) and FilterFunc(Command)) then
begin
Category:=FTranslatableCommandCategory.Strings[TCommandRec(FMethods.List[Index]^.Data^).Action.Tag];
if (Category = sCategoryName) OR (sCategoryName=('('+rsSimpleWordAll+')')) then
begin
sHotKey := '';
iHotKey := 0;
while (iHotKey < HMForm.Hotkeys.Count) and (sHotKey = '') do
begin
hotkey := HMForm.Hotkeys[iHotKey];
if hotkey.Command = Command then
sHotKey := ShortcutsToText(hotkey.Shortcuts);
Inc(iHotKey);
end;
if sHotKey='' then
begin
iControl:=0;
while (iControl<HMForm.Controls.Count) and (sHotKey='') do
begin
HMControl := HMForm.Controls[iControl];
iHotKey:=0;
while (iHotKey < HMControl.Hotkeys.Count) and (sHotKey = '') do
begin
hotkey := HMControl.Hotkeys[iHotKey];
if hotkey.Command = Command then
sHotKey := ShortcutsToText(hotkey.Shortcuts);
Inc(iHotKey);
end;
inc(iControl);
end;
end;
if CommandSortOrder=csLegacy then
begin
HeaderSortedHelper:=Format('%2.2d',[TCommandRec(FMethods.List[Index]^.Data^).Action.Tag])+
Format('%3.3d',[TCommandRec(FMethods.List[Index]^.Data^).Action.Index+1]);
if sCategoryName=('('+rsSimpleWordAll+')') then
begin
HeaderCategorySortedHelper:=Format('%2.2d',[TCommandRec(FMethods.List[Index]^.Data^).Action.Tag])+'000';
if List.IndexOf(HeaderCategorySortedHelper+Category+'||||')=-1 then
List.Add(HeaderCategorySortedHelper+Category+'||||');
end;
end;
if TCommandRec(FMethods.List[Index]^.Data^).Action.Hint <> EmptyStr then
LocalHint := TCommandRec(FMethods.List[Index]^.Data^).Action.Hint
else
LocalHint := StringReplace(TCommandRec(FMethods.List[Index]^.Data^).Action.Caption, '&', '', [rfReplaceAll]);
if LocalHint<>EmptyStr then Command:=Command+'|'+sHotKey+'|'+LocalHint+'|'+Format('%2.2d',[TCommandRec(FMethods.List[Index]^.Data^).Action.Tag]);
List.Add(HeaderSortedHelper+Command);
end;
end;
end;
List.Sort;
if CommandSortOrder=csLegacy then
for Index:=0 to pred(List.count) do List.Strings[Index]:=RightStr(List.Strings[Index],length(List.Strings[Index])-(2+3));
finally
List.EndUpdate;
end;
end;
procedure TFormCommands.ExtractCommandFields(ItemInList: string; var sCategory: string; var sCommand: string; var sHint: string; var sHotKey: string; var FlagCategoryTitle: boolean);
var
PosPipe: longint;
sWorkingString: String;
begin
FlagCategoryTitle := False;
sCommand := '';
sHint := '';
sHotKey := '';
sCategory := '';
PosPipe := Pos('|', ItemInList);
if PosPipe <> 0 then
begin
if pos('||||', ItemInList) = 0 then
begin
sCommand := Copy(ItemInList, 1, pred(PosPipe));
sWorkingString := RightStr(ItemInList, length(ItemInList) - PosPipe);
PosPipe := pos('|', sWorkingString);
if PosPipe <> 0 then
begin
sHotKey := copy(sWorkingString, 1, pred(PosPipe));
sWorkingString := rightStr(sWorkingString, length(sWorkingString) - PosPipe);
PosPipe := pos('|', sWorkingString);
if PosPipe <> 0 then
begin
sHint := copy(sWorkingString, 1, pred(PosPipe));
sCategory := rightStr(sWorkingString, length(sWorkingString) - PosPipe);
sCategory := FTranslatableCommandCategory.Strings[StrToIntDef(sCategory,0)];
end;
end;
end
else
begin
sCommand := Copy(ItemInList, 1, pred(PosPipe));
FlagCategoryTitle := True;
end;
end;
end;
class procedure TFormCommands.GetMethodsList(Instance: TObject; MethodsList: TStringHashList; ActionList: TActionList);
type
pmethodnamerec = ^tmethodnamerec;
tmethodnamerec = packed record
name : pshortstring;
addr : pointer;
end;
tmethodnametable = packed record
count : dword;
entries : tmethodnamerec; // first entry
// subsequent tmethodnamerec records follow
end;
pmethodnametable = ^tmethodnametable;
var
methodtable : pmethodnametable;
i : dword;
vmt : tclass;
pentry: pmethodnamerec;
CommandRec: PCommandRec;
Command: String;
Action: TContainedAction;
begin
vmt := Instance.ClassType;
while assigned(vmt) do
begin
methodtable := pmethodnametable((Pointer(vmt)+vmtMethodTable)^);
if assigned(methodtable) then
begin
pentry := @methodtable^.entries;
for i := 0 to methodtable^.count - 1 do
begin
Command := pentry[i].name^;
if StrBegins(Command, 'cm_') then
// TODO: Match functions parameter too.
begin
New(CommandRec);
MethodsList.Add(Command, CommandRec);
CommandRec^.Address := pentry[i].addr;
CommandRec^.Action := nil;
if Assigned(ActionList) then
begin
Action := ActionList.ActionByName('act' + Copy(Command, 4, Length(Command) - 3));
if Action is TAction then
CommandRec^.Action := TAction(Action);
end;
end;
end;
end;
vmt := pclass(pointer(vmt) + vmtParent)^;
end;
end;
class procedure TFormCommands.GetCategoriesList(List: TStrings; Translated: TStrings);
var
i: Integer;
begin
List.Clear;
Translated.Clear;
for i := Low(CommandsForms) to High(CommandsForms) do
begin
List.Add(CommandsForms[i].Name);
Translated.Add(CommandsForms[i].TranslatedName^);
end;
end;
class function TFormCommands.GetCommandsForm(CategoryName: String): TComponentClass;
var
i: Integer;
begin
for i := Low(CommandsForms) to High(CommandsForms) do
if CommandsForms[i].Name = CategoryName then
begin
Exit(CommandsForms[i].AClass);
end;
Result := nil;
end;
class procedure TFormCommands.RegisterCommandsForm(AClass: TClass; CategoryName: String; TranslatedName: PResStringRec);
begin
SetLength(CommandsForms, Length(CommandsForms) + 1);
CommandsForms[High(CommandsForms)].AClass := TComponentClass(AClass);
CommandsForms[High(CommandsForms)].Name := CategoryName;
CommandsForms[High(CommandsForms)].TranslatedName := TranslatedName;
end;
function GetDefaultParam(const Params: array of String): String;
begin
if Length(Params) > 0 then
Result := Params[0]
else
Result := '';
end;
function GetParamValue(const Params: array of String; Key: String; out Value: String): Boolean;
var
Param: String;
begin
Key := Key + '=';
for Param in Params do
if StrBegins(Param, Key) then
begin
Value := Copy(Param, Length(Key) + 1, MaxInt);
Exit(True);
end;
Value := '';
Result := False;
end;
function GetParamValue(const Param: String; Key: String; out Value: String): Boolean;
begin
Key := Key + '=';
if StrBegins(Param, Key) then
begin
Value := Copy(Param, Length(Key) + 1, MaxInt);
Exit(True);
end;
Value := '';
Result := False;
end;
function GetParamBoolValue(const Param: String; Key: String; out BoolValue: Boolean): Boolean;
var
sValue: String;
begin
Result := GetParamValue(Param, Key, sValue) and
GetBoolValue(sValue, BoolValue);
end;
function GetBoolValue(StrValue: string; out BoolValue: Boolean): Boolean;
begin
StrValue := upcase(StrValue);
if (StrValue = 'TRUE') or
(StrValue = 'YES') or
(StrValue = 'ON') or
(StrValue = '1') then
begin
BoolValue := True;
Result := True;
end
else
if (StrValue = 'FALSE') or
(StrValue = 'NO') or
(StrValue = 'OFF') or
(StrValue = '0') then
begin
BoolValue := False;
Result := True;
end
else
Result := False;
end;
{ CloneMainAction }
// Useful to implement an action in sub window form that will invoke a main action
function CloneMainAction(AMainAction:TAction; ATargetActionList:TActionList; AMenuToInsert:TMenuItem=nil; APositionToInsert:integer=-1):TAction;
var
AMenuItem:TMenuItem;
begin
result:= TAction.Create(ATargetActionList);
result.Name := AMainAction.Name;
result.Caption := AMainAction.Caption;
result.Hint := AMainAction.Hint;
result.Category := AMainAction.Category;
result.GroupIndex := AMainAction.GroupIndex;
result.ShortCut:= AMainAction.ShortCut;
result.Enabled := AMainAction.Enabled;
result.ActionList := ATargetActionList;
result.OnExecute := AMainAction.OnExecute;
if AMenuToInsert<>nil then
begin
AMenuItem:=TMenuItem.Create(AMenuToInsert);
AMenuItem.Action:=result;
if APositionToInsert=-1 then
AMenuToInsert.Add(AMenuItem)
else
AMenuToInsert.Insert(APositionToInsert,AMenuItem);
end;
end;
end.