1 unit EditorMacroListViewer;
2
3 {$mode objfpc}{$H+}
4
5 interface
6
7 uses
8 Classes, SysUtils,
9 // LCL
10 LCLType, Forms, Controls, Dialogs, StdCtrls, ButtonPanel, ComCtrls, ExtCtrls,
11 Spin, Menus, Buttons,
12 // LazUtils
13 LazFileUtils, LazStringUtils, Laz2_XMLCfg, LazUTF8, LazLoggerBase,
14 // SynEdit
15 SynMacroRecorder, SynEdit, SynEditKeyCmds,
16 // IdeIntf
17 IDEWindowIntf, IDEImagesIntf, SrcEditorIntf, IDEHelpIntf, IDECommands,
18 LazIDEIntf, IDEDialogs,
19 // IDE
20 LazarusIDEStrConsts, ProjectDefs, LazConf, Project, KeyMapping,
21 KeyMapShortCutDlg, MainIntf;
22
23 type
24 TSynEditorMacro = class(TSynMacroRecorder)
25 end;
26
27 { TIdeEditorMacro }
28
29 TIdeEditorMacro = class(TEditorMacro)
30 private
31 FMacroName: String;
32 FHasError: Boolean;
33 FErrorMsg: String;
34 FFailedText: String;
35 FSynMacro: TSynEditorMacro;
36 FKeyBinding: TEditorMacroKeyBinding;
37
38 procedure DoMacroRecorderState(Sender: TObject);
39 procedure DoMacroRecorderUserCommand({%H-}aSender: TCustomSynMacroRecorder;
40 aCmd: TSynEditorCommand; var aEvent: TSynMacroEvent);
41 protected
GetMacroNamenull42 function GetMacroName: String; override;
43 procedure SetMacroName(AValue: string); override;
GetStatenull44 function GetState: TEditorMacroState; override;
GetErrorMsgnull45 function GetErrorMsg: String; override;
GetKeyBindingnull46 function GetKeyBinding: TEditorMacroKeyBinding; override;
47
48 procedure DoRecordMacro(aEditor: TWinControl); override;
49 procedure DoPlaybackMacro(aEditor: TWinControl); override;
50 procedure DoStop; override;
51 procedure DoPause; override;
52 procedure DoResume; override;
53 public
54 constructor Create(aOwner: TComponent); override;
55 destructor Destroy; override;
56 procedure AssignEventsFrom(AMacroRecorder: TEditorMacro); override;
AddEditornull57 function AddEditor(AValue: TCustomSynEdit): integer;
58
59 procedure Clear; override;
60
GetAsSourcenull61 function GetAsSource: String; override;
62 procedure SetFromSource(const AText: String); override;
63 procedure WriteToXmlConf(AConf: TXMLConfig; const APath: String); override;
64 procedure ReadFromXmlConf(AConf: TXMLConfig; const APath: String); override;
65
IsEmptynull66 function IsEmpty: Boolean; override;
IsInvalidnull67 function IsInvalid: Boolean; override;
IsRecordingnull68 function IsRecording(AnEditor: TWinControl): Boolean; override;
69 end;
70
71
72 { TIdeEditorMacroKeyBinding }
73
74 TIdeEditorMacroKeyBinding = class(TEditorMacroKeyBinding)
75 protected
76 FIdeCmd: TIDECommand;
GetIdeCmdnull77 function GetIdeCmd: TIDECommand; override;
78 procedure ExecMacro(Sender: TObject);
79 public
80 destructor Destroy; override;
81 procedure WriteToXmlConf(AConf: TXMLConfig; const APath: String); override;
82 procedure ReadFromXmlConf(AConf: TXMLConfig; const APath: String); override;
83 procedure MacroNameChanged; override;
ShortCutAsTextnull84 function ShortCutAsText: String; override;
85 end;
86
87 { TIdeMacroEventWriter }
88
89 TIdeMacroEventWriter = class(TSynMacroEventWriter)
90 private
91 FText: String;
92 FCmdName, FParams: String;
93 FUseLineFeed: Boolean;
94 public
95 constructor Create;
96 procedure BeginEvent;
97 procedure FinishEvent;
98 procedure WriteEventCommand(const ACmd: TSynEditorCommand); override;
99 procedure WriteEventParam(const AParam: string); override;
100 procedure WriteEventParam(const AParam: integer); override;
101 property Text: String read FText;
102 property UseLineFeed: Boolean read FUseLineFeed write FUseLineFeed;
103 end;
104
105 { TIdeMacroEventReader }
106
107 TIdeMacroEventReader = class(TSynMacroEventReader)
108 private
109 FErrorText: String;
110 FEventName: String;
111 FHasError: Boolean;
112 FText, FOrigText: String;
113 FPos, FPosCompensate: Integer;
114 FEventCommand: TSynEditorCommand;
115 FParams: Array of record
116 ParamType: TSynEventParamType;
117 Text : String;
118 Num: Integer;
119 end;
120 protected
GetParamAsIntnull121 function GetParamAsInt(Index: Integer): Integer; override;
GetParamAsStringnull122 function GetParamAsString(Index: Integer): String; override;
GetParamTypenull123 function GetParamType(Index: Integer): TSynEventParamType; override;
PosToXYnull124 function PosToXY: TPoint;
AddErrornull125 function AddError(AMsg: string): Boolean;
126 public
127 constructor Create(const Atext: String);
EventCommandnull128 function EventCommand: TSynEditorCommand; override;
ParamCountnull129 function ParamCount: Integer; override;
ParseNextEventnull130 function ParseNextEvent: Boolean;
131 property EventName: String read FEventName;
132 property HasError: Boolean read FHasError;
133 property ErrorText: String read FErrorText;
134 end;
135
136 { TEditorMacroList }
137
138 TEditorMacroList = class;
139 TMacroAddedEvent = procedure(AList: TEditorMacroList; AMacro: TEditorMacro) of object;
140
141 TEditorMacroList = class
142 private
143 FList: TList;
144 FOnAdded: TMacroAddedEvent;
145 FOnChange: TNotifyEvent;
146 FOnRemove: TMacroAddedEvent;
GetMacrosnull147 function GetMacros(Index: Integer): TEditorMacro;
148 procedure DoChanged;
149 procedure DoAdded(AMacro: TEditorMacro);
150 procedure DoRemove(AMacro: TEditorMacro);
151 public
152 constructor Create;
153 destructor Destroy; override;
154 procedure WriteToXmlConf(AConf: TXMLConfig; const APath: String);
155 procedure ReadFromXmlConf(AConf: TXMLConfig; const APath: String);
156 procedure ClearAndFreeMacros;
Countnull157 function Count: Integer;
IndexOfnull158 function IndexOf(AMacro: TEditorMacro): Integer;
IndexOfNamenull159 function IndexOfName(AName: String): Integer;
UniqNamenull160 function UniqName(AName: String): String;
Addnull161 function Add(AMacro: TEditorMacro): Integer;
162 procedure Delete(AnIndex: Integer);
163 procedure Remove(AMacro: TEditorMacro);
164 property Macros[Index: Integer]: TEditorMacro read GetMacros;
165 property OnChange: TNotifyEvent read FOnChange write FOnChange;
166 property OnAdded: TMacroAddedEvent read FOnAdded write FOnAdded;
167 property OnRemove: TMacroAddedEvent read FOnRemove write FOnRemove;
168 end;
169
170 { TMacroListView }
171
172 TMacroListView = class(TForm)
173 btnEdit: TButton;
174 btnSetKeys: TButton;
175 btnPlay: TButton;
176 btnRecord: TButton;
177 btnRecordStop: TButton;
178 btnDelete: TButton;
179 btnSelect: TButton;
180 btnRename: TButton;
181 ButtonPanel1: TButtonPanel;
182 chkRepeat: TCheckBox;
183 GroupBox1: TGroupBox;
184 LabelWarning: TLabel;
185 lbMoveTo: TLabel;
186 lbMacroView: TListView;
187 mnExport: TMenuItem;
188 mnImport: TMenuItem;
189 OpenDialog1: TOpenDialog;
190 Panel1: TPanel;
191 PnlWarnClose: TPanel;
192 PanelWarnings: TPanel;
193 PanelRepeat: TPanel;
194 pnlButtons: TPanel;
195 PopupMenu1: TPopupMenu;
196 RenameButton: TPanelBitBtn;
197 edRepeat: TSpinEdit;
198 SaveDialog1: TSaveDialog;
199 BtnWarnClose: TSpeedButton;
200 ToolBar1: TToolBar;
201 tbRecorded: TToolButton;
202 tbProject: TToolButton;
203 tbIDE: TToolButton;
204 ToolBar2: TToolBar;
205 tbMoveProject: TToolButton;
206 tbMoveIDE: TToolButton;
207 ToolButton3: TToolButton;
208 ToolButton4: TToolButton;
209 procedure btnDeleteClick(Sender: TObject);
210 procedure btnEditClick(Sender: TObject);
211 procedure btnPlayClick(Sender: TObject);
212 procedure btnRecordClick(Sender: TObject);
213 procedure btnRecordStopClick(Sender: TObject);
214 procedure btnRenameClick(Sender: TObject);
215 procedure btnSelectClick(Sender: TObject);
216 procedure btnSetKeysClick(Sender: TObject);
217 procedure BtnWarnCloseClick(Sender: TObject);
218 procedure FormActivate(Sender: TObject);
219 procedure HelpButtonClick(Sender: TObject);
220 procedure lbMacroViewSelectItem(Sender: TObject; {%H-}Item: TListItem; {%H-}Selected: Boolean);
221 procedure mnExportClick(Sender: TObject);
222 procedure mnImportClick(Sender: TObject);
223 procedure tbIDEClick(Sender: TObject);
224 procedure tbMoveIDEClick(Sender: TObject);
225 procedure tbMoveProjectClick(Sender: TObject);
226 procedure tbProjectClick(Sender: TObject);
227 procedure tbRecordedClick(Sender: TObject);
228 private
229 FImageRec: Integer;
230 FImagePlay: Integer;
231 FImageSel: Integer;
232 FImageErr: Integer;
233 FIsPlaying: Boolean;
234 FIgnoreMacroChanges: Boolean;
235 procedure DoOnMacroListChange(Sender: TObject);
236 procedure DoMacroContentChanged(Sender: TObject);
237 procedure DoMacroStateChanged(Sender: TObject);
238 procedure UpdateButtons;
239 protected
240 procedure DoEditorMacroStateChanged;
241 public
242 constructor Create(TheOwner: TComponent); override;
243 destructor Destroy; override;
MacroByFullNamenull244 function MacroByFullName(AName: String): TEditorMacro;
245 procedure UpdateDisplay;
246 end;
247
MacroListViewernull248 function MacroListViewer: TMacroListView;
249 procedure ShowMacroListViewer;
250 procedure UpdateMacroListViewer;
251 procedure DoEditorMacroStateChanged;
252
253 procedure LoadProjectSpecificInfo(XMLConfig: TXMLConfig);
254 procedure SaveProjectSpecificInfo(XMLConfig: TXMLConfig; Flags: TProjectWriteFlags);
255 procedure LoadGlobalInfo;
256 procedure SaveGlobalInfo;
257
258 var
259 OnKeyMapReloaded: procedure of object;
260 OnEditorMacroStateChange: TNotifyEvent;
261 // SelectedEditorMacro: Selected, for playing with default shortcut
262 SelectedEditorMacro: TEditorMacro = nil;
263
264 const
265 EditorMacroVirtualDrive = '%Macro:|'; // do not use \ or /, they can be converted by the IDE
266
267 implementation
268
269 var
270 MacroListView: TMacroListView = nil;
271
272 CurrentEditorMacroList: TEditorMacroList = nil;
273 EditorMacroListRec, EditorMacroListProj, EditorMacroListGlob: TEditorMacroList;
274
275 // CurrentRecordingMacro:
276 // The player-macro, to wich the recording in process will be assigned
277 CurrentRecordingMacro: TEditorMacro = nil;
278
279 MacroRecCounter: Integer = 1;
280 KeyCategory: TIDECommandCategory = nil;
281
282 const
283 GlobalConfFileName = 'EditorMacros.xml';
284
MacroListViewernull285 function MacroListViewer: TMacroListView;
286 begin
287 if MacroListView = nil then
288 MacroListView := TMacroListView.Create(Application);
289 Result := MacroListView;
290
291 MacroListView.LabelWarning.Caption := MacroListViewerWarningText;
292 MacroListView.PanelWarnings.Visible := MacroListViewerWarningText <> '';
293 end;
294
295 procedure DoMacroListViewerWarningChanged({%H-}ASender: TObject);
296 begin
297 if MacroListView <> nil then begin
298 MacroListView.LabelWarning.Caption := MacroListViewerWarningText;
299 MacroListView.PanelWarnings.Visible := MacroListViewerWarningText <> '';
300 end;
301 end;
302
303 procedure ShowMacroListViewer;
304 begin
305 IDEWindowCreators.ShowForm(MacroListViewer, True);
306 end;
307
308 procedure UpdateMacroListViewer;
309 begin
310 if MacroListView <> nil then
311 MacroListView.UpdateDisplay;
312 end;
313
MacroListToNamenull314 function MacroListToName(AList: TEditorMacroList): string;
315 begin
316 Result := '';
317 if AList = EditorMacroListRec then Result := 'Rec'
318 else if AList = EditorMacroListProj then Result := 'Prj'
319 else if AList = EditorMacroListGlob then Result := 'Ide';
320 end;
321
NameToMacroListnull322 function NameToMacroList(AName: string): TEditorMacroList;
323 begin
324 Result := nil;
325 if AName = 'Rec' then Result := EditorMacroListRec
326 else if AName = 'Prj' then Result := EditorMacroListProj
327 else if AName = 'Ide' then Result := EditorMacroListGlob;
328 end;
329
330 procedure DoEditorMacroStateChanged;
331 begin
332 if EditorMacroForRecording= nil then exit;
333
334 if not(EditorMacroForRecording.State in [emRecording, emRecPaused]) and
335 (CurrentRecordingMacro <> nil)
336 then begin
337 // finished recording
338 if EditorMacroForRecording.IsEmpty then begin
339 EditorMacroListRec.Remove(CurrentRecordingMacro);
340 FreeAndNil(CurrentRecordingMacro);
341 end else begin
342 CurrentRecordingMacro.AssignEventsFrom(EditorMacroForRecording);
343 SelectedEditorMacro := CurrentRecordingMacro;
344 CurrentRecordingMacro := nil;
345 end;
346 end;
347
348 if (EditorMacroForRecording.State = emRecording) and (CurrentRecordingMacro = nil) then begin
349 CurrentRecordingMacro := EditorMacroPlayerClass.Create(nil);
350 CurrentRecordingMacro.OnStateChange := @MacroListViewer.DoMacroStateChanged;
351 CurrentRecordingMacro.OnChange := @MacroListViewer.DoMacroContentChanged;
352 CurrentRecordingMacro.MacroName := Format(lisNewMacroName, [MacroRecCounter]);
353 inc(MacroRecCounter);
354 EditorMacroListRec.Add(CurrentRecordingMacro);
355 end;
356
357 if MacroListView <> nil then
358 MacroListView.DoEditorMacroStateChanged;
359 end;
360
361 procedure LoadProjectSpecificInfo(XMLConfig: TXMLConfig);
362 begin
363 MacroListViewer.FIgnoreMacroChanges := True;
364 try
365 EditorMacroListProj.ReadFromXmlConf(XMLConfig, '');
366 finally
367 MacroListViewer.FIgnoreMacroChanges := False;
368 end;
369 end;
370
371 procedure SaveProjectSpecificInfo(XMLConfig: TXMLConfig; Flags: TProjectWriteFlags);
372 begin
373 if not (pwfSkipSeparateSessionInfo in Flags) then
374 begin
375 EditorMacroListProj.WriteToXmlConf(XMLConfig, '');
376 end;
377 end;
378
379 procedure LoadGlobalInfo;
380 var
381 Filename: String;
382 XMLConfig: TXMLConfig;
383 begin
384 MacroListViewer.FIgnoreMacroChanges := True;
385 Filename := TrimFilename(AppendPathDelim(GetPrimaryConfigPath)+GlobalConfFileName);
386 try
387 XMLConfig := TXMLConfig.Create(Filename);
388 try
389 EditorMacroListGlob.ReadFromXmlConf(XMLConfig, '');
390 finally
391 XMLConfig.Free;
392 end;
393 except
394 on E: Exception do begin
395 DebugLn('[EditorMacroListViewer.LoadGlobalInfo] error reading "',Filename,'": ',E.Message);
396 end;
397 end;
398 MacroListViewer.FIgnoreMacroChanges := False;
399 end;
400
401 procedure SaveGlobalInfo;
402 var
403 Filename: String;
404 XMLConfig: TXMLConfig;
405 begin
406 Filename := TrimFilename(AppendPathDelim(GetPrimaryConfigPath)+GlobalConfFileName);
407 try
408 XMLConfig := TXMLConfig.CreateClean(Filename);
409 try
410 EditorMacroListGlob.WriteToXmlConf(XMLConfig, '');
411 finally
412 XMLConfig.Free;
413 end;
414 except
415 on E: Exception do begin
416 DebugLn('[EditorMacroListViewer.SaveGlobalInfo] error writing "',Filename,'": ',E.Message);
417 end;
418 end;
419 end;
420
421 { TIdeEditorMacroKeyBinding }
422
GetIdeCmdnull423 function TIdeEditorMacroKeyBinding.GetIdeCmd: TIDECommand;
424 begin
425 Result := FIdeCmd;
426 end;
427
428 procedure TIdeEditorMacroKeyBinding.ExecMacro(Sender: TObject);
429 begin
430 if ActiveEditorMacro <> nil then exit;
431 FOwner.PlaybackMacro(TCustomSynEdit(SourceEditorManagerIntf.ActiveEditor.EditorControl));
432 end;
433
434 procedure TIdeEditorMacroKeyBinding.MacroNameChanged;
435 begin
436 if (IDECommandList = nil) then
437 exit;
438
439 if FOwner.IsInvalid then begin
440 if FIdeCmd <> nil then begin
441 (IDECommandList as TKeyCommandRelationList).RemoveCommand(FIdeCmd);
442 FreeAndNil(FIdeCmd);
443 end;
444 exit;
445 end;
446
447 if KeyCategory = nil then
448 KeyCategory := IDECommandList.CreateCategory(nil, 'EditorMacros',
449 'Editor Macros', IDECmdScopeSrcEditOnly);
450
451 if FIdeCmd = nil then begin
452 FIdeCmd := (IDECommandList as TKeyCommandRelationList).CreateCommand(
453 KeyCategory,
454 'EdtMacro'+FOwner.MacroName,
455 FOwner.MacroName,
456 IDEShortCut(VK_UNKNOWN, [], VK_UNKNOWN, []),
457 IDEShortCut(VK_UNKNOWN, [], VK_UNKNOWN, []),
458 @ExecMacro, nil
459 );
460 (FIdeCmd as TKeyCommandRelation).SkipSaving := True;
461 end
462 else
463 FIdeCmd.LocalizedName := FOwner.MacroName;
464
465 end;
466
467 destructor TIdeEditorMacroKeyBinding.Destroy;
468 begin
469 inherited Destroy;
470 if (FIdeCmd <> nil) and (IDECommandList <> nil) then begin
471 (IDECommandList as TKeyCommandRelationList).RemoveCommand(FIdeCmd);
472 FreeAndNil(FIdeCmd);
473 end;
474 end;
475
476 procedure TIdeEditorMacroKeyBinding.WriteToXmlConf(AConf: TXMLConfig; const APath: String);
477 procedure ClearKey(const SubPath: string);
478 begin
479 AConf.DeleteValue(SubPath+'Key1');
480 AConf.DeleteValue(SubPath+'Shift1');
481 AConf.DeleteValue(SubPath+'Key2');
482 AConf.DeleteValue(SubPath+'Shift2');
483 end;
484 procedure Store(const SubPath: string; Key: TIDEShortCut);
485 var
486 s: TShiftState;
487 begin
488 AConf.SetDeleteValue(SubPath+'Key1', key.Key1, VK_UNKNOWN);
489 if key.Key1=VK_UNKNOWN then
490 s:=[]
491 else
492 s:=key.Shift1;
493 AConf.SetDeleteValue(SubPath+'Shift1',ShiftStateToCfgStr(s),ShiftStateToCfgStr([]));
494 AConf.SetDeleteValue(SubPath+'Key2',key.Key2,VK_UNKNOWN);
495 if key.Key2=VK_UNKNOWN then
496 s:=[]
497 else
498 s:=key.Shift2;
499 AConf.SetDeleteValue(SubPath+'Shift2',ShiftStateToCfgStr(s),ShiftStateToCfgStr([]));
500 end;
501
502 begin
503 if (FIdeCmd = nil) then begin
504 ClearKey(APath + 'KeyA/');
505 ClearKey(APath + 'KeyB/');
506 end else begin
507 Store(APath + 'KeyA/', FIdeCmd.ShortcutA);
508 Store(APath + 'KeyB/', FIdeCmd.ShortcutB);
509 end;
510 end;
511
512 procedure TIdeEditorMacroKeyBinding.ReadFromXmlConf(AConf: TXMLConfig; const APath: String);
513 procedure Load(SubPath: string; out Key: TIDEShortCut);
514 begin
515 key.Key1 := AConf.GetValue(SubPath+'Key1',VK_UNKNOWN);
516 key.Shift1 := CfgStrToShiftState(AConf.GetValue(SubPath+'Shift1',''));
517 key.Key2 := AConf.GetValue(SubPath+'Key2',VK_UNKNOWN);
518 key.Shift2 := CfgStrToShiftState(AConf.GetValue(SubPath+'Shift2',''));
519 end;
520 var
521 SCut: TIDEShortCut;
522 begin
523 if (FIdeCmd <> nil) then begin
524 Load(APath+'KeyA/', SCut);
525 if (IDECommandList as TKeyCommandRelationList).Find(SCut, TSourceEditorWindowInterface) = nil then
526 FIdeCmd.ShortcutA := SCut;
527
528 Load(APath+'KeyB/', SCut);
529 if (IDECommandList as TKeyCommandRelationList).Find(SCut, TSourceEditorWindowInterface) = nil then
530 FIdeCmd.ShortcutB := SCut;
531 end;
532 end;
533
ShortCutAsTextnull534 function TIdeEditorMacroKeyBinding.ShortCutAsText: String;
535 begin
536 Result := '';
537 If FIdeCmd = nil then
538 exit;
539 if not IDEShortCutEmpty(FIdeCmd.ShortcutA) then
540 Result := Result + ' (' + KeyAndShiftStateToEditorKeyString(FIdeCmd.ShortcutA) + ')';
541 if not IDEShortCutEmpty(FIdeCmd.ShortcutB) then
542 Result := Result + ' (' + KeyAndShiftStateToEditorKeyString(FIdeCmd.ShortcutB) + ')';
543 end;
544
545 { TIdeEditorMacro }
546
GetMacroNamenull547 function TIdeEditorMacro.GetMacroName: String;
548 begin
549 Result := FMacroName;
550 end;
551
552 procedure TIdeEditorMacro.DoMacroRecorderState(Sender: TObject);
553 begin
554 DoStateChanged;
555 CheckStateAndActivated;
556 end;
557
558 procedure TIdeEditorMacro.DoMacroRecorderUserCommand(aSender: TCustomSynMacroRecorder;
559 aCmd: TSynEditorCommand; var aEvent: TSynMacroEvent);
560 begin
561 case aCmd of
562 ecSynMacroPlay, ecSynMacroRecord,
563 ecToggleFormUnit..ecViewThreads, ecViewHistory,
564 ecNextEditor, ecPrevEditor, ecNextWindow, ecPrevWindow,
565 ecPrevEditorInHistory, ecNextEditorInHistory,
566 ecGotoEditor1..ecGotoEditor0:
567 aEvent := TSynSkippedEvent.Create;
568 else
569 ;//
570 end;
571 end;
572
GetStatenull573 function TIdeEditorMacro.GetState: TEditorMacroState;
574 begin
575 case FSynMacro.state of
576 msStopped: Result := emStopped;
577 msRecording: Result := emRecording;
578 msPlaying: Result := emPlaying;
579 msPaused: Result := emRecPaused;
580 end;
581 end;
582
GetErrorMsgnull583 function TIdeEditorMacro.GetErrorMsg: String;
584 begin
585 Result := FErrorMsg;
586 end;
587
GetKeyBindingnull588 function TIdeEditorMacro.GetKeyBinding: TEditorMacroKeyBinding;
589 begin
590 if FKeyBinding = nil then
591 FKeyBinding := GetDefaultKeyBinding;
592 Result := FKeyBinding;
593 end;
594
595 procedure TIdeEditorMacro.SetMacroName(AValue: string);
596 begin
597 FMacroName := AValue;
598 FSynMacro.MacroName := AValue;
599 DoChanged;
600 end;
601
602 constructor TIdeEditorMacro.Create(aOwner: TComponent);
603 begin
604 FSynMacro := TSynEditorMacro.Create(aOwner);
605 FHasError := False;
606
607 FSynMacro.OnUserCommand := @DoMacroRecorderUserCommand;
608 FSynMacro.OnStateChange := @DoMacroRecorderState;
609 FSynMacro.RecordCommandID := ecNone; // ecSynMacroRecord;
610 FSynMacro.PlaybackCommandID := ecNone; // ecSynMacroPlay;
611 FSynMacro.RecordShortCut := 0;
612 FSynMacro.PlaybackShortCut := 0;
613 end;
614
615 destructor TIdeEditorMacro.Destroy;
616 begin
617 inherited Destroy;
618 FreeAndNil(FSynMacro);
619 FreeAndNil(FKeyBinding);
620 end;
621
622 procedure TIdeEditorMacro.AssignEventsFrom(AMacroRecorder: TEditorMacro);
623 begin
624 if AMacroRecorder = nil then
625 Clear
626 else
627 if AMacroRecorder is TIdeEditorMacro then
628 FSynMacro.AssignEventsFrom(TIdeEditorMacro(AMacroRecorder).FSynMacro)
629 else
630 SetFromSource(AMacroRecorder.GetAsSource);
631
632 DoChanged;
633 end;
634
TIdeEditorMacro.AddEditornull635 function TIdeEditorMacro.AddEditor(AValue: TCustomSynEdit): integer;
636 begin
637 Result := FSynMacro.AddEditor(AValue);
638 end;
639
640 procedure TIdeEditorMacro.DoRecordMacro(aEditor: TWinControl);
641 begin
642 FSynMacro.RecordMacro(aEditor as TCustomSynEdit);
643 end;
644
645 procedure TIdeEditorMacro.DoPlaybackMacro(aEditor: TWinControl);
646 begin
647 FSynMacro.PlaybackMacro(aEditor as TCustomSynEdit);
648 end;
649
650 procedure TIdeEditorMacro.DoStop;
651 begin
652 FSynMacro.Stop;
653 end;
654
655 procedure TIdeEditorMacro.DoPause;
656 begin
657 FSynMacro.Pause;
658 end;
659
660 procedure TIdeEditorMacro.DoResume;
661 begin
662 FSynMacro.Resume;
663 end;
664
665 procedure TIdeEditorMacro.Clear;
666 begin
667 FSynMacro.Clear;
668
669 DoChanged;
670 end;
671
TIdeEditorMacro.GetAsSourcenull672 function TIdeEditorMacro.GetAsSource: String;
673 var
674 i : integer;
675 W: TIdeMacroEventWriter;
676 begin
677 if FHasError then begin
678 Result := FFailedText;
679 exit;
680 end;
681
682 W := TIdeMacroEventWriter.Create;
683 W.UseLineFeed := True;
684 try
685 for i := 0 to FSynMacro.EventCount -1 do
686 begin
687 W.BeginEvent;
688 FSynMacro.Events[i].SaveToWriter(W);
689 W.FinishEvent;
690 end;
691 Result := w.Text;
692 finally
693 W.Free;
694 end;
695 end;
696
697 procedure TIdeEditorMacro.SetFromSource(const AText: String);
698 var
699 iEvent: TSynMacroEvent;
700 R: TIdeMacroEventReader;
701 begin
702 Stop;
703 FSynMacro.Clear;
704 FHasError := False;
705
706 R := TIdeMacroEventReader.Create(AText);
707 try
708 while R.ParseNextEvent do begin
709 iEvent := FSynMacro.CreateMacroEvent(R.EventCommand);
710 iEvent.LoadFromReader(R);
711 FSynMacro.InsertCustomEvent(FSynMacro.EventCount, iEvent);
712 end;
713 if R.HasError then begin
714 FHasError := True;
715 FErrorMsg := R.ErrorText;
716 FFailedText := AText;
717 MacroName := MacroName;
718 end;
719 finally
720 R.Free;
721 end;
722 DoChanged;
723 end;
724
725 procedure TIdeEditorMacro.WriteToXmlConf(AConf: TXMLConfig; const APath: String);
726 begin
727 AConf.SetValue(APath + 'Name', MacroName);
728 AConf.SetValue(APath + 'Code/Value', GetAsSource);
729
730 if (KeyBinding <> nil) then
731 KeyBinding.WriteToXmlConf(AConf, APath);
732 end;
733
734 procedure TIdeEditorMacro.ReadFromXmlConf(AConf: TXMLConfig; const APath: String);
735 var
736 s: String;
737 begin
738 s := AConf.GetValue(APath + 'Code/Value', '');
739 SetFromSource(s);
740 s := AConf.GetValue(APath + 'Name', '');
741 if s <> '' then MacroName := s;
742
743 if (not FHasError) and (FSynMacro.EventCount = 0) then begin
744 FHasError := True;
745 FErrorMsg := 'No content found';
746 FFailedText := s;
747 end;
748
749 if (KeyBinding <> nil) then
750 KeyBinding.ReadFromXmlConf(AConf, APath);
751
752 DoChanged;
753 end;
754
IsEmptynull755 function TIdeEditorMacro.IsEmpty: Boolean;
756 begin
757 Result := FSynMacro.IsEmpty;
758 end;
759
IsInvalidnull760 function TIdeEditorMacro.IsInvalid: Boolean;
761 begin
762 Result := FHasError;
763 end;
764
TIdeEditorMacro.IsRecordingnull765 function TIdeEditorMacro.IsRecording(AnEditor: TWinControl): Boolean;
766 begin
767 Result := (State in [emRecording, emRecPaused]) and
768 (FSynMacro.CurrentEditor = AnEditor);
769 end;
770
771 { TIdeMacroEventReader }
772
TIdeMacroEventReader.GetParamAsIntnull773 function TIdeMacroEventReader.GetParamAsInt(Index: Integer): Integer;
774 begin
775 if (Index < 0) or (Index >= Length(FParams)) or (ParamType[Index] <> ptInteger)
776 then begin
777 FHasError := True;
778 AddError('Wrong amount of param');
779 exit(0);
780 end;
781 Result := FParams[Index].Num;
782 end;
783
GetParamAsStringnull784 function TIdeMacroEventReader.GetParamAsString(Index: Integer): String;
785 begin
786 if (Index < 0) or (Index >= Length(FParams)) or (ParamType[Index] <> ptString)
787 then begin
788 FHasError := True;
789 AddError('Wrong amount of param');
790 exit('');
791 end;
792 Result := FParams[Index].Text;
793 end;
794
TIdeMacroEventReader.GetParamTypenull795 function TIdeMacroEventReader.GetParamType(Index: Integer): TSynEventParamType;
796 begin
797 if (Index < 0) or (Index >= Length(FParams)) then begin
798 FHasError := True;
799 AddError('Wrong amount of param');
800 exit(ptString); // What to return here?
801 end;
802 Result := FParams[Index].ParamType;
803 end;
804
PosToXYnull805 function TIdeMacroEventReader.PosToXY: TPoint;
806 var
807 f: TStringList;
808 begin
809 f := TStringList.Create;
810 f.Text := copy(FOrigText,1 ,FPos);
811 if f.Count = 0 then begin
812 Result.y := 1;
813 Result.x := 1;
814 end
815 else begin
816 Result.y := f.Count;
817 Result.x := length(f[f.Count-1])+1;
818 end;
819 f.Free;
820 end;
821
AddErrornull822 function TIdeMacroEventReader.AddError(AMsg: string): Boolean;
823 var
824 p: TPoint;
825 begin
826 p := PosToXY;
827 FErrorText := FErrorText + Format('Error: %s at Line %d, Column %d', [AMsg, p.y, p.x]);
828 Result := False;
829 end;
830
831 constructor TIdeMacroEventReader.Create(const Atext: String);
832 var
833 i: Integer;
834 s: String;
835 begin
836 FText := Atext;
837 FOrigText := Atext;
838 FHasError := False;
839 FErrorText := '';
840 FText := TrimRight(FText);
841 FPosCompensate := Length(FOrigText) - Length(FText);
842 FText := TrimLeft(FText);
843 i := length(FText);
844 if (i > 11) and
845 LazStartsText('begin', FText) and
846 (FText[6] in [#9,#10,#13,' ']) and
847 LazEndsText('end.', FText) and
848 (FText[i-4] in [#9,#10,#13,' '])
849 then begin
850 FText := copy(FText, 7, i-11);
851 FPosCompensate := FPosCompensate + 4;
852 s := TrimRight(FText);
853 FPosCompensate := FPosCompensate + Length(FText) - Length(s);
854 FText := Trim(FText);
855 end;
856 end;
857
EventCommandnull858 function TIdeMacroEventReader.EventCommand: TSynEditorCommand;
859 begin
860 Result := FEventCommand;
861 end;
862
ParamCountnull863 function TIdeMacroEventReader.ParamCount: Integer;
864 begin
865 Result := Length(FParams);
866 end;
867
TIdeMacroEventReader.ParseNextEventnull868 function TIdeMacroEventReader.ParseNextEvent: Boolean;
869 procedure SkipNum(var i: integer);
870 begin
871 while (i <= Length(FText)) and (FText[i] in ['0'..'9']) do
872 inc(i);
873 end;
874 procedure SkipSpace(var i: integer);
875 begin
876 while (i <= Length(FText)) and (FText[i] in [' ', #9, #13, #10]) do
877 inc(i);
878 end;
879 var
880 c,i,j,k: Integer;
881 s: String;
882 begin
883 FEventName := '';
884 FText := TrimLeft(FText);
885
886 Result := (FText <> '') and (not FHasError);
887 if not Result then exit;
888 Result := False;
889 FPos := Length(FOrigText) - Length(FText) - FPosCompensate;
890
891 FHasError := True; // Assume the worst
892
893 i := 1;
894 while (i <= Length(FText)) and (FText[i] in ['a'..'z','A'..'Z','0'..'9','_']) do
895 inc(i);
896 if i = 1 then exit(AddError('Expected Command, but found "'+UTF8Copy(FText,1,1)+'"'));
897
898 s := Copy(FText, 1, i-1);
899 j:=0;
900 if not IdentToEditorCommand(s, j) then exit(AddError('Unknown Command "'+s+'"'));
901 FEventCommand := j;
902 FEventName := s;
903
904 FPos := Length(FOrigText) - Length(FText) - FPosCompensate;
905 while (i <= Length(FText)) and (FText[i] in [' ', #9]) do
906 inc(i);
907 if (i > Length(FText)) then exit(AddError('Expected "(" or ";" bot got end of file'));
908
909 SetLength(FParams, 0);
910 c := 0;
911
912 if (FText[i] = '(') then begin
913 inc(i);
914 repeat
915 SkipSpace(i);
916 if (i > Length(FText)) then exit(AddError('Unexpected end of file in params'));
917
918 if FText[i] in ['0'..'9'] then begin
919 // Parse number
920 j := i;
921 SkipNum(i);
922 SetLength(FParams, c + 1);
923 FParams[c].ParamType := ptInteger;
924 FParams[c].Num := StrToInt(copy(FText, j, i-j));
925 inc(c);
926 end
927 else
928 if FText[i] in ['#', ''''] then begin
929 // Parse string
930 s := '';
931 repeat
932 case FText[i] of
933 ' ',#9,#10,#13: inc(i);
934 '+': begin
935 inc(i);
936 if not (FText[i] in ['''', '#']) then exit(AddError('Expected string or char after +'));
937 end;
938 '#': begin
939 inc(i);
940 j := i;
941 SkipNum(i);
942 if i = j then exit(AddError('Expected number in string after #'));
943 k := StrToInt(copy(FText, j, i-j));
944 if k > 255 then exit(AddError('Argument to long'));
945 s := s + chr(k);
946 end;
947 '''': begin
948 inc(i);
949 repeat
950 case FText[i] of
951 '''': begin
952 if (i+1 <= Length(FText)) and (FText[i+1] = '''') then begin
953 s := s + '''';
954 inc(i,2);
955 end
956 else begin
957 inc(i);
958 break;
959 end;
960 end;
961 else begin
962 s := s + FText[i];
963 inc(i);
964 end;
965 end;
966 until i > Length(FText);
967 end;
968 else
969 break;
970 end;
971 until i > Length(FText);
972
973 SetLength(FParams, c + 1);
974 FParams[c].ParamType := ptString;
975 FParams[c].Text := s;
976 inc(c);
977 end
978 else
979 if FText[i] <> ')' then
980 exit(AddError('Unknown Arguent'));
981
982 SkipSpace(i);
983 if (i >= Length(FText)) then exit(AddError('Missing ")"'));
984 if FText[i] = ')' then break;
985 if not(FText[i] = ',') then exit(AddError('Expected ","'));
986 inc(i);
987 until i > Length(FText);
988 inc(i);
989 end;
990
991 if (i > Length(FText)) then exit(AddError('Missing ";"'));
992 if (FText[i] = ';') then begin
993 Delete(FText, 1, i);
994 Result := True;
995 FHasError := False;
996 exit;
997 end;
998 AddError('Unknown Error');
999 end;
1000
1001 { TSynMacroEventWriter }
1002
1003 constructor TIdeMacroEventWriter.Create;
1004 begin
1005 FUseLineFeed := False;
1006 end;
1007
1008 procedure TIdeMacroEventWriter.BeginEvent;
1009 begin
1010 FCmdName := '';
1011 FParams := '';
1012 end;
1013
1014 procedure TIdeMacroEventWriter.FinishEvent;
1015 begin
1016 FText := FText + FCmdName;
1017 if FParams <> '' then
1018 FText := FText + '(' + FParams + ')';
1019 FText := FText + ';';
1020 if FUseLineFeed then
1021 FText := FText + LineEnding;
1022 end;
1023
1024 procedure TIdeMacroEventWriter.WriteEventCommand(const ACmd: TSynEditorCommand);
1025 begin
1026 EditorCommandToIdent(ACmd, FCmdName);
1027 end;
1028
1029 procedure TIdeMacroEventWriter.WriteEventParam(const AParam: string);
1030 var
1031 s: String;
1032 i: Integer;
1033 InQuotes: Boolean;
1034 begin
1035 if FParams <> '' then
1036 FParams := FParams + ', ';
1037 s := '';
1038 InQuotes := False;
1039 for i := 1 to length(AParam) do
1040 case AParam[i] of
1041 #0..#31: begin
1042 if InQuotes then s := s + '''';
1043 InQuotes := False;
1044 s := s + '#' + IntToStr(ord(AParam[i]));
1045 end;
1046 '''': begin
1047 if not InQuotes then s := s + '''';
1048 InQuotes := True;
1049 s := s + '''''';
1050 end;
1051 else begin
1052 if not InQuotes then s := s + '''';
1053 InQuotes := True;
1054 s := s + AParam[i];
1055 end;
1056 end;
1057 if InQuotes then s := s + '''';
1058 FParams := FParams + s;
1059 end;
1060
1061 procedure TIdeMacroEventWriter.WriteEventParam(const AParam: integer);
1062 begin
1063 if FParams <> '' then
1064 FParams := FParams + ', ';
1065 FParams := FParams + IntToStr(AParam);
1066 end;
1067
1068 { TMacroListView }
1069
1070 procedure TMacroListView.btnRenameClick(Sender: TObject);
1071 var
1072 s: String;
1073 M: TEditorMacro;
1074 begin
1075 if lbMacroView.ItemIndex < 0 then exit;
1076 M := CurrentEditorMacroList.Macros[lbMacroView.ItemIndex];
1077 s := M.MacroName;
1078 if InputQuery(lisNewMacroname2, Format(lisEnterNewNameForMacroS, [m.MacroName]), s)
1079 then begin
1080 while (s <> '') and (CurrentEditorMacroList.IndexOfName(s) >= 0) do begin
1081 case IDEMessageDialog(lisDuplicateName, lisAMacroWithThisNameAlreadyExists, mtWarning,
1082 mbOKCancel) of
1083 mrOK:
1084 if not InputQuery(lisNewMacroname2, Format(lisEnterNewNameForMacroS, [m.MacroName]), s)
1085 then s := '';
1086 else
1087 s := '';
1088 end;
1089 end;
1090
1091 if s <> '' then
1092 M.MacroName := s;
1093 UpdateDisplay;
1094 end;
1095 end;
1096
1097 procedure TMacroListView.btnPlayClick(Sender: TObject);
1098 var
1099 i: Integer;
1100 M: TEditorMacro;
1101 se: TSourceEditorInterface;
1102 begin
1103 if ActiveEditorMacro <> nil then exit;
1104 if lbMacroView.ItemIndex < 0 then exit;
1105 se := SourceEditorManagerIntf.ActiveEditor;
1106 if se = nil then Exit;
1107
1108 i := 1;
1109 if chkRepeat.Checked then i := edRepeat.Value;
1110 FIsPlaying := True;
1111 UpdateButtons;
1112
1113 M := CurrentEditorMacroList.Macros[lbMacroView.ItemIndex];
1114 try
1115 while i > 0 do begin
1116 M.PlaybackMacro(TCustomSynEdit(se.EditorControl));
1117 Application.ProcessMessages;
1118 dec(i);
1119 if not FIsPlaying then break;
1120 end;
1121 finally
1122 FIsPlaying := False;
1123 UpdateButtons;
1124 end;
1125
1126 end;
1127
1128 procedure TMacroListView.btnDeleteClick(Sender: TObject);
1129 var
1130 m: TEditorMacro;
1131 begin
1132 if lbMacroView.ItemIndex < 0 then exit;
1133 if IDEMessageDialog(lisReallyDelete, lisDeleteSelectedMacro, mtConfirmation, [
1134 mbYes, mbNo]) = mrYes
1135 then begin
1136 if SelectedEditorMacro = CurrentEditorMacroList.Macros[lbMacroView.ItemIndex] then begin
1137 SelectedEditorMacro := nil;
1138 end;
1139 m := CurrentEditorMacroList.Macros[lbMacroView.ItemIndex];
1140 CurrentEditorMacroList.Delete(lbMacroView.ItemIndex);
1141 m.Free;
1142 if CurrentEditorMacroList = EditorMacroListProj then Project1.Modified := True;
1143 if CurrentEditorMacroList = EditorMacroListGlob then MainIDEInterface.SaveEnvironment(False);
1144 UpdateDisplay;
1145 end;
1146 end;
1147
1148 procedure TMacroListView.btnEditClick(Sender: TObject);
1149 var
1150 M: TEditorMacro;
1151 begin
1152 if lbMacroView.ItemIndex < 0 then exit;
1153 M := CurrentEditorMacroList.Macros[lbMacroView.ItemIndex];
1154 if M = nil then exit;
1155 LazarusIDE.DoOpenEditorFile(
1156 EditorMacroVirtualDrive+MacroListToName(CurrentEditorMacroList)+'|'+M.MacroName,
1157 -1, -1, [ofVirtualFile, ofInternalFile]);
1158 end;
1159
1160 procedure TMacroListView.btnRecordClick(Sender: TObject);
1161 var
1162 se: TSourceEditorInterface;
1163 begin
1164 se := SourceEditorManagerIntf.ActiveEditor;
1165 if se = nil then Exit;
1166 if (ActiveEditorMacro = nil) and (EditorMacroForRecording.State = emStopped) then
1167 EditorMacroForRecording.RecordMacro(TCustomSynEdit(se.EditorControl))
1168 else
1169 if EditorMacroForRecording.State = emRecording then
1170 EditorMacroForRecording.Pause
1171 else
1172 if EditorMacroForRecording.State = emRecPaused then
1173 EditorMacroForRecording.Resume;
1174 se.EditorControl.SetFocus;
1175 end;
1176
1177 procedure TMacroListView.btnRecordStopClick(Sender: TObject);
1178 begin
1179 FIsPlaying := False;
1180 EditorMacroForRecording.Stop;
1181 end;
1182
1183 procedure TMacroListView.btnSelectClick(Sender: TObject);
1184 begin
1185 if ActiveEditorMacro <> nil then exit;
1186 if lbMacroView.ItemIndex >= 0 then
1187 SelectedEditorMacro := CurrentEditorMacroList.Macros[lbMacroView.ItemIndex]
1188 else
1189 SelectedEditorMacro:= nil;
1190 UpdateDisplay;
1191 end;
1192
1193 procedure TMacroListView.btnSetKeysClick(Sender: TObject);
1194 var
1195 i: integer;
1196 M: TEditorMacro;
1197 begin
1198 if lbMacroView.ItemIndex < 0 then exit;
1199 M := CurrentEditorMacroList.Macros[lbMacroView.ItemIndex];
1200
1201 if (M.KeyBinding = nil) or (M.KeyBinding.IdeCmd = nil) or
1202 not(M.KeyBinding.IdeCmd is TKeyCommandRelation)
1203 then // only for error macros
1204 exit;
1205
1206 i := (IDECommandList as TKeyCommandRelationList).IndexOf(M.KeyBinding.IdeCmd as TKeyCommandRelation);
1207 if (i >= 0) then
1208 if ShowKeyMappingEditForm(i, (IDECommandList as TKeyCommandRelationList)) = mrOK then begin
1209 if CurrentEditorMacroList = EditorMacroListProj then Project1.Modified := True;
1210 if CurrentEditorMacroList = EditorMacroListGlob then MainIDEInterface.SaveEnvironment(False);
1211 end;
1212 UpdateDisplay;
1213 if OnKeyMapReloaded <> nil then OnKeyMapReloaded();
1214 end;
1215
1216 procedure TMacroListView.BtnWarnCloseClick(Sender: TObject);
1217 begin
1218 PanelWarnings.Visible := False;
1219 end;
1220
1221 procedure TMacroListView.DoMacroStateChanged(Sender: TObject);
1222 begin
1223 if OnEditorMacroStateChange <> nil then
1224 OnEditorMacroStateChange(Sender);
1225 end;
1226
1227 procedure TMacroListView.FormActivate(Sender: TObject);
1228 begin
1229 lbMacroView.HideSelection := Active;
1230 end;
1231
1232 procedure TMacroListView.HelpButtonClick(Sender: TObject);
1233 begin
1234 LazarusHelp.ShowHelpForIDEControl(Self);
1235 end;
1236
1237 procedure TMacroListView.lbMacroViewSelectItem(Sender: TObject; Item: TListItem;
1238 Selected: Boolean);
1239 begin
1240 UpdateButtons;
1241 end;
1242
1243 procedure TMacroListView.mnExportClick(Sender: TObject);
1244 var
1245 Conf: TXMLConfig;
1246 begin
1247 if lbMacroView.ItemIndex < 0 then exit;
1248
1249 if SaveDialog1.Execute then begin
1250 Conf := TXMLConfig.Create(SaveDialog1.FileName);
1251 try
1252 Conf.Clear;
1253 Conf.SetValue('EditorMacros/Count', 1);
1254 CurrentEditorMacroList.Macros[lbMacroView.ItemIndex].WriteToXmlConf(Conf, 'EditorMacros/Macro1/');
1255 finally
1256 Conf.Free;
1257 end;
1258 end;
1259 end;
1260
1261 procedure TMacroListView.mnImportClick(Sender: TObject);
1262 var
1263 Conf: TXMLConfig;
1264 NewMacro: TEditorMacro;
1265 begin
1266 if OpenDialog1.Execute then begin
1267 Conf := TXMLConfig.Create(OpenDialog1.FileName);
1268 try
1269 NewMacro := EditorMacroPlayerClass.Create(nil);
1270 NewMacro.OnStateChange := @DoMacroStateChanged;
1271 NewMacro.OnChange := @DoMacroContentChanged;
1272 NewMacro.ReadFromXmlConf(Conf, 'EditorMacros/Macro1/');
1273 if not NewMacro.IsEmpty then begin
1274 CurrentEditorMacroList.Add(NewMacro);
1275 if CurrentEditorMacroList = EditorMacroListProj then Project1.Modified := True;
1276 if CurrentEditorMacroList = EditorMacroListGlob then MainIDEInterface.SaveEnvironment(False);
1277 end
1278 else
1279 NewMacro.Free;
1280 finally
1281 Conf.Free;
1282 end;
1283 end;
1284 end;
1285
1286 procedure TMacroListView.tbIDEClick(Sender: TObject);
1287 begin
1288 CurrentEditorMacroList := EditorMacroListGlob;
1289 UpdateDisplay;
1290 end;
1291
1292 procedure TMacroListView.tbMoveIDEClick(Sender: TObject);
1293 var
1294 i: Integer;
1295 begin
1296 if (lbMacroView.ItemIndex < 0) or (CurrentEditorMacroList = EditorMacroListGlob) then exit;
1297 i := lbMacroView.ItemIndex;
1298 EditorMacroListGlob.Add(CurrentEditorMacroList.Macros[i]);
1299 CurrentEditorMacroList.Delete(i);
1300 if CurrentEditorMacroList = EditorMacroListProj then Project1.Modified := True;
1301 MainIDEInterface.SaveEnvironment(False);
1302 UpdateDisplay;
1303 end;
1304
1305 procedure TMacroListView.tbMoveProjectClick(Sender: TObject);
1306 var
1307 i: Integer;
1308 begin
1309 if (lbMacroView.ItemIndex < 0) or (CurrentEditorMacroList = EditorMacroListProj) then exit;
1310 i := lbMacroView.ItemIndex;
1311 EditorMacroListProj.Add(CurrentEditorMacroList.Macros[i]);
1312 CurrentEditorMacroList.Delete(i);
1313 Project1.Modified := True;
1314 if CurrentEditorMacroList = EditorMacroListGlob then MainIDEInterface.SaveEnvironment(False);
1315 UpdateDisplay;
1316 end;
1317
1318 procedure TMacroListView.tbProjectClick(Sender: TObject);
1319 begin
1320 CurrentEditorMacroList := EditorMacroListProj;
1321 UpdateDisplay;
1322 end;
1323
1324 procedure TMacroListView.tbRecordedClick(Sender: TObject);
1325 begin
1326 CurrentEditorMacroList := EditorMacroListRec;
1327 UpdateDisplay;
1328 end;
1329
1330 procedure TMacroListView.DoOnMacroListChange(Sender: TObject);
1331 begin
1332 UpdateDisplay;
1333
1334 if Sender = EditorMacroListProj then
1335 Project1.SessionModified := True;
1336 end;
1337
1338 procedure TMacroListView.DoMacroContentChanged(Sender: TObject);
1339 begin
1340 if FIgnoreMacroChanges then exit;
1341
1342 if EditorMacroListProj.IndexOf(Sender as TEditorMacro) >= 0 then
1343 Project1.Modified := True;
1344 if EditorMacroListGlob.IndexOf(Sender as TEditorMacro) >= 0 then
1345 MainIDEInterface.SaveEnvironment(False);
1346 end;
1347
1348 procedure TMacroListView.UpdateDisplay;
1349 var
1350 NewItem: TListItem;
1351 i, idx: Integer;
1352 M: TEditorMacro;
1353 begin
1354 idx := lbMacroView.ItemIndex;
1355 lbMacroView.Items.Clear;
1356
1357 for i := 0 to CurrentEditorMacroList.Count - 1 do begin
1358 M := CurrentEditorMacroList.Macros[i];
1359 NewItem := lbMacroView.Items.Add;
1360
1361 if m.KeyBinding <> nil then
1362 NewItem.Caption := M.MacroName + M.KeyBinding.ShortCutAsText
1363 else
1364 NewItem.Caption := M.MacroName;
1365
1366 // Image
1367 if M.IsInvalid then
1368 NewItem.ImageIndex := FImageErr
1369 else
1370 if (m = CurrentRecordingMacro) then
1371 NewItem.ImageIndex := FImageRec
1372 else
1373 if (CurrentRecordingMacro = nil) then begin // If recording, then recorder is selected
1374 If (M = ActiveEditorMacro) and (M.State = emPlaying) then
1375 NewItem.ImageIndex := FImagePlay
1376 else
1377 if (M = SelectedEditorMacro) then
1378 NewItem.ImageIndex := FImageSel;
1379 end;
1380 end;
1381 if idx < lbMacroView.Items.Count then
1382 lbMacroView.ItemIndex := idx
1383 else
1384 lbMacroView.ItemIndex := -1;
1385
1386 lbMacroViewSelectItem(nil, nil, False);
1387 UpdateButtons;
1388 end;
1389
1390 procedure TMacroListView.UpdateButtons;
1391 var
1392 IsSel, IsErr, IsStopped: Boolean;
1393 M: TEditorMacro;
1394 RecState: TEditorMacroState;
1395 begin
1396 IsSel := (lbMacroView.ItemIndex >= 0);
1397 IsStopped := (ActiveEditorMacro = nil);
1398 if IsSel then
1399 M := CurrentEditorMacroList.Macros[lbMacroView.ItemIndex];
1400 IsErr := IsSel and M.IsInvalid;
1401 RecState := emStopped;
1402 if EditorMacroForRecording <> nil then
1403 RecState := EditorMacroForRecording.State;
1404
1405 btnSelect.Enabled := IsStopped and IsSel and (not FIsPlaying) and (not IsErr);
1406 btnRename.Enabled := IsStopped and IsSel and (not FIsPlaying) and (not IsErr);
1407 btnEdit.Enabled := IsStopped and IsSel and (not FIsPlaying);
1408 btnSetKeys.Enabled := IsStopped and IsSel and (not FIsPlaying) and (not IsErr);
1409 btnDelete.Enabled := IsStopped and IsSel and (not FIsPlaying);
1410
1411 btnPlay.Enabled := IsStopped and IsSel and (not FIsPlaying) and (not IsErr);
1412 chkRepeat.Enabled := IsStopped and (not FIsPlaying);
1413 edRepeat.Enabled := IsStopped and (not FIsPlaying);
1414
1415 btnRecord.Enabled := Assigned(SourceEditorManagerIntf.ActiveEditor)
1416 and (RecState in [emStopped, emRecPaused, emRecording])
1417 and (not FIsPlaying);
1418 btnRecordStop.Enabled := (not IsStopped) or FIsPlaying;
1419
1420 if (RecState = emRecording) then
1421 btnRecord.Caption := lisPause
1422 else if (RecState = emRecPaused) then
1423 btnRecord.Caption := lisContinue
1424 else
1425 btnRecord.Caption := lisRecord;
1426
1427 mnImport.Enabled := IsStopped and (not FIsPlaying);
1428 mnExport.Enabled := IsStopped and IsSel and (not FIsPlaying);
1429
1430 tbMoveProject.Visible := CurrentEditorMacroList <> EditorMacroListProj;
1431 tbMoveProject.Enabled := IsStopped and IsSel and (not FIsPlaying);
1432 tbMoveIDE.Visible := CurrentEditorMacroList <> EditorMacroListGlob;
1433 tbMoveIDE.Enabled := IsStopped and IsSel and (not FIsPlaying);
1434
1435 Update;
1436 end;
1437
MacroByFullNamenull1438 function TMacroListView.MacroByFullName(AName: String): TEditorMacro;
1439 const
1440 FolderStart = length(EditorMacroVirtualDrive)+1;
1441 NameStart = FolderStart+length('PRJ|');
1442 var
1443 Alist: TEditorMacroList;
1444 i: Integer;
1445 begin
1446 Result := nil;
1447 If not LazStartsStr(EditorMacroVirtualDrive, AName) or
1448 (copy(AName, NameStart-1, 1) <> '|')
1449 then exit;
1450 Alist := NameToMacroList(copy(AName, FolderStart, 3));
1451 if (Alist = nil) then exit;
1452 i := Alist.IndexOfName(copy(AName, NameStart, length(AName)));
1453 if i < 0 then exit;
1454 Result := Alist.Macros[i];
1455 end;
1456
1457 procedure TMacroListView.DoEditorMacroStateChanged;
1458 begin
1459 UpdateDisplay;
1460 end;
1461
1462 constructor TMacroListView.Create(TheOwner: TComponent);
1463 begin
1464 inherited Create(TheOwner);
1465 FIgnoreMacroChanges := False;
1466
1467 Caption := lisEditorMacros;
1468 EditorMacroListRec.OnChange := @DoOnMacroListChange;
1469 EditorMacroListProj.OnChange := @DoOnMacroListChange;
1470 EditorMacroListGlob.OnChange := @DoOnMacroListChange;
1471
1472 tbRecorded.Caption := lisRecordedMacros;
1473 tbProject.Caption := lisProjectMacro;
1474 tbIDE.Caption := lisIDE;
1475 tbRecorded.Hint := lisNewRecordedMacrosNotToBeSaved;
1476 tbProject.Hint := lisSavedWithProjectSession;
1477 tbIDE.Hint := lisSavedWithIDESettings;
1478 tbMoveProject.Caption := lisProjectMacro;
1479 tbMoveIDE.Caption := lisIDE;
1480 lbMoveTo.Caption := lisMoveTo + ' '; // Anchors do not work here. Use spaces.
1481
1482 btnSelect.Caption := lisMakeCurrent;
1483 btnRename.Caption := lisRename2;
1484 btnSetKeys.Caption := lisEditKey;
1485 btnEdit.Caption := lisEdit;
1486 btnDelete.Caption := lisDelete;
1487 btnPlay.Caption := lisPlay;
1488 chkRepeat.Caption := lisRepeat;
1489 btnRecord.Caption := lisRecord;
1490 btnRecordStop.Caption := lisStop;
1491
1492 SaveDialog1.Title := lisSaveMacroAs;
1493 OpenDialog1.Title := lisLoadMacroFrom;
1494 mnImport.Caption := lisDlgImport;
1495 mnExport.Caption := lisDlgExport;
1496
1497 lbMacroView.SmallImages := IDEImages.Images_16;
1498 FImageRec := IDEImages.LoadImage('Record'); // red dot
1499 FImagePlay := IDEImages.LoadImage('menu_run'); // green triangle
1500 FImageSel := IDEImages.LoadImage('arrow_right');
1501 FImageErr := IDEImages.LoadImage('state_error');
1502 FIsPlaying := False;
1503
1504 BtnWarnClose.Images := IDEImages.Images_16;
1505 BtnWarnClose.ImageIndex := IDEImages.LoadImage('menu_close');
1506
1507 UpdateDisplay;
1508 end;
1509
1510 destructor TMacroListView.Destroy;
1511 begin
1512 inherited Destroy;
1513 end;
1514
1515 { TEditorMacroList }
1516
TEditorMacroList.GetMacrosnull1517 function TEditorMacroList.GetMacros(Index: Integer): TEditorMacro;
1518 begin
1519 Result := TEditorMacro(FList[Index]);
1520 end;
1521
1522 procedure TEditorMacroList.DoChanged;
1523 begin
1524 if Assigned(FOnChange) then
1525 FOnChange(Self);
1526 end;
1527
1528 procedure TEditorMacroList.DoAdded(AMacro: TEditorMacro);
1529 begin
1530 if Assigned(FOnAdded) then
1531 FOnAdded(Self, AMacro);
1532 end;
1533
1534 procedure TEditorMacroList.DoRemove(AMacro: TEditorMacro);
1535 begin
1536 if Assigned(FOnRemove) then
1537 FOnRemove(Self, AMacro);
1538 end;
1539
1540 constructor TEditorMacroList.Create;
1541 begin
1542 FList := TList.Create;
1543 end;
1544
1545 destructor TEditorMacroList.Destroy;
1546 begin
1547 FOnChange := nil;
1548 ClearAndFreeMacros;
1549 FreeAndNil(FList);
1550 inherited Destroy;
1551 end;
1552
1553 procedure TEditorMacroList.WriteToXmlConf(AConf: TXMLConfig; const APath: String);
1554 var
1555 i: Integer;
1556 begin
1557 AConf.SetDeleteValue(APath + 'EditorMacros/Count', Count, 0);
1558 for i := 0 to Count - 1 do
1559 Macros[i].WriteToXmlConf(AConf, 'EditorMacros/Macro'+IntToStr(i+1)+'/');
1560 end;
1561
1562 procedure TEditorMacroList.ReadFromXmlConf(AConf: TXMLConfig; const APath: String);
1563 var
1564 c, i: Integer;
1565 NewMacro: TEditorMacro;
1566 begin
1567 ClearAndFreeMacros;
1568 c := AConf.GetValue(APath + 'EditorMacros/Count', 0);
1569 for i := 0 to c -1 do begin
1570 NewMacro := EditorMacroPlayerClass.Create(nil);
1571 NewMacro.OnStateChange := @MacroListViewer.DoMacroStateChanged;
1572 NewMacro.OnChange := @MacroListViewer.DoMacroContentChanged;
1573 try
1574 NewMacro.ReadFromXmlConf(AConf, 'EditorMacros/Macro'+IntToStr(i+1)+'/');
1575 finally
1576 Add(NewMacro)
1577 end;
1578 end;
1579 end;
1580
1581 procedure TEditorMacroList.ClearAndFreeMacros;
1582 var
1583 i: Integer;
1584 begin
1585 for i := 0 to Count - 1 do
1586 Macros[i].Free;
1587 FList.Clear;
1588 end;
1589
Countnull1590 function TEditorMacroList.Count: Integer;
1591 begin
1592 Result := FList.Count;
1593 end;
1594
IndexOfnull1595 function TEditorMacroList.IndexOf(AMacro: TEditorMacro): Integer;
1596 begin
1597 Result := FList.IndexOf(AMacro);
1598 end;
1599
IndexOfNamenull1600 function TEditorMacroList.IndexOfName(AName: String): Integer;
1601 begin
1602 Result := Count - 1;
1603 while Result >= 0 do
1604 if Macros[Result].MacroName = AName
1605 then break
1606 else dec(Result);
1607 end;
1608
UniqNamenull1609 function TEditorMacroList.UniqName(AName: String): String;
1610 var
1611 i: Integer;
1612 begin
1613 Result := AName;
1614 if IndexOfName(AName) < 0 then exit;
1615 i := 1;
1616 while IndexOfName(AName+'_'+IntToStr(i)) >= 0 do
1617 inc(i);
1618 Result := AName+'_'+IntToStr(i);
1619 end;
1620
Addnull1621 function TEditorMacroList.Add(AMacro: TEditorMacro): Integer;
1622 begin
1623 AMacro.MacroName := UniqName(AMacro.MacroName);
1624 Result := FList.Add(AMacro);
1625 DoAdded(AMacro);
1626 DoChanged;
1627 end;
1628
1629 procedure TEditorMacroList.Delete(AnIndex: Integer);
1630 begin
1631 DoRemove(Macros[AnIndex]);
1632 FList.Delete(AnIndex);
1633 DoChanged;
1634 end;
1635
1636 procedure TEditorMacroList.Remove(AMacro: TEditorMacro);
1637 begin
1638 DoRemove(AMacro);
1639 FList.Remove(AMacro);
1640 DoChanged;
1641 end;
1642
1643 // itmMacroListView.enabled
1644
1645 {$R *.lfm}
1646
1647 initialization
1648 if EditorMacroPlayerClass = nil then
1649 EditorMacroPlayerClass := TIdeEditorMacro;
1650 if DefaultBindingClass = nil then
1651 DefaultBindingClass := TIdeEditorMacroKeyBinding;
1652 EditorMacroListRec := TEditorMacroList.Create;
1653 EditorMacroListProj := TEditorMacroList.Create;
1654 EditorMacroListGlob := TEditorMacroList.Create;
1655 CurrentEditorMacroList := EditorMacroListRec;
1656
1657 MacroListViewerWarningChanged := @DoMacroListViewerWarningChanged;
1658
1659 finalization
1660 CurrentEditorMacroList := nil;
1661 FreeAndNil(EditorMacroListRec);
1662 FreeAndNil(EditorMacroListProj);
1663 FreeAndNil(EditorMacroListGlob);
1664
1665 end.
1666
1667