RichEditViewer.pas 13 KB

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