lazarus/components/synedit/synplugintemplateedit.pp

520 lines
17 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.
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.
-------------------------------------------------------------------------------}
unit SynPluginTemplateEdit;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Graphics, LCLType,
SynPluginSyncronizedEditBase, SynEditKeyCmds, SynEdit, SynEditMiscProcs;
type
{ TSynEditTemplateEditKeyStrokes }
TSynEditTemplateEditKeyStrokes = class(TSynEditKeyStrokes)
public
procedure ResetDefaults; override;
end;
{ TSynEditTemplateEditKeyStrokesOffCell }
TSynEditTemplateEditKeyStrokesOffCell = class(TSynEditKeyStrokes)
public
procedure ResetDefaults; override;
end;
{ TSynPluginTemplateEdit }
TSynPluginTemplateEdit = class(TSynPluginCustomSyncroEdit)
private
FCellParserEnabled: Boolean;
FKeystrokes, FKeyStrokesOffCell: TSynEditKeyStrokes;
FStartPoint: TPoint;
procedure SetKeystrokes(const AValue: TSynEditKeyStrokes);
procedure SetKeystrokesOffCell(const AValue: TSynEditKeyStrokes);
protected
procedure DoEditorRemoving(AValue: TCustomSynEdit); override;
procedure DoEditorAdded(AValue: TCustomSynEdit); override;
procedure TranslateKey(Sender: TObject; Code: word; SState: TShiftState;
var Data: pointer; var IsStartOfCombo: boolean; var Handled: boolean;
var Command: TSynEditorCommand; FinishComboOnly: Boolean;
var ComboKeyStrokes: TSynEditKeyStrokes);
procedure ProcessSynCommand(Sender: TObject; AfterProcessing: boolean;
var Handled: boolean; var Command: TSynEditorCommand;
var AChar: TUTF8Char; Data: pointer; HandlerData: pointer);
procedure NextCellOrFinal(SetSelect: Boolean = True; FirstsOnly: Boolean = False);
procedure SetFinalCaret;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure SetTemplate(aTmpl: String; aLogCaretPos: TPoint); // Replaces current selection
// Coords relativ to the template. base (1, 1)
procedure AddEditCells(aCellList: TSynPluginSyncronizedEditList);
property CellParserEnabled: Boolean read FCellParserEnabled write FCellParserEnabled;
property Keystrokes: TSynEditKeyStrokes
read FKeystrokes write SetKeystrokes;
property KeystrokesOffCell: TSynEditKeyStrokes
read FKeystrokesOffCell write SetKeystrokesOffCell;
end;
const
ecSynPTmplEdNextCell = ecPluginFirstTemplEdit + 0;
ecSynPTmplEdNextCellSel = ecPluginFirstTemplEdit + 1;
ecSynPTmplEdNextCellRotate = ecPluginFirstTemplEdit + 2;
ecSynPTmplEdNextCellSelRotate = ecPluginFirstTemplEdit + 3;
ecSynPTmplEdPrevCell = ecPluginFirstTemplEdit + 4;
ecSynPTmplEdPrevCellSel = ecPluginFirstTemplEdit + 5;
ecSynPTmplEdCellHome = ecPluginFirstTemplEdit + 6;
ecSynPTmplEdCellEnd = ecPluginFirstTemplEdit + 7;
ecSynPTmplEdCellSelect = ecPluginFirstTemplEdit + 8;
ecSynPTmplEdFinish = ecPluginFirstTemplEdit + 9;
ecSynPTmplEdEscape = ecPluginFirstTemplEdit + 10;
ecSynPTmplEdNextFirstCell = ecPluginFirstTemplEdit + 11;
ecSynPTmplEdNextFirstCellSel = ecPluginFirstTemplEdit + 12;
ecSynPTmplEdNextFirstCellRotate = ecPluginFirstTemplEdit + 13;
ecSynPTmplEdNextFirstCellSelRotate = ecPluginFirstTemplEdit + 14;
ecSynPTmplEdPrevFirstCell = ecPluginFirstTemplEdit + 15;
ecSynPTmplEdPrevFirstCellSel = ecPluginFirstTemplEdit + 16;
// If extending the list, reserve space in SynEditKeyCmds
ecSynPTmplEdCount = 17;
implementation
{ TSynPluginTemplateEdit }
constructor TSynPluginTemplateEdit.Create(AOwner: TComponent);
begin
FKeystrokes := TSynEditTemplateEditKeyStrokes.Create(Self);
FKeystrokes.ResetDefaults;
FKeyStrokesOffCell := TSynEditTemplateEditKeyStrokesOffCell.Create(self);
FKeyStrokesOffCell.ResetDefaults;
inherited Create(AOwner);
CellParserEnabled := True;
end;
destructor TSynPluginTemplateEdit.Destroy;
begin
inherited Destroy;
FreeAndNil(FKeystrokes);
FreeAndNil(FKeyStrokesOffCell);
end;
procedure TSynPluginTemplateEdit.DoEditorRemoving(AValue: TCustomSynEdit);
begin
if Editor <> nil then begin
Editor.UnRegisterKeyTranslationHandler(@TranslateKey);
Editor.UnregisterCommandHandler(@ProcessSynCommand);
end;
inherited DoEditorRemoving(AValue);
end;
procedure TSynPluginTemplateEdit.DoEditorAdded(AValue: TCustomSynEdit);
begin
inherited DoEditorAdded(AValue);
if Editor <> nil then begin
Editor.RegisterCommandHandler(@ProcessSynCommand, nil);
Editor.RegisterKeyTranslationHandler(@TranslateKey);
end;
end;
procedure TSynPluginTemplateEdit.SetKeystrokes(const AValue: TSynEditKeyStrokes);
begin
if AValue = nil then
FKeystrokes.Clear
else
FKeystrokes.Assign(AValue);
end;
procedure TSynPluginTemplateEdit.SetKeystrokesOffCell(const AValue: TSynEditKeyStrokes);
begin
if AValue = nil then
FKeyStrokesOffCell.Clear
else
FKeyStrokesOffCell.Assign(AValue);
end;
procedure TSynPluginTemplateEdit.TranslateKey(Sender: TObject; Code: word;
SState: TShiftState; var Data: pointer; var IsStartOfCombo: boolean; var Handled: boolean;
var Command: TSynEditorCommand; FinishComboOnly: Boolean;
var ComboKeyStrokes: TSynEditKeyStrokes);
var
keys: TSynEditKeyStrokes;
begin
if (not Active) or Handled then
exit;
if CurrentCell < 0 then
keys := FKeyStrokesOffCell
else
keys := FKeyStrokes;
if not FinishComboOnly then
keys.ResetKeyCombo;
Command := keys.FindKeycodeEx(Code, SState, Data, IsStartOfCombo, FinishComboOnly, ComboKeyStrokes);
Handled := (Command <> ecNone) or IsStartOfCombo;
if IsStartOfCombo then
ComboKeyStrokes := keys;
end;
procedure TSynPluginTemplateEdit.ProcessSynCommand(Sender: TObject; AfterProcessing: boolean;
var Handled: boolean; var Command: TSynEditorCommand; var AChar: TUTF8Char; Data: pointer;
HandlerData: pointer);
begin
if Handled or AfterProcessing or not Active then exit;
Handled := True;
case Command of
ecSynPTmplEdNextCell: NextCellOrFinal(False);
ecSynPTmplEdNextCellSel: NextCellOrFinal(True);
ecSynPTmplEdNextCellRotate: NextCell(False);
ecSynPTmplEdNextCellSelRotate: NextCell(True);
ecSynPTmplEdPrevCell: PreviousCell(False);
ecSynPTmplEdPrevCellSel: PreviousCell(True);
ecSynPTmplEdNextFirstCell: NextCellOrFinal(False, True);
ecSynPTmplEdNextFirstCellSel: NextCellOrFinal(True, True);
ecSynPTmplEdNextFirstCellRotate: NextCell(False, False, True);
ecSynPTmplEdNextFirstCellSelRotate: NextCell(True, False, True);
ecSynPTmplEdPrevFirstCell: PreviousCell(False, False, True);
ecSynPTmplEdPrevFirstCellSel: PreviousCell(True, False, True);
ecSynPTmplEdCellHome: CellCaretHome;
ecSynPTmplEdCellEnd: CellCaretEnd;
ecSynPTmplEdCellSelect: SelectCurrentCell;
ecSynPTmplEdFinish: SetFinalCaret;
ecSynPTmplEdEscape:
begin
Clear;
Active := False;
end;
else
Handled := False;
end;
end;
procedure TSynPluginTemplateEdit.NextCellOrFinal(SetSelect: Boolean; FirstsOnly: Boolean);
var
Pos: TPoint;
i: Integer;
begin
Pos := CaretObj.LineBytePos;
i := Cells.IndexOf(Pos.x, Pos.y, True, CurrentCell);
if i < 0 then begin
i := Cells.Count - 1;
while (i >= 0) and
((Cells[i].Group < 0) or (CompareCarets(Cells[i].LogEnd, Pos) <= 0))
do
dec(i);
end;
Repeat
inc(i);
if i >= Cells.Count then begin
SetFinalCaret;
exit;
end;
until (Cells[i].Group >= 0) and
((not FirstsOnly) or (Cells[i].FirstInGroup));
CurrentCell := i;
if CurrentCell < 0 then
exit;
CaretObj.LineBytePos := Cells[CurrentCell].LogStart;
if SetSelect then
SelectCurrentCell(True)
else
Editor.BlockBegin := Cells[CurrentCell].LogStart;
end;
procedure TSynPluginTemplateEdit.SetFinalCaret;
var
c: TSynPluginSyncronizedEditCell;
begin
if FMarkup <> nil then
FMarkup.DoInvalidate;
c := Cells.GroupCell[-2, 0];
Editor.BlockBegin := c.LogStart;
CaretObj.IncForcePastEOL;
CaretObj.LineBytePos := c.LogStart;
CaretObj.DecForcePastEOL;
Cells.Clear;
Active := False;
end;
procedure TSynPluginTemplateEdit.SetTemplate(aTmpl: String; aLogCaretPos: TPoint);
var
Temp: TStringList;
CellStart, StartPos: TPoint;
i, j, k, XOffs, Grp: Integer;
s, s2: string;
begin
Clear;
Active := False;
StartPos := Editor.BlockBegin;
FStartPoint := StartPos;
if CellParserEnabled then begin
Temp := TStringList.Create;
try
Temp.Text := aTmpl;
if (aTmpl <> '') and (aTmpl[length(aTmpl)] in [#10,#13]) then
Temp.Add('');
XOffs := StartPos.X - 1;
i := 0;
Grp := 1;
while i < Temp.Count do begin
CellStart.y := StartPos.y + i;
CellStart.x := -1;
s := Temp[i];
j := 1;
k := 1;
SetLength(s2, length(s));
while j <= length(s) do begin
case s[j] of
'{':
if (j + 1 <= Length(s)) and (s[j+1] = '{') then begin
inc(j);
s2[k] := s[j];
inc(k);
end else begin
CellStart.x := k + XOffs;
end;
'}':
if (j + 1 <= Length(s)) and (s[j+1] = '}') then begin
inc(j);
s2[k] := s[j];
inc(k);
end else
if CellStart.x > 0 then begin
with Cells.AddNew do begin
LogStart := CellStart;
LogEnd := Point(k +XOffs, CellStart.y);
Group := grp;
FirstInGroup := True;
end;
inc(grp);
CellStart.x := -1;
end;
else
begin
s2[k] := s[j];
inc(k);
end;
end;
inc(j);
end;
SetLength(s2, k-1);
Temp[i] := s2;
inc(i);
XOffs := 0;
end;
aTmpl := Temp.Text;
// strip the trailing #13#10 that was appended by the stringlist
i := Length(aTmpl);
if (i >= 1) and (aTmpl[i] in [#10,#13]) then begin
dec(i);
if (i >= 1) and (aTmpl[i] in [#10,#13]) and (aTmpl[i] <> aTmpl[i+1]) then
dec(i);
SetLength(aTmpl, i);
end;
finally
Temp.Free;
end;
end;
Editor.SelText := aTmpl;
with Cells.AddNew do begin
Group := -2;
LogStart := aLogCaretPos;
LogEnd := aLogCaretPos;
end;
if (Cells.Count > 1) then begin
Active := True;
CurrentCell := 0;
CaretObj.LineBytePos := Cells[0].LogStart;
SelectCurrentCell;
SetUndoStart;
end
else
Editor.MoveCaretIgnoreEOL(Editor.LogicalToPhysicalPos(aLogCaretPos));
end;
procedure TSynPluginTemplateEdit.AddEditCells(aCellList: TSynPluginSyncronizedEditList);
var
i, XOffs, YOffs: Integer;
CurCell: TSynPluginSyncronizedEditCell;
CaretPos: TSynPluginSyncronizedEditCell;
p: TPoint;
begin
CaretPos := nil;
if Cells.GroupCell[-2, 0] <> nil then begin
CaretPos := TSynPluginSyncronizedEditCell.Create;
CaretPos.Assign(Cells.GroupCell[-2, 0]);
end;
Cells.Clear;
XOffs := FStartPoint.x - 1;
YOffs := FStartPoint.y - 1;
for i := 0 to aCellList.Count - 1 do begin
CurCell := aCellList[i];
with Cells.AddNew do begin;
Assign(CurCell);
p := LogStart;
if p.y = 1 then
p.x := p.x + XOffs;
p.y := p.y + YOffs;
LogStart := p;
p := LogEnd;
if p.y = 1 then
p.x := p.x + XOffs;
p.y := p.y + YOffs;
LogEnd := p;
end;
end;
if CaretPos <> nil then
Cells.AddNew.Assign(CaretPos);
FreeAndNil(CaretPos);
if aCellList.Count > 0 then begin
Active := True;
CurrentCell := 0;
CaretObj.LineBytePos := Cells[0].LogStart;
SelectCurrentCell;
SetUndoStart;
end;
end;
{ TSynEditTemplateEditKeyStrokes }
procedure TSynEditTemplateEditKeyStrokes.ResetDefaults;
procedure AddKey(const ACmd: TSynEditorCommand; const AKey: word;
const AShift: TShiftState);
begin
with Add do
begin
Key := AKey;
Shift := AShift;
Command := ACmd;
end;
end;
begin
Clear;
AddKey(ecSynPTmplEdNextCellRotate, VK_RIGHT, [ssCtrl]);
AddKey(ecSynPTmplEdNextCellSel, VK_TAB, []);
AddKey(ecSynPTmplEdPrevCell, VK_LEFT, [ssCtrl]);
AddKey(ecSynPTmplEdPrevCellSel, VK_TAB, [ssShift]);
AddKey(ecSynPTmplEdCellHome, VK_HOME, []);
AddKey(ecSynPTmplEdCellEnd, VK_END, []);
AddKey(ecSynPTmplEdCellSelect, VK_A, [ssCtrl]);
AddKey(ecSynPTmplEdFinish, VK_RETURN, []);
AddKey(ecSynPTmplEdEscape, VK_ESCAPE, []);
end;
{ TSynEditTemplateEditKeyStrokesOffCell }
procedure TSynEditTemplateEditKeyStrokesOffCell.ResetDefaults;
procedure AddKey(const ACmd: TSynEditorCommand; const AKey: word;
const AShift: TShiftState);
begin
with Add do
begin
Key := AKey;
Shift := AShift;
Command := ACmd;
end;
end;
begin
Clear;
AddKey(ecSynPTmplEdNextCellRotate, VK_RIGHT, [ssCtrl]);
AddKey(ecSynPTmplEdNextCellSel, VK_TAB, []);
AddKey(ecSynPTmplEdPrevCell, VK_LEFT, [ssCtrl]);
AddKey(ecSynPTmplEdPrevCellSel, VK_TAB, [ssShift]);
AddKey(ecSynPTmplEdFinish, VK_RETURN, []);
AddKey(ecSynPTmplEdEscape, VK_ESCAPE, []);
end;
const
EditorTmplEditCommandStrs: array[0..16] of TIdentMapEntry = (
(Value: ecSynPTmplEdNextCell; Name: 'ecSynPTmplEdNextCell'),
(Value: ecSynPTmplEdNextCellSel; Name: 'ecSynPTmplEdNextCellSel'),
(Value: ecSynPTmplEdNextCellRotate; Name: 'ecSynPTmplEdNextCellRotate'),
(Value: ecSynPTmplEdNextCellSelRotate; Name: 'ecSynPTmplEdNextCellSelRotate'),
(Value: ecSynPTmplEdPrevCell; Name: 'ecSynPTmplEdPrevCell'),
(Value: ecSynPTmplEdPrevCellSel; Name: 'ecSynPTmplEdPrevCellSel'),
(Value: ecSynPTmplEdCellHome; Name: 'ecSynPTmplEdCellHome'),
(Value: ecSynPTmplEdCellEnd; Name: 'ecSynPTmplEdCellEnd'),
(Value: ecSynPTmplEdCellSelect; Name: 'ecSynPTmplEdCellSelect'),
(Value: ecSynPTmplEdFinish; Name: 'ecSynPTmplEdFinish'),
(Value: ecSynPTmplEdEscape; Name: 'ecSynPTmplEdEscape'),
(Value: ecSynPTmplEdNextFirstCell; Name: 'ecSynPTmplEdNextFirstCell'),
(Value: ecSynPTmplEdNextFirstCellSel; Name: 'ecSynPTmplEdNextFirstCellSel'),
(Value: ecSynPTmplEdNextFirstCellRotate; Name: 'ecSynPTmplEdNextFirstCellRotate'),
(Value: ecSynPTmplEdNextFirstCellSelRotate; Name: 'ecSynPTmplEdNextFirstCellSelRotate'),
(Value: ecSynPTmplEdPrevFirstCell; Name: 'ecSynPTmplEdPrevFirstCell'),
(Value: ecSynPTmplEdPrevFirstCellSel; Name: 'ecSynPTmplEdPrevFirstCellSel')
);
function IdentToTmplEditCommand(const Ident: string; var Cmd: longint): boolean;
begin
Result := IdentToInt(Ident, Cmd, EditorTmplEditCommandStrs);
end;
function TmplEditCommandToIdent(Cmd: longint; var Ident: string): boolean;
begin
Result := (Cmd >= ecPluginFirstTemplEdit) and (Cmd - ecPluginFirstTemplEdit < ecSynPTmplEdCount);
if not Result then exit;
Result := IntToIdent(Cmd, Ident, EditorTmplEditCommandStrs);
end;
procedure GetEditorCommandValues(Proc: TGetStrProc);
var
i: integer;
begin
for i := Low(EditorTmplEditCommandStrs) to High(EditorTmplEditCommandStrs) do
Proc(EditorTmplEditCommandStrs[I].Name);
end;
initialization
RegisterKeyCmdIdentProcs(@IdentToTmplEditCommand,
@TmplEditCommandToIdent);
RegisterExtraGetEditorCommandValues(@GetEditorCommandValues);
end.