1 {-------------------------------------------------------------------------------
2 The contents of this file are subject to the Mozilla Public License
3 Version 1.1 (the "License"); you may not use this file except in compliance
4 with the License. You may obtain a copy of the License at
5 http://www.mozilla.org/MPL/
6 
7 Software distributed under the License is distributed on an "AS IS" basis,
8 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
9 the specific language governing rights and limitations under the License.
10 
11 The Original Code is: SynEditPlugins.pas, released 2001-10-17.
12 
13 Author of this file is Flávio Etrusco.
14 Portions created by Flávio Etrusco are Copyright 2001 Flávio Etrusco.
15 All Rights Reserved.
16 
17 Contributors to the SynEdit project are listed in the Contributors.txt file.
18 
19 Alternatively, the contents of this file may be used under the terms of the
20 GNU General Public License Version 2 or later (the "GPL"), in which case
21 the provisions of the GPL are applicable instead of those above.
22 If you wish to allow use of your version of this file only under the terms
23 of the GPL and not to allow others to use your version of this file
24 under the MPL, indicate your decision by deleting the provisions above and
25 replace them with the notice and other provisions required by the GPL.
26 If you do not delete the provisions above, a recipient may use your version
27 of this file under either the MPL or the GPL.
28 
29 $Id$
30 
31 You may retrieve the latest version of this file at the SynEdit home page,
32 located at http://SynEdit.SourceForge.net
33 
34 Known Issues:
35 -------------------------------------------------------------------------------}
36 
37 unit SynEditPlugins;
38 
39 {$I synedit.inc}
40 
41 interface
42 
43 uses
44   Classes, Menus, LCLType, SysUtils,
45   SynEdit, SynEditKeyCmds, SynEditTypes, SynEditStrConst, SynEditMiscClasses;
46 
47 type
48 
49   { TLazSynMultiEditPlugin }
50 
51   TLazSynMultiEditPlugin = class(TLazSynEditPlugin)
52   private
53     FEditors: TList;
54     FEditorWasAdded: Boolean;
GetEditorCountnull55     function GetEditorCount: integer;
GetEditorsnull56     function GetEditors(aIndex: integer): TCustomSynEdit;
57   protected
IndexOfEditornull58     function  IndexOfEditor(const AValue: TCustomSynEdit): integer;
59     procedure BeforeEditorChange; override;
60     procedure AfterEditorChange; override;
DoRemoveEditornull61     function  DoRemoveEditor(AValue: TCustomSynEdit): integer;
62     procedure DoEditorDestroyed(const AValue: TCustomSynEdit); override;
63   public
64     destructor Destroy; override;
65     (* Add/RemoveEditor versus Editor
66        Unless stated otherwise Plugins inherting from TLazSynMultiEditPlugin can
67        either use Add/RemoveEditor or Editor (single-editor-property).
68        If Editors are added via AddEditor, then an Editor only set via "Editor:="
69        may be lost/ignored.
70        If using AddEditor, the "Editor" property may be used to set/read the
71        current Editor (out of those in the list). This does however depend on the
72        inherited class.
73     *)
AddEditornull74     function  AddEditor(AValue: TCustomSynEdit): integer;
RemoveEditornull75     function  RemoveEditor(AValue: TCustomSynEdit): integer;
76     property Editors[aIndex: integer]: TCustomSynEdit read GetEditors;
77     property EditorCount: integer read GetEditorCount;
78   end;
79 
80   TAbstractSynPlugin = TLazSynMultiEditPlugin;
81 
82   TAbstractSynHookerPlugin = class(TAbstractSynPlugin)
83   protected
84     procedure HookEditor(aEditor: TCustomSynEdit; aCommandID: TSynEditorCommand;
85       aOldShortCut, aNewShortCut: TShortCut; AFlags: THookedCommandFlags = [hcfPreExec, hcfPostExec]);
86     procedure UnHookEditor(aEditor: TCustomSynEdit;
87       aCommandID: TSynEditorCommand; aShortCut: TShortCut);
88     procedure OnCommand(Sender: TObject; AfterProcessing: boolean;
89       var Handled: boolean; var Command: TSynEditorCommand;
90       var aChar: TUTF8Char;
91       Data: pointer; HandlerData: pointer); virtual; abstract;
92   end;
93 
94   TPluginState = (psNone, psExecuting, psAccepting, psCancelling);
95 
96   TAbstractSynSingleHookPlugin = class(TAbstractSynHookerPlugin)
97   private
98     fCommandID: TSynEditorCommand;
IsShortCutStorednull99     function IsShortCutStored: Boolean;
100     procedure SetShortCut(const Value: TShortCut);
101   protected
102     fState: TPluginState;
103     fCurrentEditor: TCustomSynEdit;
104     fShortCut: TShortCut;
DefaultShortCutnull105     class function DefaultShortCut: TShortCut; virtual;
106     procedure DoEditorAdded(aEditor: TCustomSynEdit); override;
107     procedure DoEditorRemoving(aEditor: TCustomSynEdit); override;
108     {}
109     procedure DoExecute; virtual; abstract;
110     procedure DoAccept; virtual; abstract;
111     procedure DoCancel; virtual; abstract;
112   public
113     constructor Create(aOwner: TComponent); override;
114     destructor Destroy; override;
115     property CommandID: TSynEditorCommand read fCommandID;
116     {}
117     property CurrentEditor: TCustomSynEdit read fCurrentEditor;
Executingnull118     function Executing: boolean;
119     procedure Execute(aEditor: TCustomSynEdit);
120     procedure Accept;
121     procedure Cancel;
122   published
123     property ShortCut: TShortCut read fShortCut write SetShortCut
124       stored IsShortCutStored;
125   end deprecated;
126 
127   { use TAbstractSynCompletion for non-visual completion }
128 
129   TAbstractSynCompletion = class(TAbstractSynSingleHookPlugin)
130   protected
131     fCurrentString: String;
132   protected
133     procedure SetCurrentString(const Value: String); virtual;
134     procedure OnCommand(Sender: TObject; AfterProcessing: boolean;
135       var Handled: boolean; var Command: TSynEditorCommand;
136       var aChar: TUTF8Char;
137       Data: pointer; HandlerData: pointer); override;
138     procedure DoExecute; override;
139     procedure DoAccept; override;
140     procedure DoCancel; override;
GetCurrentEditorStringnull141     function GetCurrentEditorString: String; virtual;
142   public
143     procedure AddEditor(aEditor: TCustomSynEdit);
144     property CurrentString: String read fCurrentString write SetCurrentString;
145   end deprecated;
146 
NewPluginCommandnull147 function NewPluginCommand: TSynEditorCommand; deprecated; // Use AllocatePluginKeyRange
148 procedure ReleasePluginCommand(aCmd: TSynEditorCommand); deprecated;
149 
150 implementation
151 
NewPluginCommandnull152 function NewPluginCommand: TSynEditorCommand;
153 begin
154   Result := AllocatePluginKeyRange(1);
155 end;
156 
157 procedure ReleasePluginCommand(aCmd: TSynEditorCommand);
158 begin
159 end;
160 
161 { TLazSynMultiEditPlugin }
162 
TLazSynMultiEditPlugin.GetEditorCountnull163 function TLazSynMultiEditPlugin.GetEditorCount: integer;
164 begin
165   if FEditors = nil then
166     Result := 0
167   else
168     Result := FEditors.Count;
169 end;
170 
GetEditorsnull171 function TLazSynMultiEditPlugin.GetEditors(aIndex: integer): TCustomSynEdit;
172 begin
173   if FEditors = nil then
174     Result := nil
175   else
176     Result := TCustomSynEdit(FEditors[aIndex]);
177 end;
178 
IndexOfEditornull179 function TLazSynMultiEditPlugin.IndexOfEditor(const AValue: TCustomSynEdit): integer;
180 begin
181   if FEditors = nil then
182     Result := -1
183   else
184     Result := FEditors.IndexOf(AValue);
185 end;
186 
187 procedure TLazSynMultiEditPlugin.BeforeEditorChange;
188 begin
189   if (Editor = nil) or FEditorWasAdded then
190     exit;
191 
192   // Current Editor was not explicitly added by user via "AddEditor"
193   DoRemoveEditor(Editor);
194 end;
195 
196 procedure TLazSynMultiEditPlugin.AfterEditorChange;
197 begin
198   if (Editor = nil) then
199     exit;
200   FEditorWasAdded := IndexOfEditor(Editor) >= 0;
201   if FEditorWasAdded  then
202     exit;
203 
204   // Current Editor was not explicitly added by user via "AddEditor"
205   // Temporary add it
206   AddEditor(Editor);
207   FEditorWasAdded := False; // Reset Flag, after AddEditor
208 end;
209 
DoRemoveEditornull210 function TLazSynMultiEditPlugin.DoRemoveEditor(AValue: TCustomSynEdit): integer;
211 begin
212   if IndexOfEditor(AValue) < 0 then begin
213     Result := -1;
214     Exit;
215   end;
216 
217   DoEditorRemoving(AValue);
218   UnRegisterFromEditor(AValue);
219   Result := FEditors.Remove(AValue);
220 end;
221 
222 procedure TLazSynMultiEditPlugin.DoEditorDestroyed(const AValue: TCustomSynEdit);
223 begin
224   RemoveEditor(AValue);
225   if EditorCount = 0 then
226     inherited DoEditorDestroyed(nil); // Editor is nil now, so pass nil as param too
227 end;
228 
TLazSynMultiEditPlugin.AddEditornull229 function TLazSynMultiEditPlugin.AddEditor(AValue: TCustomSynEdit): integer;
230 begin
231   if AValue = Editor then
232     FEditorWasAdded := True;
233 
234   if IndexOfEditor(AValue) >= 0 then begin
235     Result := -1;
236     Exit;
237   end;
238 
239   if FEditors = nil then
240     FEditors := TList.Create;
241   Result := FEditors.Add(AValue);
242   RegisterToEditor(AValue);
243   DoEditorAdded(AValue);
244 end;
245 
RemoveEditornull246 function TLazSynMultiEditPlugin.RemoveEditor(AValue: TCustomSynEdit): integer;
247 begin
248   if AValue = Editor then
249     Editor := nil;
250 
251   Result := DoRemoveEditor(AValue);
252 end;
253 
254 destructor TLazSynMultiEditPlugin.Destroy;
255 begin
256   while EditorCount > 0 do
257     RemoveEditor(Editors[0]);
258   FreeAndNil(FEditors);
259   inherited Destroy;
260 end;
261 
262 
263 { TAbstractSynHookerPlugin }
264 
265 procedure TAbstractSynHookerPlugin.HookEditor(aEditor: TCustomSynEdit;
266   aCommandID: TSynEditorCommand; aOldShortCut, aNewShortCut: TShortCut;
267   AFlags: THookedCommandFlags = [hcfPreExec, hcfPostExec]);
268 var
269   iIndex: integer;
270   iKeystroke: TSynEditKeyStroke;
271 begin
272   Assert( aNewShortCut <> 0 );
273   { shortcurts aren't created while in design-time }
274   if [csDesigning] * ComponentState = [csDesigning] then
275   begin
276     if TCustomSynEdit(aEditor).Keystrokes.FindShortcut( aNewShortCut ) >= 0 then
277       raise ESynKeyError.Create(SYNS_EDuplicateShortCut)
278     else
279       Exit;
280   end;
281   { tries to update old Keystroke }
282   if aOldShortCut <> 0 then
283   begin
284     iIndex := TCustomSynEdit(aEditor).Keystrokes.FindShortcut( aOldShortCut );
285     if (iIndex >= 0) then
286     begin
287       iKeystroke := TCustomSynEdit(aEditor).Keystrokes[iIndex];
288       if iKeystroke.Command = aCommandID then
289       begin
290         iKeystroke.ShortCut := aNewShortCut;
291         Exit;
292       end;
293     end;
294   end;
295   { new Keystroke }
296   iKeystroke := TCustomSynEdit(aEditor).Keystrokes.Add;
297   try
298     iKeystroke.ShortCut := aNewShortCut;
299   except
300     iKeystroke.Free;
301     raise;
302   end;
303   iKeystroke.Command := aCommandID;
304 
305   if AFlags <> [] then
306     aEditor.RegisterCommandHandler( @OnCommand, Self, AFlags);
307 end;
308 
309 procedure TAbstractSynHookerPlugin.UnHookEditor(aEditor: TCustomSynEdit;
310   aCommandID: TSynEditorCommand; aShortCut: TShortCut);
311 var
312   iIndex: integer;
313 begin
314   aEditor.UnregisterCommandHandler( @OnCommand );
315   iIndex := TCustomSynEdit(aEditor).Keystrokes.FindShortcut( aShortCut );
316   if (iIndex >= 0) and
317     (TCustomSynEdit(aEditor).Keystrokes[iIndex].Command = aCommandID) then
318     TCustomSynEdit(aEditor).Keystrokes[iIndex].Free;
319 end;
320 
321 { TAbstractSynHookerPlugin }
322 
323 procedure TAbstractSynSingleHookPlugin.Accept;
324 begin
325   fState := psAccepting;
326   try
327     DoAccept;
328   finally
329     fCurrentEditor := nil;
330     fState := psNone;
331   end;
332 end;
333 
334 procedure TAbstractSynSingleHookPlugin.Cancel;
335 begin
336   fState := psCancelling;
337   try
338     DoCancel;
339   finally
340     fCurrentEditor := nil;
341     fState := psNone;
342   end;
343 end;
344 
345 constructor TAbstractSynSingleHookPlugin.Create(aOwner: TComponent);
346 begin
347   inherited;
348   // TODO: subclasses should implement per class, not per instance
349   fCommandID := AllocatePluginKeyRange(1);
350   fShortCut := DefaultShortCut;
351 end;
352 
TAbstractSynSingleHookPlugin.DefaultShortCutnull353 class function TAbstractSynSingleHookPlugin.DefaultShortCut: TShortCut;
354 begin
355   Result := 0;
356 end;
357 
358 destructor TAbstractSynSingleHookPlugin.Destroy;
359 begin
360   if Executing then
361     Cancel;
362   //ReleasePluginCommand( CommandID );
363   inherited;
364 end;
365 
366 procedure TAbstractSynSingleHookPlugin.DoEditorAdded(
367   aEditor: TCustomSynEdit);
368 begin
369   if ShortCut <> 0 then
370     HookEditor( aEditor, CommandID, 0, ShortCut );
371 end;
372 
373 procedure TAbstractSynSingleHookPlugin.Execute(aEditor: TCustomSynEdit);
374 begin
375   if Executing then
376     Cancel;
377   Assert( fCurrentEditor = nil );
378   fCurrentEditor := aEditor;
379   Assert( fState = psNone );
380   fState := psExecuting;
381   try
382     DoExecute;
383   except
384     Cancel;
385     raise;
386   end;
387 end;
388 
Executingnull389 function TAbstractSynSingleHookPlugin.Executing: boolean;
390 begin
391   Result := fState = psExecuting;
392 end;
393 
IsShortCutStorednull394 function TAbstractSynSingleHookPlugin.IsShortCutStored: Boolean;
395 begin
396   Result := fShortCut <> DefaultShortCut;
397 end;
398 
399 procedure TAbstractSynSingleHookPlugin.DoEditorRemoving(aEditor: TCustomSynEdit);
400 begin
401   if ShortCut <> 0 then
402     UnHookEditor( aEditor, CommandID, ShortCut );
403   if Executing and (CurrentEditor = aEditor) then
404     Cancel;
405 end;
406 
407 procedure TAbstractSynSingleHookPlugin.SetShortCut(const Value: TShortCut);
408 var
409   cEditor: integer;
410 begin
411   if fShortCut <> Value then
412   begin
413     if Assigned(fEditors) then
414       if Value <> 0 then
415       begin
416         for cEditor := 0 to fEditors.Count -1 do
417           HookEditor( Editors[cEditor], CommandID, fShortCut, Value );
418       end
419       else
420       begin
421         for cEditor := 0 to fEditors.Count -1 do
422           UnHookEditor( Editors[cEditor], CommandID, fShortCut );
423       end;
424     fShortCut := Value;
425   end;
426 end;
427 
428 { TAbstractSynCompletion }
429 
GetCurrentEditorStringnull430 function TAbstractSynCompletion.GetCurrentEditorString: String;
431 var
432   iString: String;
433   cCol: integer;
434   iIdentChars: TSynIdentChars;
435 begin
436   Result := '';
437   iString := CurrentEditor.LineText;
438   if (CurrentEditor.CaretX > 1) and
439     (CurrentEditor.CaretX -1 <= Length(iString)) then
440   begin
441     iIdentChars := CurrentEditor.IdentChars;
442     for cCol := CurrentEditor.CaretX -1 downto 1 do
443       if not (iString[cCol] in iIdentChars) then
444         break;
445     Result := Copy( iString, cCol +1, CurrentEditor.CaretX - cCol -1);
446   end;
447 end;
448 
449 procedure TAbstractSynCompletion.DoAccept;
450 begin
451   fCurrentString := '';
452 end;
453 
454 procedure TAbstractSynCompletion.DoCancel;
455 begin
456   fCurrentString := '';
457 end;
458 
459 procedure TAbstractSynCompletion.DoExecute;
460 begin
461   CurrentString := GetCurrentEditorString;
462 end;
463 
464 procedure TAbstractSynCompletion.OnCommand(Sender: TObject;
465   AfterProcessing: boolean; var Handled: boolean;
466   var Command: TSynEditorCommand;
467   var aChar: TUTF8Char;
468   Data, HandlerData: pointer);
469 var
470   iString: String;
471 begin
472   if not Executing then
473   begin
474     if (Command = CommandID) then
475     begin
476       Execute( Sender as TCustomSynEdit );
477       Handled := True;
478     end;
479   end
480   else { Executing }
481     if Sender = CurrentEditor then
482     begin
483       if not AfterProcessing then
484       begin
485           case Command of
486             ecChar:
487               if aChar = #27 then
488               begin
489                 Cancel;
490                 Handled := True;
491               end
492               else
493               begin
494                 if (length(aChar) <> 1)
495                 or (not (aChar[1] in CurrentEditor.IdentChars)) then
496                   Accept;
497               end;
498             ecLineBreak:
499             begin
500               Accept;
501               Handled := True;
502             end;
503             ecLeft, ecSelLeft, ecColSelLeft:
504               if CurrentString = '' then
505                 Handled := True;
506             ecDeleteLastChar:
507               if CurrentString = '' then
508                 Handled := True;
509             ecTab:
510               Accept;
511             ecDeleteChar,
512             ecRight, ecSelRight, ecColSelRight,
513             ecLostFocus, ecGotFocus:
514               ; {processed on AfterProcessing}
515             else
516               Cancel;
517           end;
518       end
519       else { AfterProcessing }
520         case Command of
521           ecLostFocus, ecGotFocus,
522           ecDeleteChar:
523             ;
524           ecDeleteLastChar,
525           ecLeft, ecSelLeft, ecColSelLeft,
526           ecChar:
527             CurrentString := GetCurrentEditorString;
528           ecRight, ecSelRight, ecColSelRight: begin
529             iString := GetCurrentEditorString;
530             if iString = '' then
531               Cancel
532             else
533               CurrentString := iString;
534           end;
535           else
536             if CurrentString <> GetCurrentEditorString then
537               Cancel;
538         end;
539     end; {endif Sender = CurrentEditor}
540 end;
541 
542 procedure TAbstractSynCompletion.SetCurrentString(const Value: String);
543 begin
544   fCurrentString := Value;
545 end;
546 
547 procedure TAbstractSynCompletion.AddEditor(aEditor: TCustomSynEdit);
548 begin
549   inherited AddEditor(aEditor);
550 end;
551 
552 end.
553