RichEditViewer.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  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. StdCtrls, RichEdit, ActiveX;
  12. type
  13. IRichEditOleCallback = interface(IUnknown)
  14. ['{00020d03-0000-0000-c000-000000000046}']
  15. function GetNewStorage(out stg: IStorage): HResult; stdcall;
  16. function GetInPlaceContext(out Frame: IOleInPlaceFrame;
  17. out Doc: IOleInPlaceUIWindow;
  18. lpFrameInfo: POleInPlaceFrameInfo): HResult; stdcall;
  19. function ShowContainerUI(fShow: BOOL): HResult; stdcall;
  20. function QueryInsertObject(const clsid: TCLSID; const stg: IStorage;
  21. cp: Longint): HResult; stdcall;
  22. function DeleteObject(const oleobj: IOleObject): HResult; stdcall;
  23. function QueryAcceptData(const dataobj: IDataObject;
  24. var cfFormat: TClipFormat; reco: DWORD; fReally: BOOL;
  25. hMetaPict: HGLOBAL): HResult; stdcall;
  26. function ContextSensitiveHelp(fEnterMode: BOOL): HResult; stdcall;
  27. function GetClipboardData(const chrg: TCharRange; reco: DWORD;
  28. out dataobj: IDataObject): HResult; stdcall;
  29. function GetDragDropEffect(fDrag: BOOL; grfKeyState: DWORD;
  30. var dwEffect: DWORD): HResult; stdcall;
  31. function GetContextMenu(seltype: Word; const oleobj: IOleObject;
  32. const chrg: TCharRange; out menu: HMENU): HResult; stdcall;
  33. end;
  34. TRichEditViewerCustomShellExecute = procedure(hWnd: HWND; Operation, FileName, Parameters, Directory: LPWSTR; ShowCmd: Integer); stdcall;
  35. TRichEditViewer = class(TMemo)
  36. private
  37. class var
  38. FCustomShellExecute: TRichEditViewerCustomShellExecute;
  39. var
  40. FUseRichEdit: Boolean;
  41. FRichEditLoaded: Boolean;
  42. FCallback: IRichEditOleCallback;
  43. class constructor Create;
  44. class destructor Destroy;
  45. procedure SetRTFTextProp(const Value: AnsiString);
  46. procedure SetUseRichEdit(Value: Boolean);
  47. procedure UpdateBackgroundColor;
  48. procedure CMColorChanged(var Message: TMessage); message CM_COLORCHANGED;
  49. procedure CMSysColorChange(var Message: TMessage); message CM_SYSCOLORCHANGE;
  50. procedure CNNotify(var Message: TWMNotify); message CN_NOTIFY;
  51. protected
  52. procedure CreateParams(var Params: TCreateParams); override;
  53. procedure CreateWnd; override;
  54. public
  55. constructor Create(AOwner: TComponent); override;
  56. destructor Destroy; override;
  57. function SetRTFText(const Value: AnsiString): Integer;
  58. property RTFText: AnsiString write SetRTFTextProp;
  59. class property CustomShellExecute: TRichEditViewerCustomShellExecute read FCustomShellExecute write FCustomShellExecute;
  60. published
  61. property UseRichEdit: Boolean read FUseRichEdit write SetUseRichEdit default True;
  62. end;
  63. procedure Register;
  64. implementation
  65. uses
  66. ShellApi, PathFunc, ComObj, ComCtrls, Themes;
  67. const
  68. RICHEDIT_CLASSW = 'RichEdit20W';
  69. MSFTEDIT_CLASS = 'RICHEDIT50W';
  70. EM_AUTOURLDETECT = WM_USER + 91;
  71. ENM_LINK = $04000000;
  72. EN_LINK = $070b;
  73. type
  74. { Basic implementation of IRichEditOleCallback to enable the viewing of images and other objects. }
  75. TBasicRichEditOleCallback = class(TInterfacedObject, IRichEditOleCallback)
  76. public
  77. function GetNewStorage(out stg: IStorage): HResult; stdcall;
  78. function GetInPlaceContext(out Frame: IOleInPlaceFrame;
  79. out Doc: IOleInPlaceUIWindow;
  80. lpFrameInfo: POleInPlaceFrameInfo): HResult; stdcall;
  81. function ShowContainerUI(fShow: BOOL): HResult; stdcall;
  82. function QueryInsertObject(const clsid: TCLSID; const stg: IStorage;
  83. cp: Longint): HResult; stdcall;
  84. function DeleteObject(const oleobj: IOleObject): HResult; stdcall;
  85. function QueryAcceptData(const dataobj: IDataObject;
  86. var cfFormat: TClipFormat; reco: DWORD; fReally: BOOL;
  87. hMetaPict: HGLOBAL): HResult; stdcall;
  88. function ContextSensitiveHelp(fEnterMode: BOOL): HResult; stdcall;
  89. function GetClipboardData(const chrg: TCharRange; reco: DWORD;
  90. out dataobj: IDataObject): HResult; stdcall;
  91. function GetDragDropEffect(fDrag: BOOL; grfKeyState: DWORD;
  92. var dwEffect: DWORD): HResult; stdcall;
  93. function GetContextMenu(seltype: Word; const oleobj: IOleObject;
  94. const chrg: TCharRange; out menu: HMENU): HResult; stdcall;
  95. end;
  96. PEnLink = ^TEnLink;
  97. TENLink = record
  98. nmhdr: TNMHdr;
  99. msg: UINT;
  100. wParam: WPARAM;
  101. lParam: LPARAM;
  102. chrg: TCharRange;
  103. end;
  104. TTextRange = record
  105. chrg: TCharRange;
  106. lpstrText: PWideChar;
  107. end;
  108. var
  109. RichEditModule: HMODULE;
  110. RichEditUseCount: Integer = 0;
  111. RichEditVersion: Integer;
  112. procedure LoadRichEdit;
  113. function GetSystemDir: String;
  114. var
  115. Buf: array[0..MAX_PATH-1] of Char;
  116. begin
  117. GetSystemDirectory(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
  118. Result := StrPas(Buf);
  119. end;
  120. begin
  121. if RichEditUseCount = 0 then begin
  122. RichEditVersion := 4;
  123. RichEditModule := LoadLibrary(PChar(AddBackslash(GetSystemDir) + 'MSFTEDIT.DLL'));
  124. if RichEditModule = 0 then begin
  125. RichEditVersion := 2;
  126. RichEditModule := LoadLibrary(PChar(AddBackslash(GetSystemDir) + 'RICHED20.DLL'));
  127. end;
  128. end;
  129. Inc(RichEditUseCount);
  130. end;
  131. procedure UnloadRichEdit;
  132. begin
  133. if RichEditUseCount > 0 then begin
  134. Dec(RichEditUseCount);
  135. if RichEditUseCount = 0 then begin
  136. FreeLibrary(RichEditModule);
  137. RichEditModule := 0;
  138. end;
  139. end;
  140. end;
  141. { TBasicRichEditOleCallback }
  142. function TBasicRichEditOleCallback.GetNewStorage(out stg: IStorage): HResult; stdcall;
  143. var
  144. LockBytes: ILockBytes;
  145. begin
  146. try
  147. OleCheck(CreateILockBytesOnHGlobal(0, True, LockBytes));
  148. OleCheck(StgCreateDocfileOnILockBytes(LockBytes, STGM_READWRITE
  149. or STGM_SHARE_EXCLUSIVE or STGM_CREATE, 0, stg));
  150. Result := S_OK;
  151. except
  152. Result := E_OUTOFMEMORY;
  153. end;
  154. end;
  155. function TBasicRichEditOleCallback.GetInPlaceContext(out Frame: IOleInPlaceFrame;
  156. out Doc: IOleInPlaceUIWindow; lpFrameInfo: POleInPlaceFrameInfo): HResult;
  157. begin
  158. Result := E_NOTIMPL;
  159. end;
  160. function TBasicRichEditOleCallback.ShowContainerUI(fShow: BOOL): HResult;
  161. begin
  162. Result := E_NOTIMPL;
  163. end;
  164. function TBasicRichEditOleCallback.QueryInsertObject(const clsid: TCLSID; const stg: IStorage;
  165. cp: Longint): HResult;
  166. begin
  167. Result := S_OK;
  168. end;
  169. function TBasicRichEditOleCallback.DeleteObject(const oleobj: IOleObject): HResult;
  170. begin
  171. if Assigned(oleobj) then
  172. oleobj.Close(OLECLOSE_NOSAVE);
  173. Result := S_OK;
  174. end;
  175. function TBasicRichEditOleCallback.QueryAcceptData(const dataobj: IDataObject;
  176. var cfFormat: TClipFormat; reco: DWORD; fReally: BOOL;
  177. hMetaPict: HGLOBAL): HResult;
  178. begin
  179. Result := S_OK;
  180. end;
  181. function TBasicRichEditOleCallback.ContextSensitiveHelp(fEnterMode: BOOL): HResult;
  182. begin
  183. Result := S_OK;
  184. end;
  185. function TBasicRichEditOleCallback.GetClipboardData(const chrg: TCharRange; reco: DWORD;
  186. out dataobj: IDataObject): HResult;
  187. begin
  188. Result := E_NOTIMPL;
  189. end;
  190. function TBasicRichEditOleCallback.GetDragDropEffect(fDrag: BOOL; grfKeyState: DWORD;
  191. var dwEffect: DWORD): HResult;
  192. begin
  193. Result := E_NOTIMPL;
  194. end;
  195. function TBasicRichEditOleCallback.GetContextMenu(seltype: Word;
  196. const oleobj: IOleObject; const chrg: TCharRange; out Menu: HMENU): HResult;
  197. begin
  198. Result := E_NOTIMPL;
  199. end;
  200. { TRichEditViewer }
  201. class constructor TRichEditViewer.Create;
  202. begin
  203. TCustomStyleEngine.RegisterStyleHook(TRichEditViewer, TRichEditStyleHook);
  204. end;
  205. constructor TRichEditViewer.Create(AOwner: TComponent);
  206. begin
  207. inherited;
  208. FUseRichEdit := True;
  209. FCallback := TBasicRichEditOleCallback.Create;
  210. end;
  211. class destructor TRichEditViewer.Destroy;
  212. begin
  213. TCustomStyleEngine.UnregisterStyleHook(TRichEditViewer, TRichEditStyleHook);
  214. end;
  215. destructor TRichEditViewer.Destroy;
  216. begin
  217. inherited;
  218. { First do all other deinitialization, then decrement the DLL use count }
  219. if FRichEditLoaded then begin
  220. FRichEditLoaded := False;
  221. UnloadRichEdit;
  222. end;
  223. end;
  224. procedure TRichEditViewer.CreateParams(var Params: TCreateParams);
  225. { Based on code from TCustomRichEdit.CreateParams }
  226. begin
  227. if UseRichEdit and not FRichEditLoaded then begin
  228. { Increment the DLL use count when UseRichEdit is True, load the DLL }
  229. FRichEditLoaded := True;
  230. LoadRichEdit;
  231. end;
  232. inherited;
  233. if UseRichEdit then begin
  234. if RichEditVersion = 4 then
  235. CreateSubClass(Params, MSFTEDIT_CLASS)
  236. else
  237. CreateSubClass(Params, RICHEDIT_CLASSW);
  238. end else
  239. { Inherited handler creates a subclass of 'EDIT'.
  240. Must have a unique class name since it uses two different classes
  241. depending on the setting of the UseRichEdit property. }
  242. StrCat(Params.WinClassName, '/Text'); { don't localize! }
  243. end;
  244. procedure TRichEditViewer.CreateWnd;
  245. var
  246. Mask: LongInt;
  247. begin
  248. inherited;
  249. UpdateBackgroundColor;
  250. if FUseRichEdit then begin
  251. if RichEditVersion >= 2 then begin
  252. Mask := ENM_LINK or SendMessage(Handle, EM_GETEVENTMASK, 0, 0);
  253. SendMessage(Handle, EM_SETEVENTMASK, 0, LPARAM(Mask));
  254. SendMessage(Handle, EM_AUTOURLDETECT, WPARAM(True), 0);
  255. end;
  256. SendMessage(Handle, EM_SETOLECALLBACK, 0, LPARAM(FCallback));
  257. end;
  258. end;
  259. procedure TRichEditViewer.UpdateBackgroundColor;
  260. begin
  261. if FUseRichEdit and HandleAllocated then
  262. SendMessage(Handle, EM_SETBKGNDCOLOR, 0, ColorToRGB(Color));
  263. end;
  264. procedure TRichEditViewer.SetUseRichEdit(Value: Boolean);
  265. begin
  266. if FUseRichEdit <> Value then begin
  267. FUseRichEdit := Value;
  268. RecreateWnd;
  269. if not Value and FRichEditLoaded then begin
  270. { Decrement the DLL use count when UseRichEdit is set to False }
  271. FRichEditLoaded := False;
  272. UnloadRichEdit;
  273. end;
  274. end;
  275. end;
  276. type
  277. PStreamLoadData = ^TStreamLoadData;
  278. TStreamLoadData = record
  279. Buf: PByte;
  280. BytesLeft: Integer;
  281. end;
  282. function StreamLoad(dwCookie: Longint; pbBuff: PByte;
  283. cb: Longint; var pcb: Longint): Longint; stdcall;
  284. begin
  285. Result := 0;
  286. with PStreamLoadData(dwCookie)^ do begin
  287. if cb > BytesLeft then
  288. cb := BytesLeft;
  289. Move(Buf^, pbBuff^, cb);
  290. Inc(Buf, cb);
  291. Dec(BytesLeft, cb);
  292. pcb := cb;
  293. end;
  294. end;
  295. function TRichEditViewer.SetRTFText(const Value: AnsiString): Integer;
  296. function StreamIn(AFormat: WPARAM): Integer;
  297. var
  298. Data: TStreamLoadData;
  299. EditStream: TEditStream;
  300. begin
  301. Data.Buf := @Value[1];
  302. Data.BytesLeft := Length(Value);
  303. { Check for UTF-16 BOM }
  304. if (AFormat and SF_TEXT <> 0) and (Data.BytesLeft >= 2) and
  305. (PWord(Pointer(Value))^ = $FEFF) then begin
  306. AFormat := AFormat or SF_UNICODE;
  307. Inc(Data.Buf, 2);
  308. Dec(Data.BytesLeft, 2);
  309. end;
  310. EditStream.dwCookie := Longint(@Data);
  311. EditStream.dwError := 0;
  312. EditStream.pfnCallback := @StreamLoad;
  313. SendMessage(Handle, EM_STREAMIN, AFormat, LPARAM(@EditStream));
  314. Result := EditStream.dwError;
  315. end;
  316. begin
  317. if not FUseRichEdit then begin
  318. Text := String(Value);
  319. Result := 0;
  320. end
  321. else begin
  322. SendMessage(Handle, EM_EXLIMITTEXT, 0, LParam($7FFFFFFE));
  323. Result := StreamIn(SF_RTF);
  324. if Result <> 0 then
  325. Result := StreamIn(SF_TEXT);
  326. end;
  327. end;
  328. procedure TRichEditViewer.SetRTFTextProp(const Value: AnsiString);
  329. begin
  330. SetRTFText(Value);
  331. end;
  332. procedure TRichEditViewer.CMColorChanged(var Message: TMessage);
  333. begin
  334. inherited;
  335. UpdateBackgroundColor;
  336. end;
  337. procedure TRichEditViewer.CMSysColorChange(var Message: TMessage);
  338. begin
  339. inherited;
  340. UpdateBackgroundColor;
  341. end;
  342. procedure TRichEditViewer.CNNotify(var Message: TWMNotify);
  343. var
  344. EnLink: PEnLink;
  345. CharRange: TCharRange;
  346. TextRange: TTextRange;
  347. Len: Integer;
  348. URL: String;
  349. begin
  350. case Message.NMHdr^.code of
  351. EN_LINK: begin
  352. EnLink := PEnLink(Message.NMHdr);
  353. if EnLink.msg = WM_LBUTTONUP then begin
  354. CharRange := EnLink.chrg;
  355. if (CharRange.cpMin >= 0) and (CharRange.cpMax > CharRange.cpMin) then begin
  356. Len := CharRange.cpMax - CharRange.cpMin;
  357. Inc(Len); { for null terminator }
  358. if Len > 1 then begin
  359. SetLength(URL, Len);
  360. TextRange.chrg := CharRange;
  361. TextRange.lpstrText := PChar(URL);
  362. SetLength(URL, SendMessage(Handle, EM_GETTEXTRANGE, 0, LParam(@TextRange)));
  363. if URL <> '' then begin
  364. if Assigned(FCustomShellExecute) then
  365. FCustomShellExecute(Handle, 'open', PChar(URL), nil, nil, SW_SHOWNORMAL)
  366. else
  367. ShellExecute(Handle, 'open', PChar(URL), nil, nil, SW_SHOWNORMAL);
  368. end;
  369. end;
  370. end;
  371. end;
  372. end;
  373. end;
  374. end;
  375. procedure Register;
  376. begin
  377. RegisterComponents('JR', [TRichEditViewer]);
  378. end;
  379. end.