RichEditViewer.pas 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. unit RichEditViewer;
  2. { TRichEditViewer by Jordan Russell and Martijn Laan
  3. Known problem:
  4. If, after assigning rich text to a TRichEditViewer component, you change
  5. a property that causes the component's handle to be recreated, all text
  6. formatting will be lost (in the interests of code size).
  7. }
  8. interface
  9. uses
  10. Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  11. {$IFDEF VCLSTYLES} Vcl.Themes, {$ELSE} Themes, {$ENDIF}
  12. StdCtrls, RichEdit, ActiveX;
  13. type
  14. IRichEditOleCallback = interface(IUnknown)
  15. ['{00020d03-0000-0000-c000-000000000046}']
  16. function GetNewStorage(out stg: IStorage): HResult; stdcall;
  17. function GetInPlaceContext(out Frame: IOleInPlaceFrame;
  18. out Doc: IOleInPlaceUIWindow;
  19. lpFrameInfo: POleInPlaceFrameInfo): HResult; stdcall;
  20. function ShowContainerUI(fShow: BOOL): HResult; stdcall;
  21. function QueryInsertObject(const clsid: TCLSID; const stg: IStorage;
  22. cp: Integer): HResult; stdcall;
  23. function DeleteObject(const oleobj: IOleObject): HResult; stdcall;
  24. function QueryAcceptData(const dataobj: IDataObject;
  25. var cfFormat: TClipFormat; reco: DWORD; fReally: BOOL;
  26. hMetaPict: HGLOBAL): HResult; stdcall;
  27. function ContextSensitiveHelp(fEnterMode: BOOL): HResult; stdcall;
  28. function GetClipboardData(const chrg: TCharRange; reco: DWORD;
  29. out dataobj: IDataObject): HResult; stdcall;
  30. function GetDragDropEffect(fDrag: BOOL; grfKeyState: DWORD;
  31. var dwEffect: DWORD): HResult; stdcall;
  32. function GetContextMenu(seltype: Word; const oleobj: IOleObject;
  33. const chrg: TCharRange; out menu: HMENU): HResult; stdcall;
  34. end;
  35. TRichEditViewerCustomShellExecute = procedure(hWnd: HWND; Operation, FileName, Parameters, Directory: LPWSTR; ShowCmd: Integer); stdcall;
  36. TRichEditViewer = class(TMemo)
  37. private
  38. class var
  39. FCustomShellExecute: TRichEditViewerCustomShellExecute;
  40. var
  41. FUseRichEdit: Boolean;
  42. FRichEditLoaded: Boolean;
  43. FCallback: IRichEditOleCallback;
  44. class constructor Create;
  45. class destructor Destroy;
  46. procedure SetRTFTextProp(const Value: AnsiString);
  47. procedure SetUseRichEdit(Value: Boolean);
  48. procedure UpdateBackgroundColor;
  49. procedure RecolorAutoForegroundText(const NewTextColor: Integer);
  50. procedure CMColorChanged(var Message: TMessage); message CM_COLORCHANGED;
  51. procedure CMSysColorChange(var Message: TMessage); message CM_SYSCOLORCHANGE;
  52. procedure CNNotify(var Message: TWMNotify); message CN_NOTIFY;
  53. protected
  54. procedure CreateParams(var Params: TCreateParams); override;
  55. procedure CreateWnd; override;
  56. public
  57. constructor Create(AOwner: TComponent); override;
  58. destructor Destroy; override;
  59. function SetRTFText(const Value: AnsiString): Integer;
  60. property RTFText: AnsiString write SetRTFTextProp;
  61. class property CustomShellExecute: TRichEditViewerCustomShellExecute read FCustomShellExecute write FCustomShellExecute;
  62. published
  63. property UseRichEdit: Boolean read FUseRichEdit write SetUseRichEdit default True;
  64. end;
  65. TRichEditViewerStyleHook = class(TScrollingStyleHook)
  66. {$IFDEF VCLSTYLES}
  67. private
  68. procedure EMSetBkgndColor(var Message: TMessage); message EM_SETBKGNDCOLOR;
  69. {$ENDIF}
  70. end;
  71. procedure Register;
  72. implementation
  73. uses
  74. ShellApi, PathFunc, ComObj;
  75. {$IF CompilerVersion < 36.0}
  76. const
  77. MSFTEDIT_CLASS = 'RICHEDIT50W';
  78. {$ENDIF}
  79. type
  80. { Basic implementation of IRichEditOleCallback to enable the viewing of images and other objects. }
  81. TBasicRichEditOleCallback = class(TInterfacedObject, IRichEditOleCallback)
  82. public
  83. function GetNewStorage(out stg: IStorage): HResult; stdcall;
  84. function GetInPlaceContext(out Frame: IOleInPlaceFrame;
  85. out Doc: IOleInPlaceUIWindow;
  86. lpFrameInfo: POleInPlaceFrameInfo): HResult; stdcall;
  87. function ShowContainerUI(fShow: BOOL): HResult; stdcall;
  88. function QueryInsertObject(const clsid: TCLSID; const stg: IStorage;
  89. cp: Integer): HResult; stdcall;
  90. function DeleteObject(const oleobj: IOleObject): HResult; stdcall;
  91. function QueryAcceptData(const dataobj: IDataObject;
  92. var cfFormat: TClipFormat; reco: DWORD; fReally: BOOL;
  93. hMetaPict: HGLOBAL): HResult; stdcall;
  94. function ContextSensitiveHelp(fEnterMode: BOOL): HResult; stdcall;
  95. function GetClipboardData(const chrg: TCharRange; reco: DWORD;
  96. out dataobj: IDataObject): HResult; stdcall;
  97. function GetDragDropEffect(fDrag: BOOL; grfKeyState: DWORD;
  98. var dwEffect: DWORD): HResult; stdcall;
  99. function GetContextMenu(seltype: Word; const oleobj: IOleObject;
  100. const chrg: TCharRange; out menu: HMENU): HResult; stdcall;
  101. end;
  102. {$IF CompilerVersion < 36.0}
  103. PEnLink = ^TEnLink;
  104. TENLink = record
  105. nmhdr: TNMHdr;
  106. msg: UINT;
  107. wParam: WPARAM;
  108. lParam: LPARAM;
  109. chrg: TCharRange;
  110. end;
  111. {$ENDIF}
  112. TTextRange = record
  113. chrg: TCharRange;
  114. lpstrText: PWideChar;
  115. end;
  116. { The following interface definitions are simplified to contain only function
  117. prototypes up to the last one we need }
  118. IRichEditOle = interface(IUnknown)
  119. ['{00020D00-0000-0000-C000-000000000046}']
  120. end;
  121. ITextFont = interface(IDispatch)
  122. ['{8CC497C3-A1DF-11CE-8098-00AA0047BE5D}']
  123. function GetDuplicate(out Font: ITextFont): HResult; stdcall;
  124. function SetDuplicate(const Font: ITextFont): HResult; stdcall;
  125. function CanChange(out Value: Integer): HResult; stdcall;
  126. function IsEqual(const Font: ITextFont; out Value: Integer): HResult; stdcall;
  127. function Reset(Value: Integer): HResult; stdcall;
  128. function GetStyle(out Value: Integer): HResult; stdcall;
  129. function SetStyle(Value: Integer): HResult; stdcall;
  130. function GetAllCaps(out Value: Integer): HResult; stdcall;
  131. function SetAllCaps(Value: Integer): HResult; stdcall;
  132. function GetAnimation(out Value: Integer): HResult; stdcall;
  133. function SetAnimation(Value: Integer): HResult; stdcall;
  134. function GetBackColor(out Value: Integer): HResult; stdcall;
  135. function SetBackColor(Value: Integer): HResult; stdcall;
  136. function GetBold(out Value: Integer): HResult; stdcall;
  137. function SetBold(Value: Integer): HResult; stdcall;
  138. function GetEmboss(out Value: Integer): HResult; stdcall;
  139. function SetEmboss(Value: Integer): HResult; stdcall;
  140. function GetForeColor(out Value: Integer): HResult; stdcall;
  141. function SetForeColor(Value: Integer): HResult; stdcall;
  142. end;
  143. ITextPara = interface(IDispatch)
  144. ['{8CC497C4-A1DF-11CE-8098-00AA0047BE5D}']
  145. end;
  146. ITextRange = interface(IDispatch)
  147. ['{8CC497C2-A1DF-11CE-8098-00AA0047BE5D}']
  148. function GetText(out Text: WideString): HResult; stdcall;
  149. function SetText(const Text: WideString): HResult; stdcall;
  150. function GetChar(out CharCode: Integer): HResult; stdcall;
  151. function SetChar(CharCode: Integer): HResult; stdcall;
  152. function GetDuplicate(out Range: ITextRange): HResult; stdcall;
  153. function GetFormattedText(out Range: ITextRange): HResult; stdcall;
  154. function SetFormattedText(const Range: ITextRange): HResult; stdcall;
  155. function GetStart(out cpFirst: Integer): HResult; stdcall;
  156. function SetStart(cpFirst: Integer): HResult; stdcall;
  157. function GetEnd(out cpLim: Integer): HResult; stdcall;
  158. function SetEnd(cpLim: Integer): HResult; stdcall;
  159. function GetFont(out Font: ITextFont): HResult; stdcall;
  160. function SetFont(const Font: ITextFont): HResult; stdcall;
  161. function GetPara(out Para: ITextPara): HResult; stdcall;
  162. function SetPara(const Para: ITextPara): HResult; stdcall;
  163. function GetStoryLength(out Count: Integer): HResult; stdcall;
  164. function GetStoryType(out TypeValue: Integer): HResult; stdcall;
  165. function Collapse(Start: Integer): HResult; stdcall;
  166. function Expand(UnitValue: Integer; out Delta: Integer): HResult; stdcall;
  167. function GetIndex(UnitValue: Integer; out Index: Integer): HResult; stdcall;
  168. function SetIndex(UnitValue, Index, Extend: Integer): HResult; stdcall;
  169. function SetRange(Anchor, Active: Integer): HResult; stdcall;
  170. function InRange(const Range: ITextRange; out InRangeValue: Integer): HResult; stdcall;
  171. function InStory(const Range: ITextRange; out InStoryValue: Integer): HResult; stdcall;
  172. function IsEqual(const Range: ITextRange; out Equal: Integer): HResult; stdcall;
  173. function Select: HResult; stdcall;
  174. function StartOf(UnitValue, Extend: Integer; out Delta: Integer): HResult; stdcall;
  175. function EndOf(UnitValue, Extend: Integer; out Delta: Integer): HResult; stdcall;
  176. function Move(UnitValue, Count: Integer; out Delta: Integer): HResult; stdcall;
  177. function MoveStart(UnitValue, Count: Integer; out Delta: Integer): HResult; stdcall;
  178. function MoveEnd(UnitValue, Count: Integer; out Delta: Integer): HResult; stdcall;
  179. end;
  180. ITextSelection = interface(ITextRange)
  181. ['{8CC497C1-A1DF-11CE-8098-00AA0047BE5D}']
  182. end;
  183. ITextDocument = interface(IDispatch)
  184. ['{8CC497C0-A1DF-11CE-8098-00AA0047BE5D}']
  185. function GetName(out Name: WideString): HResult; stdcall;
  186. function GetSelection(out Selection: ITextSelection): HResult; stdcall;
  187. function GetStoryCount(out Count: Integer): HResult; stdcall;
  188. function GetStoryRanges(out Stories: IDispatch): HResult; stdcall;
  189. function GetSaved(out Value: Integer): HResult; stdcall;
  190. function SetSaved(Value: Integer): HResult; stdcall;
  191. function GetDefaultTabStop(out Value: Single): HResult; stdcall;
  192. function SetDefaultTabStop(Value: Single): HResult; stdcall;
  193. function New: HResult; stdcall;
  194. function Open(var Data: OleVariant; Flags, CodePage: Integer): HResult; stdcall;
  195. function Save(var Data: OleVariant; Flags, CodePage: Integer): HResult; stdcall;
  196. function Freeze(out Count: Integer): HResult; stdcall;
  197. function Unfreeze(out Count: Integer): HResult; stdcall;
  198. function BeginEditCollection: HResult; stdcall;
  199. function EndEditCollection: HResult; stdcall;
  200. function Undo(Count: Integer; out Prop: Integer): HResult; stdcall;
  201. function Redo(Count: Integer; out Prop: Integer): HResult; stdcall;
  202. function Range(cp1, cp2: Integer; out Range: ITextRange): HResult; stdcall;
  203. end;
  204. var
  205. RichEditModule: HMODULE;
  206. RichEditUseCount: Integer = 0;
  207. RichEditVersion: Integer;
  208. procedure LoadRichEdit;
  209. function GetSystemDir: String;
  210. var
  211. Buf: array[0..MAX_PATH-1] of Char;
  212. begin
  213. GetSystemDirectory(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
  214. Result := StrPas(Buf);
  215. end;
  216. begin
  217. if RichEditUseCount = 0 then begin
  218. RichEditVersion := 4;
  219. RichEditModule := LoadLibrary(PChar(AddBackslash(GetSystemDir) + 'MSFTEDIT.DLL'));
  220. if RichEditModule = 0 then begin
  221. RichEditVersion := 2;
  222. RichEditModule := LoadLibrary(PChar(AddBackslash(GetSystemDir) + 'RICHED20.DLL'));
  223. end;
  224. end;
  225. Inc(RichEditUseCount);
  226. end;
  227. procedure UnloadRichEdit;
  228. begin
  229. if RichEditUseCount > 0 then begin
  230. Dec(RichEditUseCount);
  231. if RichEditUseCount = 0 then begin
  232. FreeLibrary(RichEditModule);
  233. RichEditModule := 0;
  234. end;
  235. end;
  236. end;
  237. { TBasicRichEditOleCallback }
  238. function TBasicRichEditOleCallback.GetNewStorage(out stg: IStorage): HResult; stdcall;
  239. var
  240. LockBytes: ILockBytes;
  241. begin
  242. try
  243. OleCheck(CreateILockBytesOnHGlobal(0, True, LockBytes));
  244. OleCheck(StgCreateDocfileOnILockBytes(LockBytes, STGM_READWRITE
  245. or STGM_SHARE_EXCLUSIVE or STGM_CREATE, 0, stg));
  246. Result := S_OK;
  247. except
  248. Result := E_OUTOFMEMORY;
  249. end;
  250. end;
  251. function TBasicRichEditOleCallback.GetInPlaceContext(out Frame: IOleInPlaceFrame;
  252. out Doc: IOleInPlaceUIWindow; lpFrameInfo: POleInPlaceFrameInfo): HResult;
  253. begin
  254. Result := E_NOTIMPL;
  255. end;
  256. function TBasicRichEditOleCallback.ShowContainerUI(fShow: BOOL): HResult;
  257. begin
  258. Result := E_NOTIMPL;
  259. end;
  260. function TBasicRichEditOleCallback.QueryInsertObject(const clsid: TCLSID; const stg: IStorage;
  261. cp: Integer): HResult;
  262. begin
  263. Result := S_OK;
  264. end;
  265. function TBasicRichEditOleCallback.DeleteObject(const oleobj: IOleObject): HResult;
  266. begin
  267. if Assigned(oleobj) then
  268. oleobj.Close(OLECLOSE_NOSAVE);
  269. Result := S_OK;
  270. end;
  271. function TBasicRichEditOleCallback.QueryAcceptData(const dataobj: IDataObject;
  272. var cfFormat: TClipFormat; reco: DWORD; fReally: BOOL;
  273. hMetaPict: HGLOBAL): HResult;
  274. begin
  275. Result := S_OK;
  276. end;
  277. function TBasicRichEditOleCallback.ContextSensitiveHelp(fEnterMode: BOOL): HResult;
  278. begin
  279. Result := S_OK;
  280. end;
  281. function TBasicRichEditOleCallback.GetClipboardData(const chrg: TCharRange; reco: DWORD;
  282. out dataobj: IDataObject): HResult;
  283. begin
  284. Result := E_NOTIMPL;
  285. end;
  286. function TBasicRichEditOleCallback.GetDragDropEffect(fDrag: BOOL; grfKeyState: DWORD;
  287. var dwEffect: DWORD): HResult;
  288. begin
  289. Result := E_NOTIMPL;
  290. end;
  291. function TBasicRichEditOleCallback.GetContextMenu(seltype: Word;
  292. const oleobj: IOleObject; const chrg: TCharRange; out Menu: HMENU): HResult;
  293. begin
  294. Result := E_NOTIMPL;
  295. end;
  296. { TRichEditViewer }
  297. class constructor TRichEditViewer.Create;
  298. begin
  299. TCustomStyleEngine.RegisterStyleHook(TRichEditViewer, TRichEditViewerStyleHook);
  300. end;
  301. constructor TRichEditViewer.Create(AOwner: TComponent);
  302. begin
  303. inherited;
  304. FUseRichEdit := True;
  305. FCallback := TBasicRichEditOleCallback.Create;
  306. end;
  307. class destructor TRichEditViewer.Destroy;
  308. begin
  309. TCustomStyleEngine.UnregisterStyleHook(TRichEditViewer, TRichEditViewerStyleHook);
  310. end;
  311. destructor TRichEditViewer.Destroy;
  312. begin
  313. inherited;
  314. { First do all other deinitialization, then decrement the DLL use count }
  315. if FRichEditLoaded then begin
  316. FRichEditLoaded := False;
  317. UnloadRichEdit;
  318. end;
  319. end;
  320. procedure TRichEditViewer.CreateParams(var Params: TCreateParams);
  321. { Based on code from TCustomRichEdit.CreateParams }
  322. begin
  323. if UseRichEdit and not FRichEditLoaded then begin
  324. { Increment the DLL use count when UseRichEdit is True, load the DLL }
  325. FRichEditLoaded := True;
  326. LoadRichEdit;
  327. end;
  328. inherited;
  329. if UseRichEdit then begin
  330. if RichEditVersion = 4 then
  331. CreateSubClass(Params, MSFTEDIT_CLASS)
  332. else
  333. CreateSubClass(Params, RICHEDIT_CLASSW);
  334. end else
  335. { Inherited handler creates a subclass of 'EDIT'.
  336. Must have a unique class name since it uses two different classes
  337. depending on the setting of the UseRichEdit property. }
  338. StrCat(Params.WinClassName, '/Text'); { don't localize! }
  339. end;
  340. procedure TRichEditViewer.CreateWnd;
  341. begin
  342. inherited;
  343. UpdateBackgroundColor;
  344. if FUseRichEdit then begin
  345. if RichEditVersion >= 2 then begin
  346. const Mask = ENM_LINK or SendMessage(Handle, EM_GETEVENTMASK, 0, 0);
  347. SendMessage(Handle, EM_SETEVENTMASK, 0, LPARAM(Mask));
  348. SendMessage(Handle, EM_AUTOURLDETECT, WPARAM(True), 0);
  349. end;
  350. SendMessage(Handle, EM_SETOLECALLBACK, 0, LPARAM(FCallback));
  351. end;
  352. end;
  353. procedure TRichEditViewer.UpdateBackgroundColor;
  354. begin
  355. if FUseRichEdit and HandleAllocated then
  356. SendMessage(Handle, EM_SETBKGNDCOLOR, 0, ColorToRGB(Color));
  357. end;
  358. procedure TRichEditViewer.SetUseRichEdit(Value: Boolean);
  359. begin
  360. if FUseRichEdit <> Value then begin
  361. FUseRichEdit := Value;
  362. RecreateWnd;
  363. if not Value and FRichEditLoaded then begin
  364. { Decrement the DLL use count when UseRichEdit is set to False }
  365. FRichEditLoaded := False;
  366. UnloadRichEdit;
  367. end;
  368. end;
  369. end;
  370. type
  371. PStreamLoadData = ^TStreamLoadData;
  372. TStreamLoadData = record
  373. Buf: PByte;
  374. BytesLeft: Integer;
  375. end;
  376. function StreamLoad(dwCookie: DWORD_PTR; pbBuff: PByte;
  377. cb: Integer; var pcb: Integer): Integer; stdcall;
  378. begin
  379. Result := 0;
  380. with PStreamLoadData(dwCookie)^ do begin
  381. if cb > BytesLeft then
  382. cb := BytesLeft;
  383. Move(Buf^, pbBuff^, cb);
  384. Inc(Buf, cb);
  385. Dec(BytesLeft, cb);
  386. pcb := cb;
  387. end;
  388. end;
  389. function TRichEditViewer.SetRTFText(const Value: AnsiString): Integer;
  390. function StreamIn(AFormat: WPARAM): Integer;
  391. var
  392. Data: TStreamLoadData;
  393. EditStream: TEditStream;
  394. begin
  395. Data.Buf := PByte(@Value[1]);
  396. Data.BytesLeft := Length(Value);
  397. { Check for UTF-16 BOM }
  398. if (AFormat and SF_TEXT <> 0) and (Data.BytesLeft >= 2) and
  399. (PWord(Pointer(Value))^ = $FEFF) then begin
  400. AFormat := AFormat or SF_UNICODE;
  401. Inc(Data.Buf, 2);
  402. Dec(Data.BytesLeft, 2);
  403. end;
  404. EditStream.dwCookie := DWORD_PTR(@Data);
  405. EditStream.dwError := 0;
  406. EditStream.pfnCallback := StreamLoad;
  407. SendMessage(Handle, EM_STREAMIN, AFormat, LPARAM(@EditStream));
  408. Result := EditStream.dwError;
  409. end;
  410. begin
  411. if not FUseRichEdit then begin
  412. Text := String(Value);
  413. Result := 0;
  414. end
  415. else begin
  416. SendMessage(Handle, EM_EXLIMITTEXT, 0, LParam($7FFFFFFE));
  417. Result := StreamIn(SF_RTF);
  418. if Result <> 0 then
  419. Result := StreamIn(SF_TEXT);
  420. var LStyle := StyleServices(Self);
  421. if not LStyle.Enabled or LStyle.IsSystemStyle then
  422. LStyle := nil;
  423. if (LStyle <> nil) and (seFont in StyleElements) and (seClient in StyleElements) then begin
  424. const StyleTextColor = ColorToRGB(LStyle.GetStyleFontColor(sfEditBoxTextNormal));
  425. if StyleTextColor <> ColorToRGB(clWindowText) then
  426. RecolorAutoForegroundText(StyleTextColor); { Must be done even if SF_TEXT was used above }
  427. end;
  428. end;
  429. end;
  430. procedure TRichEditViewer.RecolorAutoForegroundText(const NewTextColor: Integer);
  431. const
  432. IID_ITextDocument: TGUID = '{8CC497C0-A1DF-11CE-8098-00AA0047BE5D}';
  433. { See https://learn.microsoft.com/en-us/windows/win32/api/tom/ne-tom-tomconstants }
  434. tomAutoColor = -9999997;
  435. tomCharFormat = 13;
  436. begin
  437. if not FUseRichEdit or not HandleAllocated then
  438. Exit;
  439. var RichEditOle: IRichEditOle;
  440. var TextDocument: ITextDocument;
  441. var Range: ITextRange;
  442. var StoryLength: Integer;
  443. if (SendMessage(Handle, EM_GETOLEINTERFACE, 0, LPARAM(@RichEditOle)) = 0) or
  444. Failed(RichEditOle.QueryInterface(IID_ITextDocument, TextDocument)) or
  445. Failed(TextDocument.Range(0, 0, Range)) or
  446. Failed(Range.GetStoryLength(StoryLength)) or
  447. (StoryLength < 2) then
  448. Exit;
  449. { See https://learn.microsoft.com/en-us/windows/win32/api/tom/nn-tom-itextrange:
  450. All stories contain an undeletable final CR (0xD) character at the end }
  451. const TextLength = StoryLength-1;
  452. SendMessage(Handle, WM_SETREDRAW, 0, 0);
  453. const SaveReadOnly = ReadOnly;
  454. try
  455. ReadOnly := False;
  456. while True do begin
  457. { Move the end of the range (which initializes at 0,0) to the end of constant formatting }
  458. var Delta: Integer;
  459. if Failed(Range.MoveEnd(tomCharFormat, 1, Delta)) or (Delta = 0) then
  460. Break;
  461. { Recolor the range if the foreground color is automatic }
  462. var Font: ITextFont;
  463. var TextColor: Integer;
  464. if Succeeded(Range.GetFont(Font)) and
  465. Succeeded(Font.GetForeColor(TextColor)) and
  466. (TextColor = tomAutoColor) then
  467. Font.SetForeColor(NewTextColor); { Ignore failure }
  468. { Move the start of the range to the end of it, unless it ends at the end of the text }
  469. var EndPos: Integer;
  470. if Failed(Range.GetEnd(EndPos)) or
  471. (EndPos >= TextLength) or
  472. Failed(Range.SetStart(EndPos)) then
  473. Break;
  474. end;
  475. finally
  476. ReadOnly := SaveReadOnly;
  477. SendMessage(Handle, WM_SETREDRAW, 1, 0);
  478. Invalidate;
  479. end;
  480. end;
  481. procedure TRichEditViewer.SetRTFTextProp(const Value: AnsiString);
  482. begin
  483. SetRTFText(Value);
  484. end;
  485. procedure TRichEditViewer.CMColorChanged(var Message: TMessage);
  486. begin
  487. inherited;
  488. UpdateBackgroundColor;
  489. end;
  490. procedure TRichEditViewer.CMSysColorChange(var Message: TMessage);
  491. begin
  492. inherited;
  493. UpdateBackgroundColor;
  494. end;
  495. procedure TRichEditViewer.CNNotify(var Message: TWMNotify);
  496. var
  497. EnLink: PEnLink;
  498. CharRange: TCharRange;
  499. TextRange: TTextRange;
  500. Len: Integer;
  501. URL: String;
  502. begin
  503. case Message.NMHdr^.code of
  504. EN_LINK: begin
  505. EnLink := PEnLink(Message.NMHdr);
  506. if EnLink.msg = WM_LBUTTONUP then begin
  507. CharRange := EnLink.chrg;
  508. if (CharRange.cpMin >= 0) and (CharRange.cpMax > CharRange.cpMin) then begin
  509. Len := CharRange.cpMax - CharRange.cpMin;
  510. Inc(Len); { for null terminator }
  511. if Len > 1 then begin
  512. SetLength(URL, Len);
  513. TextRange.chrg := CharRange;
  514. TextRange.lpstrText := PChar(URL);
  515. SetLength(URL, SendMessage(Handle, EM_GETTEXTRANGE, 0, LParam(@TextRange)));
  516. if URL <> '' then begin
  517. if Assigned(FCustomShellExecute) then
  518. FCustomShellExecute(Handle, 'open', PChar(URL), nil, nil, SW_SHOWNORMAL)
  519. else
  520. ShellExecute(Handle, 'open', PChar(URL), nil, nil, SW_SHOWNORMAL);
  521. end;
  522. end;
  523. end;
  524. end;
  525. end;
  526. end;
  527. end;
  528. {$IFDEF VCLSTYLES}
  529. { TRichEditViewerStyleHook- same as Vcl.ComCtrls' TRichEditStyleHook except
  530. that it is reduced to EM_SETBKGNDCOLOR handling only }
  531. procedure TRichEditViewerStyleHook.EMSetBkgndColor(var Message: TMessage);
  532. begin
  533. if seClient in Control.StyleElements then begin
  534. Message.LParam := ColorToRGB(StyleServices.GetStyleColor(scEdit));
  535. Handled := False;
  536. end;
  537. end;
  538. {$ENDIF}
  539. procedure Register;
  540. begin
  541. RegisterComponents('JR', [TRichEditViewer]);
  542. end;
  543. end.