From 5473bf6a6f00a0161f47af3e509c230d495ec0c5 Mon Sep 17 00:00:00 2001 From: mattias Date: Sat, 17 Aug 2002 23:41:12 +0000 Subject: [PATCH] add synmemo.pas syneditplugins.pas synmacrorecorder.pas git-svn-id: trunk@2282 - --- .gitattributes | 3 + components/synedit/synedit.pp | 35 +- components/synedit/syneditkeycmds.pp | 13 + components/synedit/syneditplugins.pas | 572 +++++++++++++ components/synedit/synmacrorecorder.pas | 1028 +++++++++++++++++++++++ components/synedit/synmemo.pas | 218 +++++ 6 files changed, 1853 insertions(+), 16 deletions(-) create mode 100644 components/synedit/syneditplugins.pas create mode 100644 components/synedit/synmacrorecorder.pas create mode 100644 components/synedit/synmemo.pas diff --git a/.gitattributes b/.gitattributes index 9b977375d1..506b5f184a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -46,6 +46,7 @@ components/synedit/synedithighlighter.pp svneol=native#text/pascal components/synedit/syneditkeycmds.pp svneol=native#text/pascal components/synedit/syneditmiscclasses.pp svneol=native#text/pascal components/synedit/syneditmiscprocs.pp svneol=native#text/pascal +components/synedit/syneditplugins.pas svneol=native#text/pascal components/synedit/syneditsearch.pp svneol=native#text/pascal components/synedit/syneditstrconst.pp svneol=native#text/pascal components/synedit/synedittextbuffer.pp svneol=native#text/pascal @@ -56,6 +57,8 @@ components/synedit/synhighlighterlfm.pas svneol=native#text/pascal components/synedit/synhighlighterpas.pp svneol=native#text/pascal components/synedit/synhighlighterperl.pas svneol=native#text/pascal components/synedit/synhighlighterxml.pas svneol=native#text/pascal +components/synedit/synmacrorecorder.pas svneol=native#text/pascal +components/synedit/synmemo.pas svneol=native#text/pascal components/synedit/syntextdrawer.pp svneol=native#text/pascal debugger/breakpointsdlg.lrs svneol=native#text/pascal debugger/breakpointsdlg.pp svneol=native#text/pascal diff --git a/components/synedit/synedit.pp b/components/synedit/synedit.pp index 3da9297601..aa0be34651 100644 --- a/components/synedit/synedit.pp +++ b/components/synedit/synedit.pp @@ -597,6 +597,7 @@ type {$ENDIF} function GetWordAtRowCol(XY: TPoint): string; procedure GotoBookMark(BookMark: Integer); + function IdentChars: TSynIdentChars; procedure InvalidateGutter; procedure InvalidateLine(Line: integer); function IsBookmark(BookMark: integer): boolean; @@ -5049,6 +5050,14 @@ begin end; end; +function TCustomSynEdit.IdentChars: TSynIdentChars; +begin + if Highlighter <> nil then + Result := Highlighter.IdentChars + else + Result := [#33..#255]; +end; + procedure TCustomSynEdit.SetBookMark(BookMark: Integer; X: Integer; Y: Integer); var i: Integer; @@ -6186,33 +6195,30 @@ function TCustomSynEdit.NextWordPos: TPoint; var CX, CY, LineLen: integer; Line: string; - IdentChars, WhiteChars: TSynIdentChars; + CurIdentChars, WhiteChars: TSynIdentChars; begin CX := CaretX; CY := CaretY; // valid line? if (CY >= 1) and (CY <= Lines.Count) then begin Line := Lines[CY - 1]; - if Assigned(Highlighter) then - IdentChars := Highlighter.IdentChars - else - IdentChars := [#33..#255]; - WhiteChars := [#1..#255] - IdentChars; + CurIdentChars:=IdentChars; + WhiteChars := [#1..#255] - CurIdentChars; LineLen := Length(Line); if CX >= LineLen then begin // find first IdentChar in the next line if CY < Lines.Count then begin Line := Lines[CY]; Inc(CY); - CX := Max(1, StrScanForCharInSet(Line, 1, IdentChars)); + CX := Max(1, StrScanForCharInSet(Line, 1, CurIdentChars)); end; end else begin // find first "whitespace" if next char is an IdentChar - if Line[CX] in IdentChars then + if Line[CX] in CurIdentChars then CX := StrScanForCharInSet(Line, CX, WhiteChars); // if "whitespace" found find the first IdentChar behind if CX > 0 then - CX := StrScanForCharInSet(Line, CX, IdentChars); + CX := StrScanForCharInSet(Line, CX, CurIdentChars); // if one of those failed just position at the end of the line if CX = 0 then CX := LineLen + 1; @@ -6225,7 +6231,7 @@ function TCustomSynEdit.PrevWordPos: TPoint; var CX, CY: integer; Line: string; - IdentChars, WhiteChars: TSynIdentChars; + CurIdentChars, WhiteChars: TSynIdentChars; begin CX := CaretX; CY := CaretY; @@ -6233,11 +6239,8 @@ begin if (CY >= 1) and (CY <= Lines.Count) then begin Line := Lines[CY - 1]; CX := Min(CX, Length(Line) + 1); - if Assigned(Highlighter) then - IdentChars := Highlighter.IdentChars - else - IdentChars := [#33..#255]; - WhiteChars := [#1..#255] - IdentChars; + CurIdentChars:=IdentChars; + WhiteChars := [#1..#255] - CurIdentChars; if CX <= 1 then begin // find last IdentChar in the previous line if CY > 1 then begin @@ -6248,7 +6251,7 @@ begin end else begin // if previous char is a "whitespace" search for the last IdentChar if Line[CX - 1] in WhiteChars then - CX := StrRScanForCharInSet(Line, CX - 1, IdentChars); + CX := StrRScanForCharInSet(Line, CX - 1, CurIdentChars); if CX > 0 then // search for the first IdentChar of this "word" CX := StrRScanForCharInSet(Line, CX - 1, WhiteChars) + 1 diff --git a/components/synedit/syneditkeycmds.pp b/components/synedit/syneditkeycmds.pp index 3d9d8db0ba..0eda20ba27 100644 --- a/components/synedit/syneditkeycmds.pp +++ b/components/synedit/syneditkeycmds.pp @@ -179,8 +179,21 @@ const ecTab = 612; // Tab key ecShiftTab = 613; // Shift+Tab key + ecUpperCase = 620; // apply to the current or previous word + ecLowerCase = 621; + ecToggleCase = 622; + ecTitleCase = 623; + ecUpperCaseBlock = 625; // apply to current selection, or current char if no selection + ecLowerCaseBlock = 626; + ecToggleCaseBlock = 627; + + ecString = 630; //Insert a whole string + ecAutoCompletion = 650; + ecGotFocus = 700; + ecLostFocus = 701; + ecUserFirst = 1001; // Start of user-defined commands type diff --git a/components/synedit/syneditplugins.pas b/components/synedit/syneditplugins.pas new file mode 100644 index 0000000000..6ac7787bd5 --- /dev/null +++ b/components/synedit/syneditplugins.pas @@ -0,0 +1,572 @@ +{------------------------------------------------------------------------------- +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: SynEditPlugins.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 SynEditPlugins; + +{$I synedit.inc} + +interface + +uses + Classes, +{$IFDEF SYN_CLX} + Qt, + Types, + QMenus, +{$ELSE} + {$IFDEF SYN_LAZARUS} + LCLLinux, + {$ELSE} + Windows, + {$ENDIF} + Menus, +{$ENDIF} + SynEdit, + SynEditKeyCmds; + +type + TAbstractSynPlugin = class(TComponent) + private + procedure SetEditor(const Value: TCustomSynEdit); + function GetEditors(aIndex: integer): TCustomSynEdit; + function GetEditor: TCustomSynEdit; + function GetEditorCount: integer; + protected + fEditors: TList; + procedure Notification(aComponent: TComponent; + aOperation: TOperation); override; + procedure DoAddEditor(aEditor: TCustomSynEdit); virtual; + procedure DoRemoveEditor(aEditor: TCustomSynEdit); virtual; + function AddEditor(aEditor: TCustomSynEdit): integer; + function RemoveEditor(aEditor: TCustomSynEdit): integer; + public + destructor Destroy; override; + property Editors[aIndex: integer]: TCustomSynEdit read GetEditors; + property EditorCount: integer read GetEditorCount; + published + property Editor: TCustomSynEdit read GetEditor write SetEditor; + end; + + TAbstractSynHookerPlugin = class(TAbstractSynPlugin) + protected + procedure HookEditor(aEditor: TCustomSynEdit; aCommandID: TSynEditorCommand; + aOldShortCut, aNewShortCut: TShortCut); + procedure UnHookEditor(aEditor: TCustomSynEdit; + aCommandID: TSynEditorCommand; aShortCut: TShortCut); + procedure OnCommand(Sender: TObject; AfterProcessing: boolean; + var Handled: boolean; var Command: TSynEditorCommand; var aChar: char; + Data: pointer; HandlerData: pointer); virtual; abstract; + end; + + TPluginState = (psNone, psExecuting, psAccepting, psCancelling); + + TAbstractSynSingleHookPlugin = class(TAbstractSynHookerPlugin) + private + fCommandID: TSynEditorCommand; + function IsShortCutStored: Boolean; + procedure SetShortCut(const Value: TShortCut); + protected + fState: TPluginState; + fCurrentEditor: TCustomSynEdit; + fShortCut: TShortCut; + class function DefaultShortCut: TShortCut; virtual; + procedure DoAddEditor(aEditor: TCustomSynEdit); override; + procedure DoRemoveEditor(aEditor: TCustomSynEdit); override; + {} + procedure DoExecute; virtual; abstract; + procedure DoAccept; virtual; abstract; + procedure DoCancel; virtual; abstract; + public + constructor Create(aOwner: TComponent); override; + destructor Destroy; override; + property CommandID: TSynEditorCommand read fCommandID; + {} + property CurrentEditor: TCustomSynEdit read fCurrentEditor; + function Executing: boolean; + procedure Execute(aEditor: TCustomSynEdit); + procedure Accept; + procedure Cancel; + published + property ShortCut: TShortCut read fShortCut write SetShortCut + stored IsShortCutStored; + end; + + { use TAbstractSynCompletion for non-visual completion } + + TAbstractSynCompletion = class(TAbstractSynSingleHookPlugin) + protected + fCurrentString: String; + protected + procedure SetCurrentString(const Value: String); virtual; + procedure OnCommand(Sender: TObject; AfterProcessing: boolean; + var Handled: boolean; var Command: TSynEditorCommand; var aChar: char; + Data: pointer; HandlerData: pointer); override; + procedure DoExecute; override; + procedure DoAccept; override; + procedure DoCancel; override; + function GetCurrentEditorString: String; virtual; + public + procedure AddEditor(aEditor: TCustomSynEdit); + property CurrentString: String read fCurrentString write SetCurrentString; + end; + +function NewPluginCommand: TSynEditorCommand; +procedure ReleasePluginCommand(aCmd: TSynEditorCommand); + +implementation + +uses + SynEditTypes, + SysUtils, +{$IFDEF SYN_CLX} + QForms, +{$ELSE} + Forms, +{$ENDIF} + SynEditStrConst, + SynEditMiscProcs; + +const + ecPluginBase = 64000; + +var + gCurrentCommand: integer; + +function NewPluginCommand: TSynEditorCommand; +begin + Result := gCurrentCommand; + Inc( gCurrentCommand ); +end; + +procedure ReleasePluginCommand(aCmd: TSynEditorCommand); +begin + if aCmd = Pred( gCurrentCommand ) then + gCurrentCommand := aCmd; +end; + +{ TAbstractSynPlugin } + +function TAbstractSynPlugin.AddEditor(aEditor: TCustomSynEdit): integer; +begin + if fEditors = nil then + begin + fEditors := TList.Create; + end + else + if fEditors.IndexOf( aEditor ) >= 0 then + begin + Result := -1; + Exit; + end; + aEditor.FreeNotification( Self ); + Result := fEditors.Add( aEditor ); + DoAddEditor( aEditor ); +end; + +destructor TAbstractSynPlugin.Destroy; +begin + { RemoveEditor will free fEditors when it reaches count = 0} + while Assigned( fEditors ) do + RemoveEditor( Editors[0] ); + inherited; +end; + +procedure TAbstractSynPlugin.Notification(aComponent: TComponent; + aOperation: TOperation); +begin + inherited; + if aOperation = opRemove then + begin + if (aComponent = Editor) or (aComponent is TCustomSynEdit) then + RemoveEditor( TCustomSynEdit(aComponent) ); + end; +end; + +procedure TAbstractSynPlugin.DoAddEditor(aEditor: TCustomSynEdit); +begin + +end; + +procedure TAbstractSynPlugin.DoRemoveEditor(aEditor: TCustomSynEdit); +begin + +end; + +function TAbstractSynPlugin.RemoveEditor(aEditor: TCustomSynEdit): integer; +begin + if fEditors = nil then + begin + Result := -1; + Exit; + end; + Result := fEditors.Remove( aEditor ); + //aEditor.RemoveFreeNotification( Self ); + if fEditors.Count = 0 then + begin + fEditors.Free; + fEditors := nil; + end; + if Result >= 0 then + DoRemoveEditor( aEditor ); +end; + +procedure TAbstractSynPlugin.SetEditor(const Value: TCustomSynEdit); +var + iEditor: TCustomSynEdit; +begin + iEditor := Editor; + if iEditor <> Value then + try + if (iEditor <> nil) and (fEditors.Count = 1) then + RemoveEditor( iEditor ); + if Value <> nil then + AddEditor( Value ); + except + if [csDesigning] * ComponentState = [csDesigning] then + Application.HandleException(Self) + else + raise; + end; +end; + +function TAbstractSynPlugin.GetEditors(aIndex: integer): TCustomSynEdit; +begin + Result := TCustomSynEdit(fEditors[aIndex]); +end; + +function TAbstractSynPlugin.GetEditor: TCustomSynEdit; +begin + if fEditors <> nil then + Result := TCustomSynEdit(fEditors[0]) + else + Result := nil; +end; + +function TAbstractSynPlugin.GetEditorCount: integer; +begin + if fEditors <> nil then + Result := fEditors.Count + else + Result := 0; +end; + +{ TAbstractSynHookerPlugin } + +procedure TAbstractSynHookerPlugin.HookEditor(aEditor: TCustomSynEdit; + aCommandID: TSynEditorCommand; aOldShortCut, aNewShortCut: TShortCut); +var + iIndex: integer; + iKeystroke: TSynEditKeyStroke; +begin + Assert( aNewShortCut <> 0 ); + { shortcurts aren't created while in design-time } + if [csDesigning] * ComponentState = [csDesigning] then + begin + if TSynEdit(aEditor).Keystrokes.FindShortcut( aNewShortCut ) >= 0 then + raise ESynKeyError.Create(SYNS_EDuplicateShortCut) + else + Exit; + end; + { tries to update old Keystroke } + if aOldShortCut <> 0 then + begin + iIndex := TSynEdit(aEditor).Keystrokes.FindShortcut( aOldShortCut ); + if (iIndex >= 0) then + begin + iKeystroke := TSynEdit(aEditor).Keystrokes[iIndex]; + if iKeystroke.Command = aCommandID then + begin + iKeystroke.ShortCut := aNewShortCut; + Exit; + end; + end; + end; + { new Keystroke } + iKeystroke := TSynEdit(aEditor).Keystrokes.Add; + try + iKeystroke.ShortCut := aNewShortCut; + except + iKeystroke.Free; + raise; + end; + iKeystroke.Command := aCommandID; + aEditor.RegisterCommandHandler( {$IFDEF FPC}@{$ENDIF}OnCommand, Self ); +end; + +procedure TAbstractSynHookerPlugin.UnHookEditor(aEditor: TCustomSynEdit; + aCommandID: TSynEditorCommand; aShortCut: TShortCut); +var + iIndex: integer; +begin + aEditor.UnregisterCommandHandler( {$IFDEF FPC}@{$ENDIF}OnCommand ); + iIndex := TSynEdit(aEditor).Keystrokes.FindShortcut( aShortCut ); + if (iIndex >= 0) and + (TSynEdit(aEditor).Keystrokes[iIndex].Command = aCommandID) then + TSynEdit(aEditor).Keystrokes[iIndex].Free; +end; + +{ TAbstractSynHookerPlugin } + +procedure TAbstractSynSingleHookPlugin.Accept; +begin + fState := psAccepting; + try + DoAccept; + finally + fCurrentEditor := nil; + fState := psNone; + end; +end; + +procedure TAbstractSynSingleHookPlugin.Cancel; +begin + fState := psCancelling; + try + DoCancel; + finally + fCurrentEditor := nil; + fState := psNone; + end; +end; + +constructor TAbstractSynSingleHookPlugin.Create(aOwner: TComponent); +begin + inherited; + fCommandID := NewPluginCommand; + fShortCut := DefaultShortCut; +end; + +class function TAbstractSynSingleHookPlugin.DefaultShortCut: TShortCut; +begin + Result := 0; +end; + +destructor TAbstractSynSingleHookPlugin.Destroy; +begin + if Executing then + Cancel; + ReleasePluginCommand( CommandID ); + inherited; +end; + +procedure TAbstractSynSingleHookPlugin.DoAddEditor( + aEditor: TCustomSynEdit); +begin + if ShortCut <> 0 then + HookEditor( aEditor, CommandID, 0, ShortCut ); +end; + +procedure TAbstractSynSingleHookPlugin.Execute(aEditor: TCustomSynEdit); +begin + if Executing then + Cancel; + Assert( fCurrentEditor = nil ); + fCurrentEditor := aEditor; + Assert( fState = psNone ); + fState := psExecuting; + try + DoExecute; + except + Cancel; + raise; + end; +end; + +function TAbstractSynSingleHookPlugin.Executing: boolean; +begin + Result := fState = psExecuting; +end; + +function TAbstractSynSingleHookPlugin.IsShortCutStored: Boolean; +begin + Result := fShortCut <> DefaultShortCut; +end; + +procedure TAbstractSynSingleHookPlugin.DoRemoveEditor(aEditor: TCustomSynEdit); +begin + if ShortCut <> 0 then + UnHookEditor( aEditor, CommandID, ShortCut ); + if Executing and (CurrentEditor = aEditor) then + Cancel; +end; + +procedure TAbstractSynSingleHookPlugin.SetShortCut(const Value: TShortCut); +var + cEditor: integer; +begin + if fShortCut <> Value then + begin + if Assigned(fEditors) then + if Value <> 0 then + begin + for cEditor := 0 to fEditors.Count -1 do + HookEditor( Editors[cEditor], CommandID, fShortCut, Value ); + end + else + begin + for cEditor := 0 to fEditors.Count -1 do + UnHookEditor( Editors[cEditor], CommandID, fShortCut ); + end; + fShortCut := Value; + end; +end; + +{ TAbstractSynCompletion } + +function TAbstractSynCompletion.GetCurrentEditorString: String; +var + iString: String; + cCol: integer; + iIdentChars: TSynIdentChars; +begin + iString := CurrentEditor.LineText; + if (CurrentEditor.CaretX > 1) and + (CurrentEditor.CaretX -1 <= Length(iString)) then + begin + iIdentChars := CurrentEditor.IdentChars; + for cCol := CurrentEditor.CaretX -1 downto 1 do + if not (iString[cCol] in iIdentChars) then + break; + Result := Copy( iString, cCol +1, CurrentEditor.CaretX - cCol -1); + end; +end; + +procedure TAbstractSynCompletion.DoAccept; +begin + fCurrentString := ''; +end; + +procedure TAbstractSynCompletion.DoCancel; +begin + fCurrentString := ''; +end; + +procedure TAbstractSynCompletion.DoExecute; +begin + CurrentString := GetCurrentEditorString; +end; + +procedure TAbstractSynCompletion.OnCommand(Sender: TObject; + AfterProcessing: boolean; var Handled: boolean; + var Command: TSynEditorCommand; var aChar: char; Data, + HandlerData: pointer); +var + iString: String; +begin + if not Executing then + begin + if (Command = CommandID) then + begin + Execute( Sender as TCustomSynEdit ); + Handled := True; + end; + end + else { Executing } + if Sender = CurrentEditor then + begin + if not AfterProcessing then + begin + case Command of + ecChar: + if aChar = #27 then + begin + Cancel; + Handled := True; + end + else + begin + if not(aChar in CurrentEditor.IdentChars) then + Accept; + {don't handle the char} + end; + ecLineBreak: + begin + Accept; + Handled := True; + end; + ecLeft, ecSelLeft: + if CurrentString = '' then + Handled := True; + ecDeleteLastChar: + if CurrentString = '' then + Handled := True; + ecTab: + Accept; + ecDeleteChar, + ecRight, ecSelRight, + ecLostFocus, ecGotFocus: + ; {processed on AfterProcessing} + else + Cancel; + end; + end + else { AfterProcessing } + case Command of + ecLostFocus, ecGotFocus, + ecDeleteChar: + ; + ecDeleteLastChar, + ecLeft, ecSelLeft, + ecChar: + CurrentString := GetCurrentEditorString; + ecRight, ecSelRight: begin + iString := GetCurrentEditorString; + if iString = '' then + Cancel + else + CurrentString := iString; + end; + else + if CurrentString <> GetCurrentEditorString then + Cancel; + end; + end; {endif Sender = CurrentEditor} +end; + +procedure TAbstractSynCompletion.SetCurrentString(const Value: String); +begin + fCurrentString := Value; +end; + +procedure TAbstractSynCompletion.AddEditor(aEditor: TCustomSynEdit); +begin + inherited AddEditor(aEditor); +end; + +initialization + gCurrentCommand := ecPluginBase; + +end. diff --git a/components/synedit/synmacrorecorder.pas b/components/synedit/synmacrorecorder.pas new file mode 100644 index 0000000000..3e399a6daa --- /dev/null +++ b/components/synedit/synmacrorecorder.pas @@ -0,0 +1,1028 @@ +{------------------------------------------------------------------------------- +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} + LCLLinux, LCLType, + {$ELSE} + Windows, + {$ENDIF} + Messages, + 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; aChar: Char; 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; aChar: Char; 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: char; + function GetAsString : string; override; + procedure InitEventParameters(aStr : string); override; + public + procedure Initialize(aCmd: TSynEditorCommand; aChar: Char; aData: Pointer); + override; + procedure LoadFromStream(aStream: TStream); override; + procedure SaveToStream(aStream: TStream); override; + procedure Playback(aEditor: TCustomSynEdit); override; + public + property Key: char read fKey write fKey; + end; + + TSynStringEvent = class(TSynMacroEvent) + protected + fString: string; + function GetAsString : string; override; + procedure InitEventParameters(aStr : string); override; + public + procedure Initialize(aCmd: TSynEditorCommand; aChar: Char; 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; aChar: Char; 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; aChar: Char; 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: char; + 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 + SynEditMiscProcs, + SynEditTypes, +{$IFDEF SYN_CLX} + QForms, +{$ELSE} + Forms, +{$IFDEF SYN_COMPILER_6_UP} + RTLConsts, +{$ENDIF} +{$ENDIF} + SysUtils; + +{ TSynDataEvent } + +procedure TSynDataEvent.Initialize(aCmd: TSynEditorCommand; aChar: Char; + 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: char; 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(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(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(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 := StrToIntDef(Trim(aStr), 1); +end; + +procedure TSynBasicEvent.Initialize(aCmd: TSynEditorCommand; aChar: Char; + 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 := StrToIntDef(Trim(aStr), 1); +end; + +procedure TSynCharEvent.Initialize(aCmd: TSynEditorCommand; aChar: Char; + 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 := StrToIntDef(aStr, 1); + end; +end; + +procedure TSynPositionEvent.Initialize(aCmd: TSynEditorCommand; + aChar: Char; 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 := StrToIntDef(Trim(aStr), 1); +end; + +procedure TSynStringEvent.Initialize(aCmd: TSynEditorCommand; aChar: Char; + 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. diff --git a/components/synedit/synmemo.pas b/components/synedit/synmemo.pas new file mode 100644 index 0000000000..802fff62b7 --- /dev/null +++ b/components/synedit/synmemo.pas @@ -0,0 +1,218 @@ +{------------------------------------------------------------------------------- +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: SynMemo.pas, released 2000-04-07. +The Original Code is based on mwCustomEdit.pas by Martin Waldenburg, part of +the mwEdit component suite. +Portions created by Martin Waldenburg are Copyright (C) 1998 Martin Waldenburg. +All Rights Reserved. + +Contributors to the SynEdit and mwEdit projects 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: + - a handler for the different EM_XXX messages has to be implemented to make + this more compatible with TMemo +-------------------------------------------------------------------------------} + +unit SynMemo; + +{$I synedit.inc} + +interface + +uses + Classes, +{$IFDEF SYN_CLX} + Qt, + Types, +{$ELSE} + {$IFDEF SYN_LAZARUS} + LCLLinux, + {$ELSE} + Windows, + {$ENDIF} +{$ENDIF} + SynEdit; + +//SelStart and SelEnd are now in TCustomSynEdit //DDH Addition + +type + TCustomSynMemo = class(TCustomSynEdit) + public + function CharIndexToRowCol(Index: integer): TPoint; //as 2000-11-09 + function RowColToCharIndex(RowCol: TPoint): integer; //as 2000-11-09 + end; + + TSynMemo = class(TCustomSynMemo) +{begin} //mh 2000-09-23 + public + // TCustomSynMemo properties +{end} //mh 2000-09-23 + published + // inherited properties +(* property Align; +{$IFDEF SYN_COMPILER_4_UP} + property Anchors; + property Constraints; +{$ENDIF} + property Color; + {$IFNDEF SYN_CLX} + property Ctl3D; + {$ENDIF} + property Enabled; + property Font; + property Height; + property Name; + property ParentColor; + {$IFNDEF SYN_CLX} + property ParentCtl3D; + {$ENDIF} + property ParentFont; + property ParentShowHint; + property PopupMenu; + property ShowHint; + property TabOrder; + property TabStop default True; + property Tag; + property Visible; + property Width; + // inherited events + property OnClick; + property OnDblClick; + property OnDragDrop; + property OnDragOver; +{$IFDEF SYN_COMPILER_4_UP} +{$IFNDEF SYN_CLX} +{$IFNDEF SYN_LAZARUS} + property OnEndDock; +{$ENDIF} +{$ENDIF} +{$ENDIF} + property OnEndDrag; + property OnEnter; + property OnExit; + property OnKeyDown; + property OnKeyPress; + property OnKeyUp; + property OnMouseDown; + property OnMouseMove; + property OnMouseUp; +{$IFDEF SYN_COMPILER_4_UP} +{$IFNDEF SYN_CLX} +{$IFNDEF SYN_LAZARUS} + property OnStartDock; +{$ENDIF} +{$ENDIF} +{$ENDIF} + property OnStartDrag; + // TCustomSynEdit properties + property BookMarkOptions; + property BorderStyle; + property ExtraLineSpacing; + property Gutter; + property HideSelection; + property Highlighter; + property InsertCaret; + property InsertMode; + property Keystrokes; + property Lines; + property MaxLeftChar; + property MaxUndo; + property Options; + property OverwriteCaret; + property ReadOnly; + property RightEdge; + property RightEdgeColor; + {$IFNDEF SYN_LAZARUS} + property SearchEngine; + {$ENDIF} + property ScrollBars; + property SelectedColor; + property SelectionMode; + property TabWidth; + property WantTabs; + // TCustomSynEdit events + property OnChange; + property OnClearBookmark; // djlp 2000-08-29 + property OnCommandProcessed; + property OnDropFiles; + property OnGutterClick; + {$IFNDEF SYN_LAZARUS} + property OnGutterGetText; + property OnGutterPaint; + {$ENDIF} + property OnPaint; + property OnPlaceBookmark; + property OnProcessCommand; + property OnProcessUserCommand; + property OnReplaceText; + property OnSpecialLineColors; + property OnStatusChange; + {$IFNDEF SYN_LAZARUS} + property OnPaintTransient; + {$ENDIF} *) + end; + +implementation + +uses + SynEditStrConst, SynEditMiscProcs; + +{ TCustomSynMemo } + +function TCustomSynMemo.CharIndexToRowCol(Index: integer): TPoint; +var + x, y, Chars: integer; +begin + x := 0; + y := 0; + Chars := 0; + while y < Lines.Count do begin + x := Length(Lines[y]); + if Chars + x + 2 > Index then begin + x := Index - Chars; + break; + end; + Inc(Chars, x + 2); + x := 0; + Inc(y); + end; + Result := Point(x + 1, y + 1); +end; + +function TCustomSynMemo.RowColToCharIndex(RowCol: TPoint): integer; +var + i: integer; +begin + Result := 0; + RowCol.y := Min(Lines.Count, RowCol.y) - 1; + for i := 0 to RowCol.y - 1 do + Result := Result + Length(Lines[i]) + 2; + Result := Result + RowCol.x; +end; + +end. +