xtool/contrib/mORMot/SynMemoEx.pas

7426 lines
206 KiB
ObjectPascal

/// Synopse extended TMemo visual component
// - licensed under a MPL/GPL/LGPL tri-license; version 2.26
unit SynMemoEx;
{
This file is part of Synopse extended TMemo
Synopse SynMemoEx. Copyright (C) 2022 Arnaud Bouchez
Synopse Informatique - https://synopse.info
*** BEGIN LICENSE BLOCK *****
Version: MPL 1.1/GPL 2.0/LGPL 2.1
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 Synopse SynMemoEx.
The Initial Developer of the Original Code is Arnaud Bouchez.
Portions created by the Initial Developer are Copyright (C) 2022
the Initial Developer. All Rights Reserved.
Contributor(s):
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****
Based on TMemoEx, by R&A Library 2.03 (c) R&A, 1996-2000.
R&A Library seems now unsupported. Web site is dead, as is their email address.
We decided to put this unit, mostly rewritten, under a MPL/GPL/LGPL tri-license.
* full code rewrite for speed, stability and new features
* regroup in one unit w/out RA Consts
* no expandTabs: too slow
* FAttrs are calculated on the fly: spare RAM on syntax highlight (only text is stored)
* don't use FLines.text on often-call methods (insertText, deleteSelected...)
* fClipProtect: clipBoard Copy/Cut limited to 2K for others programs (copyrights)
* find (text) command
* a lot of bug fixes
Version 1.18
- Unicode compatibility
}
{$I Synopse.inc}
interface
{.$define CLIPBOARDPROTECT} // if defined, ClipProtect truncates clipboard to 2KB
// use only with ONE TMemoEx at once
{$IFNDEF MEMOEX_NOEDITOR}
{$DEFINE MEMOEX_EDITOR} {if not MEMOEX_EDITOR then mode = Viewer}
{$ENDIF}
{$DEFINE MEMOEX_DEFLAYOUT} {set default keyboard layout}
{$IFNDEF MEMOEX_NOUNDO}
{$DEFINE MEMOEX_UNDO} {enable undo}
{$ENDIF}
{$IFNDEF MEMOEX_NOCOMPLETION}
{$DEFINE MEMOEX_COMPLETION} {enable code completion}
{$ENDIF}
{$IFNDEF MEMOEX_EDITOR}
{$UNDEF MEMOEX_DEFLAYOUT}
{$UNDEF MEMOEX_UNDO}
{$UNDEF MEMOEX_COMPLETION}
{$ENDIF MEMOEX_EDITOR}
{ $D-,L-} // avoid jumping in the source for any EComplete exceptions e.g.
{$I Synopse.inc} // define HASINLINE CPU32 CPU64 OWNNORMTOUPPER
uses
Windows,
Messages,
SysUtils,
Classes,
Types,
Graphics,
Controls,
Forms,
ExtCtrls,
StdCtrls,
ClipBrd,
Menus
{$ifdef FPC} // FPC compatibility by alf (alfred) - thanks for the patch!
, LCLType
{$endif FPC}
{$ifdef UNICODE}
, UITypes
{$endif UNICODE};
const
RAEditorCompletionChars: set of AnsiChar =
[#8, '_', '0'..'9', 'A'..'Z', 'a'..'z'];
Separators: set of AnsiChar =
[#0, ' ', '-', #13, #10, '.', ',', '/', '\', ':', '+', '%', '*', '(', ')',
';', '=', '{', '}', '[', ']', '|', '!', '@', '"'];
GutterRightMargin = 2;
WM_EDITCOMMAND = WM_USER + $101;
RA_EX_STYLE_DEFAULT = 0;
RA_CASE_CONVERT_UPPER = 0;
RA_CASE_CONVERT_LOWER = 1;
RA_CASE_CONVERT_INVERT = 2;
type
TRAControlScrollBar95 = class
private
FKind: TScrollBarKind;
FPosition: Integer;
FMin: Integer;
FMax: Integer;
FSmallChange: TScrollBarInc;
FLargeChange: TScrollBarInc;
FPage: integer;
FHandle: hWnd;
FOnScroll: TScrollEvent;
// FVisible : boolean;
procedure SetParam(index, Value: Integer);
// procedure SetVisible(Value : boolean);
// procedure SetLargeChange(Value: TScrollBarInc);
protected
procedure Scroll(ScrollCode: TScrollCode; var ScrollPos: Integer); dynamic;
public
constructor Create;
procedure SetParams(AMin, AMax, APosition, APage: integer);
procedure DoScroll(var Message: TWMScroll);
property Kind: TScrollBarKind read FKind write FKind default sbHorizontal;
property SmallChange: TScrollBarInc read FSmallChange write FSmallChange default 1;
property LargeChange: TScrollBarInc read FLargeChange write FLargeChange default 1;
property Min: Integer index 0 read FMin write SetParam default 0;
property Max: Integer index 1 read FMax write SetParam default 100;
property Position: Integer index 2 read FPosition write SetParam default 0;
property Page: integer index 3 read FPage write SetParam;
property Handle: hWnd read FHandle write FHandle;
property OnScroll: TScrollEvent read FOnScroll write FOnScroll;
// property Visible : boolean read FVisible write SetVisible;
end;
TCellRect = record
Width: integer;
Height: integer;
end;
PLineAttr = ^TLineAttr;
TLineAttr = packed record
FC, BC: TColor;
case integer of
0:
(Style: TFontStyles;
ex_style: byte;
underlined: boolean);
1:
(LastInteger: integer);
end;
TCustomMemoEx = class;
TWordUnderCursor = record // for TOnWordClick
Text: string;
Style: integer;
CaretX, CaretY, ParaIndex, ParaOffset: integer;
TextStart: integer;
Shift: TShiftState;
end;
TLineAttrs = array of TLineAttr;
TSelAttrs = array of boolean;
TOnGetLineAttr = procedure(Sender: TObject; const Line: string; index: integer; const SelAttrs: TSelAttrs; var Attrs: TLineAttrs) of object;
TOnChangeStatus = TNotifyEvent;
TOnChangeClipboardState = procedure(Sender: TObject; const CanPaste: boolean) of object;
TOnWordClick = procedure(Sender: TObject; const Clicked: TWordUnderCursor) of object;
TOnMouseOver = procedure(Sender: TObject; WordStyle: word; var _Cursor: TCursor) of object;
TOnBreakLine = procedure(Sender: TObject; const Original: string; var _New: string) of object;
TOnConcatLine = procedure(Sender: TObject; const Original: string; var _New: string) of object;
TOnTextInsert = procedure(Sender: TObject; var Text: string) of object;
TOnCaseConversion = function(Sender: TObject; Conversion: byte; const Text: string): string of object;
TOnInsertBlock = function(Sender: TObject; var Text: string): boolean of object;
TOnSaveBlock = procedure(Sender: TObject; const Text: string) of object;
TOnInsertMacro = function(Sender: TObject; MacroID: integer): string of object;
TOnBlockOperation = function(Sender: TObject; MacroID: integer; const Text: string): string of object;
TOnSetCaretPos = procedure(Sender: TObject; CaretX, CaretY: integer) of object;
TOnClipboardPaste = function(Sender: TObject): boolean of object;
{$IFDEF MEMOEX_COMPLETION}
TOnPreprocessCompletion = function(Sender: TObject; const ID, Text: string): string of object;
{$ENDIF}
PAutoChangeWord = ^TAutoChangeWord;
TAutoChangeWord = record
OldWord, NewWord: string;
end;
PParagraph = ^TParagraph;
TParagraph = record
FPreCount, FCount: integer; // length(FString) = FStrings[0..FCount-1]
FStrings: array of string;
FObject: TObject;
end;
TEditorStrings = class(TStrings)
private
FMemoEx: TCustomMemoEx;
FList: array of TParagraph;
FParaLinesCount, FCount: integer;
FOnChanging: TNotifyEvent;
FOnAfterLoad: TNotifyEvent;
FOnBeforeSave: TNotifyEvent;
procedure Recount(Index: integer);
function _GetString(ParaIndex: integer): string;
procedure _PutString(ParaIndex: integer; const S: string);
procedure ReformatParagraph(ParaIndex: integer);
procedure Reformat;
procedure CheckLength(const st: string);
procedure Grow;
procedure InsertItem(Index: integer; const S: string);
protected
procedure Changed; virtual;
procedure Changing; virtual;
function Get(Index: integer): string; override;
function GetParaString(Index: integer): string;
function GetParagraph(Index: integer): PParagraph;
procedure Put(Index: integer; const S: string); override;
procedure PutParaString(Index: integer; const S: string);
procedure PutObject(Index: Integer; AObject: TObject); override;
function GetObject(Index: Integer): TObject; override;
procedure SetUpdateState(Updating: Boolean); override;
function GetTextStr: string; override;
procedure SetTextStr(const Value: string); override;
procedure SetInternal(Index: integer; const Value: string);
procedure SetInternalParaStr(Index: integer; const Value: string);
function GetCount: Integer; override; // make compiler happy
function AddParaStr(ParaIndex: integer; const S: string): integer;
procedure ReLine; // complete line with spaces until caret X pos
property Internal[Index: integer]: string write SetInternal;
property InternalParaStrings[Index: integer]: string write SetInternalParaStr;
public
constructor Create;
destructor Destroy; override;
procedure Clear; override;
procedure BeginUpdate;
procedure EndUpdate;
function Add(const S: string): integer; override;
procedure Delete(Index: integer); override;
procedure Insert(Index: integer; const S: string); override;
procedure LoadFromFile(const FileName: string); override;
procedure SaveToFile(const FileName: string); override;
procedure SetLockText(const Text: string);
function GetTextLength: integer; // fast get length(Text) value
function HasText: boolean; // true if Text<>''
procedure Index2ParaIndex(Index: integer; out Para, ParaIndex: integer);
function GetParagraphByIndex(Index: integer; out ParaIndex, IndexOffs: integer): string;
procedure Caret2Paragraph(X, Y: integer; out ParaIndex, IndexOffs: integer);
procedure Paragraph2Caret(ParaIndex, IndexOffs: integer; out X, Y: integer);
function GetParaOffs(ParaIndex: integer): integer; // in global Text[] string
property ParaLineCount: integer read FParaLinesCount;
property ParaStrings[Index: integer {=ParaY}]: string read GetParaString write PutParaString;
property Paragraphs[Index: integer]: PParagraph read GetParagraph; // = FList[index]
// property OnChange: TNotifyEvent read FOnChange write FOnChange;
property OnChanging: TNotifyEvent read FOnChanging write FOnChanging;
end;
TModifiedAction = (maInsert, maDelete);
TBookMark = record
X, Y: integer;
Valid: boolean;
end;
TBookMarkNum = 0..9;
TBookMarks = array[TBookMarkNum] of TBookMark;
TEditorClient = class
private
FMemoEx: TCustomMemoEx;
Top: integer;
function Left: integer;
function Height: integer;
function Width: integer;
function ClientWidth: integer;
function ClientHeight: integer;
function ClientRect: TRect;
function BoundsRect: TRect;
function GetCanvas: TCanvas;
property Canvas: TCanvas read GetCanvas;
end;
TGutter = class
private
FMemoEx: TCustomMemoEx;
public
procedure Paint;
procedure Invalidate;
end;
TOnPaintGutter = procedure(Sender: TObject; Canvas: TCanvas; const Rect: TRect) of object;
TEditCommand = word;
TMacro = string; { uses as buffer }
TEditKey = class
public
Key1, Key2: Word;
Shift1, Shift2: TShiftState;
Command: TEditCommand;
constructor Create(const ACommand: TEditCommand; const AKey1: word;
const AShift1: TShiftState);
constructor Create2(const ACommand: TEditCommand; const AKey1: word;
const AShift1: TShiftState; const AKey2: word; const AShift2: TShiftState);
end;
TKeyboard = class
private
List: TList;
public
constructor Create;
destructor Destroy; override;
procedure Add(const ACommand: TEditCommand; const AKey1: word; const AShift1: TShiftState);
procedure Add2(const ACommand: TEditCommand; const AKey1: word; const AShift1: TShiftState;
const AKey2: word; const AShift2: TShiftState);
procedure Clear;
function Command(const AKey: word; const AShift: TShiftState): TEditCommand;
function Command2(const AKey1: word; const AShift1: TShiftState;
const AKey2: word; const AShift2: TShiftState): TEditCommand;
{$IFDEF MEMOEX_DEFLAYOUT}
procedure SetDefLayout;
{$ENDIF MEMOEX_DEFLAYOUT}
end;
EComplete = class(EAbort);
EMemoExError = class(Exception);
{$IFDEF MEMOEX_UNDO}
TUndoBuffer = class;
TUndo = class
private
FMemoEx: TCustomMemoEx;
function UndoBuffer: TUndoBuffer;
public
constructor Create(const AMemoEx: TCustomMemoEx);
procedure Undo; dynamic; abstract;
procedure Redo; dynamic; abstract;
end;
TUndoBuffer = class(TList)
private
FMemoEx: TCustomMemoEx;
FPtr: integer;
FCancelUndo, InUndo: boolean;
function IsNewGroup(const AUndo: TUndo): boolean;
public
constructor Create;
procedure Add(AUndo: TUndo);
function LastUndo: TUndo;
procedure Undo;
procedure Redo;
procedure Clear; override;
procedure Delete;
end;
{$ENDIF MEMOEX_UNDO}
{$IFDEF MEMOEX_COMPLETION}
TCompletion = class;
TOnCompletion = procedure(Sender: TObject; var Cancel: boolean) of object;
{$ENDIF MEMOEX_COMPLETION}
TTabStop = (tsTabStop, tsAutoIndent);
{*** TCustomMemoEx }
TCustomMemoEx = class(TCustomControl)
private
{ internal objects }
FLines: TEditorStrings;
scbHorz: TRAControlScrollBar95;
scbVert: TRAControlScrollBar95;
EditorClient: TEditorClient;
FGutter: TGutter;
FKeyboard: TKeyboard;
FBookMarks: TBookMarks;
FUpdateLock: integer;
{$IFDEF MemoEx_UNDO}
FUndoBuffer: TUndoBuffer;
FGroupUndo: boolean;
{$ENDIF MEMOEX_UNDO}
{$IFDEF MEMOEX_COMPLETION}
FCompletion: TCompletion;
{$ENDIF MEMOEX_COMPLETION}
{ internal - Columns and rows attributes }
FCols, FRows: integer;
FLeftCol, FTopRow: integer;
// FLeftColMax, FTopRowMax : integer;
FLastVisibleCol, FLastVisibleRow: integer;
FCaretX, FCaretY: integer;
FVisibleColCount: integer;
FVisibleRowCount: integer;
{ internal - other flags and attributes }
FAllRepaint: boolean;
FCellRect: TCellRect;
{$IFDEF MEMOEX_EDITOR}
IgnoreKeyPress: boolean;
{$ENDIF MEMOEX_EDITOR}
WaitSecondKey: Boolean;
Key1: Word;
Shift1: TShiftState;
{ internal - selection attributes }
FSelected: boolean;
FSelBlock: boolean;
FSelBegX, FSelBegY, FSelEndX, FSelEndY: integer;
FUpdateSelBegX, FUpdateSelEndX, FUpdateSelBegY, FUpdateSelEndY: integer;
FSelStartX, FSelStartY: integer;
FclSelectBC, FclSelectFC: TColor;
{ mouse support }
timerScroll: TTimer;
MouseMoveY, MouseMoveXX, MouseMoveYY: integer;
{ internal }
FTabPos: array of boolean;
FTabStops: string;
{ internal - primary for TIReader support }
FEditBuffer: string;
FPEditBuffer: PChar;
FEditBufferSize: integer;
FCompound: integer;
{ FMacro - buffer of TEditCommand, each command represents by two chars }
FMacro: TMacro;
FDefMacro: TMacro;
{ visual attributes - properties }
FBorderStyle: TBorderStyle;
FGutterColor: TColor;
FGutterWidth: integer;
FRightMarginVisible: boolean;
FRightMargin: integer;
FRightMarginColor: TColor;
FScrollBars: TScrollStyle;
FDoubleClickLine: boolean;
FSmartTab: Boolean;
FBackSpaceUnindents: Boolean;
FAutoIndent: Boolean;
FKeepTrailingBlanks: Boolean;
FCursorBeyondEOF: Boolean;
FCursorBeyondEOL: Boolean;
{ FInclusive - Inclusive mode }
FInclusive: Boolean;
{ non-visual attributes - properties }
FInsertMode: boolean;
FReadOnly: boolean;
FModified: boolean;
FRecording: boolean;
{ Events }
FOnGetLineAttr: TOnGetLineAttr;
FOnChange: TNotifyEvent;
FOnSelectionChange: TNotifyEvent;
FOnChangeStatus: TOnChangeStatus;
FOnChangeClipboardState: TOnChangeClipboardState;
FOnScroll: TNotifyEvent;
FOnResize: TNotifyEvent;
FOnDblClick: TNotifyEvent;
FOnPaintGutter: TOnPaintGutter;
FOnWordClick: TOnWordClick;
FOnMouseOver: TOnMouseOver;
FOnBreakLine: TOnBreakLine;
FOnConcatLine: TOnConcatLine;
FOnTextInsert: TOnTextInsert;
FOnCaseConversion: TOnCaseConversion;
FOnInsertBlock: TOnInsertBlock;
FOnSaveBlock: TOnSaveBlock;
FOnInsertMacro: TOnInsertMacro;
FOnBlockOperation: TOnBlockOperation;
FOnSetCaretPos: TOnSetCaretPos;
{$IFDEF MEMOEX_COMPLETION}
FOnCompletionIdentifer: TOnCompletion;
FOnCompletionTemplate: TOnCompletion;
FOnCompletionDrawItem: TDrawItemEvent;
FOnCompletionMeasureItem: TMeasureItemEvent;
FOnPreprocessCompletion: TOnPreprocessCompletion;
{$ENDIF MEMOEX_COMPLETION}
FDrawBitmap: TBitmap;
FFont: TFont;
FWantTabs: boolean;
FWordWrap: boolean;
FStripInvisible: boolean;
FParaX, FParaY: integer;
NextClipViewer: THandle;
scbVertWidth, scbHorzHeight: integer;
Max_X: integer;
mouse_down, mouse_dragged, double_clicked: boolean;
FWordUnderCursor: TWordUnderCursor;
FClipPasteRtfBackSlashConvert: boolean;
{$ifdef CLIPBOARDPROTECT} // ClipProtect will trunc clipboard to 2KB
FClip: string; // AB
FClipProtect: boolean;
{$endif}
FOnClipboardPaste: TOnClipboardPaste; // AB
procedure SetMax_X(const Value: integer);
procedure UpdateEditorSize(const FullUpdate: boolean = true; const RepaintGutter: boolean = true);
procedure RedrawFrom(YFrom: integer);
function RepaintParagraph(LineIndex: integer): integer;
{$IFDEF MEMOEX_COMPLETION}
procedure DoCompletionIdentifer(var Cancel: boolean);
procedure DoCompletionTemplate(var Cancel: boolean);
function DoPreprocessCompletion(const ID, OldText: string): string;
{$ENDIF MEMOEX_COMPLETION}
procedure ScrollTimer(Sender: TObject);
procedure ReLine;
function GetDefTabStop(const X: integer; const Next: Boolean): integer;
function GetTabStop(const X, Y: integer; const What: TTabStop; const Next: Boolean): integer;
function GetBackStop(const X, Y: integer): integer;
procedure TextAllChangedInternal(const Unselect: Boolean);
{ properties }
procedure SetGutterWidth(AWidth: integer);
procedure SetGutterColor(AColor: TColor);
procedure SetFont(Value: TFont);
procedure SetBorderStyle(Value: TBorderStyle);
procedure SetLines(ALines: TEditorStrings);
function GetRealOffs(DefOffs, Index: integer): integer;
function GetSelStart: integer;
procedure SetSelStart(const ASelStart: integer);
procedure SetSelLength(const ASelLength: integer);
function GetSelLength: integer;
procedure SetMode(index: integer; Value: boolean);
procedure SetCaretPosition(const index, Pos: integer);
procedure SetCols(ACols: integer);
procedure SetRows(ARows: integer);
procedure SetScrollBars(Value: TScrollStyle);
procedure SetRightMarginVisible(Value: boolean);
procedure SetRightMargin(Value: integer);
procedure SetRightMarginColor(Value: TColor);
function ExtractStringWithStyle(XX, YY: integer; const From: string; Style: word;
const LineAttrs: TLineAttrs; out start: integer): string;
procedure GetWordUnderCursor(X, Y: integer; aShift: TShiftState = []);
function GetAfterLoad: TNotifyEvent;
procedure SetAfterLoad(Value: TNotifyEvent);
function GetBeforeSave: TNotifyEvent;
procedure SetBeforeSave(Value: TNotifyEvent);
procedure SetWordWrap(Value: boolean);
procedure SetStripInvisible(Value: boolean);
procedure SetSelectedText(Value: boolean);
procedure FontChanged(Sender: TObject);
procedure SetTopRow(const Value: integer);
function GetTextStr: string;
procedure SetTextStr(const Value: string);
protected
SelAttrs_Size: integer;
SelAttrs: TSelAttrs;
property FSelectedText: boolean read FSelected write SetSelectedText;
procedure Resize; override;
procedure CreateWnd; override;
procedure CreateParams(var Params: TCreateParams); override;
procedure Loaded; override;
procedure Paint; override;
procedure ScrollBarScroll(Sender: TObject; ScrollCode: TScrollCode; var ScrollPos: integer);
procedure Scroll(const Vert: boolean; const ScrollPos: integer);
procedure PaintLine(const Line: integer; ColBeg, ColEnd: integer);
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
{$IFDEF MEMOEX_EDITOR}
procedure KeyPress(var Key: Char); override;
procedure InsertChar(const Key: Char);
{$ENDIF MEMOEX_EDITOR}
procedure SetSel(const ASelX, ASelY: integer);
function GetAttrDelta(StartFrom, EndTo: integer; const LineAttrs: TLineAttrs): integer;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: integer); override;
procedure MouseMove(Shift: TShiftState; X, Y: integer); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: integer); override;
procedure DblClick; override;
function DoMouseWheel(Shift: TShiftState; WheelDelta: integer; MousePos: TPoint): boolean; override;
procedure DoContextPopup(MousePos: TPoint; var Handled: Boolean); override;
// procedure DrawRightMargin;
procedure PaintSelection;
procedure SetUnSelected;
procedure Mouse2Cell(const X, Y: integer; var CX, CY: integer);
procedure Mouse2Caret(const X, Y: integer; var CX, CY: integer);
procedure CaretCoord(const X, Y: integer; var CX, CY: integer);
function PosFromMouse(const X, Y: integer): integer;
procedure SetLockText(const Text: string);
// function ExpandTabs(const S: string): string;
{$IFDEF MEMOEX_UNDO}
procedure CantUndo;
{$ENDIF MEMOEX_UNDO}
procedure SetCaretInternal(X, Y: integer);
procedure ValidateEditBuffer;
procedure SetXY(X, Y: integer);
{$IFDEF MEMOEX_EDITOR}
procedure ChangeBookMark(const BookMark: TBookMarkNum; const Valid: boolean);
procedure InsertText(const Text: string);
{$ENDIF MEMOEX_EDITOR}
procedure BeginRecord;
procedure EndRecord(var AMacro: TMacro);
procedure PlayMacro(const AMacro: TMacro);
function YinBounds(AY: integer): boolean;
function DoChangeCase(const st: string; Conversion: byte): string;
{ triggers for descendants }
procedure Changed; dynamic;
procedure TextAllChanged; dynamic;
procedure StatusChanged; dynamic;
procedure SelectionChanged; dynamic;
procedure ClipboardChanged; dynamic;
procedure GetLineAttr(Line, LineIdx, LineOffs, LineLen, ColBeg, ColEnd: integer;
const ALine: string; var FAttrs: TLineAttrs); virtual;
procedure GutterPaint(Canvas: TCanvas; const Rect: TRect); dynamic;
procedure BookmarkChanged(BookMark: integer); dynamic;
{$IFDEF MEMOEX_COMPLETION}
procedure CompletionIdentifer(var Cancel: boolean); dynamic;
procedure CompletionTemplate(var Cancel: boolean); dynamic;
{$ENDIF MEMOEX_COMPLETION}
property Gutter: TGutter read FGutter;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Invalidate; override;
procedure WndProc(var Message: TMessage); override;
procedure SetLeftTop(ALeftCol, ATopRow: integer);
procedure ClipBoardCopy;
procedure ClipBoardPaste;
procedure ClipBoardCut;
procedure DeleteSelected;
function CalcCellRect(const X, Y: integer): TRect;
procedure SetCaret(const X, Y: integer);
procedure SetCaretAtParaPos(const ParaIndex, IndexOffs: integer);
procedure CaretFromPos(Pos: integer; var X, Y: integer);
function PosFromCaret(X, Y: integer): integer;
procedure PaintCaret(bShow: boolean);
function GetTextLen: integer;
function GetSelText: string;
procedure SetSelText(const AValue: string);
function GetWordOnCaret: string;
procedure BeginUpdate;
procedure EndUpdate;
procedure MakeRowVisible(ARow: integer);
procedure Command(ACommand: TEditCommand); virtual;
procedure PostCommand(ACommand: TEditCommand);
{$IFDEF MEMOEX_EDITOR}
procedure InsertTextAtCurrentPos(const _Text: string);
function FindNext(const text: string; ignCase: boolean): boolean;
procedure ReplaceWord(const NewString: string);
procedure ReplaceWord2(const NewString: string);
{$ENDIF}
procedure BeginCompound;
procedure EndCompound;
function GetText(Position: longint; Buffer: PChar; Count: longint): longint;
function IsUndoEmpty: boolean;
procedure MouseWheelScroll(Delta: integer);
{$ifdef CLIPBOARDPROTECT} // ClipProtect will trunc clipboard to 2KB
property ClipProtect: boolean read FClipProtect write FClipProtect; // AB
{$endif}
property ClipPasteRtfBackSlashConvert: boolean // AB: rtf
read FClipPasteRtfBackSlashConvert write FClipPasteRtfBackSlashConvert;
property LeftCol: integer read FLeftCol;
property TopRow: integer read FTopRow write SetTopRow;
property VisibleColCount: integer read FVisibleColCount;
property VisibleRowCount: integer read FVisibleRowCount;
property LastVisibleCol: integer read FLastVisibleCol;
property LastVisibleRow: integer read FLastVisibleRow;
property Cols: integer read FCols write SetCols;
property Rows: integer read FRows write SetRows;
property CaretX: integer index 0 read FCaretX write SetCaretPosition;
property CaretY: integer index 1 read FCaretY write SetCaretPosition;
property Modified: boolean read FModified write FModified;
property SelStart: integer read GetSelStart write SetSelStart;
property SelLength: integer read GetSelLength write SetSelLength;
property SelText: string read GetSelText write SetSelText;
property SelectedText: boolean read FSelected;
property BookMarks: TBookMarks read FBookMarks;
property Keyboard: TKeyboard read FKeyboard;
property CellRect: TCellRect read FCellRect;
{$IFDEF MEMOEX_UNDO}
property UndoBuffer: TUndoBuffer read FUndoBuffer;
{$ENDIF MEMOEX_UNDO}
property Recording: boolean read FRecording;
property WordUnderCursor: TWordUnderCursor read FWordUnderCursor;
property SelBegY: integer read FSelBegY;
property SelEndY: integer read FSelEndY;
public
{ published in descendants }
property BorderStyle: TBorderStyle read FBorderStyle write SetBorderStyle default bsSingle;
property Lines: TEditorStrings read FLines write SetLines;
property ScrollBars: TScrollStyle read FScrollBars write SetScrollBars default ssBoth;
property Cursor default crIBeam;
property Color default clWindow;
property Font: TFont read FFont write SetFont;
property Text: string read GetTextStr write SetTextStr;
property GutterWidth: integer read FGutterWidth write SetGutterWidth;
property GutterColor: TColor read FGutterColor write SetGutterColor default clBtnFace;
property RightMarginVisible: boolean read FRightMarginVisible write SetRightMarginVisible default true;
property RightMargin: integer read FRightMargin write SetRightMargin default 80;
property RightMarginColor: TColor read FRightMarginColor write SetRightMarginColor default clBtnFace;
property InsertMode: boolean index 0 read FInsertMode write SetMode default true;
property ReadOnly: boolean index 1 read FReadOnly write SetMode default false;
property DoubleClickLine: boolean read FDoubleClickLine write FDoubleClickLine default false;
{$IFDEF MEMOEX_COMPLETION}
property Completion: TCompletion read FCompletion write FCompletion;
{$ENDIF MEMOEX_COMPLETION}
property TabStops: string read FTabStops write FTabStops;
property SmartTab: Boolean read FSmartTab write FSmartTab default true;
property BackSpaceUnindents: Boolean read FBackSpaceUnindents write FBackSpaceUnindents default true;
property AutoIndent: Boolean read FAutoIndent write FAutoIndent default true;
property KeepTrailingBlanks: Boolean read FKeepTrailingBlanks write FKeepTrailingBlanks default false;
property CursorBeyondEOF: Boolean read FCursorBeyondEOF write FCursorBeyondEOF default false;
property CursorBeyondEOL: Boolean read FCursorBeyondEOL write FCursorBeyondEOL default true;
property SelForeColor: TColor read FclSelectFC write FclSelectFC;
property SelBackColor: TColor read FclSelectBC write FclSelectBC;
property StripInvisible: boolean read FStripInvisible write SetStripInvisible default false;
property WantTabs: boolean read FWantTabs write FWantTabs default true;
property WordWrap: boolean read FWordWrap write SetWordWrap default true;
property OnAfterLoad: TNotifyEvent read GetAfterLoad write SetAfterLoad;
property OnBeforeSave: TNotifyEvent read GetBeforeSave write SetBeforeSave;
property OnGetLineAttr: TOnGetLineAttr read FOnGetLineAttr write FOnGetLineAttr;
property OnChangeStatus: TOnChangeStatus read FOnChangeStatus write FOnChangeStatus;
property OnChangeClipboardState: TOnChangeClipboardState read FOnChangeClipboardState write FOnChangeClipboardState;
property OnScroll: TNotifyEvent read FOnScroll write FOnScroll;
property OnResize: TNotifyEvent read FOnResize write FOnResize;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
property OnSelectionChange: TNotifyEvent read FOnSelectionChange write FOnSelectionChange;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnDblClick: TNotifyEvent read FOnDblClick write FOnDblClick;
property OnPaintGutter: TOnPaintGutter read FOnPaintGutter write FOnPaintGutter;
property OnMouseOver: TOnMouseOver read FOnMouseOver write FOnMouseOver;
property OnWordClick: TOnWordClick read FOnWordClick write FOnWordClick;
property OnBreakLine: TOnBreakLine read FOnBreakLine write FOnBreakLine;
property OnConcatLine: TOnConcatLine read FOnConcatLine write FOnConcatLine;
property OnTextInsert: TOnTextInsert read FOnTextInsert write FOnTextInsert;
property OnCaseConversion: TOnCaseConversion read FOnCaseConversion write FOnCaseConversion;
property OnInsertBlock: TOnInsertBlock read FOnInsertBlock write FOnInsertBlock;
property OnSaveBlock: TOnSaveBlock read FOnSaveBlock write FOnSaveBlock;
property OnInsertMacro: TOnInsertMacro read FOnInsertMacro write FOnInsertMacro;
property OnBlockOperation: TOnBlockOperation read FOnBlockOperation write FOnBlockOperation;
property OnSetCaretPos: TOnSetCaretPos read FOnSetCaretPos write FOnSetCaretPos;
property OnClipboardPaste: TOnClipboardPaste read FOnClipboardPaste write FOnClipboardPaste;
{$IFDEF MEMOEX_COMPLETION}
property OnCompletionIdentifer: TOnCompletion read FOnCompletionIdentifer write FOnCompletionIdentifer;
property OnCompletionTemplate: TOnCompletion read FOnCompletionTemplate write FOnCompletionTemplate;
property OnCompletionDrawItem: TDrawItemEvent read FOnCompletionDrawItem write FOnCompletionDrawItem;
property OnCompletionMeasureItem: TMeasureItemEvent read FOnCompletionMeasureItem write FOnCompletionMeasureItem;
property OnPreprocessCompletion: TOnPreprocessCompletion read FOnPreprocessCompletion write FOnPreprocessCompletion;
{$ENDIF MEMOEX_COMPLETION}
property DockManager;
end;
TMemoEx = class(TCustomMemoEx)
public
/// a JSON syntax highlighter
class procedure JSONLineAttr(Sender: TObject; const Line: string;
index: Integer; const SelAttrs: TSelAttrs; var Attrs: TLineAttrs);
published
property TabOrder;
property BorderStyle;
property Lines;
property ScrollBars;
property GutterWidth;
property GutterColor;
property RightMarginVisible;
property RightMargin;
property RightMarginColor;
property InsertMode;
property ReadOnly;
property DoubleClickLine;
{$IFDEF MEMOEX_COMPLETION}
property Completion;
{$ENDIF MEMOEX_COMPLETION}
property TabStops;
property SmartTab;
property BackSpaceUnindents;
property AutoIndent;
property KeepTrailingBlanks;
property CursorBeyondEOF;
property CursorBeyondEOL;
property SelForeColor;
property SelBackColor;
property Text;
property StripInvisible;
property OnAfterLoad;
property OnBeforeSave;
property OnEnter;
property OnExit;
property OnGetLineAttr;
property OnChangeStatus;
property OnChangeClipboardState;
property OnScroll;
property OnResize;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnChange;
property OnSelectionChange;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnDblClick;
property OnMouseWheel;
property OnMouseWheelDown;
property OnMouseWheelUp;
property OnPaintGutter;
property OnMouseOver;
property OnWordClick;
property OnBreakLine;
property OnConcatLine;
property OnTextInsert;
property OnCaseConversion;
property OnInsertBlock;
property OnSaveBlock;
property OnInsertMacro;
property OnBlockOperation;
property OnSetCaretPos;
{$IFDEF MEMOEX_COMPLETION}
property OnCompletionIdentifer;
property OnCompletionTemplate;
property OnCompletionDrawItem;
property OnCompletionMeasureItem;
property OnPreprocessCompletion;
{$ENDIF MEMOEX_COMPLETION}
{ TCustomControl }
property Align;
property Enabled;
property Color;
{$ifndef FPC}
property Ctl3D;
property OnCanResize;
{$endif FPC}
property Font;
property ParentColor;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ShowHint;
property TabStop;
property Visible;
property Anchors;
property AutoSize;
property BiDiMode;
property Constraints;
property UseDockManager default true;
property DockSite;
property DragKind;
property ParentBiDiMode;
property WantTabs default true;
property WordWrap default true;
property OnConstrainedResize;
property OnDockDrop;
property OnDockOver;
property OnEndDock;
property OnGetSiteInfo;
property OnStartDock;
property OnUnDock;
end;
{$IFDEF MEMOEX_COMPLETION}
TCompletionList = (cmIdentifers, cmTemplates);
TCompletion = class(TPersistent)
private
FMemoEx: TCustomMemoEx;
FPopupList: TListBox;
FAutoChange: TStrings;
FAutoChangeList: TList;
FIdentifers: TStrings;
FTemplates: TStrings;
FItems: TStringList;
FItemIndex: integer;
FMode: TCompletionList;
FDefMode: TCompletionList;
FItemHeight: integer;
FTimer: TTimer;
FEnabled: boolean;
FVisible: boolean;
FDropDownCount: integer;
FDropDownWidth: integer;
FListBoxStyle: TListBoxStyle;
FCaretChar: char;
FCRLF: string;
FSeparator: string;
function DoKeyDown(Key: Word; Shift: TShiftState): boolean;
procedure DoKeyPress(Key: Char);
procedure OnTimer(Sender: TObject);
procedure FindSelItem(var Eq: boolean);
procedure ReplaceWord(const ANewString: string);
function Cmp1(const S1, S2: string): integer;
function Cmp2(const S1, S2: string): boolean;
procedure AutoChangeChanged(Sender: TObject);
procedure ClearAutoChangeList;
procedure UpdateAutoChange;
procedure SetStrings(index: integer; AValue: TStrings);
function GetItemIndex: integer;
procedure SetItemIndex(AValue: integer);
function GetInterval: cardinal;
procedure SetInterval(AValue: cardinal);
procedure MakeItems;
function GetItems: TStrings;
public
constructor Create2(AMemoEx: TCustomMemoEx);
destructor Destroy; override;
procedure DropDown(const AMode: TCompletionList; const ShowAlways: boolean);
procedure DoCompletion(const AMode: TCompletionList);
procedure CloseUp(const Apply: boolean);
procedure SelectItem;
property ItemIndex: integer read GetItemIndex write SetItemIndex;
property Visible: boolean read FVisible write FVisible;
property Mode: TCompletionList read FMode write FMode;
property Items: TStringList read FItems;
published
property DropDownCount: integer read FDropDownCount write FDropDownCount default 6;
property DropDownWidth: integer read FDropDownWidth write FDropDownWidth default 300;
property Enabled: boolean read FEnabled write FEnabled default false;
property Separator: string read FSeparator write FSeparator;
property Identifers: TStrings index 0 read FIdentifers write SetStrings;
property Templates: TStrings index 1 read FTemplates write SetStrings;
property AutoChange: TStrings index 2 read FAutoChange write SetStrings;
property ItemHeight: integer read FItemHeight write FItemHeight;
property Interval: cardinal read GetInterval write SetInterval;
property ListBoxStyle: TListBoxStyle read FListBoxStyle write FListBoxStyle;
property CaretChar: char read FCaretChar write FCaretChar;
property CRLF: string read FCRLF write FCRLF;
end;
{$ENDIF MEMOEX_COMPLETION}
const
{ Editor commands }
ecCharFirst = $00;
ecCharLast = $FF;
ecCommandFirst = $100;
ecUser = $8000; { use this for descendants }
{Cursor}
ecLeft = ecCommandFirst + 1;
ecUp = ecLeft + 1;
ecRight = ecLeft + 2;
ecDown = ecLeft + 3;
{Cursor with select}
ecSelLeft = ecCommandFirst + 9;
ecSelUp = ecSelLeft + 1;
ecSelRight = ecSelLeft + 2;
ecSelDown = ecSelLeft + 3;
{Cursor position change according to word}
ecPrevWord = ecSelDown + 1;
ecNextWord = ecPrevWord + 1;
ecSelPrevWord = ecPrevWord + 2;
ecSelNextWord = ecPrevWord + 3;
ecSelWord = ecPrevWord + 4;
ecWindowTop = ecSelWord + 1;
ecWindowBottom = ecWindowTop + 1;
ecPrevPage = ecWindowTop + 2;
ecNextPage = ecWindowTop + 3;
ecSelPrevPage = ecWindowTop + 4;
ecSelNextPage = ecWindowTop + 5;
ecBeginLine = ecSelNextPage + 1;
ecEndLine = ecBeginLine + 1;
ecBeginDoc = ecBeginLine + 2;
ecEndDoc = ecBeginLine + 3;
ecSelBeginLine = ecBeginLine + 4;
ecSelEndLine = ecBeginLine + 5;
ecSelBeginDoc = ecBeginLine + 6;
ecSelEndDoc = ecBeginLine + 7;
ecSelAll = ecBeginLine + 8;
ecScrollLineUp = ecSelAll + 1;
ecScrollLineDown = ecScrollLineUp + 1;
ecInsertPara = ecCommandFirst + 101;
ecBackspace = ecInsertPara + 1;
ecDelete = ecInsertPara + 2;
ecChangeInsertMode = ecInsertPara + 3;
ecTab = ecInsertPara + 4;
ecBackTab = ecInsertPara + 5;
ecIndent = ecInsertPara + 6;
ecUnindent = ecInsertPara + 7;
ecDeleteSelected = ecInsertPara + 10;
ecClipboardCopy = ecInsertPara + 11;
ecClipboardCut = ecClipboardCopy + 1;
ecClipBoardPaste = ecClipboardCopy + 2;
ecDeleteLine = ecClipBoardPaste + 1;
ecDeleteWord = ecDeleteLine + 1;
ecToUpperCase = ecDeleteLine + 2;
ecToLowerCase = ecToUpperCase + 1;
ecChangeCase = ecToUpperCase + 2;
ecUndo = ecChangeCase + 1;
ecRedo = ecUndo + 1;
ecBeginCompound = ecUndo + 2; { not implemented }
ecEndCompound = ecUndo + 3; { not implemented }
ecBeginUpdate = ecUndo + 4;
ecEndUpdate = ecUndo + 5;
ecSetBookmark0 = ecEndUpdate + 1;
ecSetBookmark1 = ecSetBookmark0 + 1;
ecSetBookmark2 = ecSetBookmark0 + 2;
ecSetBookmark3 = ecSetBookmark0 + 3;
ecSetBookmark4 = ecSetBookmark0 + 4;
ecSetBookmark5 = ecSetBookmark0 + 5;
ecSetBookmark6 = ecSetBookmark0 + 6;
ecSetBookmark7 = ecSetBookmark0 + 7;
ecSetBookmark8 = ecSetBookmark0 + 8;
ecSetBookmark9 = ecSetBookmark0 + 9;
ecGotoBookmark0 = ecSetBookmark9 + 1;
ecGotoBookmark1 = ecGotoBookmark0 + 1;
ecGotoBookmark2 = ecGotoBookmark0 + 2;
ecGotoBookmark3 = ecGotoBookmark0 + 3;
ecGotoBookmark4 = ecGotoBookmark0 + 4;
ecGotoBookmark5 = ecGotoBookmark0 + 5;
ecGotoBookmark6 = ecGotoBookmark0 + 6;
ecGotoBookmark7 = ecGotoBookmark0 + 7;
ecGotoBookmark8 = ecGotoBookmark0 + 8;
ecGotoBookmark9 = ecGotoBookmark0 + 9;
ecCompletionIdentifers = ecGotoBookmark9 + 1;
ecCompletionTemplates = ecCompletionIdentifers + 1;
ecRecordMacro = ecCompletionTemplates + 1;
ecPlayMacro = ecRecordMacro + 1;
ecBeginRecord = ecRecordMacro + 2;
ecEndRecord = ecRecordMacro + 3;
ecSaveBlock = ecEndRecord + 1;
ecInsertBlock = ecSaveBlock + 1;
ecInsertMacro0 = ecInsertBlock + 1;
ecInsertMacro1 = ecInsertMacro0 + 1;
ecInsertMacro2 = ecInsertMacro0 + 2;
ecInsertMacro3 = ecInsertMacro0 + 3;
ecInsertMacro4 = ecInsertMacro0 + 4;
ecInsertMacro5 = ecInsertMacro0 + 5;
ecInsertMacro6 = ecInsertMacro0 + 6;
ecInsertMacro7 = ecInsertMacro0 + 7;
ecInsertMacro8 = ecInsertMacro0 + 8;
ecInsertMacro9 = ecInsertMacro0 + 9;
ecInsertMacroA = ecInsertMacro0 + 10;
ecInsertMacroB = ecInsertMacro0 + 11;
ecInsertMacroC = ecInsertMacro0 + 12;
ecInsertMacroD = ecInsertMacro0 + 13;
ecInsertMacroE = ecInsertMacro0 + 14;
ecInsertMacroF = ecInsertMacro0 + 15;
ecInsertMacroG = ecInsertMacro0 + 16;
ecInsertMacroH = ecInsertMacro0 + 17;
ecInsertMacroI = ecInsertMacro0 + 18;
ecInsertMacroJ = ecInsertMacro0 + 19;
ecInsertMacroK = ecInsertMacro0 + 20;
ecInsertMacroL = ecInsertMacro0 + 21;
ecInsertMacroM = ecInsertMacro0 + 22;
ecInsertMacroN = ecInsertMacro0 + 23;
ecInsertMacroO = ecInsertMacro0 + 24;
ecInsertMacroP = ecInsertMacro0 + 25;
ecInsertMacroQ = ecInsertMacro0 + 26;
ecInsertMacroR = ecInsertMacro0 + 27;
ecInsertMacroS = ecInsertMacro0 + 28;
ecInsertMacroT = ecInsertMacro0 + 29;
ecInsertMacroU = ecInsertMacro0 + 30;
ecInsertMacroV = ecInsertMacro0 + 31;
ecInsertMacroW = ecInsertMacro0 + 32;
ecInsertMacroX = ecInsertMacro0 + 33;
ecInsertMacroY = ecInsertMacro0 + 34;
ecInsertMacroZ = ecInsertMacro0 + 35;
ecBlockOpA = ecInsertMacroZ + 1;
ecBlockOpB = ecBlockOpA + 1;
ecBlockOpC = ecBlockOpA + 2;
ecBlockOpD = ecBlockOpA + 3;
ecBlockOpE = ecBlockOpA + 4;
ecBlockOpF = ecBlockOpA + 5;
ecBlockOpG = ecBlockOpA + 6;
ecBlockOpH = ecBlockOpA + 7;
ecBlockOpI = ecBlockOpA + 8;
ecBlockOpJ = ecBlockOpA + 9;
ecBlockOpK = ecBlockOpA + 10;
ecBlockOpL = ecBlockOpA + 11;
ecBlockOpM = ecBlockOpA + 12;
ecBlockOpN = ecBlockOpA + 13;
ecBlockOpO = ecBlockOpA + 14;
ecBlockOpP = ecBlockOpA + 15;
ecBlockOpQ = ecBlockOpA + 16;
ecBlockOpR = ecBlockOpA + 17;
ecBlockOpS = ecBlockOpA + 18;
ecBlockOpT = ecBlockOpA + 19;
ecBlockOpU = ecBlockOpA + 20;
ecBlockOpV = ecBlockOpA + 21;
ecBlockOpW = ecBlockOpA + 22;
ecBlockOpX = ecBlockOpA + 23;
ecBlockOpY = ecBlockOpA + 24;
ecBlockOpZ = ecBlockOpA + 25;
ecBackword = ecBlockOpZ + 1;
ecScrollPageUp = ecBackword + 1;
ecScrollPageDown = ecScrollPageUp + 1;
twoKeyCommand = High(word);
const
__Brackets =['(', ')', '[', ']', '{', '}'];
__StdWordDelims =[#0..' ', ',', '.', ';', '\', ':', '''', '`']{ + __Brackets};
procedure Register;
function Max(x, y: integer): integer; {$ifdef HASINLINE}inline;{$endif}
function Min(x, y: integer): integer; {$ifdef HASINLINE}inline;{$endif}
implementation
uses
{$ifndef FPC}
Consts,
{$endif FPC}
RTLConsts;
{$ifndef CPUX86}
function PosEx(const SubStr, S: string; Offset: Integer = 1): Integer; inline;
begin
Result := System.Pos(SubStr, S, Offset);
end;
{$else}
function PosEx(const SubStr, S: AnsiString; Offset: Cardinal = 1): Integer;
// Faster Equivalent of Delphi 7 StrUtils.PosEx
asm
push ebx
push esi
push edx // @Str
test eax,eax
jz @@NotFound // Exit if SubStr = ''
test edx,edx
jz @@NotFound // Exit if Str = ''
mov esi,ecx
mov ecx,[edx-4] // Length(Str)
mov ebx,[eax-4] // Length(Search string)
add ecx,edx
sub ecx,ebx // ecx = Max Start Pos for Full Match
lea edx,[edx+esi-1] // edx = Start Position
cmp edx,ecx
jg @@NotFound // StartPos > Max Start Pos
cmp ebx,1 // Length(SubStr)
jle @@SingleChar // Length(SubStr) <= 1
push edi
push ebp
lea edi,[ebx-2] // edi = Length(Search string) - 2
mov esi,eax // esi = Search string
movzx ebx,byte ptr [eax] // bl = Search Character
@@Loop: // Compare 2 Characters per Loop
cmp bl,[edx]
je @@Char1Found
@@NotChar1:
cmp bl,[edx+1]
je @@Char2Found
@@NotChar2:
lea edx,[edx+2]
cmp edx,ecx // Next Start Position <= Max Start Position
jle @@Loop
pop ebp
pop edi
@@NotFound:
xor eax,eax // returns 0 if not found
pop edx
pop esi
pop ebx
ret
@@Char1Found:
mov ebp,edi // ebp = Length(Search string) - 2
@@Char1Loop:
movzx eax,word ptr [esi+ebp]
cmp ax,[edx+ebp] // Compare 2 Chars per Char1Loop (may include #0)
jne @@NotChar1
sub ebp,2
jnc @@Char1Loop
pop ebp
pop edi
jmp @@SetResult
@@Char2Found:
mov ebp,edi // ebp = Length(Search string) - 2
@@Char2Loop:
movzx eax,word ptr [esi+ebp]
cmp ax,[edx+ebp+1] // Compare 2 Chars per Char2Loop (may include #0)
jne @@NotChar2
sub ebp,2
jnc @@Char2Loop
pop ebp
pop edi
jmp @@CheckResult
@@SingleChar:
jl @@NotFound // Needed for Zero-Length Non-NIL Strings
movzx eax,byte ptr [eax] // Search Character
@@CharLoop:
cmp al,[edx]
je @@SetResult
cmp al,[edx+1]
je @@CheckResult
lea edx,[edx+2]
cmp edx,ecx
jle @@CharLoop
jmp @@NotFound
@@CheckResult: // Check within AnsiString
cmp edx,ecx
jge @@NotFound
add edx,1
@@SetResult:
pop ecx // @Str
pop esi
pop ebx
neg ecx
lea eax,[edx+ecx+1]
end;
{$endif UNICODE}
function StringReplaceAll(const S, OldPattern, NewPattern: string): string;
// fast replacement of StringReplace(S, OldPattern, NewPattern,[rfReplaceAll]);
procedure Process(j: integer);
var
i: integer;
begin
Result := '';
i := 1;
repeat
Result := Result + Copy(S, i, j - i) + NewPattern;
i := j + length(OldPattern);
j := PosEx(OldPattern, S, i);
if j = 0 then
begin
Result := Result + Copy(S, i, maxInt);
break;
end;
until false;
end;
var
j: integer;
begin
j := PosEx(OldPattern, S);
if j = 0 then
result := S
else
Process(j);
end;
const
_AutoChangePunctuation: set of AnsiChar =
[' ', '`', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
'_', '-', '+', '=', ';', ':', '''', '"', '[', ']', '{', '}', ',',
'.', '/', '?', '<', '>'];
function Max(x, y: integer): integer;
begin
if x > y then
Result := x
else
Result := y;
end;
function Min(x, y: integer): integer;
begin
if x < y then
Result := x
else
Result := y;
end;
function GetWordOnPosEx(const S: string; const P: integer; out iBeg, iEnd: integer): string;
begin
Result := '';
if (P > Length(S)) or (P < 1) then
exit;
iBeg := P;
if {$ifdef UNICODE} (S[P] < #255) and {$endif} (AnsiChar(S[P]) in Separators) then
if (P < 1) or ({$ifdef UNICODE}(S[P - 1] < #255) and {$endif} (AnsiChar(S[P - 1]) in Separators)) then
inc(iBeg)
else if {$ifdef UNICODE} (S[P - 1] > #255) or {$endif} not (AnsiChar(S[P - 1]) in Separators) then
dec(iBeg);
while iBeg >= 1 do
if {$ifdef UNICODE} (S[iBeg] < #255) and {$endif} (AnsiChar(S[iBeg]) in Separators) then
break
else
dec(iBeg);
inc(iBeg);
iEnd := P;
while iEnd <= Length(S) do
if {$ifdef UNICODE} (S[iEnd] < #255) and {$endif} (AnsiChar(S[iEnd]) in Separators) then
break
else
inc(iEnd);
if iEnd > iBeg then
Result := Copy(S, iBeg, iEnd - iBeg)
else
Result := S[P];
end;
var
CF_MEMOEX: integer = 0; // indicates MemoEx clipboard
function _CopyToClipboard(Handle: THandle; const SelText: string; FontCharset: TFontCharset): boolean;
var
Data: HGLOBAL;
_Text: PChar;
fmt: UINT;
begin
Result := false;
if SelText <> '' then
begin
Data := GlobalAlloc(GMEM_MOVEABLE or GMEM_ZEROINIT, (length(SelText) + 1) * SizeOf(char));
if Data <> 0 then
begin
_Text := GlobalLock(Data);
if Assigned(_Text) then
begin
StrCopy(_Text, PChar(SelText));
GlobalUnlock(Data);
{$ifdef UNICODE}
fmt := CF_UNICODETEXT;
{$else}
if FontCharset = OEM_CHARSET then
fmt := CF_OEMTEXT
else
fmt := CF_TEXT;
{$endif}
if OpenClipboard(Handle) then
begin
EmptyClipboard;
SetClipboardData(fmt, Data);
SetClipboardData(CF_MEMOEX, 0); // indicates from MemoEx
CloseClipboard;
Result := true;
end
else
GlobalFree(Data);
end
else
GlobalFree(Data);
end;
end;
end;
function _PasteFromClipboard(Handle: THandle; FontCharset: TFontCharset;
{$ifdef CLIPBOARDPROTECT}const InternalClip: string;{$endif}
DeleteCRLF: boolean = true): string;
var
fmtText, fmtOEMText: boolean;
Data: HGLOBAL;
fmt: UINT;
_Text: PChar;
Txt: string;
i: integer;
begin
{$ifdef CLIPBOARDPROTECT} // ClipProtect will trunc clipboard to 2KB
if IsClipboardFormatAvailable(CF_MEMOEX) and (InternalClip <> '') then
begin
result := InternalClip;
exit;
end;
{$endif}
Result := '';
{$ifdef UNICODE}
fmtText := IsClipboardFormatAvailable(CF_UNICODETEXT);
fmtOEMText := IsClipboardFormatAvailable(CF_TEXT);
{$else}
fmtText := IsClipboardFormatAvailable(CF_TEXT);
fmtOEMText := IsClipboardFormatAvailable(CF_OEMTEXT);
{$endif}
if fmtText or fmtOEMText then
if OpenClipboard(Handle) then
begin
{$ifdef UNICODE}
if not fmtText then
fmt := CF_TEXT
else
fmt := CF_UNICODETEXT;
{$else}
if not fmtText then
fmt := CF_OEMTEXT
else
fmt := CF_TEXT;
{$endif}
Data := GetClipboardData(fmt);
if Data <> 0 then
begin
_Text := GlobalLock(Data);
if Assigned(_Text) then
begin
{$ifdef UNICODE}
if fmt = CF_UNICODETEXT then
Txt := _Text
else
Txt := UnicodeString(AnsiString(PAnsiChar(_Text)));
{$else}
Txt := _Text;
if (FontCharset = OEM_CHARSET) and (fmt = CF_TEXT) then
CharToOEM(PChar(Txt), PChar(Txt))
else if (FontCharset = DEFAULT_CHARSET) and (fmt = CF_OEMTEXT) then
OEMToChar(PChar(Txt), PChar(Txt));
{$endif}
if DeleteCRLF then
begin
i := 1;
while i <= length(Txt) do
if Ord(Txt[i]) in [10, 13] then
System.Delete(Txt, i, 1)
else
begin
if Txt[i] < ' ' then // prevent TAB bug
Txt[i] := ' ';
inc(i);
end;
end
else
for i := 1 to length(Txt) do
if ord(Txt[i]) in [1..9, 11, 12, 14..31] then // prevent TAB bug
Txt[i] := ' ';
Result := Txt;
GlobalUnlock(Data);
end;
end;
CloseClipboard;
end;
end;
function GetWordOnPos(const S: string; const P: integer): string;
var
i, Beg: integer;
begin
Result := '';
if (P > Length(S)) or (P < 1) then
exit;
for i := P downto 1 do
if {$ifdef UNICODE} (S[i] < #255) and {$endif} (AnsiChar(S[i]) in Separators) then
break;
Beg := i + 1;
for i := P to Length(S) do
if {$ifdef UNICODE} (S[i] < #255) and {$endif} (AnsiChar(S[i]) in Separators) then
break;
if i > Beg then
Result := Copy(S, Beg, i - Beg)
else
Result := S[P];
end;
function SubStr(const S: string; const index: integer; const Separator: string): string;
// used on word completion
var
i: integer;
pB, pE: PChar;
begin
Result := '';
if (index < 0) or ((index = 0) and (Length(S) > 0) and (S[1] = Separator)) then
exit;
pB := PChar(S);
for i := 1 to index do
begin
pB := StrPos(pB, PChar(Separator));
if pB = nil then
exit;
pB := pB + Length(Separator);
end;
pE := StrPos(pB + 1, PChar(Separator));
if pE = nil then
pE := PChar(S) + Length(S);
if not (StrLIComp(pB, PChar(Separator), Length(Separator)) = 0) then
SetString(Result, pB, pE - pB);
end;
function KeyPressed(VK: integer): boolean;
begin
Result := GetKeyState(VK) and $8000 = $8000;
end;
{ TRAControlScrollBar95 }
constructor TRAControlScrollBar95.Create;
begin
FPage := 1;
FSmallChange := 1;
FLargeChange := 1;
end;
const
SBKIND: array[TScrollBarKind] of integer = (SB_HORZ, SB_VERT);
procedure TRAControlScrollBar95.SetParams(AMin, AMax, APosition, APage: integer);
var
SCROLLINFO: TSCROLLINFO;
begin
if AMax < AMin then
raise EInvalidOperation.Create(SScrollBarRange);
if APosition < AMin then
APosition := AMin;
if APosition > AMax then
APosition := AMax;
if Handle > 0 then
begin
with SCROLLINFO do
begin
cbSize := sizeof(TSCROLLINFO);
fMask := SIF_DISABLENOSCROLL;
if (AMin >= 0) or (AMax >= 0) then
fMask := fMask or SIF_RANGE;
if APosition >= 0 then
fMask := fMask or SIF_POS;
if APage >= 0 then
fMask := fMask or SIF_PAGE;
nPos := APosition;
nMin := AMin;
nMax := AMax;
nPage := APage;
end;
SetScrollInfo(Handle, // handle of window with scroll bar
SBKIND[Kind], // scroll bar flag
SCROLLINFO, // pointer to structure with scroll parameters
true // redraw flag
);
end;
end;
procedure TRAControlScrollBar95.SetParam(index, Value: Integer);
begin
case index of
0:
FMin := Value;
1:
FMax := Value;
2:
FPosition := Value;
3:
FPage := Value;
end;
if FMax < FMin then
raise EInvalidOperation.Create(SScrollBarRange);
if FPosition < FMin then
FPosition := FMin;
if FPosition > FMax then
FPosition := FMax;
SetParams(FMin, FMax, FPosition, FPage);
end;
procedure TRAControlScrollBar95.DoScroll(var Message: TWMScroll);
var
ScrollPos: Integer;
NewPos: Longint;
ScrollInfo: TScrollInfo;
begin
with Message do
begin
NewPos := FPosition;
case TScrollCode(ScrollCode) of
scLineUp:
Dec(NewPos, FSmallChange);
scLineDown:
Inc(NewPos, FSmallChange);
scPageUp:
Dec(NewPos, FLargeChange);
scPageDown:
Inc(NewPos, FLargeChange);
scPosition, scTrack:
with ScrollInfo do
begin
cbSize := SizeOf(ScrollInfo);
fMask := SIF_ALL;
GetScrollInfo(Handle, SBKIND[Kind], ScrollInfo);
NewPos := nTrackPos;
end;
scTop:
NewPos := FMin;
scBottom:
NewPos := FMax;
end;
if NewPos < FMin then
NewPos := FMin;
if NewPos > FMax then
NewPos := FMax;
ScrollPos := NewPos;
Scroll(TScrollCode(ScrollCode), ScrollPos);
end;
Position := ScrollPos;
end;
procedure TRAControlScrollBar95.Scroll(ScrollCode: TScrollCode; var ScrollPos: Integer);
begin
if Assigned(FOnScroll) then
FOnScroll(Self, ScrollCode, ScrollPos);
end;
{$IFDEF MEMOEX_UNDO}
type
TCaretUndo = class(TUndo)
private
FCaretX, FCaretY: integer;
public
constructor Create(const AMemoEx: TCustomMemoEx; const ACaretX, ACaretY: integer);
procedure Undo; override;
procedure Redo; override;
end;
TInsertUndo = class(TCaretUndo)
private
FText: string;
FOffset, FParaOffset: integer;
public
constructor Create(const AMemoEx: TCustomMemoEx; const ACaretX, ACaretY: integer;
const AText: string);
procedure Undo; override;
end;
TReLineUndo = class(TInsertUndo);
TInsertTabUndo = class(TInsertUndo);
TOverwriteUndo = class(TCaretUndo)
private
FOldText, FNewText: string;
FOffset: integer;
public
constructor Create(const AMemoEx: TCustomMemoEx; const ACaretX, ACaretY: integer;
const AOldText, ANewText: string);
procedure Undo; override;
end;
TDeleteUndo = class(TInsertUndo)
public
procedure Undo; override;
end;
TDeleteTrailUndo = class(TDeleteUndo);
TBackspaceUndo = class(TDeleteUndo)
public
procedure Undo; override;
end;
TReplaceUndo = class(TCaretUndo)
private
FBeg, FEnd: integer;
FText, FNewText: string;
public
constructor Create(const AMemoEx: TCustomMemoEx; const ACaretX, ACaretY: integer;
const ABeg, AEnd: integer; const AText, ANewText: string);
procedure Undo; override;
end;
TDeleteSelectedUndo = class(TDeleteUndo)
private
FSelBlock: boolean; { vertical block }
FSelBegX, FSelBegY, FSelEndX, FSelEndY, FSelOffs: integer;
public
constructor Create(const AMemoEx: TCustomMemoEx; const ACaretX, ACaretY: integer;
const AText: string; const ASelBlock: boolean; const ASelBegX, ASelBegY,
ASelEndX, ASelEndY, ASelOffs: integer);
procedure Undo; override;
end;
TSelectUndo = class(TCaretUndo)
private
FSelBlock: boolean; { vertical block }
FSelBegX, FSelBegY, FSelEndX, FSelEndY: integer;
public
constructor Create(const AMemoEx: TCustomMemoEx; const ACaretX, ACaretY: integer;
const ASelBlock: boolean; const ASelBegX, ASelBegY, ASelEndX, ASelEndY: integer);
procedure Undo; override;
end;
TUnselectUndo = class(TSelectUndo);
TBeginCompoundUndo = class(TUndo)
public
procedure Undo; override;
end;
TEndCompoundUndo = class(TBeginCompoundUndo);
{$ENDIF MEMOEX_UNDO}
procedure Err;
begin
MessageBeep(0);
end;
function FindNotBlankCharPos(const S: string): integer;
begin
for result := 1 to Length(S) do
if S[result] <> ' ' then
Exit;
Result := 1;
end;
function ANSIChangeCase(const S: string): string;
var
i: integer;
Up: char;
begin
Result := S;
for i := 1 to Length(Result) do
begin
Up := upcase(Result[i]);
if Result[i] = Up then
Result[i] := chr(ord(Result[i]) + 32)
else
Result[i] := Up;
end;
end;
type
{$ifdef CPU64}
PPtrInt = ^Int64;
{$else}
PPtrInt = ^integer;
{$endif}
function StringDynArrayGetSize(V: PPtrInt; n: integer): integer;
// usage: Siz := StringDynArrayGetSize(pointer(FStrings),FCount);
var
i: integer;
begin
result := 0;
for i := 1 to n do begin
{$ifdef FPC}
inc(result, length(string(V^));
{$else}
if V^ <> 0 then
inc(result, PInteger(V^ - 4)^);
{$endif}
inc(V);
end;
end;
function StringDynArrayToPChar(V: PPtrInt; n: integer; P: PChar): PChar;
// usage: StringDynArrayToPChar(pointer(FStrings),FCount,pointer(Result))
var
i, Size: integer;
begin
for i := 1 to n do begin
{$ifdef FPC}
size := length(V^);
if size <> 0 then
begin
{$else}
if V^ <> 0 then
begin
size := PInteger(V^ - 4)^;
{$endif}
move(pointer(V^)^, P^, size * SizeOf(char));
inc(P, size);
end;
inc(V);
end;
result := P;
end;
{ TEditorStrings }
procedure TEditorStrings.LoadFromFile(const FileName: string);
begin
BeginUpdate;
inherited LoadFromFile(FileName);
if Assigned(FOnAfterLoad) then
FOnAfterLoad(FMemoEx);
EndUpdate;
end;
procedure TEditorStrings.SaveToFile(const FileName: string);
begin
if Assigned(FOnBeforeSave) then
FOnBeforeSave(FMemoEx);
inherited SaveToFile(FileName);
end;
procedure TEditorStrings.Recount(Index: integer);
var
i: integer;
begin
if FCount = 0 then
exit;
if Index = 0 then
begin
FList[0].FPreCount := 0;
inc(Index);
end;
for i := Index to FCount - 1 do
with FList[i - 1] do
FList[i].FPreCount := FPreCount + FCount;
end;
procedure TEditorStrings.Index2ParaIndex(Index: integer; out Para, ParaIndex: integer);
var
L, H, I: integer;
begin
if (not FMemoEx.FWordWrap) or (FParaLinesCount = FCount) then
begin
Para := Index;
if Para > FCount - 1 then
Para := FCount - 1;
ParaIndex := 0;
end
else
begin
{ fast find paragraph index using MinMax (binary search) algo }
Para := -1;
ParaIndex := -1;
L := 0;
H := FCount - 1;
while L <= H do
begin
I := (L + H) shr 1;
with FList[I] do
if Index > FPreCount + FCount - 1 then
L := I + 1
else
begin
H := I - 1;
if (Index <= FPreCount + FCount - 1) and (Index >= FPreCount) then
begin // found
Para := I;
ParaIndex := Index - FPreCount;
break;
end;
end;
end;
end;
end;
procedure ListIndexError(Index: integer);
// outside procedure -> no temp string -> less heap
{$ifdef CPUX86}
function ReturnAddr: Pointer;
asm
MOV EAX, [EBP + 4]
end;
{$endif}
begin
raise EStringListError.CreateFmt(SListIndexError, [Index]){$ifdef CPUX86} at ReturnAddr{$endif};
end;
function TEditorStrings.GetParagraphByIndex(Index: integer; out ParaIndex, IndexOffs: integer): string;
var
_P, _PI: integer;
begin
IndexOffs := 0;
ParaIndex := 0;
Result := '';
Index2ParaIndex(Index, _P, _PI);
if (_P = -1) or (_PI = -1) then
ListIndexError(Index);
ParaIndex := _P;
result := _GetString(_P);
IndexOffs := StringDynArrayGetSize(pointer(FList[_P].FStrings), _PI);
end;
procedure TEditorStrings.Caret2Paragraph(X, Y: integer; out ParaIndex, IndexOffs: integer);
var
_P, _PI: integer;
begin
ParaIndex := 0;
IndexOffs := 0;
Index2ParaIndex(Y, _P, _PI);
if (_P = -1) or (_PI = -1) then
ListIndexError(Y);
ParaIndex := _P;
IndexOffs := X + StringDynArrayGetSize(pointer(FList[_P].FStrings), _PI);
end;
procedure TEditorStrings.Paragraph2Caret(ParaIndex, IndexOffs: integer; out X, Y: integer);
var
i, j, k: integer;
found: boolean;
begin
X := 0;
Y := ParaIndex;
found := false;
k := 0;
for i := 0 to FCount - 1 do
with Flist[i] do
begin
if i >= Y then
begin
for j := 0 to FCount - 1 do
begin
inc(X, length(FStrings[j]));
if X >= IndexOffs then
begin
found := true;
Y := k + j;
X := IndexOffs - (X - length(FStrings[j]));
break;
end;
end;
if found then
break;
end;
inc(k, FCount);
end;
if not found then
begin
if X > 0 then
begin
Y := k;
X := length(FList[Y].FStrings[FList[Y].FCount - 1]);
exit;
end;
Y := FCount - 1;
if Y >= 0 then
X := length(FList[Y].FStrings[FList[Y].FCount - 1])
else
begin
X := 0;
Y := 0;
end;
end;
end;
function TEditorStrings.GetParaOffs(ParaIndex: integer): integer;
var
i: integer;
begin
Result := ParaIndex * 2;
for i := 0 to ParaIndex - 1 do
with FList[i] do
inc(Result, StringDynArrayGetSize(pointer(FStrings), FCount));
end;
procedure TEditorStrings.ReformatParagraph(ParaIndex: integer);
var // full rewrite by AB: much faster and use less ram
s: string;
c, d, b: PChar;
L: integer;
begin
with FList[ParaIndex] do
begin
dec(FParaLinesCount, FCount);
FPreCount := 0;
if FMemoEx.FWordWrap then
begin
s := _GetString(ParaIndex); // whole paragraph text in s
L := FMemoEx.FRightMargin;
if length(s) <= L then
begin
if FCount > 1 then
begin
SetLength(FStrings, 1);
FCount := 1;
FStrings[0] := s;
end;
end
else
begin
FCount := 0;
{$ifdef FPC} UniqueString(s); {$endif} // we change FStrings[0] below
c := @s[1];
d := c;
b := c;
while (c^ <> #0) do
begin
if c^ = ' ' then
b := c; // b = last ' '
if ((c - d) >= L) then
begin
inc(FCount);
if FCount >= length(FStrings) then
Setlength(FStrings, FCount + 10);
if b = d then
b := d + L; // if no ' '
SetString(FStrings[FCount - 1], d, b - d + 1);
c := b + 1;
b := c;
d := c;
if c^ = #0 then
break;
end;
inc(c);
end;
if d <> c then
begin // append last chars (if any) as last line
inc(FCount);
SetString(FStrings[FCount - 1], d, c - d);
end;
Setlength(FStrings, FCount); // set exact line count
end;
end
else if FCount > 1 then
begin // something to reformat only if FCount>1
s := _GetString(ParaIndex);
SetLength(FStrings, 1);
FCount := 1;
FStrings[0] := s;
end;
inc(FParaLinesCount, FCount);
end;
end;
procedure TEditorStrings.Reformat;
var
i: integer;
begin
for i := 0 to FCount - 1 do
ReformatParagraph(i);
Recount(0);
Changed;
end;
procedure TEditorStrings.CheckLength(const st: string);
begin
if length(st) > FMemoEx.Max_X then
FMemoEx.SetMax_X(length(st) + 1);
end;
constructor TEditorStrings.Create;
begin
inherited Create;
FParaLinesCount := 0;
FOnAfterLoad := nil;
FOnBeforeSave := nil;
end;
destructor TEditorStrings.Destroy;
begin
// FOnChange := nil;
FOnChanging := nil;
inherited Destroy;
end;
function TEditorStrings.Add(const S: string): integer;
begin
Result := FCount;
InsertItem(Result, S);
end;
procedure TEditorStrings.Delete(Index: integer);
begin
if (Index < 0) or (Index >= FCount) then
ListIndexError(Index);
Changing;
dec(FParaLinesCount, FList[Index].FCount);
Dec(FCount);
Finalize(FList[Index]); // avoid memory bug: release deleted FStrings
if Index < FCount then
begin
System.Move(FList[Index + 1], FList[Index], (FCount - Index) * SizeOf(TParagraph));
FillChar(FList[FCount], sizeof(TParagraph), 0); // avoid memory bug
Recount(Index);
end;
Changed;
end;
procedure TEditorStrings.Insert(Index: integer; const S: string);
begin
if (Index < 0) or (Index > FCount) then
ListIndexError(Index);
InsertItem(Index, S);
end;
procedure TEditorStrings.InsertItem(Index: integer; const S: string);
begin
Changing;
if FCount = length(FList) then
Grow;
if Index < FCount then
System.Move(FList[Index], FList[Index + 1], (FCount - Index) * SizeOf(TParagraph));
fillchar(FList[Index], sizeof(TParagraph), 0); // avoid memory bug
Inc(FCount);
AddParaStr(Index, S);
end;
{$WARNINGS OFF}
function TEditorStrings.AddParaStr(ParaIndex: integer; const S: string): integer;
begin
if (ParaIndex < 0) or (ParaIndex >= FCount) then
ListIndexError(ParaIndex);
with FList[ParaIndex] do
begin
inc(FCount);
inc(FParaLinesCount);
SetLength(FStrings, FCount);
FStrings[FCount - 1] := S;
CheckLength(S);
end;
ReformatParagraph(ParaIndex);
Recount(ParaIndex);
Changed;
end;
{$WARNINGS ON}
procedure TEditorStrings.Changed;
begin
if (csLoading in FMemoEx.ComponentState) then
exit;
if FMemoEx.FUpdateLock = 0 then
begin
FMemoEx.TextAllChanged;
if Assigned(FMemoEx.FOnChange) then
FMemoEx.FOnChange(Self);
end;
end;
procedure TEditorStrings.Changing;
begin
if (FMemoEx.FUpdateLock = 0) and Assigned(FOnChanging) then
FOnChanging(Self);
end;
procedure TEditorStrings.Clear;
begin
if FCount <> 0 then
begin
Changing;
FCount := 0;
FParaLinesCount := 0;
Finalize(FList); // free all memory
Changed;
end;
fMemoEx.SetCaret(0, 0);
end;
procedure TEditorStrings.BeginUpdate;
begin
inc(FMemoEx.FUpdateLock);
end;
procedure TEditorStrings.EndUpdate;
begin
dec(FMemoEx.FUpdateLock);
Changed;
end;
function TEditorStrings._GetString(ParaIndex: integer): string;
begin
with FList[ParaIndex] do
if FCount > 1 then
begin
SetString(Result, nil, StringDynArrayGetSize(pointer(FStrings), FCount));
StringDynArrayToPChar(pointer(FStrings), FCount, pointer(Result));
end
else
result := FStrings[0];
end;
function TEditorStrings.Get(Index: integer): string;
begin
if cardinal(Index) >= cardinal(FCount) then
ListIndexError(Index);
Result := _GetString(Index);
end;
function TEditorStrings.GetParagraph(Index: integer): PParagraph;
begin
if cardinal(Index) >= cardinal(FCount) then
ListIndexError(Index);
Result := @FList[Index];
end;
function TEditorStrings.GetParaString(Index: integer): string;
var
_P, _PI: integer;
begin
if not FMemoEx.FWordWrap or (FParaLinesCount = FCount) then
Result := Get(Index)
else
begin
Index2ParaIndex(Index, _P, _PI);
if (_P = -1) or (_PI = -1) then
ListIndexError(Index);
Result := FList[_P].FStrings[_PI];
end;
end;
procedure TEditorStrings.Grow;
var
Delta: integer;
begin
Delta := length(FList);
if Delta > 64 * 4 then
inc(Delta, Delta shr 2)
else
inc(Delta, 64); // AB
SetLength(FList, Delta);
end;
procedure TEditorStrings._PutString(ParaIndex: integer; const S: string);
var
old_count, old_precount: integer;
begin
old_count := FList[ParaIndex].FCount;
old_precount := FList[ParaIndex].FPreCount;
dec(FParaLinesCount, FList[ParaIndex].FCount);
with FList[ParaIndex] do
begin
FCount := 1;
SetLength(FStrings, 1);
FStrings[0] := S;
FObject := nil;
end;
inc(FParaLinesCount);
ReformatParagraph(ParaIndex);
if old_count <> FList[ParaIndex].FCount then
Recount(ParaIndex)
else
FList[ParaIndex].FPreCount := old_precount;
end;
procedure TEditorStrings.Put(Index: integer; const S: string);
begin
if (Index < 0) or (Index >= FCount) then
ListIndexError(Index);
CheckLength(S);
Changing;
_PutString(Index, S);
Changed;
end;
procedure TEditorStrings.PutObject(Index: Integer; AObject: TObject);
begin
if (Index < 0) or (Index >= FCount) then
ListIndexError(Index);
FList[Index].FObject := AObject;
end;
function TEditorStrings.GetObject(Index: Integer): TObject;
begin
if (Index < 0) or (Index >= FCount) then
ListIndexError(Index);
result := FList[Index].FObject;
end;
procedure TEditorStrings.PutParaString(Index: integer; const S: string);
var
_P, _PI: integer;
old_count, old_precount: integer;
begin
if not FMemoEx.FWordWrap then
Put(Index, S)
else
begin
Index2ParaIndex(Index, _P, _PI);
if (_P = -1) or (_PI = -1) then
ListIndexError(Index);
Changing;
old_count := FList[_P].FCount;
old_precount := FList[_P].FPreCount;
CheckLength(S);
FList[_P].FStrings[_PI] := S;
ReformatParagraph(_P);
if old_count <> FList[_P].FCount then
Recount(_P)
else
FList[_P].FPreCount := old_precount;
Changed;
end;
end;
procedure TEditorStrings.SetUpdateState(Updating: Boolean);
begin
if Updating then
Changing
else
Changed;
end;
procedure TEditorStrings.SetTextStr(const Value: string);
begin
inc(FMemoEx.FUpdateLock);
inherited SetTextStr(Value);
dec(FMemoEx.FUpdateLock);
if FMemoEx.FUpdateLock = 0 then
begin
{$IFDEF MEMOEX_EDITOR}
FMemoEx.CantUndo;
{$ENDIF MEMOEX_EDITOR}
FMemoEx.TextAllChanged;
FMemoEx.SetCaretInternal(0, 0);
end;
end;
procedure TEditorStrings.ReLine;
// complete line with spaces if caret X is after real chars
var
L: integer;
begin
inc(FMemoEx.FUpdateLock);
try
{$IFDEF MEMOEX_UNDO}
if FParaLinesCount = 0 then
L := FMemoEx.FCaretX
else
L := Length(ParaStrings[FParaLinesCount - 1]);
while FMemoEx.FCaretY > FParaLinesCount - 1 do
begin
if FParaLinesCount > 0 then
TReLineUndo.Create(FMemoEx, L, FMemoEx.FCaretY, #13#10);
L := 0;
Add('');
end;
{$ENDIF MEMOEX_UNDO}
if FMemoEx.FCaretX > Length(ParaStrings[FMemoEx.FCaretY]) then
begin
L := FMemoEx.FCaretX - Length(ParaStrings[FMemoEx.FCaretY]);
{$IFDEF MEMOEX_UNDO}
TReLineUndo.Create(FMemoEx, Length(ParaStrings[FMemoEx.FCaretY]), FMemoEx.FCaretY, StringOfChar(' ', L));
{$ENDIF MEMOEX_UNDO}
PutParaString(FMemoEx.FCaretY, ParaStrings[FMemoEx.FCaretY] + StringOfChar(' ', L));
end;
finally
dec(FMemoEx.FUpdateLock);
end;
end; { ReLine }
procedure TEditorStrings.SetLockText(const Text: string);
begin
inc(FMemoEx.FUpdateLock);
try
inherited SetTextStr(Text);
finally
dec(FMemoEx.FUpdateLock);
end;
end;
procedure TEditorStrings.SetInternalParaStr(Index: integer; const Value: string);
begin
inc(FMemoEx.FUpdateLock);
try
PutParaString(Index, Value);
finally
dec(FMemoEx.FUpdateLock);
end;
end;
procedure TEditorStrings.SetInternal(Index: integer; const Value: string);
begin
inc(FMemoEx.FUpdateLock);
try
Put(Index, Value);
finally
dec(FMemoEx.FUpdateLock);
end;
end;
function TEditorStrings.GetTextStr: string;
var
i, sizetot: integer;
P: PChar;
begin
// result := inherited GetTextStr; exit; 8 times slower than code below
SizeTot := GetTextLength;
Setlength(Result, Sizetot);
P := Pointer(Result);
for i := 0 to FCount - 1 do
with FList[i] do
begin
P := StringDynArrayToPChar(pointer(FStrings), FCount, P); // line
{$ifdef UNICODE}
pCardinal(P)^ := $000A000D;
{$else}
pWord(P)^ := $0A0D;
{$endif}
inc(P, 2); // CRLF
end;
assert((p - pointer(result)) = sizeTot);
end;
function TEditorStrings.GetCount: Integer;
begin
result := FCount;
end;
function TEditorStrings.GetTextLength: integer;
var
i: integer;
begin
Result := FCount * 2; // CRLF char size
for i := 0 to FCount - 1 do
with FList[i] do
inc(Result, StringDynArrayGetSize(pointer(FStrings), FCount));
end;
function TEditorStrings.HasText: boolean;
var
i: integer;
begin
result := true;
for i := 0 to FCount - 1 do
with FList[i] do
if StringDynArrayGetSize(pointer(FStrings), FCount) <> 0 then
exit;
result := false;
end;
{ TEditorClient }
function TEditorClient.GetCanvas: TCanvas;
begin
Result := FMemoEx.Canvas;
end;
function TEditorClient.Left: integer;
begin
Result := FMemoEx.GutterWidth + GutterRightMargin;
end;
function TEditorClient.Height: integer;
begin
Result := FMemoEx.ClientHeight;
end;
function TEditorClient.Width: integer;
begin
Result := Max(FMemoEx.ClientWidth - Left, 0);
end;
function TEditorClient.ClientWidth: integer;
begin
Result := Width;
end;
function TEditorClient.ClientHeight: integer;
begin
Result := Height;
end;
function TEditorClient.ClientRect: TRect;
begin
Result := Bounds(Left, Top, Width, Height);
end;
function TEditorClient.BoundsRect: TRect;
begin
Result := Bounds(0, 0, Width, Height);
end;
procedure TGutter.Invalidate;
begin
Paint;
end;
procedure TGutter.Paint;
var
Rect: TRect;
begin
with FMemoEx, Canvas do
begin
Brush.Style := bsSolid;
Brush.Color := FGutterColor;
Rect.Left := 0;
Rect.Top := EditorClient.Top;
Rect.Right := GutterWidth;
Rect.Bottom := Rect.Top + EditorClient.Height;
FillRect(Rect);
Pen.Width := 1;
Pen.Color := Color;
MoveTo(GutterWidth - 2, EditorClient.Top);
LineTo(GutterWidth - 2, EditorClient.Top + EditorClient.Height);
Pen.Width := 2;
MoveTo(GutterWidth + 1, EditorClient.Top);
LineTo(GutterWidth + 1, EditorClient.Top + EditorClient.Height);
Pen.Width := 1;
Pen.Color := clGray;
MoveTo(GutterWidth - 1, EditorClient.Top);
LineTo(GutterWidth - 1, EditorClient.Top + EditorClient.Height);
end;
with FMemoEx do
GutterPaint(Canvas, Rect);
end;
constructor TCustomMemoEx.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ControlStyle := [csCaptureMouse, csClickEvents {, csOpaque}, csDoubleClicks, csReplicatable];
FInsertMode := true;
FReadOnly := false;
FWantTabs := true;
FLines := TEditorStrings.Create;
FLines.FMemoEx := Self;
FKeyboard := TKeyboard.Create;
FRows := 1;
FCols := 1;
{$IFDEF MEMOEX_UNDO}
FUndoBuffer := TUndoBuffer.Create;
FUndoBuffer.FMemoEx := Self;
FGroupUndo := true;
{$ENDIF MEMOEX_UNDO}
FDrawBitmap := TBitmap.Create;
FFont := TFont.Create;
with FFont do
begin
if Screen.Fonts.IndexOf('Consolas')>=0 then
begin
Name := 'Consolas';
Size := 9;
end
else
begin
Name := 'Courier New';
Size := 10;
end;
Pitch := fpFixed;
OnChange := FontChanged;
end;
FRightMarginVisible := true;
FRightMargin := 80;
FBorderStyle := bsSingle;
Ctl3d := true;
Height := 40;
Width := 150;
ParentColor := false;
Cursor := crIBeam;
TabStop := true;
FTabStops := '8';
FSmartTab := true;
FBackSpaceUnindents := true;
FAutoIndent := true;
FKeepTrailingBlanks := false;
FCursorBeyondEOF := false;
FCursorBeyondEOL := true;
FWordWrap := true;
FScrollBars := ssBoth;
scbHorz := TRAControlScrollBar95.Create;
scbVert := TRAControlScrollBar95.Create;
scbVert.Kind := sbVertical;
scbHorz.OnScroll := ScrollBarScroll;
scbVert.OnScroll := ScrollBarScroll;
Color := clWindow;
FGutterColor := clBtnFace;
FclSelectBC := clHighLight;
FclSelectFC := clHighLightText;
FRightMarginColor := clSilver;
EditorClient := TEditorClient.Create;
EditorClient.FMemoEx := Self;
FGutter := TGutter.Create;
FGutter.FMemoEx := Self;
FLeftCol := 0;
FTopRow := 0;
FSelected := false;
FCaretX := 0;
FCaretY := 0;
timerScroll := TTimer.Create(Self);
timerScroll.Enabled := false;
timerScroll.Interval := 100;
timerScroll.OnTimer := ScrollTimer;
SelAttrs_Size := 0;
FTabPos := nil;
SetMax_X(512);
mouse_down := false;
double_clicked := false;
mouse_dragged := false;
{$IFDEF MEMOEX_EDITOR}
{$IFDEF MEMOEX_DEFLAYOUT}
FKeyboard.SetDefLayout;
{$ENDIF MEMOEX_DEFLAYOUT}
{$IFDEF MEMOEX_COMPLETION}
FCompletion := TCompletion.Create2(Self);
{$ENDIF MEMOEX_COMPLETION}
{$ENDIF MEMOEX_EDITOR}
end;
destructor TCustomMemoEx.Destroy;
begin
FLines.Free;
scbHorz.Free;
scbVert.Free;
EditorClient.Free;
FKeyboard.Free;
{$IFDEF MEMOEX_EDITOR}
{$IFDEF MEMOEX_COMPLETION}
FCompletion.Free;
{$ENDIF MEMOEX_COMPLETION}
{$ENDIF MEMOEX_EDITOR}
FGutter.Free;
FDrawBitmap.Free;
FFont.Free;
SelAttrs := nil;
FTabPos := nil;
{$IFDEF MEMOEX_UNDO}
FreeAndNil(FUndoBuffer);
{$ENDIF MEMOEX_UNDO}
inherited Destroy;
end;
procedure TCustomMemoEx.Invalidate;
begin
if (csLoading in ComponentState) then
exit;
if FUpdateLock = 0 then
inherited;
end;
procedure TCustomMemoEx.Loaded;
begin
inherited Loaded;
scbVertWidth := GetSystemMetrics(SM_CXVSCROLL);
scbHorzHeight := GetSystemMetrics(SM_CYHSCROLL);
NextClipViewer := SetClipboardViewer(Handle);
UpdateEditorSize;
Changed;
SelectionChanged;
ClipboardChanged;
FModified := false;
{$IFDEF MEMOEX_COMPLETION}
FCompletion.UpdateAutoChange;
{$ENDIF}
end;
procedure TCustomMemoEx.CreateParams(var Params: TCreateParams);
const
BorderStyles: array[TBorderStyle] of cardinal = (0, WS_BORDER);
ScrollStyles: array[TScrollStyle] of cardinal = (0, WS_HSCROLL, WS_VSCROLL, WS_HSCROLL or WS_VSCROLL);
begin
inherited CreateParams(Params);
with Params do
begin
Style := Style or BorderStyles[FBorderStyle] or ScrollStyles[FScrollBars];
if NewStyleControls and Ctl3D and (FBorderStyle = bsSingle) then
begin
Style := Style and not WS_BORDER;
ExStyle := ExStyle or WS_EX_CLIENTEDGE;
end;
WindowClass.style := WindowClass.style and not (CS_HREDRAW or CS_VREDRAW);
end;
end;
procedure TCustomMemoEx.Resize;
begin
if not (csLoading in ComponentState) then
begin
UpdateEditorSize(true, false);
Invalidate;
end;
end;
procedure TCustomMemoEx.CreateWnd;
begin
inherited CreateWnd;
if FScrollBars in [ssHorizontal, ssBoth] then
scbHorz.Handle := Handle;
if FScrollBars in [ssVertical, ssBoth] then
scbVert.Handle := Handle;
FAllRepaint := true;
end;
procedure TCustomMemoEx.SetBorderStyle(Value: TBorderStyle);
begin
if FBorderStyle <> Value then
begin
FBorderStyle := Value;
RecreateWnd;
end;
end;
procedure TCustomMemoEx.PaintSelection;
var
iR: integer;
begin
for iR := FUpdateSelBegY to FUpdateSelEndY do
PaintLine(iR, -1, -1);
end;
procedure TCustomMemoEx.SetUnSelected;
begin
if FSelected then
begin
FSelectedText := false;
{$IFDEF MEMOEX_UNDO}
TUnselectUndo.Create(Self, FCaretX, FCaretY, FSelBlock, FSelBegX, FSelBegY, FSelEndX, FSelEndY);
{$ENDIF MEMOEX_UNDO}
PaintSelection;
end;
end;
function IsRectEmpty(R: TRect): boolean;
begin
Result := (R.Top = R.Bottom) and (R.Left = R.Right);
end;
function TCustomMemoEx.CalcCellRect(const X, Y: integer): TRect;
begin
Result := Bounds(EditorClient.Left + X * FCellRect.Width + 1,
EditorClient.Top + Y * FCellRect.Height, FCellRect.Width, FCellRect.Height)
end;
procedure TCustomMemoEx.Paint;
var
iR: integer;
ECR: TRect;
BX, EX, BY, EY: integer;
begin
if FUpdateLock > 0 then
exit;
{$IFDEF MEMOEX_NOOPTIMIZE}
FAllRepaint := true;
{$ENDIF}
PaintCaret(false);
ECR := EditorClient.Canvas.ClipRect;
OffsetRect(ECR, -FGutterWidth, 0);
if FAllRepaint then
ECR := EditorClient.BoundsRect;
BX := ECR.Left div FCellRect.Width - 1;
EX := ECR.Right div FCellRect.Width + 1;
BY := ECR.Top div FCellRect.Height;
EY := ECR.Bottom div FCellRect.Height + 1;
for iR := BY to EY do
PaintLine(FTopRow + iR, FLeftCol + BX, FLeftCol + EX);
PaintCaret(true);
FGutter.Paint;
FAllRepaint := false;
end;
procedure TCustomMemoEx.BeginUpdate;
begin
if self = nil then
exit;
inc(FUpdateLock);
end;
procedure TCustomMemoEx.EndUpdate;
begin
if self = nil then
exit;
if FUpdateLock = 0 then
Exit; { Error ? }
dec(FUpdateLock);
if FUpdateLock = 0 then
begin
FAllRepaint := true;
UpdateEditorSize(false);
Changed;
StatusChanged;
Invalidate;
end;
end;
{ FTabPos... }
procedure TCustomMemoEx.SetMax_X(const Value: integer);
begin
Max_X := Value;
SetLength(FTabPos, Max_X);
end;
{ FullUpdate }
procedure TCustomMemoEx.UpdateEditorSize(const FullUpdate: boolean; const RepaintGutter: boolean);
const
BiggestSymbol = 'W';
var
Size: TSize;
begin
if (csLoading in ComponentState) then
exit;
if FullUpdate then
begin
EditorClient.Canvas.Font := Font;
EditorClient.Canvas.Font.Style := [fsBold, fsItalic];
Size := EditorClient.Canvas.TextExtent(BiggestSymbol);
FCellRect.Width := Max(1, Size.cx);
FCellRect.Height := Max(1, Size.cy);
EditorClient.Canvas.Font := Font;
Size := EditorClient.Canvas.TextExtent(BiggestSymbol);
{
if FCellRect.Width <> Max(1, Size.cx) then
raise EMemoExError.CreateFmt('Font %s has inconsistent width vs style', [Font.Name]);
}
FDrawBitmap.Canvas.Font.Assign(Font);
FDrawBitmap.Canvas.Brush.Assign(EditorClient.Canvas.Brush);
FDrawBitmap.Width := Width;
FDrawBitmap.Height := FCellRect.Height;
end;
FVisibleColCount := Trunc(EditorClient.ClientWidth / FCellRect.Width);
FVisibleRowCount := Trunc(EditorClient.ClientHeight / FCellRect.Height);
FLastVisibleCol := FLeftCol + FVisibleColCount - 1;
FLastVisibleRow := FTopRow + FVisibleRowCount - 1;
FCols := -1;
FRows := -1;
Rows := FLines.ParaLineCount;
if FWordWrap then
Cols := FRightMargin
else
Cols := Max_X;
if RepaintGutter then
FGutter.Invalidate;
end;
procedure TCustomMemoEx.PaintLine(const Line: integer; ColBeg, ColEnd: integer);
var
Ch: string;
R: TRect;
F, k, x, i, j, iC, jC, SL, MX, PX, PY: integer;
T, S: string;
FAttrs: TLineAttrs;
LA, LB: TLineAttr;
begin
if (Line < FTopRow) or (Line > FTopRow + FVisibleRowCount) or (FUpdateLock > 0) then
exit;
if ColBeg < FLeftCol then
ColBeg := FLeftCol;
if (ColEnd < 0) or (ColEnd > FLeftCol + FVisibleColCount) then
ColEnd := FLeftCol + FVisibleColCount;
ColEnd := Min(ColEnd, Max_X - 1);
j := 0;
i := ColBeg;
if (Line > -1) and (Line < FLines.ParaLineCount) then
with FDrawBitmap do
begin
T := FLines.GetParagraphByIndex(Line, PY, PX);
S := FLines.ParaStrings[Line];
if not FWordWrap then
begin
iC := ColBeg;
jC := ColEnd;
end
else
begin
iC := 0;
jC := length(T);
end;
GetLineAttr(PY, Line, PX, length(S), iC, jC, T, FAttrs);
Canvas.Brush.Color := Color;
Canvas.FillRect(Bounds(EditorClient.Left, 0, 1, FCellRect.Height));
SL := Length(S);
if SL > ColEnd then
MX := ColEnd
else
MX := SL;
if FStripInvisible and FReadOnly and (ColBeg > 0) then
begin
x := PX + ColBeg;
if x >= SelAttrs_Size then
x := SelAttrs_Size - 1;
for k := PX to x do
if FAttrs[k].FC = FAttrs[k].BC then
inc(j);
end;
while i < MX do
with Canvas do
begin
iC := i + 1;
jC := iC + 1;
if iC <= SL then
Ch := S[iC]
else
Ch := ' ';
if (iC + PX > SelAttrs_Size) or (jC + PX > SelAttrs_Size) then
break;
LA := FAttrs[iC + PX - 1];
if SelAttrs[iC + PX - 1] then
with LA do
begin
FC := FclSelectFC;
BC := FclSelectBC;
end;
while (jC <= MX) and (jC + PX <= SelAttrs_Size) do
begin
// append chars with same attrs into Ch
LB := FAttrs[jC + PX - 1];
if SelAttrs[jC + PX - 1] then
with LB do
begin
FC := FclSelectFC;
BC := FclSelectBC;
end;
if (LA.FC = LB.FC) and (LA.BC = LB.BC) and (LA.LastInteger = LB.LastInteger) then
begin
if jC <= SL then
Ch := Ch + S[jC]
else
Ch := Ch + ' ';
inc(jC);
end
else
break;
end;
if (not ((LA.BC = LA.FC) and FStripInvisible)) or not readonly then
begin
// draw Ch into bitmap
Brush.Color := LA.BC;
Font.Color := LA.FC;
Font.Style := LA.Style;
R.Left := EditorClient.Left + (i - FLeftCol - j) * FCellRect.Width + 1;
R.Top := 0;
R.Right := R.Left + FCellRect.Width * Length(Ch);
R.Bottom := FCellRect.Height;
FillRect(R);
TextOut(R.Left, 0, Ch);
end
else
inc(j, length(Ch));
i := jC - 1;
end;
end
else
begin
FDrawBitmap.Canvas.Brush.Color := Color;
FDrawBitmap.Canvas.FillRect(Bounds(EditorClient.Left, 0, 1, FCellRect.Height));
end;
R := Bounds(CalcCellRect(i - j - FLeftCol, Line - FTopRow).Left, 0,
(FLeftCol + FVisibleColCount - i{ - j} + 10) * FCellRect.Width, FCellRect.Height);
FDrawBitmap.Canvas.Brush.Color := Color;
FDrawBitmap.Canvas.FillRect(R);
R := Bounds(EditorClient.Left, (Line - FTopRow) * FCellRect.Height,
(FVisibleColCount + 2) * FCellRect.Width, FCellRect.Height);
if FRightMarginVisible and (FRightMargin > FLeftCol) and (FRightMargin < FLastVisibleCol + 3) then
with FDrawBitmap.Canvas do
begin
Pen.Color := FRightMarginColor;
F := CalcCellRect(FRightMargin - FLeftCol, 0).Left;
MoveTo(F, 0);
LineTo(F, FCellRect.Height);
end;
BitBlt(EditorClient.Canvas.Handle, R.Left, R.Top, R.Right - R.Left, FCellRect.Height,
FDrawBitmap.Canvas.Handle, R.Left, 0, SRCCOPY);
end;
procedure TCustomMemoEx.GetLineAttr(Line, LineIdx, LineOffs, LineLen, ColBeg, ColEnd: integer;
const ALine: string; var FAttrs: TLineAttrs);
procedure SetAttrs(A: pLineAttr; count: integer);
var
i: integer;
begin
for i := 1 to Count do
begin
A^.FC := clWindowText;
A^.BC := clWindow;
A^.LastInteger := 0;
inc(A);
end;
end;
procedure ChangeSelectedAttr;
procedure DoChange(const iBeg, iEnd: integer);
var
i: integer;
begin
if (iBeg + LineOffs < SelAttrs_Size) and (iEnd + LineOffs < SelAttrs_Size) then
for i := iBeg + LineOffs to iEnd + LineOffs do
SelAttrs[i] := true;
end;
begin
if SelAttrs_Size > 0 then
FillChar(SelAttrs[0], SelAttrs_Size, 0);
if not FSelected then
exit;
if (LineIdx = FSelBegY) and (LineIdx = FSelEndY) then
DoChange(FSelBegX, Min(LineLen - 1, FSelEndX - 1 + integer(FInclusive)))
else
begin
if LineIdx = FSelBegY then
DoChange(FSelBegX, LineLen - 1);
if (LineIdx > FSelBegY) and (LineIdx < FSelEndY) then
DoChange(0, LineLen - 1);
if LineIdx = FSelEndY then
DoChange(0, Min(LineLen - 1, FSelEndX - 1 + integer(FInclusive)));
end
end;
begin
if SelAttrs_Size <> length(ALine) + 1 then
begin
SelAttrs_Size := length(ALine) + 1;
SetLength(SelAttrs, SelAttrs_Size);
end;
ChangeSelectedAttr;
SetLength(FAttrs, SelAttrs_Size);
SetAttrs(@FAttrs[0], SelAttrs_Size);
if (ALine <> '') and Assigned(FOnGetLineAttr) then
FOnGetLineAttr(Self, ALine, Line, SelAttrs, FAttrs);
end;
procedure TCustomMemoEx.ScrollBarScroll(Sender: TObject; ScrollCode: TScrollCode;
var ScrollPos: integer);
begin
case ScrollCode of
scLineUp..scPageDown, scTrack:
if Sender = scbVert then
Scroll(true, ScrollPos)
else if Sender = scbHorz then
Scroll(false, ScrollPos);
end;
end;
procedure TCustomMemoEx.Scroll(const Vert: boolean; const ScrollPos: integer);
var
R, RClip, RUpdate: TRect;
OldFTopRow: integer;
begin
if FUpdateLock = 0 then
begin
PaintCaret(false);
if Vert then
begin {Vertical Scroll}
OldFTopRow := FTopRow;
FTopRow := ScrollPos;
if Abs((OldFTopRow - ScrollPos) * FCellRect.Height) < EditorClient.Height then
begin
R := EditorClient.ClientRect;
R.Bottom := R.Top + CellRect.Height * (FVisibleRowCount + 1); {??}
RClip := R;
ScrollDC(EditorClient.Canvas.Handle, // handle of device context
0, // horizontal scroll units
(OldFTopRow - ScrollPos) * FCellRect.Height, // vertical scroll units
R, // address of structure for scrolling rectangle
RClip, // address of structure for clipping rectangle
0, // handle of scrolling region
@RUpdate // address of structure for update rectangle
);
InvalidateRect(Handle, @RUpdate, false);
if Assigned(OnPaintGutter) then
Gutter.Paint;
end
else
Invalidate;
Update;
end
else {Horizontal Scroll}
begin
FLeftCol := ScrollPos;
Invalidate;
end;
end
else { FUpdateLock > 0 }
begin
if Vert then
FTopRow := ScrollPos
else
FLeftCol := ScrollPos;
end;
FLastVisibleRow := FTopRow + FVisibleRowCount - 1;
FLastVisibleCol := FLeftCol + FVisibleColCount - 1;
if FUpdateLock = 0 then
begin
// DrawRightMargin;
PaintCaret(true);
end;
if Assigned(FOnScroll) then
FOnScroll(Self);
end;
procedure TCustomMemoEx.PaintCaret(bShow: boolean);
begin
if not bShow then
HideCaret(Handle)
else if Focused then
begin
with CalcCellRect(FCaretX - FLeftCol, FCaretY - FTopRow) do
SetCaretPos(Left - 1, Top + 1);
ShowCaret(Handle)
end;
end;
procedure TCustomMemoEx.SetCaretInternal(X, Y: integer);
begin
if (X = FCaretX) and (Y = FCaretY) then
exit;
if not FCursorBeyondEOF then
Y := Min(Y, FLines.ParaLineCount - 1);
Y := Max(Y, 0);
X := Min(X, Max_X);
X := Max(X, 0);
if Y < FTopRow then
SetLeftTop(FLeftCol, Y)
else if Y > Max(FLastVisibleRow, 0) then
SetLeftTop(FLeftCol, Y - FVisibleRowCount + 1);
if X < 0 then
X := 0;
if X < FLeftCol then
SetLeftTop(X, FTopRow)
else if X > FLastVisibleCol then
SetLeftTop(X - FVisibleColCount {+ 1}, FTopRow);
with CalcCellRect(X - FLeftCol, Y - FTopRow) do
SetCaretPos(Left - 1, Top + 1);
if (FCaretX <> X) or (FCaretY <> Y) then
begin
FCaretX := X;
FCaretY := Y;
if Assigned(OnSetCaretPos) then
OnSetCaretPos(Self, X, Y);
StatusChanged;
end;
FCaretX := X;
FCaretY := Y;
end;
procedure TCustomMemoEx.SetCaret(const X, Y: integer);
begin
if (X = FCaretX) and (Y = FCaretY) then
exit;
{$IFDEF MEMOEX_UNDO}
TCaretUndo.Create(Self, FCaretX, FCaretY);
{$ENDIF MEMOEX_UNDO}
SetCaretInternal(X, Y);
if FUpdateLock = 0 then
StatusChanged;
end;
procedure TCustomMemoEx.SetCaretAtParaPos(const ParaIndex, IndexOffs: integer);
var
X, Y: integer;
begin
Lines.Paragraph2Caret(ParaIndex, IndexOffs, X, Y);
SetCaret(X, Y);
end;
procedure TCustomMemoEx.SetCaretPosition(const index, Pos: integer);
begin
if index = 0 then
SetCaret(Pos, FCaretY)
else
SetCaret(FCaretX, Pos)
end;
procedure TCustomMemoEx.KeyDown(var Key: Word; Shift: TShiftState);
var
Form: TCustomForm;
{$IFDEF MEMOEX_EDITOR}
Com: word;
{$ENDIF MEMOEX_EDITOR}
begin
{$IFDEF MEMOEX_COMPLETION}
if FCompletion.FVisible then begin
if FCompletion.DoKeyDown(Key, Shift) then
exit;
end
else
FCompletion.FTimer.Enabled := false;
{$ENDIF MEMOEX_COMPLETION}
if (Key = VK_TAB) and ((Shift = []) or (Shift = [ssShift])) then
if FReadOnly or (not FWantTabs) then
begin
Form := GetParentForm(Self);
if Assigned(Form) then
begin
Key := 0;
if Shift = [] then
Form.Perform(WM_NEXTDLGCTL, 0, 0)
else
Form.Perform(WM_NEXTDLGCTL, 1, 0);
end;
exit;
end;
{$IFDEF MEMOEX_EDITOR}
if WaitSecondKey then
begin
Com := FKeyboard.Command2(Key1, Shift1, Key, Shift);
WaitSecondKey := false;
IgnoreKeyPress := true;
end
else
begin
inherited KeyDown(Key, Shift);
Key1 := Key;
Shift1 := Shift;
Com := FKeyboard.Command(Key, Shift);
if Com = twoKeyCommand then
begin
IgnoreKeyPress := true;
WaitSecondKey := true;
end
else
IgnoreKeyPress := Com > 0;
end;
if (Com > 0) and (Com <> twoKeyCommand) then
begin
Key := 0;
Shift := [];
Command(Com);
end;
{$IFDEF MEMOEX_COMPLETION}
if (Com = ecBackSpace) then
FCompletion.DoKeyPress(#8);
{$ENDIF MEMOEX_COMPLETION}
{$ENDIF MEMOEX_EDITOR}
end;
{$IFDEF MEMOEX_EDITOR}
procedure TCustomMemoEx.ReLine;
begin
FLines.ReLine;
end;
procedure TCustomMemoEx.KeyPress(var Key: Char);
begin
if IgnoreKeyPress then
begin
IgnoreKeyPress := false;
exit
end;
if FReadOnly then
exit;
PaintCaret(false);
inherited KeyPress(Key);
Command(ord(Key));
PaintCaret(true);
end;
function AutoChangeCompare(Item1, Item2: pointer): integer;
var
i, j: integer;
begin
i := length(PAutoChangeWord(Item1)^.OldWord);
j := length(PAutoChangeWord(Item2)^.OldWord);
if i = j then
Result := 0
else if i > j then
Result := 1
else
Result := -1;
end;
procedure TCustomMemoEx.InsertChar(const Key: Char);
{$IFDEF MEMOEX_COMPLETION}
function GetAutoChangeWord(const CurrentWord: string; var NewWord: string): boolean;
var
i, j, k: integer;
s, t: string;
begin
Result := false;
t := DoChangeCase(CurrentWord, RA_CASE_CONVERT_LOWER);
j := length(t);
for i := 0 to FCompletion.FAutoChangeList.Count - 1 do
begin
s := PAutoChangeWord(FCompletion.FAutoChangeList[i])^.OldWord;
k := length(s);
if j < k then
break
else if j = k then
if t = s then
begin
Result := true;
NewWord := PAutoChangeWord(FCompletion.FAutoChangeList[i])^.NewWord;
break;
end;
end;
end;
{$ENDIF}
var
S: string;
{$IFDEF MEMOEX_COMPLETION}
T, old_str, new_str: string;
k1, k2, str_pos: integer;
AutoChanged, AddKeyToNewStr: boolean;
{$ENDIF}
oldChar: string;
i, _X, _Y, Y: integer;
b: boolean;
KeyAnsiChar: AnsiChar;
begin
ReLine;
if Key < #32 then
exit;
{$ifdef UNICODE}
if Key > #255 then
KeyAnsiChar := #255
else
{$endif}
KeyAnsiChar := AnsiChar(Key);
{$IFDEF MEMOEX_COMPLETION}
if not (KeyAnsiChar in RAEditorCompletionChars) then
FCompletion.DoKeyPress(Key);
{$ENDIF MEMOEX_COMPLETION}
DeleteSelected;
FLines.Caret2Paragraph(FCaretX, FCaretY, FParaY, FParaX);
{$IFDEF MEMOEX_COMPLETION}
str_pos := 0;
if (KeyAnsiChar in _AutoChangePunctuation) and (FCompletion.FAutoChangeList.Count > 0) then
begin
S := FLines[FParaY];
AutoChanged := false;
AddKeyToNewStr := false;
str_pos := FParaX - 1;
k1 := length(PAutoChangeWord(FCompletion.FAutoChangeList[0])^.OldWord);
k2 := length(PAutoChangeWord(FCompletion.FAutoChangeList[FCompletion.FAutoChangeList.Count - 1])^.OldWord);
while (str_pos > -1) and (FParaX - str_pos <= k2) do
begin
if FParaX - str_pos >= k1 then
begin
old_str := System.Copy(S, str_pos + 1, FParaX - str_pos);
AutoChanged := GetAutoChangeWord(old_str, new_str);
if not AutoChanged then
AutoChanged := GetAutoChangeWord(old_str + Key, new_str)
else
AddKeyToNewStr := true;
if AutoChanged then
if ((str_pos > 0) and ({$ifdef UNICODE}(S[str_pos] < #255) and {$endif}
(AnsiChar(S[str_pos]) in _AutoChangePunctuation))) or (str_pos = 0) then
break
else
AutoChanged := false;
end;
dec(str_pos);
end;
if AutoChanged then
if AddKeyToNewStr then
if GetAutoChangeWord(Key, T) then
new_str := new_str + T
else
new_str := new_str + Key
else
else
begin
AutoChanged := GetAutoChangeWord(Key, new_str);
if AutoChanged then
begin
str_pos := FParaX;
old_str := '';
end;
end;
end
else
AutoChanged := false;
if AutoChanged then
begin
{$IFDEF MEMOEX_UNDO}
// undo
BeginCompound;
TCaretUndo.Create(Self, FCaretX, FCaretY);
FLines.Paragraph2Caret(FParaY, str_pos, _X, _Y);
if (length(old_str) + integer(not FInsertMode) > 0) and (length(S) > 0) then
begin
if FInsertMode then
T := old_str
else
T := old_str + S[FParaX + 1];
TDeleteUndo.Create(Self, _X, _Y, T);
end;
if length(new_str) > 0 then
TInsertUndo.Create(Self, _X, _Y, new_str);
EndCompound;
{$ENDIF}
k1 := FLines.Paragraphs[FParaY].FCount;
System.Delete(S, str_pos + 1, length(old_str) + integer(not FInsertMode));
System.Insert(new_str, S, str_pos + 1);
FLines.Internal[FParaY] := S;
B := k1 <> FLines.Paragraphs[FParaY].FCount;
FParaX := str_pos + length(new_str);
end
else
{$ENDIF MEMOEX_COMPLETION}
begin
S := FLines.ParaStrings[FCaretY];
if FInsertMode then
begin
{$IFDEF MEMOEX_UNDO}
TInsertUndo.Create(Self, FCaretX, FCaretY, Key);
{$ENDIF}
Insert(Key, S, FCaretX + 1);
end
else
begin
if FCaretX + 1 <= Length(S) then
begin
oldChar := S[FCaretX + 1];
S[FCaretX + 1] := Key;
end
else
begin
oldChar := '';
S := S + Key;
end;
{$IFDEF MEMOEX_UNDO}
TOverwriteUndo.Create(Self, FCaretX, FCaretY, oldChar, Key);
{$ENDIF MEMOEX_UNDO}
end;
Y := FCaretY;
i := FLines.Paragraphs[FParaY].FCount;
FLines.InternalParaStrings[Y] := S;
inc(FParaX);
B := i <> FLines.Paragraphs[FParaY].FCount;
end;
i := RepaintParagraph(FCaretY);
if B then
begin
UpdateEditorSize(false);
RedrawFrom(i + 1);
end;
FLines.Paragraph2Caret(FParaY, FParaX, _X, _Y);
SetCaretInternal(_X, _Y);
Changed;
{$IFDEF MEMOEX_COMPLETION}
if KeyAnsiChar in RAEditorCompletionChars then
FCompletion.DoKeyPress(Key);
{$ENDIF MEMOEX_COMPLETION}
end;
{$ENDIF MEMOEX_EDITOR}
procedure TCustomMemoEx.RedrawFrom(YFrom: integer);
var
i: integer;
begin
for i := YFrom - 1 to FLastVisibleRow + 1 do
PaintLine(i, -1, -1);
end;
function TCustomMemoEx.RepaintParagraph(LineIndex: integer): integer;
var
P, PI, i, j, k: integer;
begin
FLines.Index2ParaIndex(LineIndex, P, PI);
j := LineIndex - PI;
k := j + FLines.FList[P].FCount - 1;
if j < FTopRow - 1 then
j := FTopRow - 1;
j := Max(0, j);
if k > FLastVisibleRow + 1 then
k := FLastVisibleRow + 1;
Result := k;
for i := j to k do
PaintLine(i, -1, -1);
end;
function TCustomMemoEx.IsUndoEmpty: boolean;
begin
{$IFDEF MEMOEX_EDITOR} Result := FUndoBuffer.FPtr < 0; {$endif}
end;
function TCustomMemoEx.YinBounds(AY: integer): boolean;
begin
Result := (AY > -1) and (AY < FLines.ParaLineCount);
end;
function TCustomMemoEx.DoChangeCase(const st: string; Conversion: byte): string;
begin
if Assigned(FOnCaseConversion) then
Result := FOnCaseConversion(Self, Conversion, st)
else
case Conversion of
RA_CASE_CONVERT_UPPER:
Result := ANSIUpperCase(st);
RA_CASE_CONVERT_LOWER:
Result := ANSILowerCase(st);
else
Result := ANSIChangeCase(st);
end;
end;
{$IFDEF MEMOEX_EDITOR}
procedure TCustomMemoEx.Command(ACommand: TEditCommand);
var
X, Y: integer;
{$IFDEF MEMOEX_UNDO}
CaretUndo: boolean;
{$ENDIF MEMOEX_UNDO}
type
TPr = procedure of object;
procedure DoAndCorrectXY(Pr: TPr);
begin
Pr;
X := FCaretX;
Y := FCaretY;
{$IFDEF MEMOEX_COMPLETION}
CaretUndo := false;
{$ENDIF MEMOEX_COMPLETION}
end;
function Com(const Args: array of TEditCommand): boolean;
var
i: integer;
begin
Result := true;
for i := 0 to High(Args) do
if Args[i] = ACommand then
exit;
Result := false;
end;
procedure SetSel1(X, Y: integer);
begin
SetSel(X, Y);
{$IFDEF MEMOEX_UNDO}
CaretUndo := false;
{$ENDIF MEMOEX_UNDO}
end;
procedure SetSelText1(S: string);
begin
SelText := S;
CaretUndo := false;
end;
procedure Complete;
begin
raise EComplete.Create('');
end;
var
F, _Y: integer;
S, S2, T: string;
B: boolean;
iBeg, iEnd: integer;
begin
X := FCaretX;
Y := FCaretY;
{$IFDEF MEMOEX_UNDO}
CaretUndo := true;
{$ENDIF MEMOEX_UNDO}
PaintCaret(false);
{ macro recording }
if FRecording and not Com([ecRecordMacro, ecBeginCompound]) and (FCompound = 0) then
FMacro := FMacro + Char(Lo(ACommand)) + Char(Hi(ACommand));
try
try
case ACommand of
{ caret movements }
ecLeft, ecRight, ecSelLeft, ecSelRight:
begin
if Com([ecSelLeft, ecSelRight]) and not FSelected then
SetSel1(X, Y);
B := Com([ecLeft, ecSelLeft]);
if B then
dec(X)
else
inc(X);
if (not FCursorBeyondEOL) and (YinBounds(Y)) then
begin
_Y := 0;
if (B) and (X < 0) then
_Y := -1
else if (not B) and (X > length(FLines.ParaStrings[Y])) then
_Y := 1;
if (_Y <> 0) and (YinBounds(Y + _Y)) then
begin
Y := Y + _Y;
if B then
X := length(FLines.ParaStrings[Y])
else
X := 0;
end
else if X > length(FLines.ParaStrings[Y]) then
X := length(FLines.ParaStrings[Y]);
end
else if not CursorBeyondEOL then
X := 0;
if Com([ecSelLeft, ecSelRight]) then
SetSel1(X, Y)
else
SetUnSelected;
end;
ecUp, ecDown, ecSelUp, ecSelDown:
if (Com([ecUp, ecSelUp]) and (Y > 0)) or
(Com([ecDown, ecSelDown]) and ((Y < FRows - 1) or (FCursorBeyondEOF))) then
begin
if Com([ecSelUp, ecSelDown]) and not FSelected then
SetSel1(X, Y);
if Com([ecUp, ecSelUp]) then
dec(Y)
else
inc(Y);
if (not FCursorBeyondEOL) and (YinBounds(Y)) then
if X > length(FLines.ParaStrings[Y]) then
X := length(FLines.ParaStrings[Y]);
if Com([ecSelUp, ecSelDown]) then
SetSel1(X, Y)
else
SetUnSelected;
end;
ecPrevWord, ecSelPrevWord:
if FLines.ParaLineCount > 0 then
begin
S := FLines.ParaStrings[Y];
if X > length(S) then
X := length(S);
if X = 0 then
if Y > 0 then
begin
dec(Y);
X := length(FLines.ParaStrings[Y]);
end
else
else
begin
if (ACommand = ecSelPrevWord) and not FSelected then
SetSel1(FCaretX, FCaretY);
B := false;
for F := X - 1 downto 0 do
if B then
if {$ifdef UNICODE} (S[F + 1] < #255) and {$endif} (AnsiChar(S[F + 1]) in Separators) then
begin
X := F + 1;
break;
end
else
else if {$ifdef UNICODE} (S[F + 1] > #255) or {$endif} not (AnsiChar(S[F + 1]) in Separators) then
B := true;
if X = FCaretX then
X := 0;
if ACommand = ecSelPrevWord then
SetSel1(X, Y)
else
SetUnselected;
if (not B) and (X = 0) and (Y > 0) then
begin
FCaretX := X;
Command(ACommand);
Complete;
end;
end;
end;
ecNextWord, ecSelNextWord:
if FLines.ParaLineCount > 0 then
begin
if X >= length(FLines.ParaStrings[Y]) then
begin
if Y < FLines.ParaLineCount - 1 then
begin
inc(Y);
X := 0;
if length(FLines.ParaStrings[Y]) > 0 then
if FLines.ParaStrings[Y][X + 1] = #32 then
begin
FCaretX := X;
FCaretY := Y;
Command(ACommand);
Complete;
end;
end;
end
else
begin
if (ACommand = ecSelNextWord) and not FSelected then
SetSel1(FCaretX, FCaretY);
S := FLines.ParaStrings[Y];
B := false;
for F := X to Length(S) - 1 do
if B then
if {$ifdef UNICODE} (S[F + 1] > #255) or {$endif} not (AnsiChar(S[F + 1]) in Separators) then
begin
X := F;
break;
end
else
else if {$ifdef UNICODE} (S[F + 1] < #255) and {$endif} (AnsiChar(S[F + 1]) in Separators) then
B := true;
if X = FCaretX then
begin
B := X <> length(S);
X := length(S);
end;
if ACommand = ecSelNextWord then
SetSel1(X, Y)
else
SetUnselected;
if (not B) and (X = length(S)) and (Y < FLines.ParaLineCount - 1) then
begin
FCaretX := X;
Command(ACommand);
Complete;
end;
end;
end;
ecScrollLineUp, ecScrollLineDown, ecScrollPageUp, ecScrollPageDown:
begin
if not ((ACommand = ecScrollLineDown) and (Y >= FLines.ParaLineCount - 1) and (Y = FTopRow)) then
begin
case ACommand of
ecScrollLineUp:
F := -1;
ecScrollLineDown:
F := 1;
ecScrollPageUp:
F := -scbVert.LargeChange;
else
F := scbVert.LargeChange;
end;
scbVert.Position := scbVert.Position + F;
Scroll(true, scbVert.Position);
end;
if Y < FTopRow then
Y := FTopRow
else if Y > FLastVisibleRow then
Y := FLastVisibleRow;
end;
ecBeginLine, ecSelBeginLine, ecBeginDoc, ecSelBeginDoc, ecEndLine,
ecSelEndLine, ecEndDoc, ecSelEndDoc:
begin
if Com([ecSelBeginLine, ecSelBeginDoc, ecSelEndLine, ecSelEndDoc]) and not FSelected then
SetSel1(FCaretX, Y);
if Com([ecBeginLine, ecSelBeginLine]) then
X := 0
else if Com([ecBeginDoc, ecSelBeginDoc]) then
begin
X := 0;
Y := 0;
SetLeftTop(0, 0);
end
else if Com([ecEndLine, ecSelEndLine]) then
begin
if FLines.ParaLineCount > 0 then
X := Length(FLines.ParaStrings[Y])
else
X := 0;
end
else if Com([ecEndDoc, ecSelEndDoc]) then
if FLines.ParaLineCount > 0 then
begin
Y := FLines.ParaLineCount - 1;
X := Length(FLines.ParaStrings[Y]);
SetLeftTop(X - FVisibleColCount, Y - FVisibleRowCount + 1{ div 2});
end;
if Com([ecSelBeginLine, ecSelBeginDoc, ecSelEndLine, ecSelEndDoc]) then
SetSel1(X, Y)
else
SetUnSelected;
end;
ecPrevPage:
begin
scbVert.Position := scbVert.Position - scbVert.LargeChange;
Scroll(true, scbVert.Position);
Y := Y - FVisibleRowCount;
SetUnSelected;
end;
ecNextPage:
begin
scbVert.Position := scbVert.Position + scbVert.LargeChange;
Scroll(true, scbVert.Position);
Y := Y + FVisibleRowCount;
SetUnSelected;
end;
ecSelPrevPage:
begin
BeginUpdate;
SetSel1(X, Y);
scbVert.Position := scbVert.Position - scbVert.LargeChange;
Scroll(true, scbVert.Position);
Y := Y - FVisibleRowCount;
SetSel1(X, Y);
EndUpdate;
end;
ecSelNextPage:
begin
BeginUpdate;
SetSel1(X, Y);
scbVert.Position := scbVert.Position + scbVert.LargeChange;
Scroll(true, scbVert.Position);
Y := Y + FVisibleRowCount;
if Y <= FLines.ParaLineCount - 1 then
SetSel1(X, Y)
else
SetSel1(X, FLines.ParaLineCount - 1);
EndUpdate;
end;
ecSelWord:
if not FSelected and
(GetWordOnPosEx(FLines.ParaStrings[Y] + ' ', X + 1, iBeg, iEnd) <> '') then
begin
SetSel1(iBeg - 1, Y);
SetSel1(iEnd - 1, Y);
X := iEnd - 1;
end;
ecWindowTop:
begin
Y := FTopRow;
if (not FCursorBeyondEOL) and (YinBounds(Y)) then
if X > length(FLines.ParaStrings[Y]) then
X := length(FLines.ParaStrings[Y]);
SetUnSelected;
end;
ecWindowBottom:
begin
Y := FTopRow + FVisibleRowCount - 1;
if (not FCursorBeyondEOL) and (YinBounds(Y)) then
if X > length(FLines.ParaStrings[Y]) then
X := length(FLines.ParaStrings[Y]);
SetUnSelected;
end;
{ editing }
{$IFDEF MEMOEX_EDITOR}
ecCharFirst..ecCharLast:
if not FReadOnly then
begin
InsertChar(Char(ACommand - ecCharFirst));
// Changed; // AB
Complete;
end;
ecInsertPara:
if not FReadOnly then
begin
DeleteSelected;
ReLine;
FLines.Caret2Paragraph(X, Y, FParaY, FParaX);
S := FLines[FParaY];
S2 := Copy(S, FParaX + 1, length(S));
T := S2;
if Assigned(FOnBreakLine) then
FOnBreakLine(Self, S, S2);
if S2 = T then
begin
{$IFDEF MEMOEX_UNDO}
BeginCompound;
TInsertUndo.Create(Self, FCaretX, FCaretY, #13#10);
CaretUndo := false;
{$ENDIF MEMOEX_UNDO}
if FAutoIndent then
F := length(S2) - length(TrimLeft(S2));
FLines.Insert(FParaY + 1, S2);
FLines.Internal[FParaY] := Copy(S, 1, FParaX);
inc(Y);
{ smart tab }
if (FAutoIndent) and (Trim(FLines.ParaStrings[FCaretY]) <> '')
{ (FLines.ParaStrings[FCaretY][1] = ' ')) or
((Trim(FLines.ParaStrings[FCaretY]) = '') and (X > 0)))} then
begin
X := GetTabStop(0, Y, tsAutoIndent, true);
if X > F then
begin
{$IFDEF MEMOEX_UNDO}
TInsertUndo.Create(Self, 0, Y, StringOfChar(' ', X - F));
{$ENDIF MEMOEX_UNDO}
FLines.Internal[FParaY + 1] := StringOfChar(' ', X - F) + S2;
end;
end
else if (FAutoIndent) and (S2 = '') then
X := length(FLines.ParaStrings[FCaretY])
else
X := 0;
{$IFDEF MEMOEX_UNDO}
EndCompound;
{$ENDIF MEMOEX_UNDO}
end
else
begin
T := Copy(S, 1, FParaX) + #13#10 + S2 + #13#10;
F := FLines.GetParaOffs(FParaY);
S2 := FLines.Text;
System.Delete(S2, F + 1, length(S) + 2);
System.Insert(T, S2, F + 1);
FLines.Paragraph2Caret(FParaY, 0, F, _Y);
{$IFDEF MEMOEX_UNDO}
CaretUndo := false;
BeginCompound;
TCaretUndo.Create(Self, FCaretX, FCaretY);
TDeleteUndo.Create(Self, 0, _Y, S + #13#10);
TInsertUndo.Create(Self, 0, _Y, T);
EndCompound;
{$ENDIF MEMOEX_UNDO}
FLines.SetLockText(S2);
inc(Y);
X := 0;
end;
UpdateEditorSize(false);
{ Invalidate }
F := RepaintParagraph(FCaretY);
RedrawFrom(F + 1);
Changed;
end;
ecBackword:
if not FReadOnly then
begin
if length(FLines.ParaStrings[Y]) > 0 then
begin
Command(ecBeginCompound);
Command(ecBeginUpdate);
Command(ecSelPrevWord);
Command(ecDeleteSelected);
Command(ecEndUpdate);
Command(ecEndCompound);
end
else
Command(ecBackspace);
Complete;
end;
ecBackspace:
if not FReadOnly then
begin
if FSelected then
begin
DoAndCorrectXY(DeleteSelected);
Changed;
end
else
begin
ReLine;
FLines.Caret2Paragraph(X, Y, FParaY, FParaX);
if X > 0 then
begin
if FBackSpaceUnindents then
X := GetBackStop(FCaretX, FCaretY)
else
X := FCaretX - 1;
S := Copy(FLines.ParaStrings[FCaretY], X + 1, FCaretX - X);
dec(FParaX, length(S));
F := FLines.Paragraphs[FParaY].FCount;
FLines.InternalParaStrings[Y] := Copy(FLines.ParaStrings[Y], 1, X) + Copy(FLines.ParaStrings[Y], FCaretX + 1, Length(FLines.ParaStrings[Y]));
FLines.Paragraph2Caret(FParaY, FParaX, X, Y);
{$IFDEF MEMOEX_UNDO}
TBackspaceUndo.Create(Self, X, Y, S);
CaretUndo := false;
{$ENDIF MEMOEX_UNDO}
B := F <> FLines.Paragraphs[FParaY].FCount;
F := RepaintParagraph(Y);
if B then
begin
UpdateEditorSize(false);
RedrawFrom(F + 1);
end;
Changed;
end
else if Y > 0 then
begin
if FParaX > 0 then
begin
T := FLines[FParaY];
S := Copy(T, FParaX, 1);
System.Delete(T, FParaX, 1);
FLines.Internal[FParaY] := T;
dec(FParaX);
FLines.Paragraph2Caret(FParaY, FParaX, X, Y);
{$IFDEF MEMOEX_UNDO}
TBackspaceUndo.Create(Self, X, Y, S);
CaretUndo := false;
{$ENDIF MEMOEX_UNDO}
end
else if FParaY > 0 then
begin
inc(FUpdateLock);
S := FLines[FParaY - 1];
S2 := FLines[FParaY];
if Assigned(FOnConcatLine) then
FOnConcatLine(Self, S, S2);
{$IFDEF MEMOEX_UNDO}
CaretUndo := false;
FLines.Paragraph2Caret(FParaY - 1, 0, F, _Y);
BeginCompound;
TCaretUndo.Create(Self, X, Y);
TDeleteUndo.Create(Self, 0, _Y, FLines[FParaY - 1] + #13#10 + FLines[FParaY] + #13#10);
TInsertUndo.Create(Self, 0, _Y, S + S2 + #13#10);
EndCompound;
{$ENDIF MEMOEX_UNDO}
FLines.Internal[FParaY - 1] := S + S2;
FLines.Delete(FParaY);
dec(FUpdateLock);
FLines.Paragraph2Caret(FParaY - 1, length(S), X, Y);
end
else
Complete;
UpdateEditorSize(false);
F := RepaintParagraph(Y);
RedrawFrom(F + 1);
Changed;
end;
end;
end;
ecDelete:
if not FReadOnly then
begin
if FLines.ParaLineCount = 0 then
FLines.Add('');
if FSelected then
begin
DoAndCorrectXY(DeleteSelected);
Changed;
end
else
begin
ReLine;
FLines.Caret2Paragraph(X, Y, FParaY, FParaX);
if X < Length(FLines.ParaStrings[Y]) then
begin
{$IFDEF MEMOEX_UNDO}
TDeleteUndo.Create(Self, FCaretX, FCaretY, FLines.ParaStrings[Y][X + 1]);
CaretUndo := false;
{$ENDIF MEMOEX_UNDO}
F := FLines.Paragraphs[FParaY].FCount;
FLines.InternalParaStrings[Y] := Copy(FLines.ParaStrings[Y], 1, X) + Copy(FLines.ParaStrings[Y], X + 2, Length(FLines.ParaStrings[Y]));
FLines.Paragraph2Caret(FParaY, FParaX, X, Y);
B := F <> FLines.Paragraphs[FParaY].FCount;
F := RepaintParagraph(Y);
if B then
begin
UpdateEditorSize(false);
RedrawFrom(F + 1);
end;
Changed;
end
else if (Y >= 0) and (Y <= FLines.ParaLineCount - 2) then
begin
S := FLines[FParaY];
if FParaX < length(S) then
begin
{$IFDEF MEMOEX_UNDO}
TDeleteUndo.Create(Self, FCaretX, FCaretY, System.Copy(S, FParaX + 1, 1));
CaretUndo := false;
{$ENDIF MEMOEX_UNDO}
System.Delete(S, FParaX + 1, 1);
FLines.Internal[FParaY] := S;
FLines.Paragraph2Caret(FParaY, FParaX, X, Y);
end
else
begin
inc(FUpdateLock);
S := FLines[FParaY];
S2 := FLines[FParaY + 1];
if Assigned(FOnConcatLine) then
FOnConcatLine(Self, S, S2);
{$IFDEF MEMOEX_UNDO}
CaretUndo := false;
FLines.Paragraph2Caret(FParaY, 0, F, _Y);
BeginCompound;
TCaretUndo.Create(Self, X, Y);
TDeleteUndo.Create(Self, 0, _Y, FLines[FParaY] + #13#10 + FLines[FParaY + 1] + #13#10);
TInsertUndo.Create(Self, 0, _Y, S + S2 + #13#10);
EndCompound;
{$ENDIF MEMOEX_UNDO}
FLines.Internal[FParaY] := S + S2;
FLines.Delete(FParaY + 1);
dec(FUpdateLock);
FLines.Paragraph2Caret(FParaY, length(S), X, Y);
end;
UpdateEditorSize(false);
F := RepaintParagraph(FCaretY);
RedrawFrom(F + 1);
Changed;
end;
end;
end;
ecTab, ecBackTab:
if not FReadOnly then
begin
if FSelected then
begin
if ACommand = ecTab then
PostCommand(ecIndent)
else
PostCommand(ecUnindent);
end
else
begin
ReLine;
X := GetTabStop(FCaretX, FCaretY, tsTabStop, ACommand = ecTab);
if (ACommand = ecTab) and FInsertMode then
begin
S := FLines.ParaStrings[FCaretY];
FLines.Caret2Paragraph(FCaretX, FCaretY, FParaY, FParaX);
S2 := StringOfChar(' ', X - FCaretX);
{$IFDEF MEMOEX_UNDO}
TInsertTabUndo.Create(Self, FCaretX, FCaretY, S2);
CaretUndo := false;
{$ENDIF MEMOEX_UNDO}
Insert(S2, S, FCaretX + 1);
F := FLines.Paragraphs[FParaY].FCount;
FLines.InternalParaStrings[FCaretY] := S;
inc(FParaX, X - FCaretX);
FLines.Paragraph2Caret(FParaY, FParaX, X, Y);
B := F <> FLines.Paragraphs[FParaY].FCount;
F := RepaintParagraph(FCaretY);
if B then
begin
UpdateEditorSize(false);
RedrawFrom(F + 1);
end;
Changed;
end;
end;
end;
ecIndent:
if not FReadOnly and FSelected and (FSelBegY <> FSelEndY) and
(FSelBegX = 0) and (FSelEndX = 0) then
begin
F := FindNotBlankCharPos(FLines.ParaStrings[FCaretY]);
S2 := StringOfChar(' ', GetDefTabStop(F, true) - FCaretX);
S := SelText;
S := StringReplaceAll(S, #13#10, #13#10 + S2);
Delete(S, Length(S) - Length(S2) + 1, Length(S2));
SetSelText1(S2 + S)
end;
ecUnIndent:
if not FReadOnly and FSelected and (FSelBegY <> FSelEndY) and
(FSelBegX = 0) and (FSelEndX = 0) then
begin
F := FindNotBlankCharPos(FLines.ParaStrings[FCaretY]);
S2 := StringOfChar(' ', GetDefTabStop(F, true) - FCaretX);
S := SelText;
S := StringReplaceAll(S, #13#10 + S2, #13#10);
for iBeg := 1 to Length(S2) do
if S[1] = ' ' then
Delete(S, 1, 1)
else
Break;
SetSelText1(S);
end;
ecChangeInsertMode:
begin
FInsertMode := not FInsertMode;
StatusChanged;
end;
ecClipBoardCut:
if not FReadOnly then
DoAndCorrectXY(ClipBoardCut);
{$ENDIF MEMOEX_EDITOR}
ecClipBoardCopy:
ClipBoardCopy;
{$IFDEF MEMOEX_EDITOR}
ecClipBoardPaste:
if not FReadOnly then
DoAndCorrectXY(ClipBoardPaste);
ecDeleteSelected:
if not FReadOnly and FSelected then
DoAndCorrectXY(DeleteSelected);
ecDeleteWord:
if not FReadOnly then
begin
if length(FLines.ParaStrings[Y]) = 0 then
Command(ecDelete)
else
begin
Command(ecBeginCompound);
Command(ecBeginUpdate);
Command(ecSelNextWord);
Command(ecDeleteSelected);
Command(ecEndUpdate);
Command(ecEndCompound);
Complete;
end;
end;
ecDeleteLine:
if (not FReadOnly) and (Y >= 0) and (Y <= FLines.ParaLineCount - 1) then
begin
FLines.Index2ParaIndex(Y, F, _Y);
B := (not FWordWrap) or (_Y = FLines.Paragraphs[F].FCount - 1);
Command(ecBeginCompound);
Command(ecBeginUpdate);
Command(ecBeginLine);
Command(ecSelEndLine);
Command(ecDeleteSelected);
if B then
Command(ecDelete);
Command(ecEndUpdate);
Command(ecEndCompound);
Complete;
end;
ecSelAll:
begin
Command(ecBeginCompound);
Command(ecBeginUpdate);
Command(ecBeginDoc);
Command(ecSelEndDoc);
Command(ecEndUpdate);
Command(ecEndCompound);
Complete;
end;
ecToUpperCase:
if (not FReadOnly) and (FSelected) then
SelText := DoChangeCase(SelText, RA_CASE_CONVERT_UPPER);
ecToLowerCase:
if (not FReadOnly) and (FSelected) then
SelText := DoChangeCase(SelText, RA_CASE_CONVERT_LOWER);
ecChangeCase:
if (not FReadOnly) and (FSelected) then
SelText := DoChangeCase(SelText, RA_CASE_CONVERT_INVERT);
{$ENDIF MEMOEX_EDITOR}
{$IFDEF MEMOEX_UNDO}
ecUndo:
if not FReadOnly then
begin
FUndoBuffer.Undo;
PaintCaret(true);
Complete;
end;
ecRedo:
if not FReadOnly then
begin
FUndoBuffer.Redo;
PaintCaret(true);
Complete;
end;
ecBeginCompound:
BeginCompound;
ecEndCompound:
EndCompound;
{$ENDIF MEMOEX_UNDO}
ecSetBookmark0..ecSetBookmark9:
ChangeBookMark(ACommand - ecSetBookmark0, true);
ecGotoBookmark0..ecGotoBookmark9:
begin
ChangeBookMark(ACommand - ecGotoBookmark0, false);
X := FCaretX;
Y := FCaretY;
end;
ecInsertMacro0..ecInsertMacroZ:
if (Assigned(FOnInsertMacro)) and (not FReadOnly) then
begin
S := FOnInsertMacro(Self, ACommand - ecInsertMacro0);
if S = '' then
exit;
InsertTextAtCurrentPos(S);
PaintCaret(true);
Complete;
end;
ecBlockOpA..ecBlockOpZ:
if (not FReadOnly) and (Assigned(FOnBlockOperation)) and (FSelected) then
SelText := FOnBlockOperation(Self, ACommand - ecBlockOpA, SelText);
{$IFDEF MEMOEX_COMPLETION}
ecCompletionIdentifers:
if not FReadOnly then
begin
FCompletion.DoCompletion(cmIdentifers);
PaintCaret(true);
Complete;
end;
ecCompletionTemplates:
if not FReadOnly then
begin
FCompletion.DoCompletion(cmTemplates);
PaintCaret(true);
Complete;
end;
{$ENDIF MEMOEX_COMPLETION}
ecBeginUpdate:
BeginUpdate;
ecEndUpdate:
EndUpdate;
ecRecordMacro:
if FRecording then
EndRecord(FDefMacro)
else
BeginRecord;
ecPlayMacro:
begin
PlayMacro(FDefMacro);
Complete;
end;
ecSaveBlock:
if (FSelected) and (Assigned(FOnSaveBlock)) then
FOnSaveBlock(Self, SelText);
ecInsertBlock:
if (not FReadOnly) and (Assigned(FOnInsertBlock)) then
begin
if FOnInsertBlock(Self, S) then
InsertTextAtCurrentPos(S);
PaintCaret(true);
Complete;
end;
end;
{$IFDEF MEMOEX_UNDO}
if CaretUndo then
SetCaret(X, Y)
else
SetCaretInternal(X, Y);
{$ELSE}
SetCaret(X, Y);
{$ENDIF MEMOEX_UNDO}
except
on E: EComplete do { OK }
;
end;
finally
// dec(FUpdateLock);
PaintCaret(true);
end;
end;
{$ENDIF}
procedure TCustomMemoEx.PostCommand(ACommand: TEditCommand);
begin
PostMessage(Handle, WM_EDITCOMMAND, ACommand, 0);
end; { PostCommand }
procedure TCustomMemoEx.ClipboardChanged;
begin
if (csLoading in ComponentState) or (csDestroying in ComponentState) then
exit;
if Assigned(FOnChangeClipboardState) then
FOnChangeClipboardState(Self, IsClipboardFormatAvailable(CF_TEXT) or
IsClipboardFormatAvailable({$ifdef UNICODE}CF_UNICODETEXT{$else}CF_OEMTEXT{$endif}));
end;
procedure TCustomMemoEx.WndProc(var Message: TMessage);
var
Form: TCustomForm;
pt, temp: TPoint;
begin
case Message.Msg of
CM_COLORCHANGED:
begin
Message.Result := 0;
Invalidate;
exit;
end;
WM_MOUSEWHEEL:
begin
MouseWheelHandler(Message);
Message.Result := 0;
exit;
end;
WM_SYSCHAR:
if Message.wParam = VK_BACK then
begin
Message.Result := 0;
exit;
end;
WM_ERASEBKGND:
begin
{$IFDEF MEMOEX_NOOPTIMIZE}
inherited WndProc(Message);
Message.Result := 1;
{$ELSE}
Message.Result := 0;
{$ENDIF}
exit;
end;
WM_SETFOCUS:
begin
Form := GetParentForm(Self);
if (Form <> nil) and (not Form.SetFocusedControl(Self)) then
exit;
CreateCaret(Handle, 0, 2, CellRect.Height - 2);
PaintCaret(true);
DoEnter;
end;
WM_KILLFOCUS:
begin
if csFocusing in ControlState then
exit;
{$IFDEF MEMOEX_COMPLETION}
if FCompletion.FVisible then
FCompletion.CloseUp(false);
{$ENDIF MEMOEX_COMPLETION}
DestroyCaret;
DoExit;
end;
WM_GETDLGCODE:
begin
inherited WndProc(Message);
TWMGetDlgCode(Message).Result := DLGC_WANTARROWS or DLGC_WANTCHARS;
if FWantTabs then
TWMGetDlgCode(Message).Result := TWMGetDlgCode(Message).Result or DLGC_WANTTAB;
exit;
end;
WM_HSCROLL:
begin
scbHorz.DoScroll(TWMHScroll(Message));
exit;
end;
WM_VSCROLL:
begin
scbVert.DoScroll(TWMVScroll(Message));
exit;
end;
WM_SETTINGCHANGE:
begin
scbVertWidth := GetSystemMetrics(SM_CXVSCROLL);
scbHorzHeight := GetSystemMetrics(SM_CYHSCROLL);
end;
WM_EDITCOMMAND:
begin
Command(Message.WParam);
Message.Result := ord(true);
exit;
end;
WM_CHANGECBCHAIN:
begin
Message.Result := 0;
if TWMChangeCBChain(Message).Remove = NextClipViewer then
NextClipViewer := TWMChangeCBChain(Message).Next
else
SendMessage(NextClipViewer, WM_CHANGECBCHAIN, TWMChangeCBChain(Message).Remove,
TWMChangeCBChain(Message).Next);
exit;
end;
WM_DRAWCLIPBOARD:
begin
ClipboardChanged;
SendMessage(NextClipViewer, WM_DRAWCLIPBOARD, 0, 0);
exit;
end;
WM_DESTROY:
ChangeClipboardChain(Handle, NextClipViewer);
WM_CONTEXTMENU:
begin
pt := SmallPointToPoint(TWMContextMenu(Message).Pos);
if pt.X < 0 then
temp := pt
else
temp := ScreenToClient(pt);
if PtInRect(ClientRect, temp) then
GetWordUnderCursor(temp.X, temp.Y);
end;
WM_COPY:
begin
PostCommand(ecClipboardCopy);
Message.Result := ord(true);
exit;
end;
{$IFDEF MEMOEX_EDITOR}
WM_CUT:
begin
if not FReadOnly then
PostCommand(ecClipboardCut);
Message.Result := ord(true);
exit;
end;
WM_PASTE:
begin
if not FReadOnly then
PostCommand(ecClipBoardPaste);
Message.Result := ord(true);
exit;
end;
{$ENDIF}
end;
inherited WndProc(Message);
end;
{$IFDEF MEMOEX_EDITOR}
procedure TCustomMemoEx.SetXY(X, Y: integer);
var
X1, Y1: integer;
begin
X1 := FLeftCol;
Y1 := FTopRow;
if (Y < FTopRow) or (Y > FLastVisibleRow) then
Y1 := Y - (FVisibleRowCount div 2);
if (X < FLeftCol) or (X > FVisibleColCount) then
X1 := X - (FVisibleColCount div 2);
SetLeftTop(X1, Y1);
SetCaret(X, Y);
end;
procedure TCustomMemoEx.ChangeBookMark(const BookMark: TBookMarkNum; const Valid: boolean);
begin
if Valid then
with FBookMarks[Bookmark] do
if Valid and (Y = FCaretY) then
Valid := false
else
begin
X := FCaretX;
Y := FCaretY;
Valid := true;
end
else
with FBookMarks[Bookmark] do
if Valid then
SetXY(X, Y);
BookmarkChanged(BookMark);
end;
{$ENDIF}
procedure TCustomMemoEx.BookmarkChanged(BookMark: integer);
begin
FGutter.Invalidate;
end;
procedure TCustomMemoEx.SelectionChanged;
begin
if not (csLoading in ComponentState) then
if Assigned(FOnSelectionChange) then
FOnSelectionChange(Self);
end;
procedure TCustomMemoEx.SetSel(const ASelX, ASelY: integer);
procedure UpdateSelected;
var
iR: integer;
begin
if FUpdateLock = 0 then
begin
if (FUpdateSelBegY <> FSelBegY) or (FUpdateSelBegX <> FSelBegX) then
for iR := Min(FUpdateSelBegY, FSelBegY) to Max(FUpdateSelBegY, FSelBegY) do
PaintLine(iR, -1, -1);
if (FUpdateSelEndY <> FSelEndY) or (FUpdateSelEndX <> FSelEndX) then
for iR := Min(FUpdateSelEndY, FSelEndY) to Max(FUpdateSelEndY, FSelEndY) do
PaintLine(iR, -1, -1);
SelectionChanged;
end;
end;
var
SelX, SelY: integer;
begin
if ASelX < 0 then
SelX := 0
else
SelX := ASelX;
if ASelY < 0 then
SelY := 0
else
SelY := ASelY;
if not FSelected then
begin
FSelStartX := SelX;
FSelStartY := SelY;
FSelEndX := SelX;
FSelEndY := SelY;
FSelBegX := SelX;
FSelBegY := SelY;
FSelected := true;
end
else
begin
{$IFDEF MEMOEX_UNDO}
TSelectUndo.Create(Self, FCaretX, FCaretY, FSelBlock, FSelBegX, FSelBegY, FSelEndX, FSelEndY);
{$ENDIF MEMOEX_UNDO}
FUpdateSelBegX := FSelBegX;
FUpdateSelBegY := FSelBegY;
FUpdateSelEndX := FSelEndX;
FUpdateSelEndY := FSelEndY;
if SelY <= FSelStartY then
FSelBegY := SelY;
if SelY >= FSelStartY then
FSelEndY := SelY;
if (SelY < FSelStartY) or ((SelY = FSelStartY) and (SelX <= FSelStartX)) then
begin
FSelBegX := SelX;
FSelEndX := FSelStartX;
FSelEndY := FSelStartY;
end
else if (SelY > FSelStartY) or ((SelY = FSelStartY) and (SelX >= FSelStartX)) then
begin
FSelBegX := FSelStartX;
FSelBegY := FSelStartY;
FSelEndX := SelX;
end;
if FSelBegY < 0 then
FSelBegY := 0;
FSelected := true;
if FCompound = 0 then
UpdateSelected;
end;
if FUpdateSelBegY > FSelBegY then
FUpdateSelBegY := FSelBegY;
if FUpdateSelEndY < FSelEndY then
FUpdateSelEndY := FSelEndY;
end;
procedure TCustomMemoEx.Mouse2Cell(const X, Y: integer; var CX, CY: integer);
begin
CX := Round((X - EditorClient.Left) / FCellRect.Width);
CY := (Y - EditorClient.Top) div FCellRect.Height;
end;
procedure TCustomMemoEx.Mouse2Caret(const X, Y: integer; var CX, CY: integer);
begin
Mouse2Cell(X, Y, CX, CY);
if CX < 0 then
CX := 0;
if CY < 0 then
CY := 0;
CX := CX + FLeftCol;
CY := CY + FTopRow;
if CX > FLastVisibleCol then
CX := FLastVisibleCol;
if CY > FLines.ParaLineCount - 1 then
CY := FLines.ParaLineCount - 1;
end;
procedure TCustomMemoEx.CaretCoord(const X, Y: integer; var CX, CY: integer);
begin
CX := X - FLeftCol;
CY := Y - FTopRow;
if CX < 0 then
CX := 0;
if CY < 0 then
CY := 0;
CX := FCellRect.Width * CX;
CY := FCellRect.Height * CY;
end;
function TCustomMemoEx.ExtractStringWithStyle(XX, YY: integer; const From: string;
Style: word; const LineAttrs: TLineAttrs; out start: integer): string;
var
i: integer;
last: integer;
begin
if Style <> RA_EX_STYLE_DEFAULT then
begin
start := XX;
last := XX;
if XX <= length(From) then
for i := XX downto 0 do
if LineAttrs[i].ex_style = Style then
start := i
else
break;
for i := XX + 1 to length(From) - 1 do
if LineAttrs[i].ex_style = Style then
last := i
else
break;
result := copy(From, start + 1, last - start + 1);
end
else
start := XX;
end;
{ strip invisible }
function TCustomMemoEx.GetAttrDelta(StartFrom, EndTo: integer; const LineAttrs: TLineAttrs): integer;
var
i, j: integer;
begin
Result := 0;
if (readonly) and (FStripInvisible) then
begin
j := EndTo;
i := StartFrom;
while (i <= j) and (i < SelAttrs_Size) do
begin
if LineAttrs[i].FC = LineAttrs[i].BC then
begin
inc(Result);
inc(j);
end;
inc(i);
end;
end;
end;
function TCustomMemoEx.DoMouseWheel(Shift: TShiftState; WheelDelta: integer; MousePos: TPoint): boolean;
begin
MouseWheelScroll(WheelDelta);
Result := inherited DoMouseWheel(Shift, WheelDelta, MousePos);
end;
procedure TCustomMemoEx.MouseWheelScroll(Delta: integer);
var
i: integer;
begin
i := Mouse.WheelScrollLines;
if Delta > 0 then
i := -i;
scbVert.Position := scbVert.Position + i;
Scroll(true, scbVert.Position);
end;
procedure TCustomMemoEx.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: integer);
var
XX, YY: integer;
begin
if double_clicked then
begin
double_clicked := false;
exit;
end;
{$IFDEF MEMOEX_COMPLETION}
if FCompletion.FVisible then
FCompletion.CloseUp(false);
{$ENDIF MEMOEX_COMPLETION}
if Button <> mbRight then
begin // right click = popup menu -> no caret change
mouse_down := true;
mouse_dragged := false;
Mouse2Caret(X, Y, XX, YY);
PaintCaret(false);
if (Button = mbLeft) and (not (ssShift in Shift)) then
SetUnSelected;
SetFocus;
if YinBounds(YY) then
begin
if not FCursorBeyondEOL then
if XX > length(FLines.ParaStrings[YY]) then
XX := length(FLines.ParaStrings[YY]);
if (ssShift in Shift) and (SelLength = 0) then
SetSel(FCaretX, FCaretY);
SetCaret(XX, YY);
if ssShift in Shift then
SetSel(XX, YY);
end;
PaintCaret(true);
end;
inherited MouseDown(Button, Shift, X, Y);
end;
procedure TCustomMemoEx.DblClick;
var
i, PY, PX, iBeg, iEnd: integer;
FAttrs: TLineAttrs;
s: string;
begin
double_clicked := true;
if Assigned(FOnDblClick) then
FOnDblClick(Self);
if FDoubleClickLine then
begin
PaintCaret(false);
SetSel(0, FCaretY);
if FCaretY = FLines.ParaLineCount - 1 then
begin
SetSel(Length(FLines.ParaStrings[FCaretY]), FCaretY);
SetCaret(Length(FLines.ParaStrings[FCaretY]), FCaretY);
end
else
begin
SetSel(0, FCaretY + 1);
SetCaret(0, FCaretY + 1);
end;
PaintCaret(true);
end
else if YinBounds(FCaretY) then
begin
s := FLines.GetParagraphByIndex(FCaretY, PY, PX);
GetLineAttr(PY, FCaretY, PX, length(s), 0, length(s), s, FAttrs);
i := GetAttrDelta(PX, FCaretX + PX, FAttrs);
if GetWordOnPosEx(FLines.ParaStrings[FCaretY] + ' ', FCaretX + 1 + i, iBeg, iEnd) <> '' then
begin
PaintCaret(false);
SetSel(iBeg - 1, FCaretY);
SetSel(iEnd - 1, FCaretY);
SetCaret(iEnd - 1 - i, FCaretY);
PaintCaret(true);
end;
end;
end;
procedure TCustomMemoEx.GetWordUnderCursor(X, Y: integer; aShift: TShiftState = []);
var
XX, YY, PX, PY, i: integer;
s: string;
FAttrs: TLineAttrs;
start, delta: integer;
begin
Mouse2Caret(X, Y, XX, YY);
if YinBounds(YY) then
begin
s := FLines.GetParagraphByIndex(YY, PY, PX);
GetLineAttr(PY, YY, PX, length(s), 0, length(s), s, FAttrs);
delta := GetAttrDelta(PX, XX + PX, FAttrs);
i := XX + PX + delta;
with FWordUnderCursor do
begin
if (i > 0) and (i < SelAttrs_Size) then
begin
Style := FAttrs[i - 1].ex_style;
Text := ExtractStringWithStyle(i, YY, s, Style, FAttrs, start);
TextStart := FLines.GetParaOffs(PY) + start + 1 - delta;
end
else
begin
Text := ''; // mark no word found
TextStart := 0;
end;
CaretX := XX;
CaretY := YY;
ParaIndex := PY;
ParaOffset := PX;
Shift := aShift;
end;
end;
end;
procedure TCustomMemoEx.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: integer);
begin
timerScroll.Enabled := false;
mouse_down := false;
if (Button = mbLeft) and (not mouse_dragged) then
if Assigned(FOnWordClick) then
begin
GetWordUnderCursor(X, Y, Shift);
if FWordUnderCursor.Text <> '' then
FOnWordClick(Self, FWordUnderCursor);
end;
inherited MouseUp(Button, Shift, X, Y)
end;
procedure TCustomMemoEx.MouseMove(Shift: TShiftState; X, Y: integer);
var
C: TCursor;
S: string;
i, PY, PX: integer;
FAttrs: TLineAttrs;
begin
MouseMoveY := Y;
Mouse2Caret(X, Y, MouseMoveXX, MouseMoveYY);
if X < FGutterWidth then
Cursor := crArrow;
if (Shift = [ssLeft]) and (mouse_down) then
begin
mouse_dragged := true;
Cursor := crIBeam;
PaintCaret(false);
if MouseMoveYY <= FLastVisibleRow then
begin
if not FCursorBeyondEOL then
if YinBounds(MouseMoveYY) then begin
if MouseMoveXX > length(FLines.ParaStrings[MouseMoveYY]) then
MouseMoveXX := length(FLines.ParaStrings[MouseMoveYY]);
end
else
MouseMoveXX := 0;
SetSel(MouseMoveXX, MouseMoveYY);
SetCaret(MouseMoveXX, MouseMoveYY);
end;
timerScroll.Enabled := (Y < 0) or (Y > ClientHeight);
PaintCaret(true);
end
else if (Assigned(FOnMouseOver)) and (YinBounds(MouseMoveYY)) then
begin
S := FLines.GetParagraphByIndex(MouseMoveYY, PY, PX);
GetLineAttr(PY, MouseMoveYY, PX, length(S), 0, MouseMoveXX + 1 + PX, S, FAttrs);
i := MouseMoveXX + PX + GetAttrDelta(PX, MouseMoveXX + PX, FAttrs) - 1;
if i < SelAttrs_Size then
begin
C := crIBeam;
FOnMouseOver(Self, FAttrs[i].ex_style, C);
if C <> Cursor then
Cursor := C;
end
else
Cursor := crIBeam;
end;
inherited MouseMove(Shift, X, Y);
end;
procedure TCustomMemoEx.ScrollTimer(Sender: TObject);
begin
if (MouseMoveY < 0) or (MouseMoveY > ClientHeight) then
begin
if (MouseMoveY < -20) then
dec(MouseMoveYY, FVisibleRowCount)
else if (MouseMoveY < 0) then
dec(MouseMoveYY)
else if (MouseMoveY > ClientHeight + 20) then
inc(MouseMoveYY, FVisibleRowCount)
else if (MouseMoveY > ClientHeight) then
inc(MouseMoveYY);
PaintCaret(false);
SetSel(MouseMoveXX, MouseMoveYY);
SetCaret(MouseMoveXX, MouseMoveYY);
PaintCaret(true);
end;
end;
function TCustomMemoEx.GetRealOffs(DefOffs, Index: integer): integer;
var
l: integer;
begin
Result := DefOffs;
if (Index > -1) and (Index < FLines.FParaLinesCount) then
begin
l := length(FLines.ParaStrings[Index]);
if l > 0 then
if Result > l then
Result := l
else
else
Result := 0;
end;
end;
function TCustomMemoEx.GetSelText: string;
var
sb, se: integer;
begin
Result := '';
if not FSelected then
exit;
if not FSelBlock then
begin
if (FSelBegY < 0) or (FSelBegY > FLines.ParaLineCount - 1) or
(FSelEndY < 0) or (FSelEndY > FLines.ParaLineCount - 1) then
begin
Err;
Exit;
end;
sb := GetRealOffs(FSelBegX, FSelBegY);
se := GetRealOffs(FSelEndX, FSelEndY);
if (se = sb) and (FSelBegY = FSelEndY) then
exit;
if FSelBegY <> FSelEndY then
begin
sb := PosFromCaret(sb, FSelBegY);
se := PosFromCaret(se, FSelEndY);
Result := System.Copy(FLines.Text, sb + 1, se - sb + integer(FInclusive));
end
else
begin // AB faster if without #13
result := system.Copy(FLines.ParaStrings[FSelBegY], sb + 1, se - sb + integer(FInclusive));
end;
end;
end;
function TCustomMemoEx.GetSelLength: integer;
begin
// Result := Length(GetSelText);
Result := 0;
if not FSelected then
exit;
if not FSelBlock then
begin
if (FSelBegY < 0) or (FSelBegY > FLines.ParaLineCount - 1) or (FSelEndY < 0) or
(FSelEndY > FLines.ParaLineCount - 1) then
Exit;
result := PosFromCaret(FSelEndX, FSelEndY) - PosFromCaret(FSelBegX, FSelBegY) + integer(FInclusive);
end;
end;
procedure TCustomMemoEx.SetSelText(const AValue: string);
begin
BeginUpdate;
try
BeginCompound;
DeleteSelected;
if AValue <> '' then
begin
InsertText(AValue);
FSelectedText := true;
SelStart := PosFromCaret(FSelBegX, FSelBegY) + 1;
SelLength := Length(AValue);
end;
EndCompound;
finally
EndUpdate;
end;
end;
procedure TCustomMemoEx.ClipBoardCopy;
var
s: string;
begin
if not FSelBlock then
begin
s := GetSelText;
{$ifdef CLIPBOARDPROTECT} // ClipProtect will trunc clipboard to 2KB
FClip := s;
if FClipProtect and (length(s) > 2000) then
setLength(s, 2000);
{$endif}
_CopyToClipboard(Handle, s, Font.Charset);
end;
end;
{$IFDEF MEMOEX_EDITOR}
procedure TCustomMemoEx.ReplaceWord(const NewString: string);
var
iBeg, iEnd: integer;
function GetWordOnPos2(S: string; P: integer): string;
begin
Result := '';
if P < 1 then
exit;
if {$ifdef UNICODE} (S[P] < #255) and {$endif} (AnsiChar(S[P]) in Separators) and
((P < 1) or ({$ifdef UNICODE}(S[P - 1] < #255) and {$endif} (AnsiChar(S[P - 1]) in Separators))) then
inc(P);
iBeg := P;
while iBeg >= 1 do
if {$ifdef UNICODE} (S[iBeg] < #255) and {$endif} (AnsiChar(S[iBeg]) in Separators) then
break
else
dec(iBeg);
inc(iBeg);
iEnd := P;
while iEnd <= Length(S) do
if {$ifdef UNICODE} (S[iEnd] < #255) and {$endif} (AnsiChar(S[iEnd]) in Separators) then
break
else
inc(iEnd);
if iEnd > iBeg then
Result := Copy(S, iBeg, iEnd - iBeg)
else
Result := S[P];
end;
var
S, W: string;
X: integer;
begin
PaintCaret(false);
BeginUpdate;
S := FLines.ParaStrings[FCaretY];
while FCaretX > Length(S) do
S := S + ' ';
W := Trim(GetWordOnPos2(S, FCaretX));
if W = '' then
begin
iBeg := FCaretX + 1;
iEnd := FCaretX
end;
{$IFDEF MEMOEX_UNDO}
CantUndo;
// TReplaceUndo.Create(Self, FCaretX - Length(W), FCaretY, iBeg, iEnd, W, NewString);
{$ENDIF MEMOEX_UNDO}
Delete(S, iBeg, iEnd - iBeg);
Insert(NewString, S, iBeg);
FLines.InternalParaStrings[FCaretY] := S;
X := iBeg + Length(NewString) - 1;
PaintLine(FCaretY, -1, -1);
SetCaretInternal(X, FCaretY);
Changed;
EndUpdate;
PaintCaret(true);
end;
procedure TCustomMemoEx.ReplaceWord2(const NewString: string);
var
S, S1, W: string;
P, X, Y: integer;
iBeg, iEnd: integer;
NewCaret: integer;
begin
PaintCaret(false);
if FCaretX > Length(FLines.ParaStrings[FCaretY]) then
FLines.InternalParaStrings[FCaretY] := FLines.ParaStrings[FCaretY] + StringOfChar(' ', FCaretX - Length(FLines.ParaStrings[FCaretY]));
S := FLines.Text;
P := PosFromCaret(FCaretX, FCaretY);
W := Trim(GetWordOnPosEx(S, P, iBeg, iEnd));
if W = '' then
begin
iBeg := P + 1;
iEnd := P
end;
S1 := NewString;
NewCaret := Length(NewString);
{$IFDEF MEMOEX_UNDO}
TReplaceUndo.Create(Self, FCaretX, FCaretY, iBeg, iEnd, W, S1);
{$ENDIF MEMOEX_UNDO}
Delete(S, iBeg, iEnd - iBeg);
Insert(S1, S, iBeg);
FLines.Text := S;
CaretFromPos(iBeg + NewCaret - 1, X, Y);
SetCaretInternal(X, Y);
Changed;
PaintCaret(true);
end;
procedure TCustomMemoEx.InsertText(const Text: string);
var
S: string;
P: integer;
X, Y: integer;
begin
if Text <> '' then
begin
PaintCaret(false);
BeginUpdate;
Reline;
{$IFDEF MEMOEX_UNDO}
TInsertUndo.Create(Self, FCaretX, FCaretY, Text);
{$ENDIF MEMOEX_UNDO}
P := PosFromCaret(FCaretX, FCaretY);
if PosEx(#13, text) > 0 then
begin // insert with #13 -> old slow method
S := FLines.Text;
Insert(Text, S, P + 1);
FLines.SetLockText(S);
end
else
begin // new fast method from AB
S := FLines.ParaStrings[FCaretY];
while FCaretX > Length(S) do
S := S + ' ';
insert(text, S, FCaretX + 1);
FLines.InternalParaStrings[FCaretY] := S; // will call reformat paragraph
end;
CaretFromPos(P + Length(Text), X, Y);
SetCaretInternal(X, Y);
Changed;
EndUpdate;
PaintCaret(true);
end;
end;
procedure TCustomMemoEx.InsertTextAtCurrentPos(const _Text: string);
var
S: string;
begin
BeginUpdate;
S := AdjustLineBreaks(_Text);
if Assigned(FOnTextInsert) then
FOnTextInsert(Self, S);
DeleteSelected;
InsertText(S);
EndUpdate;
scbVert.Position := FTopRow;
end;
function CountChar(P: PChar; Ch: char): integer;
begin
result := 0;
if P <> nil then
while P^ <> #0 do
begin
if P^ = Ch then
inc(result);
inc(P);
end;
end;
procedure Replace(S, D: PChar);
begin // faster than PosEx()+Insert()
repeat
if S^ = '\' then
begin
D[0] := '\';
D[1] := '\';
inc(D, 2);
inc(S);
end
else if S^ = #0 then
begin
D^ := S^;
break;
end
else
begin
D^ := S^;
inc(D);
inc(S);
end;
until false;
end;
function RtfBackSlash(const Text: string): string;
var
i: integer;
begin
i := CountChar(pointer(Text), '\');
if i = 0 then
result := Text
else
begin
SetLength(result, length(Text) + i);
Replace(pointer(Text), pointer(result));
end;
end;
procedure TCustomMemoEx.ClipBoardPaste;
var
ClipS: string;
begin
if readonly then
exit;
if Assigned(OnClipboardPaste) and OnClipboardPaste(Self) then
Exit;
ClipS := _PasteFromClipboard(Handle, Font.Charset, {$ifdef CLIPBOARDPROTECT}FClip, {$endif} false);
if ClipPasteRtfBackSlashConvert and not IsClipboardFormatAvailable(CF_MEMOEX) then
InsertTextAtCurrentPos(RtfBackSlash(ClipS))
else // not from TMemoEx -> '\' -> '\\'
InsertTextAtCurrentPos(ClipS);
end;
procedure TCustomMemoEx.ClipBoardCut;
begin
ClipBoardCopy;
DeleteSelected;
end;
procedure TCustomMemoEx.DeleteSelected;
var
S, S1: string;
iBeg, X, Y: integer;
begin
if FSelected then
begin
S1 := GetSelText;
FSelectedText := false;
if S1 = '' then
exit;
PaintCaret(false);
iBeg := PosFromCaret(FSelBegX, FSelBegY);
{$IFDEF MEMOEX_UNDO}
TDeleteSelectedUndo.Create(Self, FCaretX, FCaretY, S1, FSelBlock, FSelBegX,
FSelBegY, FSelEndX, FSelEndY, iBeg);
{$ENDIF MEMOEX_UNDO}
if FSelBegY <> FSelEndY then
begin // delete with #13 -> old slow method
S := FLines.Text;
Delete(S, iBeg + 1, length(S1));
FLines.SetLockText(S);
end
else
begin // new fast method from AB
s := FLines.ParaStrings[FSelBegY];
while FSelBegX > Length(S) do
S := S + ' ';
Delete(S, FSelBegX + 1, length(S1));
FLines.InternalParaStrings[FSelBegY] := S; // contient reformat paragraph
end;
CaretFromPos(iBeg, X, Y);
SetCaretInternal(X, Y);
Changed;
UpdateEditorSize(false);
if FUpdateLock = 0 then
Invalidate;
PaintCaret(true);
end;
end;
{$ENDIF MEMOEX_EDITOR}
procedure TCustomMemoEx.SetGutterWidth(AWidth: integer);
begin
if FGutterWidth <> AWidth then
begin
FGutterWidth := AWidth;
UpdateEditorSize;
Invalidate;
end;
end;
procedure TCustomMemoEx.SetGutterColor(AColor: TColor);
begin
if FGutterColor <> AColor then
begin
FGutterColor := AColor;
FGutter.Invalidate;
end;
end;
procedure TCustomMemoEx.SetFont(Value: TFont);
begin
FFont.Assign(Value);
end;
procedure TCustomMemoEx.SetLines(ALines: TEditorStrings);
begin
if ALines <> nil then
FLines.Assign(ALines);
CantUndo;
end;
procedure TCustomMemoEx.TextAllChanged;
begin
TextAllChangedInternal(true);
end;
procedure TCustomMemoEx.TextAllChangedInternal(const Unselect: Boolean);
begin
if Unselect then
FSelectedText := false;
UpdateEditorSize(false);
if (Showing) and (FUpdateLock = 0) then
Invalidate;
end;
procedure TCustomMemoEx.SetCols(ACols: integer);
begin
if FCols <> ACols then
begin
FCols := Max(ACols, 1);
if FCols > FVisibleColCount then
begin
scbHorz.Max := FCols;
scbHorz.Min := 0;
end
else
begin
scbHorz.Min := 0;
scbHorz.Max := 0;
end;
{ if FVisibleColCount < scbHorz.Max then scbHorz.Page := scbHorz.Max - (scbHorz.Max div Max(1, FVisibleColCount)) * FVisibleColCount
else scbHorz.Page := 1;}
scbHorz.LargeChange := FVisibleColCount;
end;
end;
procedure TCustomMemoEx.SetRows(ARows: integer);
begin
if FRows <> ARows then
begin
FRows := Max(ARows, 1);
if FRows > FVisibleRowCount then
begin
scbVert.Max := FRows - FVisibleRowCount;
scbVert.Min := 0;
end
else
begin
scbVert.Min := 0;
scbVert.Max := 0;
end;
{ if FVisibleRowCount < scbVert.Max then scbVert.Page := scbVert.Max div Max(1, FVisibleRowCount + FVisibleRowCount div 4)
else scbVert.Page := 1;}
scbVert.LargeChange := FVisibleRowCount;
end;
end;
procedure TCustomMemoEx.SetLeftTop(ALeftCol, ATopRow: integer);
begin
if ALeftCol < 0 then
ALeftCol := 0;
if FLeftCol <> ALeftCol then
begin
scbHorz.Position := ALeftCol;
Scroll(false, ALeftCol);
end;
if ATopRow < 0 then
ATopRow := 0;
if FTopRow <> ATopRow then
begin
scbVert.Position := ATopRow;
Scroll(true, ATopRow);
end;
end;
procedure TCustomMemoEx.SetScrollBars(Value: TScrollStyle);
begin
if FScrollBars <> Value then
begin
FScrollBars := Value;
RecreateWnd;
UpdateEditorSize;
end;
end;
procedure TCustomMemoEx.SetRightMarginVisible(Value: boolean);
begin
if FRightMarginVisible <> Value then
begin
FRightMarginVisible := Value;
Invalidate;
end;
end;
procedure TCustomMemoEx.SetRightMargin(Value: integer);
begin
if FRightMargin <> Value then
begin
FRightMargin := Value;
FLines.Reformat;
end;
end;
procedure TCustomMemoEx.SetRightMarginColor(Value: TColor);
begin
if FRightMarginColor <> Value then
begin
FRightMarginColor := Value;
Invalidate;
end;
end;
procedure TCustomMemoEx.Changed;
begin
FModified := true;
FPEditBuffer := nil;
if Assigned(FOnChange) then
FOnChange(Self);
StatusChanged;
end;
procedure TCustomMemoEx.StatusChanged;
begin
if not ((csDestroying in ComponentState) or (csLoading in ComponentState)) then
if Assigned(FOnChangeStatus) then
FOnChangeStatus(Self);
end;
procedure TCustomMemoEx.CaretFromPos(Pos: integer; var X, Y: integer);
var
i, j, k: integer;
begin
k := 0;
X := -1;
Y := -1;
for i := 0 to FLines.Count - 1 do
with FLines.FList[i] do
begin
for j := 0 to FCount - 1 do
begin
inc(Y);
inc(k, length(FStrings[j]));
if k >= Pos then
begin
X := Pos - (k - length(FStrings[j]));
exit;
end;
end;
inc(k, 2);
end;
Y := FLines.ParaLineCount - 1;
if Y >= 0 then
X := length(FLines.ParaStrings[Y]);
end;
function TCustomMemoEx.PosFromCaret(X, Y: integer): integer;
var
i, j, k: integer;
begin
if Y > FLines.ParaLineCount - 1 then // if after eof -> get max len
Result := GetTextLen
else if Y < 0 then // before bof -> -1
Result := -1
else
begin
Result := 0; // get position
k := 0;
for i := 0 to FLines.FCount - 1 do
with FLines.FList[i] do
begin
if k + (FCount - 1) < Y then
begin
inc(Result, StringDynArrayGetSize(pointer(FStrings), FCount) + 2);
inc(k, FCount);
end
else
begin
for j := 0 to FCount - 1 do
if k + j < Y then
inc(Result, length(FStrings[j]));
inc(Result, X);
break;
end;
end;
end;
end;
function TCustomMemoEx.PosFromMouse(const X, Y: integer): integer;
var
X1, Y1: integer;
begin
Mouse2Caret(X, Y, X1, Y1);
if (X1 < 0) or (Y1 < 0) then
Result := -1
else
Result := PosFromCaret(X1, Y1);
end;
function TCustomMemoEx.GetTextLen: integer;
var
i: integer;
begin
Result := FLines.FCount * 2; // #13+#10 for each line
for i := 0 to FLines.FCount - 1 do
with FLines.FList[i] do
inc(Result, StringDynArrayGetSize(pointer(FStrings), FCount));
end;
function TCustomMemoEx.GetSelStart: integer;
begin
if FSelectedText then
Result := PosFromCaret(GetRealOffs(FSelBegX, FSelBegY), FSelBegY) + 1
else
Result := PosFromCaret(GetRealOffs(FCaretX, FCaretY), FCaretY) + 1;
end;
procedure TCustomMemoEx.SetSelStart(const ASelStart: integer);
begin
FSelectedText := true;
CaretFromPos(ASelStart - 1, FSelBegX, FSelBegY);
SetCaretInternal(FSelBegX, FSelBegY);
SetSelLength(0);
MakeRowVisible(FSelBegY);
end;
procedure TCustomMemoEx.MakeRowVisible(ARow: integer);
begin
if (ARow < FTopRow) or (ARow > FLastVisibleRow) then
begin
ARow := FCaretY - Trunc(VisibleRowCount / 2);
if ARow < 0 then
ARow := 0;
SetLeftTop(FLeftCol, ARow);
end;
end;
procedure TCustomMemoEx.SetSelLength(const ASelLength: integer);
begin
FSelectedText := ASelLength > 0;
CaretFromPos(SelStart + ASelLength - 1, FSelEndX, FSelEndY);
FUpdateSelBegY := FSelBegY;
FUpdateSelEndY := FSelEndY;
SetCaretInternal(FSelEndX, FSelEndY);
Invalidate;
end;
procedure TCustomMemoEx.SetLockText(const Text: string);
begin
FLines.SetLockText(Text);
end;
procedure TCustomMemoEx.GutterPaint(Canvas: TCanvas; const Rect: TRect);
begin
if Assigned(FOnPaintGutter) then
FOnPaintGutter(Self, Canvas, Rect);
end;
procedure TCustomMemoEx.SetMode(index: integer; Value: boolean);
var
PB: ^boolean;
begin
case index of
0:
PB := @FInsertMode;
else {1 :}
PB := @FReadOnly;
end;
if PB^ <> Value then
begin
PB^ := Value;
if index = 1 then
Invalidate;
StatusChanged;
end;
end;
function TCustomMemoEx.GetWordOnCaret: string;
begin
Result := GetWordOnPos(FLines.ParaStrings[CaretY], CaretX + 1);
end;
function TCustomMemoEx.GetTabStop(const X, Y: integer; const What: TTabStop; const Next: Boolean): integer;
procedure UpdateTabStops;
var
S: string;
j, i: integer;
function ProcessString: boolean;
begin
Result := false;
if (What = tsTabStop) and (length(S) > 0) then
FTabPos[length(S) - 1] := true;
while i <= length(S) do
begin
if S[i] = ' ' then
begin
FTabPos[i - 1] := true;
if i >= X then
Result := true;
end;
inc(i);
end;
end;
begin
FillChar(FTabPos[0], Max_X, false);
if (FSmartTab) and (What = tsTabStop) then
begin
j := 1;
i := 1;
while Y - j >= 0 do
begin
S := TrimRight(FLines.ParaStrings[Y - j]);
if ProcessString then
break;
if i >= Max_X div 4 then
Break;
if j >= FVisibleRowCount * 2 then
Break;
inc(j);
end;
end
else if (What = tsAutoIndent) and FAutoIndent then
begin
FLines.Index2ParaIndex(Y, i, j);
if i - 1 >= 0 then
begin
S := FLines[i - 1];
i := 1;
ProcessString;
end;
end;
end;
var
i: integer;
begin
UpdateTabStops;
Result := X;
if Next then
begin
for i := X + 1 to High(FTabPos) do
if (not FTabPos[i - 1]) and (What = tsAutoIndent) then
begin
Result := i - 1;
exit;
end
else if (not FTabPos[i]) and (i > 0) then
if FTabPos[i - 1] then
begin
Result := i;
Exit;
end;
if Result = X then
Result := GetDefTabStop(X, true);
end
else if Result = X then
Result := GetDefTabStop(X, false);
end;
function TCustomMemoEx.GetDefTabStop(const X: integer; const Next: Boolean): integer;
var
S: string;
i: integer;
begin
S := Trim(SubStr(FTabStops, 0, ' '));
try
i := StrToInt(S);
except
i := 8;
end;
if i = 0 then
Result := X
else if i > X then
Result := i
else if X mod i = 0 then
Result := X + i
else
Result := ((X div i) + 1) * i;
end;
function TCustomMemoEx.GetBackStop(const X, Y: integer): integer;
procedure UpdateBackStops;
var
S: string;
j, i, k: integer;
begin
j := 1;
i := X - 1;
FillChar(FTabPos[0], Max_X, false);
FTabPos[0] := true;
while Y - j >= 0 do
begin
S := FLines.ParaStrings[Y - j];
for k := 1 to Min(Length(S), i) do { Iterate }
if S[k] <> ' ' then
begin
i := k;
FTabPos[i - 1] := true;
Break;
end;
if i = 1 then
Break;
if j >= FVisibleRowCount * 2 then
Break;
inc(j);
end;
end;
var
i: integer;
S: string;
begin
Result := X - 1;
S := TrimRight(FLines.ParaStrings[Y]);
if (Trim(Copy(S, 1, X)) = '') and ((X + 1 > Length(S)) or (S[X + 1] <> ' ')) then
begin
UpdateBackStops;
for i := X downto 0 do
if FTabPos[i] then
begin
Result := i;
Exit;
end;
end;
end;
procedure TCustomMemoEx.BeginCompound;
begin
inc(FCompound);
TBeginCompoundUndo.Create(Self);
end;
procedure TCustomMemoEx.EndCompound;
begin
TEndCompoundUndo.Create(Self);
dec(FCompound);
end;
procedure TCustomMemoEx.BeginRecord;
begin
FMacro := '';
FRecording := true;
StatusChanged;
end;
procedure TCustomMemoEx.EndRecord(var AMacro: TMacro);
begin
FRecording := false;
AMacro := FMacro;
StatusChanged;
end;
procedure TCustomMemoEx.PlayMacro(const AMacro: TMacro);
var
i: integer;
begin
BeginUpdate;
BeginCompound;
try
i := 1;
while i < Length(AMacro) do
begin
Command(byte(AMacro[i]) + byte(AMacro[i + 1]) shl 8);
inc(i, 2);
end;
finally
EndCompound;
EndUpdate;
end;
end;
constructor TEditKey.Create(const ACommand: TEditCommand; const AKey1: word; const AShift1: TShiftState);
begin
Key1 := AKey1;
Shift1 := AShift1;
Command := ACommand;
end;
constructor TEditKey.Create2(const ACommand: TEditCommand; const AKey1: word; const AShift1: TShiftState; const AKey2: word; const AShift2: TShiftState);
begin
Key1 := AKey1;
Shift1 := AShift1;
Key2 := AKey2;
Shift2 := AShift2;
Command := ACommand;
end;
constructor TKeyboard.Create;
begin
List := TList.Create;
end;
destructor TKeyboard.Destroy;
begin
Clear;
List.Free;
inherited Destroy;
end;
procedure TKeyboard.Add(const ACommand: TEditCommand; const AKey1: word; const AShift1: TShiftState);
begin
List.Add(TEditKey.Create(ACommand, AKey1, AShift1));
end;
procedure TKeyboard.Add2(const ACommand: TEditCommand; const AKey1: word; const AShift1: TShiftState; const AKey2: word; const AShift2: TShiftState);
begin
List.Add(TEditKey.Create2(ACommand, AKey1, AShift1, AKey2, AShift2));
end;
procedure TKeyboard.Clear;
var
i: integer;
begin
for i := 0 to List.Count - 1 do
TObject(List[i]).Free;
List.Clear;
end;
function TKeyboard.Command(const AKey: word; const AShift: TShiftState): TEditCommand;
var
i: integer;
begin
Result := 0;
for i := 0 to List.Count - 1 do
with TEditKey(List[i]) do
if (Key1 = AKey) and (Shift1 = AShift) then
begin
if Key2 = 0 then
Result := Command
else
Result := twoKeyCommand;
Exit;
end;
end;
function TKeyboard.Command2(const AKey1: word; const AShift1: TShiftState; const AKey2: word; const AShift2: TShiftState): TEditCommand;
var
i: integer;
begin
Result := 0;
for i := 0 to List.Count - 1 do
with TEditKey(List[i]) do
if (Key1 = AKey1) and (Shift1 = AShift1) and (Key2 = AKey2) and (Shift2 = AShift2) then
begin
Result := Command;
Exit;
end;
end;
{$IFDEF MEMOEX_EDITOR}
{$IFDEF MEMOEX_DEFLAYOUT}
procedure TKeyboard.SetDefLayout;
begin
Clear;
Add(ecLeft, VK_LEFT, []);
Add(ecRight, VK_RIGHT, []);
Add(ecUp, VK_UP, []);
Add(ecDown, VK_DOWN, []);
Add(ecSelLeft, VK_LEFT, [ssShift]);
Add(ecSelRight, VK_RIGHT, [ssShift]);
Add(ecSelUp, VK_UP, [ssShift]);
Add(ecSelDown, VK_DOWN, [ssShift]);
Add(ecBeginLine, VK_HOME, []);
Add(ecSelBeginLine, VK_HOME, [ssShift]);
Add(ecBeginDoc, VK_HOME, [ssCtrl]);
Add(ecSelBeginDoc, VK_HOME, [ssCtrl, ssShift]);
Add(ecEndLine, VK_END, []);
Add(ecSelEndLine, VK_END, [ssShift]);
Add(ecEndDoc, VK_END, [ssCtrl]);
Add(ecSelEndDoc, VK_END, [ssCtrl, ssShift]);
Add(ecPrevWord, VK_LEFT, [ssCtrl]);
Add(ecNextWord, VK_RIGHT, [ssCtrl]);
Add(ecSelPrevWord, VK_LEFT, [ssCtrl, ssShift]);
Add(ecSelNextWord, VK_RIGHT, [ssCtrl, ssShift]);
Add(ecSelAll, ord('A'), [ssCtrl]);
Add(ecWindowTop, VK_PRIOR, [ssCtrl]);
Add(ecWindowBottom, VK_NEXT, [ssCtrl]);
Add(ecPrevPage, VK_PRIOR, []);
Add(ecNextPage, VK_NEXT, []);
Add(ecSelPrevPage, VK_PRIOR, [ssShift]);
Add(ecSelNextPage, VK_NEXT, [ssShift]);
Add(ecScrollLineUp, VK_UP, [ssCtrl]);
Add(ecScrollLineDown, VK_DOWN, [ssCtrl]);
Add(ecChangeInsertMode, VK_INSERT, []);
Add(ecInsertPara, VK_RETURN, []);
Add(ecBackspace, VK_BACK, []);
Add(ecBackspace, VK_BACK, [ssShift]);
Add(ecBackword, VK_BACK, [ssCtrl]);
Add(ecDelete, VK_DELETE, []);
Add(ecTab, VK_TAB, []);
Add(ecBackTab, VK_TAB, [ssShift]);
Add(ecDeleteSelected, VK_DELETE, [ssCtrl]);
Add(ecClipboardCopy, VK_INSERT, [ssCtrl]);
Add(ecClipboardCut, VK_DELETE, [ssShift]);
Add(ecClipBoardPaste, VK_INSERT, [ssShift]);
Add(ecClipboardCopy, ord('C'), [ssCtrl]);
Add(ecClipboardCut, ord('X'), [ssCtrl]);
Add(ecClipBoardPaste, ord('V'), [ssCtrl]);
Add(ecSetBookmark0, ord('0'), [ssCtrl, ssShift]);
Add(ecSetBookmark1, ord('1'), [ssCtrl, ssShift]);
Add(ecSetBookmark2, ord('2'), [ssCtrl, ssShift]);
Add(ecSetBookmark3, ord('3'), [ssCtrl, ssShift]);
Add(ecSetBookmark4, ord('4'), [ssCtrl, ssShift]);
Add(ecSetBookmark5, ord('5'), [ssCtrl, ssShift]);
Add(ecSetBookmark6, ord('6'), [ssCtrl, ssShift]);
Add(ecSetBookmark7, ord('7'), [ssCtrl, ssShift]);
Add(ecSetBookmark8, ord('8'), [ssCtrl, ssShift]);
Add(ecSetBookmark9, ord('9'), [ssCtrl, ssShift]);
Add(ecGotoBookmark0, ord('0'), [ssCtrl]);
Add(ecGotoBookmark1, ord('1'), [ssCtrl]);
Add(ecGotoBookmark2, ord('2'), [ssCtrl]);
Add(ecGotoBookmark3, ord('3'), [ssCtrl]);
Add(ecGotoBookmark4, ord('4'), [ssCtrl]);
Add(ecGotoBookmark5, ord('5'), [ssCtrl]);
Add(ecGotoBookmark6, ord('6'), [ssCtrl]);
Add(ecGotoBookmark7, ord('7'), [ssCtrl]);
Add(ecGotoBookmark8, ord('8'), [ssCtrl]);
Add(ecGotoBookmark9, ord('9'), [ssCtrl]);
Add2(ecSetBookmark0, ord('K'), [ssCtrl], ord('0'), []);
Add2(ecSetBookmark0, ord('K'), [ssCtrl], ord('0'), [ssCtrl]);
Add2(ecSetBookmark1, ord('K'), [ssCtrl], ord('1'), []);
Add2(ecSetBookmark1, ord('K'), [ssCtrl], ord('1'), [ssCtrl]);
Add2(ecSetBookmark2, ord('K'), [ssCtrl], ord('2'), []);
Add2(ecSetBookmark2, ord('K'), [ssCtrl], ord('2'), [ssCtrl]);
Add2(ecSetBookmark3, ord('K'), [ssCtrl], ord('3'), []);
Add2(ecSetBookmark3, ord('K'), [ssCtrl], ord('3'), [ssCtrl]);
Add2(ecSetBookmark4, ord('K'), [ssCtrl], ord('4'), []);
Add2(ecSetBookmark4, ord('K'), [ssCtrl], ord('4'), [ssCtrl]);
Add2(ecSetBookmark5, ord('K'), [ssCtrl], ord('5'), []);
Add2(ecSetBookmark5, ord('K'), [ssCtrl], ord('5'), [ssCtrl]);
Add2(ecSetBookmark6, ord('K'), [ssCtrl], ord('6'), []);
Add2(ecSetBookmark6, ord('K'), [ssCtrl], ord('6'), [ssCtrl]);
Add2(ecSetBookmark7, ord('K'), [ssCtrl], ord('7'), []);
Add2(ecSetBookmark7, ord('K'), [ssCtrl], ord('7'), [ssCtrl]);
Add2(ecSetBookmark8, ord('K'), [ssCtrl], ord('8'), []);
Add2(ecSetBookmark8, ord('K'), [ssCtrl], ord('8'), [ssCtrl]);
Add2(ecSetBookmark9, ord('K'), [ssCtrl], ord('9'), []);
Add2(ecSetBookmark9, ord('K'), [ssCtrl], ord('9'), [ssCtrl]);
Add2(ecGotoBookmark0, ord('Q'), [ssCtrl], ord('0'), []);
Add2(ecGotoBookmark0, ord('Q'), [ssCtrl], ord('0'), [ssCtrl]);
Add2(ecGotoBookmark1, ord('Q'), [ssCtrl], ord('1'), []);
Add2(ecGotoBookmark1, ord('Q'), [ssCtrl], ord('1'), [ssCtrl]);
Add2(ecGotoBookmark2, ord('Q'), [ssCtrl], ord('2'), []);
Add2(ecGotoBookmark2, ord('Q'), [ssCtrl], ord('2'), [ssCtrl]);
Add2(ecGotoBookmark3, ord('Q'), [ssCtrl], ord('3'), []);
Add2(ecGotoBookmark3, ord('Q'), [ssCtrl], ord('3'), [ssCtrl]);
Add2(ecGotoBookmark4, ord('Q'), [ssCtrl], ord('4'), []);
Add2(ecGotoBookmark4, ord('Q'), [ssCtrl], ord('4'), [ssCtrl]);
Add2(ecGotoBookmark5, ord('Q'), [ssCtrl], ord('5'), []);
Add2(ecGotoBookmark5, ord('Q'), [ssCtrl], ord('5'), [ssCtrl]);
Add2(ecGotoBookmark6, ord('Q'), [ssCtrl], ord('6'), []);
Add2(ecGotoBookmark6, ord('Q'), [ssCtrl], ord('6'), [ssCtrl]);
Add2(ecGotoBookmark7, ord('Q'), [ssCtrl], ord('7'), []);
Add2(ecGotoBookmark7, ord('Q'), [ssCtrl], ord('7'), [ssCtrl]);
Add2(ecGotoBookmark8, ord('Q'), [ssCtrl], ord('8'), []);
Add2(ecGotoBookmark8, ord('Q'), [ssCtrl], ord('8'), [ssCtrl]);
Add2(ecGotoBookmark9, ord('Q'), [ssCtrl], ord('9'), []);
Add2(ecGotoBookmark9, ord('Q'), [ssCtrl], ord('9'), [ssCtrl]);
{
Add2(ecInsertMacro0, ord('S'), [ssCtrl], ord('0'), [ssCtrl]);
Add2(ecInsertMacro0, ord('S'), [ssCtrl], ord('0'), []);
Add2(ecInsertMacro1, ord('S'), [ssCtrl], ord('1'), [ssCtrl]);
Add2(ecInsertMacro1, ord('S'), [ssCtrl], ord('1'), []);
Add2(ecInsertMacro2, ord('S'), [ssCtrl], ord('2'), [ssCtrl]);
Add2(ecInsertMacro2, ord('S'), [ssCtrl], ord('2'), []);
Add2(ecInsertMacro3, ord('S'), [ssCtrl], ord('3'), [ssCtrl]);
Add2(ecInsertMacro3, ord('S'), [ssCtrl], ord('3'), []);
Add2(ecInsertMacro4, ord('S'), [ssCtrl], ord('4'), [ssCtrl]);
Add2(ecInsertMacro4, ord('S'), [ssCtrl], ord('4'), []);
Add2(ecInsertMacro5, ord('S'), [ssCtrl], ord('5'), [ssCtrl]);
Add2(ecInsertMacro5, ord('S'), [ssCtrl], ord('5'), []);
Add2(ecInsertMacro6, ord('S'), [ssCtrl], ord('6'), [ssCtrl]);
Add2(ecInsertMacro6, ord('S'), [ssCtrl], ord('6'), []);
Add2(ecInsertMacro7, ord('S'), [ssCtrl], ord('7'), [ssCtrl]);
Add2(ecInsertMacro7, ord('S'), [ssCtrl], ord('7'), []);
Add2(ecInsertMacro8, ord('S'), [ssCtrl], ord('8'), [ssCtrl]);
Add2(ecInsertMacro8, ord('S'), [ssCtrl], ord('8'), []);
Add2(ecInsertMacro9, ord('S'), [ssCtrl], ord('9'), [ssCtrl]);
Add2(ecInsertMacro9, ord('S'), [ssCtrl], ord('9'), []);
Add2(ecInsertMacroA, ord('S'), [ssCtrl], ord('A'), [ssCtrl]);
Add2(ecInsertMacroA, ord('S'), [ssCtrl], ord('A'), []);
Add2(ecInsertMacroB, ord('S'), [ssCtrl], ord('B'), [ssCtrl]);
Add2(ecInsertMacroB, ord('S'), [ssCtrl], ord('B'), []);
Add2(ecInsertMacroC, ord('S'), [ssCtrl], ord('C'), [ssCtrl]);
Add2(ecInsertMacroC, ord('S'), [ssCtrl], ord('C'), []);
Add2(ecInsertMacroD, ord('S'), [ssCtrl], ord('D'), [ssCtrl]);
Add2(ecInsertMacroD, ord('S'), [ssCtrl], ord('D'), []);
Add2(ecInsertMacroE, ord('S'), [ssCtrl], ord('E'), [ssCtrl]);
Add2(ecInsertMacroE, ord('S'), [ssCtrl], ord('E'), []);
Add2(ecInsertMacroF, ord('S'), [ssCtrl], ord('F'), [ssCtrl]);
Add2(ecInsertMacroF, ord('S'), [ssCtrl], ord('F'), []);
Add2(ecInsertMacroG, ord('S'), [ssCtrl], ord('G'), [ssCtrl]);
Add2(ecInsertMacroG, ord('S'), [ssCtrl], ord('G'), []);
Add2(ecInsertMacroH, ord('S'), [ssCtrl], ord('H'), [ssCtrl]);
Add2(ecInsertMacroH, ord('S'), [ssCtrl], ord('H'), []);
Add2(ecInsertMacroI, ord('S'), [ssCtrl], ord('I'), [ssCtrl]);
Add2(ecInsertMacroI, ord('S'), [ssCtrl], ord('I'), []);
Add2(ecInsertMacroJ, ord('S'), [ssCtrl], ord('J'), [ssCtrl]);
Add2(ecInsertMacroJ, ord('S'), [ssCtrl], ord('J'), []);
Add2(ecInsertMacroK, ord('S'), [ssCtrl], ord('K'), [ssCtrl]);
Add2(ecInsertMacroK, ord('S'), [ssCtrl], ord('K'), []);
Add2(ecInsertMacroL, ord('S'), [ssCtrl], ord('L'), [ssCtrl]);
Add2(ecInsertMacroL, ord('S'), [ssCtrl], ord('L'), []);
Add2(ecInsertMacroM, ord('S'), [ssCtrl], ord('M'), [ssCtrl]);
Add2(ecInsertMacroM, ord('S'), [ssCtrl], ord('M'), []);
Add2(ecInsertMacroN, ord('S'), [ssCtrl], ord('N'), [ssCtrl]);
Add2(ecInsertMacroN, ord('S'), [ssCtrl], ord('N'), []);
Add2(ecInsertMacroO, ord('S'), [ssCtrl], ord('O'), [ssCtrl]);
Add2(ecInsertMacroO, ord('S'), [ssCtrl], ord('O'), []);
Add2(ecInsertMacroP, ord('S'), [ssCtrl], ord('P'), [ssCtrl]);
Add2(ecInsertMacroP, ord('S'), [ssCtrl], ord('P'), []);
Add2(ecInsertMacroQ, ord('S'), [ssCtrl], ord('Q'), [ssCtrl]);
Add2(ecInsertMacroQ, ord('S'), [ssCtrl], ord('Q'), []);
Add2(ecInsertMacroR, ord('S'), [ssCtrl], ord('R'), [ssCtrl]);
Add2(ecInsertMacroR, ord('S'), [ssCtrl], ord('R'), []);
Add2(ecInsertMacroS, ord('S'), [ssCtrl], ord('S'), [ssCtrl]);
Add2(ecInsertMacroS, ord('S'), [ssCtrl], ord('S'), []);
Add2(ecInsertMacroT, ord('S'), [ssCtrl], ord('T'), [ssCtrl]);
Add2(ecInsertMacroT, ord('S'), [ssCtrl], ord('T'), []);
Add2(ecInsertMacroU, ord('S'), [ssCtrl], ord('U'), [ssCtrl]);
Add2(ecInsertMacroU, ord('S'), [ssCtrl], ord('U'), []);
Add2(ecInsertMacroV, ord('S'), [ssCtrl], ord('V'), [ssCtrl]);
Add2(ecInsertMacroV, ord('S'), [ssCtrl], ord('V'), []);
Add2(ecInsertMacroW, ord('S'), [ssCtrl], ord('W'), [ssCtrl]);
Add2(ecInsertMacroW, ord('S'), [ssCtrl], ord('W'), []);
Add2(ecInsertMacroX, ord('S'), [ssCtrl], ord('X'), [ssCtrl]);
Add2(ecInsertMacroX, ord('S'), [ssCtrl], ord('X'), []);
Add2(ecInsertMacroY, ord('S'), [ssCtrl], ord('Y'), [ssCtrl]);
Add2(ecInsertMacroY, ord('S'), [ssCtrl], ord('Y'), []);
Add2(ecInsertMacroZ, ord('S'), [ssCtrl], ord('Z'), [ssCtrl]);
Add2(ecInsertMacroZ, ord('S'), [ssCtrl], ord('Z'), []);
}
{$IFDEF MEMOEX_UNDO}
Add(ecUndo, ord('Z'), [ssCtrl]);
Add(ecUndo, VK_BACK, [ssAlt]);
{$ENDIF MEMOEX_UNDO}
{$IFDEF MEMOEX_COMPLETION}
Add(ecCompletionIdentifers, VK_SPACE, [ssCtrl]);
Add(ecCompletionTemplates, ord('J'), [ssCtrl]);
{$ENDIF MEMOEX_COMPLETION}
{ cursor movement - default and classic }
Add2(ecBeginDoc, ord('Q'), [ssCtrl], ord('R'), []);
Add2(ecEndDoc, ord('Q'), [ssCtrl], ord('C'), []);
Add2(ecBeginLine, ord('Q'), [ssCtrl], ord('S'), []);
Add2(ecEndLine, ord('Q'), [ssCtrl], ord('D'), []);
Add2(ecWindowTop, ord('Q'), [ssCtrl], ord('E'), []);
Add2(ecWindowBottom, ord('Q'), [ssCtrl], ord('X'), []);
Add2(ecWindowTop, ord('Q'), [ssCtrl], ord('T'), []);
Add2(ecWindowBottom, ord('Q'), [ssCtrl], ord('U'), []);
Add(ecDeleteWord, ord('T'), [ssCtrl]);
Add(ecInsertPara, ord('N'), [ssCtrl]);
Add(ecDeleteLine, ord('Y'), [ssCtrl]);
Add2(ecSelWord, ord('K'), [ssCtrl], ord('T'), [ssCtrl]);
Add2(ecToUpperCase, ord('K'), [ssCtrl], ord('O'), [ssCtrl]);
Add2(ecToLowerCase, ord('K'), [ssCtrl], ord('N'), [ssCtrl]);
Add2(ecChangeCase, ord('O'), [ssCtrl], ord('U'), [ssCtrl]);
Add2(ecInsertBlock, ord('K'), [ssCtrl], ord('R'), [ssCtrl]);
Add2(ecSaveBlock, ord('K'), [ssCtrl], ord('W'), [ssCtrl]);
Add(ecRecordMacro, ord('R'), [ssCtrl, ssShift]);
Add(ecPlayMacro, ord('P'), [ssCtrl]);
{
Add2(ecBlockOpA, ord('B'), [ssCtrl], ord('A'), [ssCtrl]);
Add2(ecBlockOpA, ord('B'), [ssCtrl], ord('A'), []);
Add2(ecBlockOpB, ord('B'), [ssCtrl], ord('B'), [ssCtrl]);
Add2(ecBlockOpB, ord('B'), [ssCtrl], ord('B'), []);
Add2(ecBlockOpC, ord('B'), [ssCtrl], ord('C'), [ssCtrl]);
Add2(ecBlockOpC, ord('B'), [ssCtrl], ord('C'), []);
Add2(ecBlockOpD, ord('B'), [ssCtrl], ord('D'), [ssCtrl]);
Add2(ecBlockOpD, ord('B'), [ssCtrl], ord('D'), []);
Add2(ecBlockOpE, ord('B'), [ssCtrl], ord('E'), [ssCtrl]);
Add2(ecBlockOpE, ord('B'), [ssCtrl], ord('E'), []);
Add2(ecBlockOpF, ord('B'), [ssCtrl], ord('F'), [ssCtrl]);
Add2(ecBlockOpF, ord('B'), [ssCtrl], ord('F'), []);
Add2(ecBlockOpG, ord('B'), [ssCtrl], ord('G'), [ssCtrl]);
Add2(ecBlockOpG, ord('B'), [ssCtrl], ord('G'), []);
Add2(ecBlockOpH, ord('B'), [ssCtrl], ord('H'), [ssCtrl]);
Add2(ecBlockOpH, ord('B'), [ssCtrl], ord('H'), []);
Add2(ecBlockOpI, ord('B'), [ssCtrl], ord('I'), [ssCtrl]);
Add2(ecBlockOpI, ord('B'), [ssCtrl], ord('I'), []);
Add2(ecBlockOpJ, ord('B'), [ssCtrl], ord('J'), [ssCtrl]);
Add2(ecBlockOpJ, ord('B'), [ssCtrl], ord('J'), []);
Add2(ecBlockOpK, ord('B'), [ssCtrl], ord('K'), [ssCtrl]);
Add2(ecBlockOpK, ord('B'), [ssCtrl], ord('K'), []);
Add2(ecBlockOpL, ord('B'), [ssCtrl], ord('L'), [ssCtrl]);
Add2(ecBlockOpL, ord('B'), [ssCtrl], ord('L'), []);
Add2(ecBlockOpM, ord('B'), [ssCtrl], ord('M'), [ssCtrl]);
Add2(ecBlockOpM, ord('B'), [ssCtrl], ord('M'), []);
Add2(ecBlockOpN, ord('B'), [ssCtrl], ord('N'), [ssCtrl]);
Add2(ecBlockOpN, ord('B'), [ssCtrl], ord('N'), []);
Add2(ecBlockOpO, ord('B'), [ssCtrl], ord('O'), [ssCtrl]);
Add2(ecBlockOpO, ord('B'), [ssCtrl], ord('O'), []);
Add2(ecBlockOpP, ord('B'), [ssCtrl], ord('P'), [ssCtrl]);
Add2(ecBlockOpP, ord('B'), [ssCtrl], ord('P'), []);
Add2(ecBlockOpQ, ord('B'), [ssCtrl], ord('Q'), [ssCtrl]);
Add2(ecBlockOpQ, ord('B'), [ssCtrl], ord('Q'), []);
Add2(ecBlockOpR, ord('B'), [ssCtrl], ord('R'), [ssCtrl]);
Add2(ecBlockOpR, ord('B'), [ssCtrl], ord('R'), []);
Add2(ecBlockOpS, ord('B'), [ssCtrl], ord('S'), [ssCtrl]);
Add2(ecBlockOpS, ord('B'), [ssCtrl], ord('S'), []);
Add2(ecBlockOpT, ord('B'), [ssCtrl], ord('T'), [ssCtrl]);
Add2(ecBlockOpT, ord('B'), [ssCtrl], ord('T'), []);
Add2(ecBlockOpU, ord('B'), [ssCtrl], ord('U'), [ssCtrl]);
Add2(ecBlockOpU, ord('B'), [ssCtrl], ord('U'), []);
Add2(ecBlockOpV, ord('B'), [ssCtrl], ord('V'), [ssCtrl]);
Add2(ecBlockOpV, ord('B'), [ssCtrl], ord('V'), []);
Add2(ecBlockOpW, ord('B'), [ssCtrl], ord('W'), [ssCtrl]);
Add2(ecBlockOpW, ord('B'), [ssCtrl], ord('W'), []);
Add2(ecBlockOpX, ord('B'), [ssCtrl], ord('X'), [ssCtrl]);
Add2(ecBlockOpX, ord('B'), [ssCtrl], ord('X'), []);
Add2(ecBlockOpY, ord('B'), [ssCtrl], ord('Y'), [ssCtrl]);
Add2(ecBlockOpY, ord('B'), [ssCtrl], ord('Y'), []);
Add2(ecBlockOpZ, ord('B'), [ssCtrl], ord('Z'), [ssCtrl]);
Add2(ecBlockOpZ, ord('B'), [ssCtrl], ord('Z'), []);
}
end;
{$ENDIF MEMOEX_DEFLAYOUT}
{$IFDEF MEMOEX_UNDO}
procedure RedoNotImplemented;
begin
raise EMemoExError.Create('Redo not yet implemented');
end;
procedure TCustomMemoEx.CantUndo;
begin
FUndoBuffer.Clear;
end;
{ TUndoBuffer }
constructor TUndoBuffer.Create;
begin
FCancelUndo := false;
FPtr := -1;
end;
procedure TUndoBuffer.Add(AUndo: TUndo);
begin
if (self = nil) or InUndo then
exit;
if FCancelUndo then
Clear
else
FMemoEx.StatusChanged;
while (Count > 0) and (FPtr < Count - 1) do
begin
TUndo(Items[FPtr + 1]).Free;
inherited Delete(FPtr + 1);
end;
inherited Add(AUndo);
FPtr := Count - 1;
end;
procedure TUndoBuffer.Undo;
var
UndoClass: TClass;
Compound: integer;
begin
InUndo := true;
try
if LastUndo <> nil then
begin
Compound := 0;
UndoClass := LastUndo.ClassType;
while (LastUndo <> nil) and ((UndoClass = LastUndo.ClassType) or
(LastUndo is TDeleteTrailUndo) or (LastUndo is TReLineUndo) or (Compound > 0)) do
begin
if LastUndo.ClassType = TBeginCompoundUndo then
begin
dec(Compound);
UndoClass := nil;
end
else if LastUndo.ClassType = TEndCompoundUndo then
inc(Compound);
LastUndo.Undo;
dec(FPtr);
if (UndoClass = TDeleteTrailUndo) or (UndoClass = TReLineUndo) then
UndoClass := LastUndo.ClassType;
if not FMemoEx.FGroupUndo then
break;
// FMemoEx.Paint; {DEBUG !!!!!!!!!}
end;
if FMemoEx.FUpdateLock = 0 then
begin
FMemoEx.TextAllChangedInternal(false);
FMemoEx.Changed;
end;
end;
finally
InUndo := false;
end;
end;
procedure TUndoBuffer.Redo;
begin
inc(FPtr);
LastUndo.Redo;
end;
procedure TUndoBuffer.Clear;
var
i: integer;
begin // AB: memory leak correction
for i := 0 to Count - 1 do
TUndo(Items[i]).Free;
inherited;
FCancelUndo := false;
FMemoEx.StatusChanged;
end;
procedure TUndoBuffer.Delete;
begin
if Count > 0 then
begin
TUndo(Items[Count - 1]).Free;
inherited Delete(Count - 1);
end;
end;
function TUndoBuffer.LastUndo: TUndo;
begin
if (FPtr >= 0) and (Count > 0) then
Result := TUndo(Items[FPtr])
else
Result := nil;
end;
function TUndoBuffer.IsNewGroup(const AUndo: TUndo): boolean;
begin
Result := (LastUndo = nil) or (LastUndo.ClassType <> AUndo.ClassType)
end;
{ TUndo }
constructor TUndo.Create(const AMemoEx: TCustomMemoEx);
begin
FMemoEx := AMemoEx;
UndoBuffer.Add(Self);
end;
function TUndo.UndoBuffer: TUndoBuffer;
begin
if FMemoEx <> nil then
Result := FMemoEx.FUndoBuffer
else
Result := nil;
end;
{ TCaretUndo }
constructor TCaretUndo.Create(const AMemoEx: TCustomMemoEx; const ACaretX, ACaretY: integer);
begin
inherited Create(AMemoEx);
FCaretX := ACaretX;
FCaretY := ACaretY;
end;
procedure TCaretUndo.Undo;
begin
with UndoBuffer do
begin
dec(FPtr);
while FMemoEx.FGroupUndo and (FPtr >= 0) and not IsNewGroup(Self) do
dec(FPtr);
inc(FPtr);
with TCaretUndo(Items[FPtr]) do
FMemoEx.SetCaretInternal(FCaretX, FCaretY);
end;
end;
procedure TCaretUndo.Redo;
begin
RedoNotImplemented;
end;
{ TInsertUndo }
constructor TInsertUndo.Create(const AMemoEx: TCustomMemoEx; const ACaretX, ACaretY: integer;
const AText: string);
var
i: integer;
begin
inherited Create(AMemoEx, ACaretX, ACaretY);
FText := AText;
AMemoEx.FLines.Caret2Paragraph(ACaretX, ACaretY, i, FParaOffset);
FOffset := AMemoEx.FLines.GetParaOffs(i) + FParaOffset;
end;
procedure TInsertUndo.Undo;
var
S, Text: string;
_P, _PI: integer;
begin
Text := '';
with UndoBuffer do
begin
while (FPtr >= 0) and not IsNewGroup(Self) do
begin
Text := TInsertUndo(LastUndo).FText + Text;
dec(FPtr);
if not FMemoEx.FGroupUndo then
break;
end;
inc(FPtr);
end;
with TInsertUndo(UndoBuffer.Items[UndoBuffer.FPtr]) do
begin
if PosEx(#13, text) > 0 then
begin
S := FMemoEx.FLines.Text;
Delete(S, FOffset + 1, Length(Text));
FMemoEx.FLines.SetLockText(S);
end
else
begin // new fast method by AB
FMemoEx.FLines.Index2ParaIndex(FCaretY, _P, _PI);
s := FMemoEX.FLines[_P];
delete(s, FParaOffset + 1, length(Text));
FMemoEx.FLines[_P] := S; // will reformat paragraph
end;
FMemoEx.SetCaretInternal(FCaretX, FCaretY);
end;
end;
{ TOverwriteUndo }
constructor TOverwriteUndo.Create(const AMemoEx: TCustomMemoEx;
const ACaretX, ACaretY: integer; const AOldText, ANewText: string);
var
i, j: integer;
begin
inherited Create(AMemoEx, ACaretX, ACaretY);
FOldText := AOldText;
FNewText := ANewText;
AMemoEx.FLines.Caret2Paragraph(ACaretX, ACaretY, i, j);
FOffset := AMemoEx.FLines.GetParaOffs(i) + j;
end;
procedure TOverwriteUndo.Undo;
var
S, OldText, NewText: string;
begin
NewText := '';
OldText := '';
with UndoBuffer do
begin
while (FPtr >= 0) and not IsNewGroup(Self) do
begin
OldText := TOverwriteUndo(LastUndo).FOldText + OldText;
NewText := TOverwriteUndo(LastUndo).FNewText + NewText;
dec(FPtr);
if not FMemoEx.FGroupUndo then
break;
end;
inc(FPtr);
end;
with TOverwriteUndo(UndoBuffer.Items[UndoBuffer.FPtr]) do
begin
S := FMemoEx.FLines.Text;
Delete(S, FOffset + 1, Length(NewText));
Insert(OldText, S, FOffset + 1);
FMemoEx.FLines.SetLockText(S);
FMemoEx.SetCaretInternal(FCaretX, FCaretY);
end;
end;
{ TDeleteUndo }
procedure TDeleteUndo.Undo;
var
X, Y: integer;
S, Text: string;
iBeg: integer;
begin
Text := '';
X := -1;
Y := -1;
with UndoBuffer do
begin
while (FPtr >= 0) and not IsNewGroup(Self) do
begin
if (X = -1) or (Y = -1) then
begin
X := TDeleteUndo(LastUndo).FCaretX;
Y := TDeleteUndo(LastUndo).FCaretY;
end;
Text := TDeleteUndo(LastUndo).FText + Text;
dec(FPtr);
if not FMemoEx.FGroupUndo then
break;
end;
inc(FPtr);
end;
if (X <> -1) and (Y <> -1) then
with TDeleteUndo(UndoBuffer.Items[UndoBuffer.FPtr]) do
begin
S := FMemoEx.FLines.Text;
iBeg := FMemoEx.PosFromCaret(X, Y);
Insert(Text, S, iBeg + 1);
FMemoEx.FLines.SetLockText(S);
FMemoEx.CaretFromPos(iBeg, X, Y);
FMemoEx.SetCaretInternal(X, Y);
end;
end;
{ TBackspaceUndo }
procedure TBackspaceUndo.Undo;
var
S, Text: string;
iBeg: integer;
X, Y: integer;
begin
Text := '';
X := -1;
Y := -1;
with UndoBuffer do
begin
while (FPtr >= 0) and not IsNewGroup(Self) do
begin
if (X = -1) or (Y = -1) then
begin
X := TDeleteUndo(LastUndo).FCaretX;
Y := TDeleteUndo(LastUndo).FCaretY;
end;
Text := Text + TDeleteUndo(LastUndo).FText;
dec(FPtr);
if not FMemoEx.FGroupUndo then
break;
end;
inc(FPtr);
end;
if (X <> -1) and (Y <> -1) then
with TDeleteUndo(UndoBuffer.Items[UndoBuffer.FPtr]) do
begin
S := FMemoEx.FLines.Text;
iBeg := FMemoEx.PosFromCaret(X, Y);
Insert(Text, S, iBeg + 1);
FMemoEx.FLines.SetLockText(S);
FMemoEx.CaretFromPos(iBeg + length(Text), X, Y);
FMemoEx.SetCaretInternal(X, Y);
end;
end;
{ TReplaceUndo }
constructor TReplaceUndo.Create(const AMemoEx: TCustomMemoEx; const ACaretX, ACaretY: integer;
const ABeg, AEnd: integer; const AText, ANewText: string);
begin
inherited Create(AMemoEx, ACaretX, ACaretY);
FBeg := ABeg;
FEnd := AEnd;
FText := AText;
FNewText := ANewText;
end;
procedure TReplaceUndo.Undo;
var
S: string;
begin
S := FMemoEx.FLines.Text;
Delete(S, FBeg, Length(FNewText));
Insert(FText, S, FBeg);
FMemoEx.FLines.SetLockText(S);
FMemoEx.SetCaretInternal(FCaretX, FCaretY);
end;
{ TDeleteSelectedUndo }
constructor TDeleteSelectedUndo.Create(const AMemoEx: TCustomMemoEx;
const ACaretX, ACaretY: integer; const AText: string; const ASelBlock: boolean;
const ASelBegX, ASelBegY, ASelEndX, ASelEndY, ASelOffs: integer);
begin
inherited Create(AMemoEx, ACaretX, ACaretY, AText);
FSelBlock := ASelBlock;
FSelBegX := ASelBegX;
FSelBegY := ASelBegY;
FSelEndX := ASelEndX;
FSelEndY := ASelEndY;
FSelOffs := ASelOffs;
end;
procedure TDeleteSelectedUndo.Undo;
var
S, Text: string;
begin
Text := '';
with UndoBuffer do
begin
while (FPtr >= 0) and not IsNewGroup(Self) do
begin
Text := TDeleteUndo(LastUndo).FText + Text;
dec(FPtr);
if not FMemoEx.FGroupUndo then
break;
end;
inc(FPtr);
end;
with TDeleteUndo(UndoBuffer.Items[UndoBuffer.FPtr]) do
begin
S := FMemoEx.FLines.Text;
// iBeg := FMemoEx.PosFromCaret(FSelBegX, FSelBegY); BUG if wordwrap
Insert(Text, S, FSelOffs + 1);
FMemoEx.FLines.SetLockText(S);
FMemoEx.FSelBlock := FSelBlock;
FMemoEx.FSelBegX := FSelBegX;
FMemoEx.FSelBegY := FSelBegY;
FMemoEx.FSelEndX := FSelEndX;
FMemoEx.FSelEndY := FSelEndY;
FMemoEx.FSelectedText := Length(FText) > 0;
FMemoEx.SetCaretInternal(FCaretX, FCaretY);
end;
end;
{ TSelectUndo }
constructor TSelectUndo.Create(const AMemoEx: TCustomMemoEx;
const ACaretX, ACaretY: integer; const ASelBlock: boolean;
const ASelBegX, ASelBegY, ASelEndX, ASelEndY: integer);
begin
inherited Create(AMemoEx, ACaretX, ACaretY);
FSelBlock := ASelBlock;
FSelBegX := ASelBegX;
FSelBegY := ASelBegY;
FSelEndX := ASelEndX;
FSelEndY := ASelEndY;
end;
procedure TSelectUndo.Undo;
begin
FMemoEx.FSelectedText := (FSelBegX <> FSelEndX) or (FSelBegY <> FSelEndY);
FMemoEx.FSelBegX := FSelBegX;
FMemoEx.FSelBegY := FSelBegY;
FMemoEx.FSelEndX := FSelEndX;
FMemoEx.FSelEndY := FSelEndY;
FMemoEx.FSelBlock := FSelBlock;
FMemoEx.SetCaretInternal(FCaretX, FCaretY);
end;
{ TBeginCompoundUndo }
procedure TBeginCompoundUndo.Undo;
begin
{ nothing }
end;
{$ENDIF MEMOEX_UNDO}
{$IFDEF MEMOEX_COMPLETION}
procedure TCustomMemoEx.CompletionIdentifer(var Cancel: boolean);
begin
{abstract}
end;
procedure TCustomMemoEx.CompletionTemplate(var Cancel: boolean);
begin
{abstract}
end;
procedure TCustomMemoEx.DoCompletionIdentifer(var Cancel: boolean);
begin
CompletionIdentifer(Cancel);
if Assigned(FOnCompletionIdentifer) then
FOnCompletionIdentifer(Self, Cancel);
end;
procedure TCustomMemoEx.DoCompletionTemplate(var Cancel: boolean);
begin
CompletionTemplate(Cancel);
if Assigned(FOnCompletionTemplate) then
FOnCompletionTemplate(Self, Cancel);
end;
function TCustomMemoEx.DoPreprocessCompletion(const ID, OldText: string): string;
begin
if Assigned(FOnPreprocessCompletion) then
Result := FOnPreprocessCompletion(Self, ID, OldText)
else
Result := OldText;
end;
type
TMemoExCompletionList = class(TListBox)
private
FTimer: TTimer;
YY: integer;
// HintWindow : THintWindow;
procedure CMHintShow(var Message: TMessage); message CM_HINTSHOW;
procedure WMCancelMode(var Message: TMessage); message WM_CancelMode;
procedure OnTimer(Sender: TObject);
protected
procedure CreateParams(var Params: TCreateParams); override;
procedure CreateWnd; override;
procedure DestroyWnd; override;
procedure MouseMove(Shift: TShiftState; X, Y: integer); override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: integer); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: integer); override;
procedure DrawItem(Index: integer; Rect: TRect; State: TOwnerDrawState); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
constructor TCompletion.Create2(AMemoEx: TCustomMemoEx);
begin
inherited Create;
FMemoEx := AMemoEx;
FPopupList := TMemoExCompletionList.Create(FMemoEx);
FItemHeight := FPopupList.ItemHeight;
FDropDownCount := 6;
FDropDownWidth := 300;
FTimer := TTimer.Create(nil);
FTimer.Enabled := false;
FTimer.Interval := 800;
FTimer.OnTimer := OnTimer;
FIdentifers := TStringList.Create;
FTemplates := TStringList.Create;
FItems := TStringList.Create;
FAutoChange := TStringList.Create;
TStringList(FAutoChange).OnChange := AutoChangeChanged;
FAutoChangeList := TList.Create;
FDefMode := cmIdentifers;
FCaretChar := '|';
FCRLF := '/n';
FSeparator := '=';
end;
destructor TCompletion.Destroy;
begin
inherited Destroy;
FPopupList.Free;
FIdentifers.Free;
FTemplates.Free;
FItems.Free;
FAutoChange.Free;
ClearAutoChangeList;
FAutoChangeList.Free;
FTimer.Free;
end;
function TCompletion.GetItems: TStrings;
begin
case FMode of
cmIdentifers:
Result := FIdentifers;
cmTemplates:
Result := FTemplates;
else
Result := nil;
end;
end;
procedure TCompletion.ReplaceWord(const ANewString: string);
var
S, S1, W, NewString: string;
P, X, Y: integer;
iBeg, iEnd: integer;
NewCaret, LNum, CX, CY, i: integer;
begin
with FMemoEx do
begin
PaintCaret(false);
BeginUpdate;
ReLine;
S := FLines.Text;
P := PosFromCaret(FCaretX, FCaretY);
W := Trim(GetWordOnPosEx(S, P, iBeg, iEnd));
LNum := 0;
if W = '' then
begin
iBeg := P + 1;
iEnd := P
end;
CaretFromPos(iBeg, CX, CY);
if CX < 1 then
CX := FCaretX + 1;
NewString := DoPreprocessCompletion(W, ANewString);
case FMode of
cmIdentifers:
begin
S1 := NewString;
NewCaret := Length(NewString);
end;
cmTemplates:
begin
S1 := StringReplaceAll(NewString, FCRLF, #13#10 + StringOfChar(' ', CX - 1));
S1 := StringReplaceAll(S1, FCaretChar, '');
NewCaret := Pos(FCaretChar, NewString) - 1;
if NewCaret = -1 then
NewCaret := Length(NewString);
for i := 1 to NewCaret do
if S1[i] = #13 then
inc(LNum);
end
else
raise EMemoExError.Create('Invalid MemoEx Completion Mode');
end;
{$IFDEF MEMOEX_UNDO}
TReplaceUndo.Create(FMemoEx, FCaretX, FCaretY, iBeg, iEnd, W, S1);
{$ENDIF MEMOEX_UNDO}
{ // LW := Length(W);
if FSelected then
begin
if (FSelBegY <= FCaretY) or (FCaretY >= FSelEndY) then
end;}
Delete(S, iBeg, iEnd - iBeg);
Insert(S1, S, iBeg);
FLines.SetLockText(S);
CaretFromPos(iBeg - 1 + (CX - 1) * LNum + NewCaret, X, Y);
SetCaretInternal(X, Y);
FMemoEx.TextAllChanged; // Invalidate; {!!!}
Changed;
EndUpdate;
PaintCaret(true);
end;
end;
procedure TCompletion.DoKeyPress(Key: Char);
begin
if FVisible then
if {$ifdef UNICODE} (Key < #127) and {$endif}(AnsiChar(Key) in RAEditorCompletionChars) then
SelectItem
else
CloseUp(true)
else if FEnabled then
FTimer.Enabled := true;
end;
function TCompletion.DoKeyDown(Key: Word; Shift: TShiftState): boolean;
begin
Result := true;
case Key of
VK_ESCAPE:
CloseUp(false);
VK_RETURN:
CloseUp(true);
VK_UP, VK_DOWN, VK_PRIOR, VK_NEXT:
FPopupList.Perform(WM_KEYDOWN, Key, 0);
else
Result := false;
end;
end;
procedure TCompletion.DoCompletion(const AMode: TCompletionList);
var
Eq: boolean;
Cancel: boolean;
begin
if FMemoEx.FReadOnly then
exit;
if FPopupList.Visible then
CloseUp(false);
FMode := AMode;
case FMode of
cmIdentifers:
DropDown(AMode, true);
cmTemplates:
begin
Cancel := false;
// FMemoEx.DoCompletionIdentifer(Cancel);
FMemoEx.DoCompletionTemplate(Cancel);
if Cancel or (FTemplates.Count = 0) then
exit;
MakeItems;
FindSelItem(Eq);
if Eq then
ReplaceWord(SubStr(FItems[ItemIndex], 2, FSeparator))
else
DropDown(AMode, true);
end;
end;
end;
procedure TCompletion.DropDown(const AMode: TCompletionList; const ShowAlways: boolean);
var
ItemCount: integer;
P: TPoint;
Y: integer;
PopupWidth, PopupHeight: integer;
SysBorderWidth, SysBorderHeight: integer;
R: TRect;
Cancel: boolean;
Eq: boolean;
begin
CloseUp(false);
FMode := AMode;
with FMemoEx do
begin
Cancel := false;
case FMode of
cmIdentifers:
FMemoEx.DoCompletionIdentifer(Cancel);
cmTemplates:
FMemoEx.DoCompletionTemplate(Cancel)
end;
MakeItems;
FindSelItem(Eq);
// Cancel := not Visible and (ItemIndex = -1);
if Cancel or (FItems.Count = 0) or (((ItemIndex = -1) or Eq) and not ShowAlways) then
exit;
FPopupList.Items := FItems;
FPopupList.ItemHeight := FItemHeight;
FVisible := true;
SetItemIndex(FItemIndex);
if FListBoxStyle in [lbStandard] then
FPopupList.Style := lbOwnerDrawFixed
else
FPopupList.Style := FListBoxStyle;
FPopupList.OnMeasureItem := FMemoEx.FOnCompletionMeasureItem;
FPopupList.OnDrawItem := FMemoEx.FOnCompletionDrawItem;
ItemCount := FItems.Count;
SysBorderWidth := GetSystemMetrics(SM_CXBORDER);
SysBorderHeight := GetSystemMetrics(SM_CYBORDER);
R := CalcCellRect(FCaretX - FLeftCol, FCaretY - FTopRow + 1);
P := R.TopLeft;
P.X := ClientOrigin.X + P.X;
P.Y := ClientOrigin.Y + P.Y;
Dec(P.X, 2 * SysBorderWidth);
Dec(P.Y, SysBorderHeight);
if ItemCount > FDropDownCount then
ItemCount := FDropDownCount;
PopupHeight := ItemHeight * ItemCount + 2;
Y := P.Y;
if (Y + PopupHeight) > Screen.Height then
begin
Y := P.Y - PopupHeight - FCellRect.Height + 1;
if Y < 0 then
Y := P.Y;
end;
PopupWidth := FDropDownWidth;
if PopupWidth = 0 then
PopupWidth := Width + 2 * SysBorderWidth;
end;
FPopupList.Left := P.X;
FPopupList.Top := Y;
FPopupList.Width := PopupWidth;
FPopupList.Height := PopupHeight;
SetWindowPos(FPopupList.Handle, HWND_TOP, P.X, Y, 0, 0, SWP_NOSIZE or SWP_NOACTIVATE or SWP_SHOWWINDOW);
FPopupList.Visible := true;
end;
function TCompletion.Cmp1(const S1, S2: string): integer;
var
T1, T2: string;
begin
T1 := FMemoEx.DoChangeCase(S1, RA_CASE_CONVERT_LOWER);
T2 := FMemoEx.DoChangeCase(S2, RA_CASE_CONVERT_LOWER);
Result := StrLIComp(PChar(T1), PChar(T2), Length(T2));
end;
function TCompletion.Cmp2(const S1, S2: string): boolean;
var
T1, T2: string;
begin
T1 := FMemoEx.DoChangeCase(S1, RA_CASE_CONVERT_LOWER);
T2 := FMemoEx.DoChangeCase(S2, RA_CASE_CONVERT_LOWER);
Result := SameText(T1, T2);
end;
procedure TCompletion.MakeItems;
var
i: integer;
S: string;
begin
FItems.Clear;
case FMode of
cmIdentifers:
for i := 0 to FIdentifers.Count - 1 do
FItems.Add(FIdentifers[i]);
cmTemplates:
begin
with FMemoEx do
S := GetWordOnPos(FLines.ParaStrings[CaretY], CaretX);
for i := 0 to FTemplates.Count - 1 do
if Cmp1(FTemplates[i], S) = 0 then
FItems.Add(FTemplates[i]);
if FItems.Count = 0 then
FItems.Assign(FTemplates);
end;
end;
end;
procedure TCompletion.FindSelItem(var Eq: boolean);
function FindFirst(Ss: TSTrings; S: string): integer;
var
i: integer;
begin
for i := 0 to Ss.Count - 1 do
if Cmp1(Ss[i], S) = 0 then
begin
Result := i;
exit;
end;
Result := -1;
end;
var
S: string;
begin
with FMemoEx do
if FLines.Count > 0 then
S := GetWordOnPos(FLines.ParaStrings[CaretY], CaretX)
else
S := '';
if Trim(S) = '' then
ItemIndex := -1
else
ItemIndex := FindFirst(FItems, S);
Eq := (ItemIndex > -1) and Cmp2(Trim(SubStr(FItems[ItemIndex], 0, FSeparator)), S);
end;
procedure TCompletion.SelectItem;
var
Cancel: boolean;
Param: boolean;
begin
FindSelItem(Param);
Cancel := not Visible and (ItemIndex = -1);
case FMode of
cmIdentifers:
FMemoEx.DoCompletionIdentifer(Cancel);
cmTemplates:
FMemoEx.DoCompletionTemplate(Cancel);
end;
if Cancel or (GetItems.Count = 0) then
CloseUp(false);
end;
procedure TCompletion.CloseUp(const Apply: boolean);
begin
FItemIndex := ItemIndex;
FPopupList.Visible := false;
// (FPopupList as TMemoExCompletionList). HintWindow.ReleaseHandle;
FVisible := false;
FTimer.Enabled := false;
if Apply and (ItemIndex > -1) then
case FMode of
cmIdentifers:
ReplaceWord(SubStr(FItems[ItemIndex], 0, FSeparator));
cmTemplates:
ReplaceWord(SubStr(FItems[ItemIndex], 2, FSeparator));
end;
end;
procedure TCompletion.OnTimer(Sender: TObject);
begin
DropDown(FDefMode, false);
end;
procedure TCompletion.ClearAutoChangeList;
var
i: integer;
begin
for i := 0 to FAutoChangeList.Count - 1 do
Dispose(FAutoChangeList[i]);
FAutoChangeList.Clear;
end;
procedure TCompletion.UpdateAutoChange;
begin
AutoChangeChanged(FAutoChange);
end;
procedure TCompletion.AutoChangeChanged(Sender: TObject);
procedure AddAutoChangeWord(const OldWord, NewWord: string);
var
ACW: PAutoChangeWord;
begin
if OldWord <> '' then
begin
New(ACW);
ACW.OldWord := FMemoEx.DoChangeCase(OldWord, RA_CASE_CONVERT_LOWER);
ACW.NewWord := NewWord;
FAutoChangeList.Add(ACW);
end;
end;
var
i: integer;
begin
ClearAutoChangeList;
for i := 0 to FAutoChange.Count - 1 do
AddAutoChangeWord(SubStr(FAutoChange.Strings[i], 0, FSeparator), SubStr(FAutoChange.Strings[i], 1, FSeparator));
FAutoChangeList.Sort(AutoChangeCompare);
end;
procedure TCompletion.SetStrings(index: integer; AValue: TStrings);
begin
case index of
0:
FIdentifers.Assign(AValue);
1:
FTemplates.Assign(AValue);
2:
FAutoChange.Assign(AValue);
end;
end;
function TCompletion.GetItemIndex: integer;
begin
Result := FItemIndex;
if FVisible then
Result := FPopupList.ItemIndex;
end;
procedure TCompletion.SetItemIndex(AValue: integer);
begin
FItemIndex := AValue;
if FVisible then
FPopupList.ItemIndex := FItemIndex;
end;
function TCompletion.GetInterval: cardinal;
begin
Result := FTimer.Interval;
end;
procedure TCompletion.SetInterval(AValue: cardinal);
begin
FTimer.Interval := AValue;
end;
constructor TMemoExCompletionList.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Left := -1000;
Visible := false;
TabStop := false;
ParentFont := false;
Parent := Owner as TCustomMemoEx;
Ctl3D := false;
FTimer := TTimer.Create(nil);
FTimer.Enabled := false;
FTimer.Interval := 200;
FTimer.OnTimer := OnTimer;
Style := lbOwnerDrawFixed;
ItemHeight := 13;
// HintWindow := THintWindow.Create(Self);
end;
destructor TMemoExCompletionList.Destroy;
begin
FTimer.Free;
// HintWindow.Free;
inherited Destroy;
end;
procedure TMemoExCompletionList.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
with Params do
begin
Style := Style {or WS_POPUP} or WS_BORDER;
ExStyle := ExStyle or WS_EX_TOOLWINDOW;
WindowClass.Style := WindowClass.Style or CS_SAVEBITS;
end;
end;
procedure TMemoExCompletionList.CreateWnd;
begin
inherited CreateWnd;
if not (csDesigning in ComponentState) then
Windows.SetParent(Handle, 0);
// CallWindowProc(DefWndProc, Handle, WM_SETFOCUS, 0, 0); {??}
end;
procedure TMemoExCompletionList.DestroyWnd;
begin
inherited DestroyWnd;
// HintWindow.ReleaseHandle;
end;
procedure TMemoExCompletionList.MouseMove(Shift: TShiftState; X, Y: integer);
var
F: integer;
begin
YY := Y;
// F := ItemAtPos(Point(X, Y), true);
if KeyPressed(VK_LBUTTON) then
begin
F := ItemAtPos(Point(X, Y), true);
if F > -1 then
ItemIndex := F;
FTimer.Enabled := (Y < 0) or (Y > ClientHeight);
if (Y < -ItemHeight) or (Y > ClientHeight + ItemHeight) then
FTimer.Interval := 50
else
FTimer.Interval := 200;
end;
// if (F > -1) and not FTimer.Enabled then
// begin
// Application.CancelHint;
// Hint := Items[F];
// HintWindow.ActivateHint(Bounds(ClientOrigin.X + X, ClientOrigin.Y + Y, 300, ItemHeight), Items[F]);
// end;
end;
procedure TMemoExCompletionList.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: integer);
var
F: integer;
begin
MouseCapture := true;
F := ItemAtPos(Point(X, Y), true);
if F > -1 then
ItemIndex := F;
end;
procedure TMemoExCompletionList.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: integer);
begin
MouseCapture := false;
(Owner as TCustomMemoEx).FCompletion.CloseUp((Button = mbLeft) and PtInRect(ClientRect, Point(X, Y)));
end;
procedure TMemoExCompletionList.OnTimer(Sender: TObject);
begin
if (YY < 0) then
Perform(WM_VSCROLL, SB_LINEUP, 0)
else if (YY > ClientHeight) then
Perform(WM_VSCROLL, SB_LINEDOWN, 0);
end;
procedure TMemoExCompletionList.WMCancelMode(var Message: TMessage);
begin
(Owner as TCustomMemoEx).FCompletion.CloseUp(false);
end;
procedure TMemoExCompletionList.CMHintShow(var Message: TMessage);
begin
Message.Result := 1;
end;
procedure TMemoExCompletionList.DrawItem(Index: integer; Rect: TRect; State: TOwnerDrawState);
var
Offset, W: integer;
S: string;
begin
if Assigned(OnDrawItem) then
OnDrawItem(Self, Index, Rect, State)
else
begin
Canvas.FillRect(Rect);
Offset := 3;
with (Owner as TCustomMemoEx).FCompletion do
case FMode of
cmIdentifers:
Canvas.TextOut(Rect.Left + Offset, Rect.Top, SubStr(Items[Index], 1, Separator));
cmTemplates:
begin
Canvas.TextOut(Rect.Left + Offset, Rect.Top, SubStr(Items[Index], 1, Separator));
Canvas.Font.Style := [fsBold];
S := SubStr(Items[Index], 0, Separator);
W := Canvas.TextWidth(S);
Canvas.TextOut(Rect.Right - 2 * Offset - W, Rect.Top, S);
end;
end;
end;
end;
{$ENDIF MEMOEX_COMPLETION}
{$ENDIF MEMOEX_EDITOR}
procedure TCustomMemoEx.ValidateEditBuffer;
begin
if FPEditBuffer = nil then
begin
FEditBuffer := Lines.Text;
FPEditBuffer := PChar(FEditBuffer);
FEditBufferSize := Length(FEditBuffer);
end;
end;
function TCustomMemoEx.GetText(Position: longint; Buffer: PChar; Count: longint): longint;
begin
ValidateEditBuffer;
if Position <= FEditBufferSize then
begin
Result := Min(FEditBufferSize - Position, Count);
Move(FPEditBuffer[Position], Buffer[0], Result * SizeOf(char));
end
else
Result := 0;
end;
procedure TCustomMemoEx.SetWordWrap(Value: boolean);
var
p, x, y: integer; // AB : don't loose position
begin
if Value <> FWordWrap then
begin
p := PosFromCaret(CaretX, CaretY);
FWordWrap := Value;
FLines.Reformat;
CantUndo;
CaretFromPos(p, x, y);
SetCaret(0, Y); // X=0 to avoid new line length overflow issue
end;
end;
procedure TCustomMemoEx.SetStripInvisible(Value: boolean);
begin
if Value <> FStripInvisible then
begin
FStripInvisible := Value;
if FReadOnly then
Invalidate;
end;
end;
procedure TCustomMemoEx.FontChanged(Sender: TObject);
begin
UpdateEditorSize({fullupdate=}true);
Invalidate;
end;
function TCustomMemoEx.GetAfterLoad: TNotifyEvent;
begin
Result := FLines.FOnAfterLoad;
end;
procedure TCustomMemoEx.SetAfterLoad(Value: TNotifyEvent);
begin
FLines.FOnAfterLoad := Value;
end;
function TCustomMemoEx.GetBeforeSave: TNotifyEvent;
begin
Result := FLines.FOnBeforeSave;
end;
procedure TCustomMemoEx.SetBeforeSave(Value: TNotifyEvent);
begin
FLines.FOnBeforeSave := Value;
end;
procedure TCustomMemoEx.SetSelectedText(Value: boolean);
begin
if FSelected <> Value then
begin
FSelected := Value;
SelectionChanged;
end;
end;
function TCustomMemoEx.FindNext(const text: string; ignCase: boolean): boolean;
var
X, Found, para, paraIndex: integer;
UpText: string;
begin
if ignCase then
UpText := UpperCase(Text);
SetUnSelected;
X := FCaretX + 1; // ignore chars for first string
FLines.Index2ParaIndex(FCaretY, para, paraIndex);
while para < FLines.FCount do // search in all paragraphs
with FLines.FList[para] do
begin
while paraIndex < FCount do
begin // search in this paragraph
if ignCase then
Found := PosEx(UpText, UpperCase(FStrings[paraIndex]), X)
else
Found := PosEx(text, FStrings[paraIndex], X);
if Found > 0 then
begin
SetSel(Found - 1, FPreCount + paraIndex);
SetSel(FSelStartX + length(text), FSelStartY);
SetCaret(FSelEndX, FSelEndY);
result := true;
exit;
end;
X := 1; // now whole lines are searched
inc(paraIndex); // next line of this paragraph
end;
inc(para); // next paragraph
paraIndex := 0; // begin with first line
end;
result := false; // not found
end;
procedure TCustomMemoEx.SetTopRow(const Value: integer);
begin
SetLeftTop(0, Value);
end;
procedure TCustomMemoEx.DoContextPopup(MousePos: TPoint; var Handled: Boolean);
var
aPopupMenu: TPopupMenu;
begin
if InvalidPoint(MousePos) then
begin
// in case of menu from keyboard -> show popupmenu at caret position
aPopupMenu := GetPopupMenu;
if (aPopupMenu = nil) or not aPopupMenu.AutoPopup then
exit;
SendCancelMode(nil);
aPopupMenu.PopupComponent := Self;
with ClientToScreen(CalcCellRect(FCaretX - FLeftCol, FCaretY - FTopRow).TopLeft) do
aPopupMenu.Popup(X, Y);
Handled := true;
end
else
inherited;
end;
function TCustomMemoEx.GetTextStr: string;
begin
result := FLines.GetTextStr;
end;
procedure TCustomMemoEx.SetTextStr(const Value: string);
begin
FLines.SetTextStr(Value);
end;
{ TMemoEx }
class procedure TMemoEx.JSONLineAttr(Sender: TObject; const Line: string;
index: Integer; const SelAttrs: TSelAttrs; var Attrs: TLineAttrs);
var
i, c: integer;
FC: TColor;
begin // limitation: work only properly if the JSON is in a single line
FC := clWindowText;
i := 0;
if Line <> '' then
repeat
case Line[i + 1] of
#0:
break;
'[', ']':
begin
Attrs[i].FC := clNavy;
Attrs[i].Style := [fsBold];
inc(i);
end;
'"':
begin
repeat
Attrs[i].FC := clOlive;
inc(i);
if Line[i + 1] = #0 then
exit;
until (Line[i + 1] = '"') and (Line[i] <> '\');
Attrs[i].FC := clOlive;
inc(i);
end;
'-', '0'..'9':
begin
repeat
Attrs[i].FC := clNavy;
inc(i);
until {$ifdef UNICODE} (Line[i + 1] > #127) or {$endif}
not (AnsiChar(Line[i + 1]) in ['0'..'9', 'e', 'E', '+', '-']);
end;
'_', 'a'..'z', 'A'..'Z', '$':
begin
// see SynCommons.IsJsonIdentifierFirstChar
{$ifdef UNICODE}
c := ord(Line[i + 1]) + ord(Line[i + 2]) shl 8 + ord(Line[i + 3]) shl 16 + ord(Line[i + 4]) shl 24;
{$else}
c := PInteger(PAnsiChar(pointer(Line)) + i)^;
{$endif}
if (c = ord('n') + ord('u') shl 8 + ord('l') shl 16 + ord('l') shl 24) or
(c = ord('f') + ord('a') shl 8 + ord('l') shl 16 + ord('s') shl 24) or
(c = ord('t') + ord('r') shl 8 + ord('u') shl 16 + ord('e') shl 24) then
repeat
Attrs[i].FC := clNavy;
Attrs[i].Style := [fsBold];
inc(i);
until {$ifdef UNICODE} (Line[i + 1] > #127) or {$endif}
not (AnsiChar(Line[i + 1]) in ['a'..'z'])
else
repeat
Attrs[i].FC := clMaroon;
inc(i);
until {$ifdef UNICODE} (Line[i + 1] > #127) or {$endif}
not (AnsiChar(Line[i + 1]) in ['_', '0'..'9', 'a'..'z', 'A'..'Z', '.', '[', ']']);
end;
else
begin
Attrs[i].FC := FC;
inc(i);
end;
end;
until false;
end;
procedure Register;
begin
RegisterComponents('Standard', [TMemoEx]);
end;
initialization
CF_MEMOEX := RegisterClipBoardFormat('CF_MEMOEX');
end.