mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-01 02:03:41 +02:00
1050 lines
28 KiB
ObjectPascal
1050 lines
28 KiB
ObjectPascal
{-------------------------------------------------------------------------------
|
|
The contents of this file are subject to the Mozilla Public License
|
|
Version 1.1 (the "License"); you may not use this file except in compliance
|
|
with the License. You may obtain a copy of the License at
|
|
http://www.mozilla.org/MPL/
|
|
|
|
Software distributed under the License is distributed on an "AS IS" basis,
|
|
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
|
the specific language governing rights and limitations under the License.
|
|
|
|
The Original Code is: SynMacroRecorder.pas, released 2001-10-17.
|
|
|
|
Author of this file is Flávio Etrusco.
|
|
Portions created by Flávio Etrusco are Copyright 2001 Flávio Etrusco.
|
|
All Rights Reserved.
|
|
|
|
Contributors to the SynEdit project are listed in the Contributors.txt file.
|
|
|
|
Alternatively, the contents of this file may be used under the terms of the
|
|
GNU General Public License Version 2 or later (the "GPL"), in which case
|
|
the provisions of the GPL are applicable instead of those above.
|
|
If you wish to allow use of your version of this file only under the terms
|
|
of the GPL and not to allow others to use your version of this file
|
|
under the MPL, indicate your decision by deleting the provisions above and
|
|
replace them with the notice and other provisions required by the GPL.
|
|
If you do not delete the provisions above, a recipient may use your version
|
|
of this file under either the MPL or the GPL.
|
|
|
|
$Id$
|
|
|
|
You may retrieve the latest version of this file at the SynEdit home page,
|
|
located at http://SynEdit.SourceForge.net
|
|
|
|
Known Issues:
|
|
-------------------------------------------------------------------------------}
|
|
|
|
unit SynMacroRecorder;
|
|
|
|
{$I synedit.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes,
|
|
SynEdit,
|
|
SynEditKeyCmds, //js 06-04-2002 got consts out here and qconsts in
|
|
{$IFDEF SYN_CLX}
|
|
QConsts,
|
|
QStdCtrls,
|
|
QControls,
|
|
Qt,
|
|
Types,
|
|
QGraphics,
|
|
QMenus,
|
|
{$ELSE}
|
|
StdCtrls,
|
|
Controls,
|
|
{$IFDEF SYN_LAZARUS}
|
|
{$IFDEF USE_UTF8BIDI_LCL}
|
|
utf8bidi,
|
|
{$ENDIF}
|
|
FileUtil, Types, LCLIntf, LCLType,
|
|
{$ELSE}
|
|
Windows, Messages,
|
|
{$ENDIF}
|
|
Graphics,
|
|
Menus,
|
|
{$ENDIF}
|
|
SynEditPlugins;
|
|
|
|
{$IFDEF SYN_COMPILER_3_UP}
|
|
resourcestring
|
|
{$ELSE}
|
|
const
|
|
{$ENDIF}
|
|
sCannotRecord = 'Cannot record macro when recording';
|
|
sCannotPlay = 'Cannot playback macro when recording';
|
|
sCannotPause = 'Can only pause when recording';
|
|
sCannotResume = 'Can only resume when paused';
|
|
|
|
type
|
|
TSynMacroState = (msStopped, msRecording, msPlaying, msPaused);
|
|
TSynMacroCommand = (mcRecord, mcPlayback);
|
|
|
|
TSynMacroEvent = class(TObject)
|
|
protected
|
|
fRepeatCount: Byte;
|
|
function GetAsString : string; virtual; abstract;
|
|
procedure InitEventParameters(aStr : string); virtual; abstract;
|
|
public
|
|
constructor Create; {$IFNDEF FPC}virtual;{$ENDIF}
|
|
procedure Initialize(aCmd: TSynEditorCommand;
|
|
{$IFDEF SYN_LAZARUS}const aChar: TUTF8Char{$ELSE}aChar: Char{$ENDIF};
|
|
aData: Pointer); virtual; abstract;
|
|
{ the CommandID must not be read inside LoadFromStream/SaveToStream. It's read by the
|
|
MacroRecorder component to decide which MacroEvent class to instanciate }
|
|
procedure LoadFromStream(aStream: TStream); virtual; abstract;
|
|
procedure SaveToStream(aStream: TStream); virtual; abstract;
|
|
procedure Playback(aEditor: TCustomSynEdit); virtual; abstract;
|
|
property AsString : string read GetAsString;
|
|
property RepeatCount : Byte read fRepeatCount write fRepeatCount;
|
|
end;
|
|
|
|
TSynBasicEvent = class(TSynMacroEvent)
|
|
protected
|
|
fCommand: TSynEditorCommand;
|
|
function GetAsString : string; override;
|
|
procedure InitEventParameters(aStr : string); override;
|
|
public
|
|
procedure Initialize(aCmd: TSynEditorCommand;
|
|
{$IFDEF SYN_LAZARUS}const aChar: TUTF8Char{$ELSE}aChar: Char{$ENDIF};
|
|
aData: Pointer); override;
|
|
procedure LoadFromStream(aStream: TStream); override;
|
|
procedure SaveToStream(aStream: TStream); override;
|
|
procedure Playback(aEditor: TCustomSynEdit); override;
|
|
public
|
|
property Command: TSynEditorCommand read fCommand write fCommand;
|
|
end;
|
|
|
|
TSynCharEvent = class(TSynMacroEvent)
|
|
protected
|
|
fKey: {$IFDEF SYN_LAZARUS}TUTF8Char{$ELSE}Char{$ENDIF};
|
|
function GetAsString : string; override;
|
|
procedure InitEventParameters(aStr : string); override;
|
|
public
|
|
procedure Initialize(aCmd: TSynEditorCommand;
|
|
{$IFDEF SYN_LAZARUS}const aChar: TUTF8Char{$ELSE}aChar: Char{$ENDIF};
|
|
aData: Pointer); override;
|
|
procedure LoadFromStream(aStream: TStream); override;
|
|
procedure SaveToStream(aStream: TStream); override;
|
|
procedure Playback(aEditor: TCustomSynEdit); override;
|
|
public
|
|
{$IFDEF SYN_LAZARUS}
|
|
property Key: TUTF8Char read fKey write fKey;
|
|
{$ELSE}
|
|
property Key: Char read fKey write fKey;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
TSynStringEvent = class(TSynMacroEvent)
|
|
protected
|
|
fString: string;
|
|
function GetAsString : string; override;
|
|
procedure InitEventParameters(aStr : string); override;
|
|
public
|
|
procedure Initialize(aCmd: TSynEditorCommand;
|
|
{$IFDEF SYN_LAZARUS}const aChar: TUTF8Char{$ELSE}aChar: Char{$ENDIF};
|
|
aData: Pointer); override;
|
|
procedure LoadFromStream(aStream: TStream); override;
|
|
procedure SaveToStream(aStream: TStream); override;
|
|
procedure Playback(aEditor: TCustomSynEdit); override;
|
|
public
|
|
property Value: string read fString write fString;
|
|
end;
|
|
|
|
TSynPositionEvent = class(TSynBasicEvent)
|
|
protected
|
|
fPosition: TPoint;
|
|
function GetAsString : string; override;
|
|
procedure InitEventParameters(aStr : string); override;
|
|
public
|
|
procedure Initialize(aCmd: TSynEditorCommand;
|
|
{$IFDEF SYN_LAZARUS}const aChar: TUTF8Char{$ELSE}aChar: Char{$ENDIF};
|
|
aData: Pointer); override;
|
|
procedure LoadFromStream(aStream: TStream); override;
|
|
procedure SaveToStream(aStream: TStream); override;
|
|
procedure Playback(aEditor: TCustomSynEdit); override;
|
|
public
|
|
property Position: TPoint read fPosition write fPosition;
|
|
end;
|
|
|
|
TSynDataEvent = class(TSynBasicEvent)
|
|
protected
|
|
fData: Pointer;
|
|
public
|
|
procedure Initialize(aCmd: TSynEditorCommand;
|
|
{$IFDEF SYN_LAZARUS}const aChar: TUTF8Char{$ELSE}aChar: Char{$ENDIF};
|
|
aData: Pointer); override;
|
|
procedure LoadFromStream(aStream: TStream); override;
|
|
procedure SaveToStream(aStream: TStream); override;
|
|
procedure Playback(aEditor: TCustomSynEdit); override;
|
|
end;
|
|
|
|
TCustomSynMacroRecorder = class;
|
|
|
|
TSynUserCommandEvent = procedure (aSender: TCustomSynMacroRecorder;
|
|
aCmd: TSynEditorCommand; var aEvent: TSynMacroEvent) of object;
|
|
|
|
{ TCustomSynMacroRecorder
|
|
OnStateChange:
|
|
occurs right after start playing, recording, pausing or stopping
|
|
SaveMarkerPos:
|
|
if true, Bookmark position is recorded in the macro. Otherwise, the Bookmark
|
|
is created in the position the Caret is at the time of playback.
|
|
}
|
|
|
|
TCustomSynMacroRecorder = class(TAbstractSynHookerPlugin)
|
|
private
|
|
fShortCuts: array [TSynMacroCommand] of TShortCut;
|
|
fOnStateChange: TNotifyEvent;
|
|
fOnUserCommand: TSynUserCommandEvent;
|
|
fMacroName: string;
|
|
fSaveMarkerPos: boolean;
|
|
function GetCommandIDs(Index: integer): TSynEditorCommand;
|
|
function GetEvent(aIndex: integer): TSynMacroEvent;
|
|
function GetEventCount: integer;
|
|
function GetAsString: string;
|
|
procedure SetAsString(const Value: string);
|
|
protected
|
|
fCurrentEditor: TCustomSynEdit;
|
|
fState: TSynMacroState;
|
|
fEvents: TList;
|
|
fCommandIDs: array [TSynMacroCommand] of TSynEditorCommand;
|
|
procedure SetShortCut(const Index: Integer; const Value: TShortCut);
|
|
function GetIsEmpty: boolean;
|
|
procedure StateChanged;
|
|
procedure DoAddEditor(aEditor: TCustomSynEdit); override;
|
|
procedure DoRemoveEditor(aEditor: TCustomSynEdit); override;
|
|
procedure OnCommand(Sender: TObject; AfterProcessing: boolean;
|
|
var Handled: boolean; var Command: TSynEditorCommand;
|
|
var aChar: {$IFDEF SYN_LAZARUS}TUTF8Char{$ELSE}Char{$ENDIF};
|
|
Data: pointer; HandlerData: pointer); override;
|
|
function CreateMacroEvent(aCmd: TSynEditorCommand): TSynMacroEvent;
|
|
protected
|
|
property RecordCommandID: TSynEditorCommand index ord(mcRecord) read GetCommandIDs;
|
|
property PlaybackCommandID: TSynEditorCommand index ord(mcPlayback) read GetCommandIDs;
|
|
function GetShortCuts(Cmd: integer): TShortCut;
|
|
public
|
|
constructor Create(aOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
procedure Error(const aMsg: String);
|
|
procedure AddEditor(aEditor: TCustomSynEdit);
|
|
procedure RemoveEditor(aEditor: TCustomSynEdit);
|
|
procedure RecordMacro(aEditor: TCustomSynEdit);
|
|
procedure PlaybackMacro(aEditor: TCustomSynEdit);
|
|
procedure Stop;
|
|
procedure Pause;
|
|
procedure Resume;
|
|
property IsEmpty: boolean read GetIsEmpty;
|
|
property State: TSynMacroState read fState;
|
|
procedure Clear;
|
|
procedure AddEvent(aCmd: TSynEditorCommand; aChar: char; aData: pointer);
|
|
procedure InsertEvent(aIndex: integer; aCmd: TSynEditorCommand; aChar: char;
|
|
aData: pointer);
|
|
procedure AddCustomEvent(aEvent: TSynMacroEvent);
|
|
procedure InsertCustomEvent(aIndex: integer; aEvent: TSynMacroEvent);
|
|
procedure DeleteEvent(aIndex: integer);
|
|
procedure LoadFromStream(aSrc: TStream);
|
|
procedure LoadFromStreamEx(aSrc: TStream; aClear: boolean);
|
|
procedure SaveToStream(aDest: TStream);
|
|
procedure LoadFromFile(aFilename : string);
|
|
procedure SaveToFile(aFilename : string);
|
|
property EventCount: integer read GetEventCount;
|
|
property Events[aIndex: integer]: TSynMacroEvent read GetEvent;
|
|
property RecordShortCut: TShortCut index Ord(mcRecord)
|
|
read GetShortCuts write SetShortCut;
|
|
property PlaybackShortCut: TShortCut index Ord(mcPlayback)
|
|
read GetShortCuts write SetShortCut;
|
|
property SaveMarkerPos: boolean read fSaveMarkerPos
|
|
write fSaveMarkerPos default False;
|
|
property AsString : string read GetAsString write SetAsString;
|
|
property MacroName : string read fMacroName write fMacroName;
|
|
property OnStateChange: TNotifyEvent read fOnStateChange write fOnStateChange;
|
|
property OnUserCommand: TSynUserCommandEvent read fOnUserCommand
|
|
write fOnUserCommand;
|
|
end;
|
|
|
|
TSynMacroRecorder = class(TCustomSynMacroRecorder)
|
|
published
|
|
property SaveMarkerPos;
|
|
property RecordShortCut;
|
|
property PlaybackShortCut;
|
|
property OnStateChange;
|
|
property OnUserCommand;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
{$IFNDEF SYN_LAZARUS}
|
|
SynEditMiscProcs,
|
|
SynEditTypes,
|
|
{$ENDIF}
|
|
{$IFDEF SYN_CLX}
|
|
QForms,
|
|
{$ELSE}
|
|
Forms,
|
|
{$IFDEF SYN_COMPILER_6_UP}
|
|
RTLConsts,
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
SysUtils;
|
|
|
|
{ TSynDataEvent }
|
|
|
|
procedure TSynDataEvent.Initialize(aCmd: TSynEditorCommand;
|
|
{$IFDEF SYN_LAZARUS}const aChar: TUTF8Char{$ELSE}aChar: Char{$ENDIF};
|
|
aData: Pointer);
|
|
begin
|
|
fCommand := aCmd;
|
|
Assert( aChar = #0 );
|
|
fData := aData;
|
|
end;
|
|
|
|
procedure TSynDataEvent.LoadFromStream(aStream: TStream);
|
|
begin
|
|
aStream.Read( fData, SizeOf(fData) );
|
|
end;
|
|
|
|
procedure TSynDataEvent.Playback(aEditor: TCustomSynEdit);
|
|
begin
|
|
aEditor.CommandProcessor( Command, #0, fData );
|
|
end;
|
|
|
|
procedure TSynDataEvent.SaveToStream(aStream: TStream);
|
|
begin
|
|
inherited;
|
|
aStream.Write( fData, SizeOf(fData) );
|
|
end;
|
|
|
|
{ TCustomSynMacroRecorder }
|
|
|
|
procedure TCustomSynMacroRecorder.AddCustomEvent(aEvent: TSynMacroEvent);
|
|
begin
|
|
InsertCustomEvent( EventCount, aEvent );
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.AddEditor(aEditor: TCustomSynEdit);
|
|
begin
|
|
inherited AddEditor(aEditor);
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.AddEvent(aCmd: TSynEditorCommand;
|
|
aChar: char; aData: pointer);
|
|
begin
|
|
InsertEvent( EventCount, aCmd, aChar, aData );
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.Clear;
|
|
var
|
|
I: Integer;
|
|
Obj: TObject;
|
|
begin
|
|
if Assigned(fEvents) then
|
|
begin
|
|
for I := fEvents.Count-1 downto 0 do
|
|
begin
|
|
Obj := TObject(fEvents[I]);
|
|
fEvents.Delete(I);
|
|
Obj.Free;
|
|
end;
|
|
FreeAndNil( fEvents );
|
|
end;
|
|
end;
|
|
|
|
constructor TCustomSynMacroRecorder.Create(aOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
fMacroName := 'unnamed';
|
|
fCommandIDs[mcRecord] := NewPluginCommand;
|
|
fCommandIDs[mcPlayback] := NewPluginCommand;
|
|
{$IFDEF SYN_CLX} //js 06-04-2002 not only for linux, should also use qmenus when in clx for windows
|
|
fShortCuts[mcRecord] := QMenus.ShortCut( Ord('R'), [ssCtrl, ssShift] );
|
|
fShortCuts[mcPlayback] := QMenus.ShortCut( Ord('P'), [ssCtrl, ssShift] );
|
|
{$ELSE}
|
|
fShortCuts[mcRecord] := Menus.ShortCut( Ord('R'), [ssCtrl, ssShift] );
|
|
fShortCuts[mcPlayback] := Menus.ShortCut( Ord('P'), [ssCtrl, ssShift] );
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function TCustomSynMacroRecorder.CreateMacroEvent(aCmd: TSynEditorCommand): TSynMacroEvent;
|
|
|
|
function WantDefaultEvent(var aEvent: TSynMacroEvent): boolean;
|
|
begin
|
|
if Assigned( OnUserCommand ) then
|
|
OnUserCommand( Self, aCmd, aEvent );
|
|
Result := aEvent = nil;
|
|
end;
|
|
|
|
begin
|
|
case aCmd of
|
|
ecGotoXY, ecSelGotoXY, ecSetMarker0..ecSetMarker9:
|
|
begin
|
|
Result := TSynPositionEvent.Create;
|
|
TSynPositionEvent(Result).Command := aCmd;
|
|
end;
|
|
ecChar:
|
|
Result := TSynCharEvent.Create;
|
|
ecString:
|
|
Result := TSynStringEvent.Create;
|
|
else begin
|
|
Result := nil;
|
|
if (aCmd < ecUserFirst) or WantDefaultEvent( Result ) then
|
|
begin
|
|
Result := TSynBasicEvent.Create;
|
|
TSynBasicEvent(Result).Command := aCmd;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TCustomSynMacroRecorder.GetShortCuts(Cmd: integer): TShortCut;
|
|
begin
|
|
Result:=fShortCuts[TSynMacroCommand(Cmd)];
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.DeleteEvent(aIndex: integer);
|
|
var
|
|
iObj: Pointer;
|
|
begin
|
|
iObj := fEvents[ aIndex ];
|
|
fEvents.Delete( aIndex );
|
|
TObject( iObj ).Free;
|
|
end;
|
|
|
|
destructor TCustomSynMacroRecorder.Destroy;
|
|
begin
|
|
Clear;
|
|
inherited;
|
|
ReleasePluginCommand( PlaybackCommandID );
|
|
ReleasePluginCommand( RecordCommandID );
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.DoAddEditor(aEditor: TCustomSynEdit);
|
|
begin
|
|
HookEditor( aEditor, RecordCommandID, 0, RecordShortCut );
|
|
HookEditor( aEditor, PlaybackCommandID, 0, PlaybackShortCut );
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.DoRemoveEditor(aEditor: TCustomSynEdit);
|
|
begin
|
|
UnHookEditor( aEditor, RecordCommandID, RecordShortCut );
|
|
UnHookEditor( aEditor, PlaybackCommandID, PlaybackShortCut );
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.Error(const aMsg: String);
|
|
begin
|
|
raise Exception.Create(aMsg);
|
|
end;
|
|
|
|
function TCustomSynMacroRecorder.GetEvent(aIndex: integer): TSynMacroEvent;
|
|
begin
|
|
Result := TSynMacroEvent( fEvents[aIndex] );
|
|
end;
|
|
|
|
function TCustomSynMacroRecorder.GetCommandIDs(Index: integer
|
|
): TSynEditorCommand;
|
|
begin
|
|
Result:=fCommandIDs[TSynMacroCommand(Index)];
|
|
end;
|
|
|
|
function TCustomSynMacroRecorder.GetEventCount: integer;
|
|
begin
|
|
if fEvents = nil then
|
|
Result := 0
|
|
else
|
|
Result := fEvents.Count;
|
|
end;
|
|
|
|
function TCustomSynMacroRecorder.GetIsEmpty: boolean;
|
|
begin
|
|
Result := (fEvents = nil) or (fEvents.Count = 0);
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.InsertCustomEvent(aIndex: integer;
|
|
aEvent: TSynMacroEvent);
|
|
begin
|
|
if fEvents = nil then
|
|
fEvents := TList.Create;
|
|
fEvents.Insert( aIndex, aEvent );
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.InsertEvent(aIndex: integer;
|
|
aCmd: TSynEditorCommand; aChar: char; aData: pointer);
|
|
var
|
|
iEvent: TSynMacroEvent;
|
|
begin
|
|
iEvent := CreateMacroEvent( aCmd );
|
|
try
|
|
iEvent.Initialize( aCmd, aChar, aData );
|
|
InsertCustomEvent( aIndex, iEvent );
|
|
except
|
|
iEvent.Free;
|
|
raise;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.LoadFromStream(aSrc: TStream);
|
|
begin
|
|
LoadFromStreamEx( aSrc, True );
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.LoadFromStreamEx(aSrc: TStream;
|
|
aClear: boolean);
|
|
var
|
|
iCommand : TSynEditorCommand;
|
|
iEvent: TSynMacroEvent;
|
|
cnt, i : Integer;
|
|
begin
|
|
Stop;
|
|
if aClear then
|
|
Clear;
|
|
fEvents := TList.Create;
|
|
aSrc.Read(cnt, sizeof(cnt));
|
|
i := 0;
|
|
fEvents.Capacity := aSrc.Size div SizeOf( TSynEditorCommand );
|
|
while (aSrc.Position < aSrc.Size) and (i < cnt) do
|
|
begin
|
|
aSrc.Read( iCommand, SizeOf(TSynEditorCommand) );
|
|
iEvent := CreateMacroEvent( iCommand );
|
|
iEvent.Initialize( iCommand, #0, nil );
|
|
iEvent.LoadFromStream( aSrc );
|
|
fEvents.Add( iEvent );
|
|
Inc(i);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.OnCommand(Sender: TObject;
|
|
AfterProcessing: boolean; var Handled: boolean;
|
|
var Command: TSynEditorCommand;
|
|
var aChar: {$IFDEF SYN_LAZARUS}TUTF8Char{$ELSE}Char{$ENDIF}; Data,
|
|
HandlerData: pointer);
|
|
var
|
|
iEvent: TSynMacroEvent;
|
|
begin
|
|
if AfterProcessing then
|
|
begin
|
|
if (Sender = fCurrentEditor) and (State = msRecording) and (not Handled) then
|
|
begin
|
|
iEvent := CreateMacroEvent( Command );
|
|
iEvent.Initialize( Command, aChar, Data );
|
|
fEvents.Add( iEvent );
|
|
if SaveMarkerPos and (Command >= ecSetMarker0) and
|
|
(Command <= ecSetMarker9) and (Data = nil) then
|
|
begin
|
|
TSynPositionEvent(iEvent).Position := fCurrentEditor.CaretXY;
|
|
end;
|
|
end;
|
|
end
|
|
else begin
|
|
{not AfterProcessing}
|
|
case State of
|
|
msStopped:
|
|
if Command = RecordCommandID then
|
|
begin
|
|
RecordMacro( TCustomSynEdit( Sender ) );
|
|
Handled := True;
|
|
end
|
|
else if Command = PlaybackCommandID then
|
|
begin
|
|
PlaybackMacro( TCustomSynEdit( Sender ) );
|
|
Handled := True;
|
|
end;
|
|
msPlaying:
|
|
;
|
|
msPaused:
|
|
if Command = PlaybackCommandID then
|
|
begin
|
|
Resume;
|
|
Handled := True;
|
|
end;
|
|
msRecording:
|
|
if Command = PlaybackCommandID then
|
|
begin
|
|
Pause;
|
|
Handled := True;
|
|
end
|
|
else if Command = RecordCommandID then
|
|
begin
|
|
Stop;
|
|
Handled := True;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.Pause;
|
|
begin
|
|
if State <> msRecording then
|
|
Error( sCannotPause );
|
|
fState := msPaused;
|
|
StateChanged;
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.PlaybackMacro(aEditor: TCustomSynEdit);
|
|
var
|
|
cEvent: integer;
|
|
begin
|
|
if State <> msStopped then
|
|
Error( sCannotPlay );
|
|
fState := msPlaying;
|
|
try
|
|
StateChanged;
|
|
for cEvent := 0 to EventCount -1 do
|
|
Events[ cEvent ].Playback( aEditor );
|
|
finally
|
|
fState := msStopped;
|
|
StateChanged;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.RecordMacro(aEditor: TCustomSynEdit);
|
|
begin
|
|
if fState <> msStopped then
|
|
Error( sCannotRecord );
|
|
Clear;
|
|
fEvents := TList.Create;
|
|
fEvents.Capacity := 512;
|
|
fState := msRecording;
|
|
fCurrentEditor := aEditor;
|
|
StateChanged;
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.RemoveEditor(aEditor: TCustomSynEdit);
|
|
begin
|
|
inherited RemoveEditor( aEditor );
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.Resume;
|
|
begin
|
|
if fState <> msPaused then
|
|
Error( sCannotResume );
|
|
fState := msRecording;
|
|
StateChanged;
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.SaveToStream(aDest: TStream);
|
|
var
|
|
cEvent, eCnt : integer;
|
|
begin
|
|
eCnt := EventCount;
|
|
aDest.Write(eCnt, sizeof(eCnt));
|
|
for cEvent := 0 to eCnt -1 do
|
|
Events[ cEvent ].SaveToStream( aDest );
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.SetShortCut(const Index: Integer;
|
|
const Value: TShortCut);
|
|
var
|
|
cEditor: integer;
|
|
begin
|
|
if fShortCuts[TSynMacroCommand(Index)] <> Value then
|
|
begin
|
|
if Assigned(fEditors) then
|
|
if Value <> 0 then
|
|
begin
|
|
for cEditor := 0 to fEditors.Count -1 do
|
|
HookEditor( Editors[cEditor], fCommandIDs[TSynMacroCommand(Index)],
|
|
fShortCuts[TSynMacroCommand(Index)], Value );
|
|
end else
|
|
begin
|
|
for cEditor := 0 to fEditors.Count -1 do
|
|
UnHookEditor( Editors[cEditor], fCommandIDs[TSynMacroCommand(Index)],
|
|
fShortCuts[TSynMacroCommand(Index)] );
|
|
end;
|
|
fShortCuts[TSynMacroCommand(Index)] := Value;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.StateChanged;
|
|
begin
|
|
if Assigned( OnStateChange ) then
|
|
OnStateChange( Self );
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.Stop;
|
|
begin
|
|
if fState = msStopped then
|
|
Exit;
|
|
fState := msStopped;
|
|
fCurrentEditor := nil;
|
|
if fEvents.Count = 0 then
|
|
FreeAndNil( fEvents );
|
|
StateChanged;
|
|
end;
|
|
|
|
function TCustomSynMacroRecorder.GetAsString: string;
|
|
var
|
|
i : integer;
|
|
eStr : string;
|
|
begin
|
|
Result := 'macro ' + MacroName + #13#10 + 'begin' + #13#10;
|
|
if Assigned(fEvents) then
|
|
begin
|
|
for i := 0 to fEvents.Count -1 do
|
|
begin
|
|
eStr := Events[i].AsString;
|
|
if eStr <> '' then
|
|
Result := Result + ' ' + eStr + #13#10;
|
|
end;
|
|
end;
|
|
Result := Result + 'end';
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.SetAsString(const Value: string);
|
|
var
|
|
i, p, Cmd : Integer;
|
|
S : TStrings;
|
|
cmdStr : string;
|
|
iEvent: TSynMacroEvent;
|
|
begin
|
|
Stop;
|
|
Clear;
|
|
fEvents := TList.Create;
|
|
// process file line by line and create events
|
|
S := TStringList.Create;
|
|
try
|
|
S.Text := Value;
|
|
for i := 0 to S.Count - 1 do
|
|
begin
|
|
cmdStr := Trim(S[i]);
|
|
p := Pos(' ', cmdStr);
|
|
if p = 0 then p := Length(cmdStr)+1;
|
|
Cmd := ecNone;
|
|
if IdentToEditorCommand(Copy(cmdStr, 1, p-1), Longint(Cmd)) then // D2 needs type-cast
|
|
begin
|
|
Delete(cmdStr, 1, p);
|
|
iEvent := CreateMacroEvent(TSynEditorCommand(Cmd));
|
|
try
|
|
fEvents.Add(iEvent);
|
|
iEvent.InitEventParameters(cmdStr);
|
|
except
|
|
iEvent.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
S.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.LoadFromFile(aFilename: string);
|
|
var
|
|
F : TFileStream;
|
|
begin
|
|
F := TFileStream.Create(UTF8ToSys(aFilename), fmOpenRead);
|
|
try
|
|
LoadFromStream(F);
|
|
MacroName := ChangeFileExt(ExtractFileName(aFilename), '');
|
|
finally
|
|
F.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomSynMacroRecorder.SaveToFile(aFilename: string);
|
|
var
|
|
F : TFileStream;
|
|
begin
|
|
F := TFileStream.Create(UTF8ToSys(aFilename), fmCreate);
|
|
try
|
|
SaveToStream(F);
|
|
finally
|
|
F.Free;
|
|
end;
|
|
end;
|
|
|
|
{ TSynBasicEvent }
|
|
|
|
function TSynBasicEvent.GetAsString: string;
|
|
begin
|
|
Result := '';
|
|
EditorCommandToIdent(Command, Result);
|
|
if RepeatCount > 1 then
|
|
Result := Result + ' ' + IntToStr(RepeatCount);
|
|
end;
|
|
|
|
procedure TSynBasicEvent.InitEventParameters(aStr: string);
|
|
begin
|
|
// basic events have no parameters but can contain an optional repeat count
|
|
RepeatCount := Byte(StrToIntDef(Trim(aStr), 1));
|
|
end;
|
|
|
|
procedure TSynBasicEvent.Initialize(aCmd: TSynEditorCommand;
|
|
{$IFDEF SYN_LAZARUS}const aChar: TUTF8Char{$ELSE}aChar: Char{$ENDIF};
|
|
aData: Pointer);
|
|
begin
|
|
Command := aCmd;
|
|
{$IFDEF SYN_DEVELOPMENT_CHECKS}
|
|
if (aChar <> #0) or (aData <> nil) then
|
|
raise Exception.Create('TSynBasicEvent cannot handle Char <> #0 or Data <> nil');
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TSynBasicEvent.LoadFromStream(aStream: TStream);
|
|
begin
|
|
aStream.Read( fRepeatCount, SizeOf(fRepeatCount) );
|
|
end;
|
|
|
|
procedure TSynBasicEvent.Playback(aEditor: TCustomSynEdit);
|
|
var
|
|
i : Integer;
|
|
begin
|
|
for i := 1 to RepeatCount do
|
|
aEditor.CommandProcessor( Command, #0, nil );
|
|
end;
|
|
|
|
procedure TSynBasicEvent.SaveToStream(aStream: TStream);
|
|
begin
|
|
aStream.Write( Command, SizeOf(TSynEditorCommand) );
|
|
aStream.Write( RepeatCount, SizeOf(RepeatCount) );
|
|
end;
|
|
|
|
{ TSynCharEvent }
|
|
|
|
function TSynCharEvent.GetAsString: string;
|
|
begin
|
|
Result := '';
|
|
EditorCommandToIdent(ecChar, Result);
|
|
Result := Result + ' ' + Key;
|
|
if RepeatCount > 1 then
|
|
Result := Result + ' ' + IntToStr(RepeatCount);
|
|
end;
|
|
|
|
procedure TSynCharEvent.InitEventParameters(aStr: string);
|
|
begin
|
|
// aStr should be a Key value one character in length
|
|
// with an optional repeat count whitespace separated
|
|
if Length(aStr) >= 1 then
|
|
Key := aStr[1]
|
|
else
|
|
Key := ' ';
|
|
Delete(aStr, 1, 1); // if possible delete the first character
|
|
RepeatCount := Byte(StrToIntDef(Trim(aStr), 1));
|
|
end;
|
|
|
|
procedure TSynCharEvent.Initialize(aCmd: TSynEditorCommand;
|
|
{$IFDEF SYN_LAZARUS}const aChar: TUTF8Char{$ELSE}aChar: Char{$ENDIF};
|
|
aData: Pointer);
|
|
begin
|
|
Key := aChar;
|
|
Assert( aData = nil );
|
|
end;
|
|
|
|
procedure TSynCharEvent.LoadFromStream(aStream: TStream);
|
|
begin
|
|
aStream.Read( fKey, SizeOf(Key) );
|
|
aStream.Read( fRepeatCount, SizeOf(fRepeatCount) );
|
|
end;
|
|
|
|
procedure TSynCharEvent.Playback(aEditor: TCustomSynEdit);
|
|
var
|
|
i : Integer;
|
|
begin
|
|
for i := 1 to RepeatCount do
|
|
aEditor.CommandProcessor( ecChar, Key, nil );
|
|
end;
|
|
|
|
procedure TSynCharEvent.SaveToStream(aStream: TStream);
|
|
const
|
|
iCharCommand: TSynEditorCommand = ecChar;
|
|
begin
|
|
aStream.Write( iCharCommand, SizeOf(TSynEditorCommand) );
|
|
aStream.Write( Key, SizeOf(Key) );
|
|
aStream.Write( RepeatCount, SizeOf(RepeatCount) );
|
|
end;
|
|
|
|
{ TSynPositionEvent }
|
|
|
|
function TSynPositionEvent.GetAsString: string;
|
|
begin
|
|
Result := inherited GetAsString;
|
|
// add position data here
|
|
Result := Result + Format(' (%d, %d)', [Position.x, Position.y]);
|
|
if RepeatCount > 1 then
|
|
Result := Result + ' ' + IntToStr(RepeatCount);
|
|
end;
|
|
|
|
procedure TSynPositionEvent.InitEventParameters(aStr: string);
|
|
var
|
|
i, o, c, x, y : Integer;
|
|
valStr : string;
|
|
begin
|
|
inherited;
|
|
// aStr should be (x, y) with optional repeat count whitespace separated
|
|
aStr := Trim(aStr);
|
|
i := Pos(',', aStr);
|
|
o := Pos('(', aStr);
|
|
c := Pos(')', aStr);
|
|
if (not ((i = 0) or (o = 0) or (c = 0))) and
|
|
((i > o) and (i < c)) then
|
|
begin
|
|
valStr := Copy(aStr, o+1, i-o-1);
|
|
x := StrToIntDef(valStr, 1);
|
|
Delete(aStr, 1, i);
|
|
aStr := Trim(aStr);
|
|
c := Pos(')', aStr);
|
|
valStr := Copy(aStr, 1, c-1);
|
|
y := StrToIntDef(valStr, 1);
|
|
Position := Point(x, y);
|
|
Delete(aStr, 1, c);
|
|
aStr := Trim(aStr);
|
|
RepeatCount := Byte(StrToIntDef(aStr, 1));
|
|
end;
|
|
end;
|
|
|
|
procedure TSynPositionEvent.Initialize(aCmd: TSynEditorCommand;
|
|
{$IFDEF SYN_LAZARUS}const aChar: TUTF8Char{$ELSE}aChar: Char{$ENDIF};
|
|
aData: Pointer);
|
|
begin
|
|
inherited;
|
|
if aData <> nil then
|
|
Position := PPoint( aData )^
|
|
else
|
|
Position := Point( 0, 0 );
|
|
end;
|
|
|
|
procedure TSynPositionEvent.LoadFromStream(aStream: TStream);
|
|
begin
|
|
aStream.Read( fPosition, SizeOf(Position) );
|
|
end;
|
|
|
|
procedure TSynPositionEvent.Playback(aEditor: TCustomSynEdit);
|
|
begin
|
|
if (Position.x <> 0) or (Position.y <> 0) then
|
|
aEditor.CommandProcessor( Command, #0, @Position )
|
|
else
|
|
aEditor.CommandProcessor( Command, #0, nil );
|
|
end;
|
|
|
|
procedure TSynPositionEvent.SaveToStream(aStream: TStream);
|
|
begin
|
|
inherited;
|
|
aStream.Write( Position, SizeOf(Position) );
|
|
end;
|
|
|
|
{ TSynStringEvent }
|
|
|
|
{$IFNDEF SYN_COMPILER_3_UP}
|
|
function QuotedStr(const S: string; QuoteChar: Char): string;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result := S;
|
|
for i := Length(Result) downto 1 do
|
|
if Result[i] = QuoteChar then
|
|
Insert(QuoteChar, Result, i);
|
|
Result := QuoteChar + Result + QuoteChar;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
function TSynStringEvent.GetAsString: string;
|
|
begin
|
|
Result := '';
|
|
EditorCommandToIdent(ecString, Result);
|
|
{$IFDEF SYN_COMPILER_3_UP}
|
|
Result := Result + ' ' + AnsiQuotedStr(Value, #39);
|
|
{$ELSE}
|
|
Result := Result + ' ' + QuotedStr(Value, #39);
|
|
{$ENDIF}
|
|
if RepeatCount > 1 then
|
|
Result := Result + ' ' + IntToStr(RepeatCount);
|
|
end;
|
|
|
|
procedure TSynStringEvent.InitEventParameters(aStr: string);
|
|
var
|
|
o, c : Integer;
|
|
valStr : string;
|
|
begin
|
|
// aStr = 'test' with optional whitespace separated repeat count
|
|
o := Pos('''', aStr);
|
|
c := LastDelimiter('''', aStr);
|
|
valStr := Copy(aStr, o+1, c-o-1);
|
|
Value := StringReplace(valStr, '''''', '''', [rfReplaceAll]);
|
|
Delete(aStr, 1, c);
|
|
RepeatCount := Byte(StrToIntDef(Trim(aStr), 1));
|
|
end;
|
|
|
|
procedure TSynStringEvent.Initialize(aCmd: TSynEditorCommand;
|
|
{$IFDEF SYN_LAZARUS}const aChar: TUTF8Char{$ELSE}aChar: Char{$ENDIF};
|
|
aData: Pointer);
|
|
begin
|
|
Value := String(aData);
|
|
end;
|
|
|
|
procedure TSynStringEvent.LoadFromStream(aStream: TStream);
|
|
var
|
|
l : Integer;
|
|
Buff : PChar;
|
|
begin
|
|
aStream.Read(l, SizeOf(l));
|
|
GetMem(Buff, l);
|
|
try
|
|
{$IFDEF FPC}
|
|
FillChar(Buff^,l,0);
|
|
{$ELSE}
|
|
{$IFNDEF SYN_CLX} //js 07-04-2002 changed from IFDEF WINDOWS
|
|
FillMemory(Buff, l, 0);
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
aStream.Read(Buff^, l);
|
|
fString := Buff;
|
|
finally
|
|
FreeMem(Buff);
|
|
end;
|
|
aStream.Read( fRepeatCount, SizeOf(fRepeatCount) );
|
|
end;
|
|
|
|
procedure TSynStringEvent.Playback(aEditor: TCustomSynEdit);
|
|
var
|
|
i, j : Integer;
|
|
begin
|
|
for j := 1 to RepeatCount do
|
|
begin
|
|
// aEditor.CommandProcessor( ecString, #0, Pointer(Value) );
|
|
// SynEdit doesn't actually support the ecString command so we convert
|
|
// it into ecChar commands
|
|
for i := 1 to Length(Value) do
|
|
aEditor.CommandProcessor(ecChar, Value[i], nil);
|
|
end;
|
|
end;
|
|
|
|
procedure TSynStringEvent.SaveToStream(aStream: TStream);
|
|
const
|
|
StrCommand: TSynEditorCommand = ecString;
|
|
var
|
|
l : Integer;
|
|
Buff : PChar;
|
|
begin
|
|
aStream.Write(StrCommand, SizeOf(StrCommand));
|
|
l := Length(Value) + 1;
|
|
aStream.Write(l, sizeof(l));
|
|
GetMem(Buff, l);
|
|
try
|
|
{$IFDEF FPC}
|
|
FillChar(Buff^,l,0);
|
|
{$ELSE}
|
|
{$IFNDEF SYN_CLX} //js 07-04-2002 changed from IFDEF WINDOWS
|
|
FillMemory(Buff, l, 0);
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
StrPCopy(Buff, Value);
|
|
aStream.Write(Buff^, l);
|
|
finally
|
|
FreeMem(Buff);
|
|
end;
|
|
aStream.Write( RepeatCount, SizeOf(RepeatCount) );
|
|
end;
|
|
|
|
|
|
|
|
{ TSynMacroEvent }
|
|
|
|
constructor TSynMacroEvent.Create;
|
|
begin
|
|
inherited Create;
|
|
fRepeatCount := 1;
|
|
end;
|
|
|
|
end.
|