
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@5115 8e941d3f-bd1b-0410-a28a-d453659cc2b4
1011 lines
31 KiB
ObjectPascal
1011 lines
31 KiB
ObjectPascal
{
|
|
win32richmemoproc.pas
|
|
|
|
Author: Dmitry 'skalogryz' Boyarintsev
|
|
|
|
*****************************************************************************
|
|
* *
|
|
* This file is part of the Lazarus Component Library (LCL) *
|
|
* *
|
|
* See the file COPYING.modifiedLGPL.txt, included in this distribution, *
|
|
* for details about the copyright. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
* *
|
|
*****************************************************************************
|
|
}
|
|
|
|
unit Win32RichMemoProc;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
// windows units
|
|
Windows,richedit,
|
|
// RTL units
|
|
Classes, SysUtils,
|
|
// LCL units
|
|
Graphics,
|
|
// RichMemoUnits
|
|
RichMemo, WSRichMemo, RichMemoUtils,
|
|
// Win32 widgetset units
|
|
win32proc, ActiveX, ComObj;
|
|
|
|
const
|
|
IID_IRichEditOle: TGUID = '{00020D00-0000-0000-C000-000000000046}';
|
|
IID_IRichEditOleCallback: TGUID = '{00020D03-0000-0000-C000-000000000046}';
|
|
CLSID_NULL: TGUID = '{00000000-0000-0000-0000-000000000000}';
|
|
|
|
const
|
|
OLERENDER_NONE = 0;
|
|
OLERENDER_DRAW = 1;
|
|
OLERENDER_FORMAT = 2;
|
|
OLERENDER_ASIS = 3;
|
|
|
|
const
|
|
REO_GETOBJ_NO_INTERFACES = 0;
|
|
REO_GETOBJ_POLEOBJ = 1;
|
|
REO_GETOBJ_PSTG = 2;
|
|
REO_GETOBJ_POLESITE = 4;
|
|
REO_GETOBJ_ALL_INTERFACES = 7;
|
|
REO_CP_SELECTION = -1;
|
|
REO_IOB_SELECTION = -1;
|
|
REO_IOB_USE_CP = -2;
|
|
|
|
REO_NULL = $00000000;
|
|
|
|
REO_RESIZABLE = $00000001;
|
|
REO_BELOWBASELINE = $00000002;
|
|
REO_INVERTEDSELECT = $00000004;
|
|
REO_DYNAMICSIZE = $00000008;
|
|
REO_BLANK = $00000010;
|
|
REO_DONTNEEDPALETTE = $00000020;
|
|
|
|
// Rich edit 3.0
|
|
REO_OWNERDRAWSELECT = $00000040;
|
|
REO_CANROTATE = $00000080;
|
|
REO_ALIGNTORIGHT = $00000100;
|
|
REO_WRAPTEXTAROUND = $00000200;
|
|
REO_USEASBACKGROUND = $00000400;
|
|
|
|
REO_READWRITEMASK = $000007FF;
|
|
|
|
REO_LINKAVAILABLE = $00800000;
|
|
REO_GETMETAFILE = $00400000;
|
|
REO_HILITED = $01000000;
|
|
REO_INPLACEACTIVE = $02000000;
|
|
REO_OPEN = $04000000;
|
|
REO_SELECTED = $08000000;
|
|
REO_STATIC = $40000000;
|
|
REO_LINK = $80000000;
|
|
|
|
RECO_PASTE = 0;
|
|
RECO_DROP = 1;
|
|
RECO_COPY = 2;
|
|
RECO_CUT = 3;
|
|
RECO_DRAG = 4;
|
|
|
|
|
|
|
|
type
|
|
TREOBJECT = packed record
|
|
cbStruct : DWORD;
|
|
cp : LONG;
|
|
clsid : CLSID;
|
|
poleobj : IOLEOBJECT;
|
|
pstg : ISTORAGE;
|
|
polesite : IOLECLIENTSITE;
|
|
sizel : SIZEL;
|
|
dvaspect : DWORD;
|
|
dwFlags : DWORD;
|
|
dwUser : DWORD;
|
|
end;
|
|
|
|
type
|
|
IRichEditOle = interface(IUnknown)
|
|
['{00020D00-0000-0000-C000-000000000046}']
|
|
// *** IRichEditOle methods ***
|
|
function GetClientSite(out clientSite: IOleClientSite): HRESULT; stdcall;
|
|
function GetObjectCount: LongInt; stdcall;
|
|
function GetLinkCount: LongInt; stdcall;
|
|
function GetObject(iob: LongInt; out ReObject: TReObject;
|
|
dwFlags: DWORD): HRESULT; stdcall;
|
|
function InsertObject(var ReObject: TReObject): HRESULT; stdcall;
|
|
function ConvertObject(iob: LongInt; const clsidNew: TCLSID;
|
|
lpStrUserTypeNew: LPCSTR): HRESULT; stdcall;
|
|
function ActivateAs(const clsid, clsidAs: TCLSID): HRESULT; stdcall;
|
|
function SetHostNames(lpstrContainerApp: LPCSTR;
|
|
lpstrContainerObj: LPCSTR): HRESULT; stdcall;
|
|
function SetLinkAvailable(iob: LongInt; fAvailable: BOOL): HRESULT; stdcall;
|
|
function SetDvaspect(iob: LongInt; dvaspect: DWORD): HRESULT; stdcall;
|
|
function HandsOffStorage(iob: LongInt): HRESULT; stdcall;
|
|
function SaveCompleted(iob: LongInt; const stg: IStorage): HRESULT; stdcall;
|
|
function InPlaceDeactivate: HRESULT; stdcall;
|
|
function ContextSensitiveHelp(fEnterMode: BOOL): HRESULT; stdcall;
|
|
function GetClipboardData(const chrg: TCharRange; reco: DWORD;
|
|
out dataobj: IDataObject): HRESULT; stdcall;
|
|
function ImportDataObject(const dataobj: IDataObject; cf: TClipFormat;
|
|
hMetaPict: HGLOBAL): HRESULT; stdcall;
|
|
end;
|
|
|
|
IRichEditOleCallback = interface(IUnknown)
|
|
['{00020D03-0000-0000-C000-000000000046}']
|
|
// *** IRichEditOleCallback methods ***
|
|
function GetNewStorage(out stg: IStorage): HRESULT; stdcall;
|
|
function GetInPlaceContext(out Frame: IOleInPlaceFrame;
|
|
out Doc: IOleInPlaceUIWindow;
|
|
lpFrameInfo: POleInPlaceFrameInfo): HRESULT; stdcall;
|
|
function ShowContainerUI(fShow: BOOL): HRESULT; stdcall;
|
|
function QueryInsertObject(const clsid: TCLSID; const stg: IStorage;
|
|
cp: LongInt): HRESULT; stdcall;
|
|
function DeleteObject(const oleobj: IOleObject): HRESULT; stdcall;
|
|
function QueryAcceptData(const dataobj: IDataObject;
|
|
var cfFormat: TClipFormat; reco: DWORD; fReally: BOOL;
|
|
hMetaPict: HGLOBAL): HRESULT; stdcall;
|
|
function ContextSensitiveHelp(fEnterMode: BOOL): HRESULT; stdcall;
|
|
function GetClipboardData(const chrg: TCharRange; reco: DWORD;
|
|
out dataobj: IDataObject): HRESULT; stdcall;
|
|
function GetDragDropEffect(fDrag: BOOL; grfKeyState: DWORD;
|
|
var dwEffect: DWORD): HRESULT; stdcall;
|
|
function GetContextMenu(seltype: Word; oleobj: IOleObject;
|
|
const chrg: TCharRange; var menu: HMENU): HRESULT; stdcall;
|
|
end;
|
|
|
|
type
|
|
{ TRichEditManager }
|
|
|
|
TRichEditManager = class(TObject)
|
|
public
|
|
class function SetEventMask(RichEditWnd: Handle; eventmask: integer): Integer;
|
|
|
|
class function GetTextLength(RichEditWnd: Handle): Integer;
|
|
class function SetDefaultTextStyle(RichEditWnd: Handle; Params: TIntFontParams): Boolean; virtual;
|
|
class function SetSelectedTextStyle(RichEditWnd: Handle; Params: TIntFontParams;
|
|
useMask: Boolean = false; AModifyMask: TTextModifyMask = []): Boolean; virtual;
|
|
class function GetSelectedTextStyle(RichEditWnd: Handle; var Params: TIntFontParams): Boolean; virtual;
|
|
class procedure SetTextUIStyle(RichEditWnd: Handle; const ui: TTextUIParam); virtual;
|
|
class function GetTextUIStyle(RichEditWnd: Handle; var ui: TTextUIParam): Boolean; virtual;
|
|
|
|
class function GetStyleRange(RichEditWnd: Handle; TextStart: Integer; var RangeStart, RangeLen: Integer): Boolean; virtual;
|
|
class procedure GetSelection(RichEditWnd: Handle; var TextStart, TextLen: Integer); virtual;
|
|
class procedure SetSelection(RichEditWnd: Handle; TextStart, TextLen: Integer); virtual;
|
|
class procedure SetHideSelection(RichEditWnd: Handle; AValue: Boolean); virtual;
|
|
class function LoadRichText(RichEditWnd: Handle; ASrc: TStream): Boolean; virtual;
|
|
class function SaveRichText(RichEditWnd: Handle; ADst: TStream): Boolean; virtual;
|
|
|
|
class procedure SetText(RichEditWnd: Handle; const Text: WideString; TextStart, ReplaceLength: Integer); virtual;
|
|
class function GetTextW(RichEditWnd: Handle; inSelection: Boolean): WideString; virtual;
|
|
class function GetTextA(RichEditWnd: Handle; inSelection: Boolean): AnsiString; virtual;
|
|
class function GetTextUtf8(RichEditWnd: Handle; inSelection: Boolean): string;
|
|
|
|
class procedure GetPara2(RichEditWnd: Handle; TextStart: Integer; var para: PARAFORMAT2); virtual;
|
|
class procedure SetPara2(RichEditWnd: Handle; TextStart, TextLen: Integer; const para: PARAFORMAT2); virtual;
|
|
// the ugly Find() overload, might go away eventually
|
|
class function Find(RichEditWnd: THandle; const ANiddle: WideString; const ASearch: TIntSearchOpt; var TextLen: Integer): Integer; virtual; overload;
|
|
class function Find(RichEditWnd: THandle; const ANiddle: WideString; const ASearch: TIntSearchOpt): Integer; overload;
|
|
class procedure GetParaRange(RichEditWnd: Handle; TextStart: integer; var para: TParaRange); virtual;
|
|
end;
|
|
TRichManagerClass = class of TRichEditManager;
|
|
|
|
var
|
|
RichEditManager : TRichManagerClass = nil;
|
|
|
|
function InitRichEdit: Boolean;
|
|
function GetRichEditClass: AnsiString;
|
|
procedure CopyStringToCharArray(const s: String; var Chrs: array of Char; ChrsSize: integer);
|
|
function FontStylesToEffects(Styles: TFontStyles): LongWord;
|
|
function EffectsToFontStyles(Effects: LongWord): TFontStyles;
|
|
|
|
const
|
|
GT_SELECTION = 2;
|
|
CP_UNICODE = 1200;
|
|
HardBreak = #13;
|
|
|
|
CFE_PROTECTED = $00000010;
|
|
CFE_LINK = $00000020;
|
|
CFM_BACKCOLOR = $04000000;
|
|
CFE_AUTOBACKCOLOR = CFM_BACKCOLOR;
|
|
|
|
ST_DEFAULT = $00000000;
|
|
ST_KEEPUNDO = $00000001;
|
|
ST_SELECTION = $00000002;
|
|
ST_NEWCHARS = $00000004;
|
|
ST_UNICODE = $00000008;
|
|
|
|
const
|
|
PFNS_PAREN = $0000;
|
|
PFNS_PARENS = $0100;
|
|
PFNS_PERIOD = $0200;
|
|
PFNS_PLAIN = $0300;
|
|
PFNS_NONUMBER = $0400;
|
|
PFNS_NEWNUMBER = $8000;
|
|
PFNS_SOMESEPCHAR = PFNS_PARENS or PFNS_PERIOD or PFNS_PLAIN;
|
|
|
|
const
|
|
// this is the list of CHARFORMAT attributes that RichMemo supports
|
|
CFM_RICHMEMO_ATTRS = CFM_COLOR or CFM_FACE or CFM_SIZE or CFM_EFFECTS
|
|
or CFM_SUBSCRIPT or CFM_SUBSCRIPT or CFM_BACKCOLOR;
|
|
|
|
type
|
|
TSetTextEx = packed record
|
|
flags : DWORD;
|
|
codepage : UINT;
|
|
end;
|
|
|
|
implementation
|
|
|
|
const
|
|
GlobalRichClass : AnsiString = '';
|
|
UnicodeEnabledOS : Boolean = true; // todo: implement it to work with Windows 9x, if necessary
|
|
|
|
const
|
|
TwipsInFontSize = 20; // see MSDN for CHARFORMAT Structure CFM_SIZE
|
|
|
|
function GetRichEditClass: AnsiString;
|
|
begin
|
|
Result := GlobalRichClass;
|
|
end;
|
|
|
|
function InitRichEdit: Boolean;
|
|
begin
|
|
if GlobalRichClass = '' then begin
|
|
if LoadLibrary('Msftedit.dll') <> 0 then begin
|
|
GlobalRichClass := 'RichEdit50W';
|
|
end else if LoadLibrary('RICHED20.DLL') <> 0 then begin
|
|
if UnicodeEnabledOS then GlobalRichClass := 'RichEdit20W'
|
|
else
|
|
GlobalRichClass := 'RichEdit20A'
|
|
end else if LoadLibrary('RICHED32.DLL') <> 0 then begin
|
|
GlobalRichClass := 'RichEdit';
|
|
end;
|
|
|
|
if not Assigned(RichEditManager) then
|
|
RichEditManager := TRichEditManager;
|
|
|
|
Result := GlobalRichClass <> '';
|
|
end;
|
|
end;
|
|
|
|
procedure CopyStringToCharArray(const s: String; var Chrs: array of Char; ChrsSize: integer);
|
|
begin
|
|
if length(s) < ChrsSize then ChrsSize := length(s);
|
|
if length(s) > 0 then Move(s[1], Chrs[0], ChrsSize);
|
|
end;
|
|
|
|
function FontStylesToEffects(Styles: TFontStyles): LongWord;
|
|
begin
|
|
Result := 0;
|
|
if fsBold in Styles then Result := Result or CFE_BOLD;
|
|
if fsItalic in Styles then Result := Result or CFE_ITALIC;
|
|
if fsStrikeOut in Styles then Result := Result or CFE_STRIKEOUT;
|
|
if fsUnderline in Styles then Result := Result or CFE_UNDERLINE;
|
|
end;
|
|
|
|
function EffectsToFontStyles(Effects: LongWord): TFontStyles;
|
|
begin
|
|
Result := [];
|
|
if Effects and CFE_BOLD > 0 then Include(Result, fsBold);
|
|
if Effects and CFE_ITALIC > 0 then Include(Result, fsItalic);
|
|
if Effects and CFE_STRIKEOUT > 0 then Include(Result, fsStrikeOut);
|
|
if Effects and CFE_UNDERLINE > 0 then Include(Result, fsUnderline);
|
|
end;
|
|
|
|
function VScriptPosToEffects(vpos: TVScriptPos): LongWord;
|
|
const
|
|
EffMask : array [TVScriptPos] of LongWord = (0, CFE_SUBSCRIPT, CFE_SUPERSCRIPT);
|
|
begin
|
|
Result:=EffMask[vpos];
|
|
end;
|
|
|
|
function EffectsToVScriptPost(Effects: LongWord): TVScriptPos;
|
|
begin
|
|
if Effects and CFE_SUBSCRIPT > 0 then Result:=vpSubScript
|
|
else if Effects and CFE_SUBSCRIPT > 0 then Result:=vpSuperScript
|
|
else Result:=vpNormal;
|
|
end;
|
|
|
|
procedure CharFormatToFontParams(const fmt: TCHARFORMAT; var Params: TIntFontParams);
|
|
begin
|
|
Params.Name := fmt.szFaceName;
|
|
Params.Size := Round(fmt.yHeight/TwipsInFontSize);
|
|
Params.Color := fmt.crTextColor;
|
|
Params.Style := EffectsToFontStyles(fmt.dwEffects);
|
|
end;
|
|
|
|
procedure CharFormatToFontParams(const fmt: TCHARFORMAT2; var Params: TIntFontParams);
|
|
begin
|
|
Params.Name := fmt.szFaceName;
|
|
Params.Size := Round(fmt.yHeight/TwipsInFontSize);
|
|
Params.Color := fmt.crTextColor;
|
|
Params.Style := EffectsToFontStyles(fmt.dwEffects);
|
|
if fmt.cbSize > sizeof(CHARFORMAT) then begin
|
|
Params.HasBkClr:=(fmt.dwEffects and CFE_AUTOBACKCOLOR) = 0;
|
|
if Params.HasBkClr then Params.Color:=Params.Color;
|
|
Params.VScriptPos:=EffectsToVScriptPost(fmt.dwEffects);
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TRichEditManager }
|
|
|
|
class function TRichEditManager.SetEventMask(RichEditWnd: Handle; eventmask: integer): Integer;
|
|
begin
|
|
Result := SendMessage(RichEditWnd, EM_GETEVENTMASK, 0, 0);
|
|
SendMessage(RichEditWnd, EM_SETEVENTMASK, 0, eventmask);
|
|
end;
|
|
|
|
class function TRichEditManager.GetTextLength(RichEditWnd: Handle): Integer;
|
|
var
|
|
textlen : TGETTEXTEX;
|
|
begin
|
|
FillChar(textlen, sizeof(textlen), 0);
|
|
textlen.flags := GTL_NUMCHARS or GTL_PRECISE;
|
|
textlen.codepage := CP_UNICODE;
|
|
Result := SendMessage(RichEditWnd, EM_GETTEXTLENGTHEX, WPARAM(@textlen), 0);
|
|
end;
|
|
|
|
class function TRichEditManager.SetDefaultTextStyle(RichEditWnd: Handle;
|
|
Params: TIntFontParams): Boolean;
|
|
var
|
|
w : WPARAM;
|
|
fmt : TCHARFORMAT2;
|
|
begin
|
|
if RichEditWnd = 0 then begin
|
|
Result := false;
|
|
Exit;
|
|
end;
|
|
|
|
w := SCF_DEFAULT;
|
|
|
|
FillChar(fmt, sizeof(fmt), 0);
|
|
fmt.cbSize := sizeof(fmt);
|
|
|
|
fmt.dwMask := fmt.dwMask or CFM_COLOR;
|
|
fmt.crTextColor := Params.Color;
|
|
|
|
fmt.dwMask := fmt.dwMask or CFM_FACE;
|
|
// keep last char for Null-termination?
|
|
CopyStringToCharArray(Params.Name, fmt.szFaceName, LF_FACESIZE-1);
|
|
|
|
fmt.dwMask := fmt.dwMask or CFM_SIZE;
|
|
fmt.yHeight := Params.Size * TwipsInFontSize;
|
|
|
|
fmt.dwMask := fmt.dwMask or CFM_EFFECTS or CFM_SUBSCRIPT or CFM_SUPERSCRIPT;
|
|
fmt.dwEffects := FontStylesToEffects(Params.Style) or VScriptPosToEffects(Params.VScriptPos);
|
|
|
|
Result := SendMessage(RichEditWnd, EM_SETCHARFORMAT, w, PtrInt(@fmt))>0;
|
|
end;
|
|
|
|
class function TRichEditManager.SetSelectedTextStyle(RichEditWnd: Handle;
|
|
Params: TIntFontParams; useMask: Boolean; AModifyMask: TTextModifyMask): Boolean;
|
|
var
|
|
w : WPARAM;
|
|
fmt : TCHARFORMAT2;
|
|
const
|
|
CFM_STYLESONLY = CFM_BOLD or CFM_ITALIC or CFM_UNDERLINE or CFM_STRIKEOUT or CFM_SUBSCRIPT or CFM_SUPERSCRIPT;
|
|
begin
|
|
if RichEditWnd = 0 then begin
|
|
Result := false;
|
|
Exit;
|
|
end;
|
|
|
|
w := SCF_SELECTION;
|
|
|
|
FillChar(fmt, sizeof(fmt), 0);
|
|
fmt.cbSize := sizeof(fmt);
|
|
|
|
if not useMask or (tmm_Color in AModifyMask) then begin
|
|
fmt.dwMask := fmt.dwMask or CFM_COLOR;
|
|
fmt.crTextColor := Params.Color;
|
|
end;
|
|
|
|
if not useMask or (tmm_Name in AModifyMask) then begin
|
|
fmt.dwMask := fmt.dwMask or CFM_FACE;
|
|
// keep last char for Null-termination?
|
|
CopyStringToCharArray(Params.Name, fmt.szFaceName, LF_FACESIZE-1);
|
|
end;
|
|
|
|
if not useMask or (tmm_Size in AModifyMask) then begin
|
|
fmt.dwMask := fmt.dwMask or CFM_SIZE;
|
|
fmt.yHeight := Params.Size * TwipsInFontSize;
|
|
end;
|
|
|
|
if not useMask or (tmm_Styles in AModifyMask) then begin
|
|
fmt.dwMask := fmt.dwMask or CFM_STYLESONLY;
|
|
fmt.dwEffects := FontStylesToEffects(Params.Style) or VScriptPosToEffects(Params.VScriptPos);
|
|
end;
|
|
|
|
if not useMask or (tmm_BackColor in AModifyMask) then begin
|
|
if Params.HasBkClr then begin
|
|
fmt.dwMask := fmt.dwMask or CFM_BACKCOLOR;
|
|
fmt.crBackColor := Params.BkColor;
|
|
end else begin
|
|
fmt.dwMask := fmt.dwMask or CFM_BACKCOLOR;
|
|
fmt.dwEffects := fmt.dwEffects or CFE_AUTOBACKCOLOR;
|
|
end;
|
|
end;
|
|
|
|
Result := SendMessage(RichEditWnd, EM_SETCHARFORMAT, w, PtrInt(@fmt))>0;
|
|
end;
|
|
|
|
class function TRichEditManager.GetSelectedTextStyle(RichEditWnd: Handle;
|
|
var Params: TIntFontParams): Boolean;
|
|
var
|
|
w : WPARAM;
|
|
fmt : TCHARFORMAT2;
|
|
|
|
begin
|
|
Result := false;
|
|
if RichEditWnd = 0 then Exit;
|
|
|
|
w := SCF_SELECTION;
|
|
|
|
FillChar(fmt, sizeof(fmt), 0);
|
|
fmt.cbSize := sizeof(fmt);
|
|
fmt.dwMask := CFM_RICHMEMO_ATTRS;
|
|
|
|
SendMessage(RichEditWnd, EM_GETCHARFORMAT, w, PtrInt(@fmt));
|
|
|
|
CharFormatToFontParams(fmt, Params);
|
|
Result := true;
|
|
end;
|
|
|
|
class procedure TRichEditManager.SetTextUIStyle(RichEditWnd: Handle; const ui: TTextUIParam);
|
|
var
|
|
w : WPARAM;
|
|
fmt : TCHARFORMAT2;
|
|
{ st : TSetTextEx;
|
|
linkrtf : String;
|
|
txt : WideString;
|
|
txtrtf : String;}
|
|
begin
|
|
if RichEditWnd = 0 then Exit;
|
|
|
|
w := SCF_SELECTION;
|
|
|
|
FillChar(fmt, sizeof(fmt), 0);
|
|
fmt.cbSize := sizeof(fmt);
|
|
|
|
fmt.dwMask := CFM_LINK;
|
|
(* txt := GetTextW(RichEditWnd, true);
|
|
st.codepage:=CP_ACP;
|
|
st.flags:=ST_SELECTION;
|
|
txtrtf:=txt;
|
|
writeln('txtrtf = ', txtrtf);
|
|
linkrtf:=Format('{\rtf1{\field{\*\fldinst{ HYPERLINK "%s"}}{\fldrslt{%s}}}}',
|
|
[ui.linkref, txtrtf]);
|
|
SendMessage(RichEditWnd, EM_SETTEXTEX, WPARAM(@st), LParam(@linkrtf[1])); *)
|
|
|
|
if uiLink in ui.features then fmt.dwEffects := fmt.dwEffects or CFE_LINK;
|
|
|
|
SendMessage(RichEditWnd, EM_SETCHARFORMAT, w, PtrInt(@fmt));
|
|
end;
|
|
|
|
class function TRichEditManager.GetTextUIStyle(RichEditWnd: Handle; var ui: TTextUIParam): Boolean;
|
|
var
|
|
w : WPARAM;
|
|
fmt : TCHARFORMAT2;
|
|
begin
|
|
Result:=false;
|
|
if RichEditWnd = 0 then Exit;
|
|
|
|
w := SCF_SELECTION;
|
|
|
|
FillChar(fmt, sizeof(fmt), 0);
|
|
fmt.cbSize := sizeof(fmt);
|
|
|
|
fmt.dwMask := CFM_LINK;
|
|
|
|
SendMessage(RichEditWnd, EM_GETCHARFORMAT, w, PtrInt(@fmt));
|
|
InitTextUIParams(ui);
|
|
if fmt.dwEffects and CFE_LINK > 0 then
|
|
Include(ui.features, uiLink);
|
|
Result:=true;
|
|
end;
|
|
|
|
type
|
|
richedit_gettextlengthex = packed record
|
|
flags : DWORD;
|
|
codepage : LongWord;
|
|
end;
|
|
Tgettextlengthex = richedit_gettextlengthex;
|
|
|
|
class function TRichEditManager.GetStyleRange(RichEditWnd: Handle; TextStart: Integer;
|
|
var RangeStart, RangeLen: Integer): Boolean;
|
|
var
|
|
len : integer;
|
|
fmt : TCHARFORMAT;
|
|
textlen : Tgettextlengthex;
|
|
sel : TCHARRANGE;
|
|
d : Integer;
|
|
last : Integer;
|
|
initMask : DWORD;
|
|
const
|
|
ALL_MASK = CFM_RICHMEMO_ATTRS;
|
|
begin
|
|
Result := false;
|
|
if (RichEditWnd = 0) then Exit;
|
|
|
|
FillChar(textlen, sizeof(textlen), 0);
|
|
textlen.flags := GTL_NUMCHARS or GTL_PRECISE;
|
|
textlen.codepage := CP_UNICODE;
|
|
len := SendMessage(RichEditWnd, EM_GETTEXTLENGTHEX, WPARAM(@textlen), 0);
|
|
Result := TextStart < len;
|
|
if not Result then Exit;
|
|
|
|
FillChar(fmt, sizeof(fmt), 0);
|
|
fmt.cbSize := sizeof(fmt);
|
|
|
|
sel.cpMin := TextStart;
|
|
sel.cpMax := TextStart;
|
|
SendMessage(RichEditWnd, EM_EXSETSEL, 0, LPARAM(@sel));
|
|
SendMessage(RichEditWnd, EM_GETCHARFORMAT, SCF_SELECTION, PtrInt(@fmt));
|
|
initMask := fmt.dwMask and ALL_MASK;
|
|
|
|
FillChar(fmt, sizeof(fmt), 0);
|
|
fmt.cbSize := sizeof(fmt);
|
|
|
|
sel.cpMin := TextStart;
|
|
sel.cpMax := len+1;
|
|
SendMessage(RichEditWnd, EM_EXSETSEL, 0, LPARAM(@sel));
|
|
SendMessage(RichEditWnd, EM_GETCHARFORMAT, SCF_SELECTION, PtrInt(@fmt));
|
|
fmt.dwMask:=fmt.dwMask and ALL_MASK;
|
|
|
|
if fmt.dwMask <> initMask then begin
|
|
d := (len - sel.cpMin);
|
|
while d > 1 do begin
|
|
d := d div 2;
|
|
if fmt.dwMask = initMask then
|
|
sel.cpMax := sel.cpMax + d
|
|
else
|
|
sel.cpMax := sel.cpMax - d;
|
|
SendMessage(RichEditWnd, EM_EXSETSEL, 0, LPARAM(@sel));
|
|
SendMessage(RichEditWnd, EM_GETCHARFORMAT, SCF_SELECTION, PtrInt(@fmt));
|
|
fmt.dwMask:=fmt.dwMask and ALL_MASK;
|
|
end;
|
|
if fmt.dwMask = initMask then begin
|
|
while (sel.cpMax <= len) and (fmt.dwMask = initMask) do begin
|
|
inc(sel.cpMax);
|
|
SendMessage(RichEditWnd, EM_EXSETSEL, 0, LPARAM(@sel));
|
|
SendMessage(RichEditWnd, EM_GETCHARFORMAT, SCF_SELECTION, PtrInt(@fmt));
|
|
fmt.dwMask:=fmt.dwMask and ALL_MASK;
|
|
end;
|
|
end else begin
|
|
while (sel.cpMax > sel.cpMin) and (fmt.dwMask <> initMask) do begin
|
|
dec(sel.cpMax);
|
|
SendMessage(RichEditWnd, EM_EXSETSEL, 0, LPARAM(@sel));
|
|
SendMessage(RichEditWnd, EM_GETCHARFORMAT, SCF_SELECTION, PtrInt(@fmt));
|
|
fmt.dwMask:=fmt.dwMask and ALL_MASK;
|
|
end;
|
|
inc(sel.cpMax);
|
|
end;
|
|
end;
|
|
last := sel.cpMax;
|
|
|
|
sel.cpMin := 0;
|
|
sel.cpMax := TextStart+1;
|
|
SendMessage(RichEditWnd, EM_EXSETSEL, 0, LPARAM(@sel));
|
|
SendMessage(RichEditWnd, EM_GETCHARFORMAT, SCF_SELECTION, PtrInt(@fmt));
|
|
fmt.dwMask:=fmt.dwMask and ALL_MASK;
|
|
if fmt.dwMask <> initMask then begin
|
|
d := TextStart;
|
|
while d > 1 do begin
|
|
d := d div 2;
|
|
if fmt.dwMask = initMask then
|
|
dec(sel.cpMin,d)
|
|
else
|
|
inc(sel.cpMin,d);
|
|
SendMessage(RichEditWnd, EM_EXSETSEL, 0, LPARAM(@sel));
|
|
SendMessage(RichEditWnd, EM_GETCHARFORMAT, SCF_SELECTION, PtrInt(@fmt));
|
|
fmt.dwMask:=fmt.dwMask and ALL_MASK;
|
|
end;
|
|
if (fmt.dwMask = initMask) then begin
|
|
while (sel.cpMin > 0) and (fmt.dwMask = initMask) do begin
|
|
dec(sel.cpMin);
|
|
SendMessage(RichEditWnd, EM_EXSETSEL, 0, LPARAM(@sel));
|
|
SendMessage(RichEditWnd, EM_GETCHARFORMAT, SCF_SELECTION, PtrInt(@fmt));
|
|
fmt.dwMask:=fmt.dwMask and ALL_MASK;
|
|
end;
|
|
if (fmt.dwMask = initMask) then inc(sel.cpMin);
|
|
end else begin
|
|
while (sel.cpMin < TextStart) and (fmt.dwMask <> initMask) do begin
|
|
inc(sel.cpMin);
|
|
SendMessage(RichEditWnd, EM_EXSETSEL, 0, LPARAM(@sel));
|
|
SendMessage(RichEditWnd, EM_GETCHARFORMAT, SCF_SELECTION, PtrInt(@fmt));
|
|
fmt.dwMask:=fmt.dwMask and ALL_MASK;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
RangeStart := sel.cpMin;
|
|
RangeLen := last - sel.cpMin - 1;
|
|
|
|
Result := true;
|
|
end;
|
|
|
|
class procedure TRichEditManager.GetSelection(RichEditWnd: Handle; var TextStart, TextLen: Integer);
|
|
var
|
|
Range : TCHARRANGE;
|
|
begin
|
|
Range.cpMax := 0;
|
|
Range.cpMin := 0;
|
|
SendMessage(RichEditWnd, EM_EXGETSEL, 0, PtrInt(@Range));
|
|
TextStart := Range.cpMin;
|
|
TextLen := Range.cpMax-Range.cpMin;
|
|
end;
|
|
|
|
class procedure TRichEditManager.SetSelection(RichEditWnd: Handle; TextStart, TextLen: Integer);
|
|
var
|
|
Range : TCHARRANGE;
|
|
begin
|
|
Range.cpMin := TextStart;
|
|
Range.cpMax := TextStart + TextLen;
|
|
SendMessage(RichEditWnd, EM_EXSETSEL, 0, PtrInt(@Range));
|
|
end;
|
|
|
|
class procedure TRichEditManager.SetHideSelection(RichEditWnd: Handle; AValue: Boolean);
|
|
var
|
|
style : LResult;
|
|
begin
|
|
// res-setting options might RichEdit style. Must restore it, after option is changed
|
|
style := GetWindowLong(RichEditWnd, GWL_STYLE);
|
|
if AValue then
|
|
SendMessage(RichEditWnd, EM_SETOPTIONS, ECOOP_AND, not ECO_NOHIDESEL)
|
|
else
|
|
SendMessage(RichEditWnd, EM_SETOPTIONS, ECOOP_OR, ECO_NOHIDESEL);
|
|
SetWindowLong(RichEditWnd, GWL_STYLE, style);
|
|
end;
|
|
|
|
type
|
|
TEditStream_ = packed record
|
|
dwCookie : PDWORD;
|
|
dwError : DWORD;
|
|
pfnCallback : EDITSTREAMCALLBACK;
|
|
end;
|
|
|
|
function RTFLoadCallback(dwCookie:PDWORD; pbBuff:LPBYTE; cb:LONG; var pcb:LONG):DWORD; stdcall;
|
|
var
|
|
s : TStream;
|
|
begin
|
|
try
|
|
s := TStream(dwCookie);
|
|
pcb := s.Read(pbBuff^, cb);
|
|
Result := 0;
|
|
except
|
|
Result := 1;
|
|
end;
|
|
end;
|
|
|
|
class function TRichEditManager.LoadRichText(RichEditWnd: Handle; ASrc: TStream): Boolean;
|
|
var
|
|
cbs : TEditStream_;
|
|
begin
|
|
cbs.dwCookie := PDWORD(ASrc);
|
|
cbs.dwError := 0;
|
|
cbs.pfnCallback := @RTFLoadCallback;
|
|
SendMessage(RichEditWnd, EM_STREAMIN, SF_RTF, LPARAM(@cbs) );
|
|
Result := cbs.dwError = 0;
|
|
end;
|
|
|
|
function RTFSaveCallback(dwCookie:PDWORD; pbBuff:LPBYTE; cb:LONG; var pcb:LONG):DWORD; stdcall;
|
|
var
|
|
s : TStream;
|
|
begin
|
|
try
|
|
s := TStream(dwCookie);
|
|
pcb := s.Write(pbBuff^, cb);
|
|
Result := 0;
|
|
except
|
|
Result := 1;
|
|
end;
|
|
end;
|
|
|
|
class function TRichEditManager.SaveRichText(RichEditWnd: Handle; ADst: TStream): Boolean;
|
|
var
|
|
cbs : TEditStream_;
|
|
begin
|
|
cbs.dwCookie := PDWORD(ADst);
|
|
cbs.dwError := 0;
|
|
cbs.pfnCallback := @RTFSaveCallback;
|
|
SendMessage(RichEditWnd, EM_STREAMOUT, SF_RTF, LPARAM(@cbs) );
|
|
Result := cbs.dwError = 0;
|
|
end;
|
|
|
|
class procedure TRichEditManager.SetText(RichEditWnd:Handle;
|
|
const Text: WideString; TextStart, ReplaceLength:Integer);
|
|
var
|
|
AnsiText : AnsiString;
|
|
txt : PChar;
|
|
s, l : Integer;
|
|
begin
|
|
GetSelection(RichEditWnd, s, l);
|
|
SetSelection(RichEditWnd, TextStart, ReplaceLength);
|
|
|
|
txt:=nil;
|
|
if UnicodeEnabledOS then begin
|
|
if Text<>'' then txt:=@Text[1];
|
|
SendMessageW(RichEditWnd, EM_REPLACESEL, 0, LPARAM(txt));
|
|
end else begin
|
|
AnsiText:=Text;
|
|
if AnsiText<>'' then txt:=@AnsiText[1];
|
|
SendMessageA(RichEditWnd, EM_REPLACESEL, 0, LPARAM(txt));
|
|
end;
|
|
|
|
SetSelection(RichEditWnd, s, l);
|
|
end;
|
|
|
|
class function TRichEditManager.GetTextW(RichEditWnd: Handle;
|
|
inSelection: Boolean): WideString;
|
|
var
|
|
t : GETTEXTEX;
|
|
res : Integer;
|
|
w : WideString;
|
|
st : Integer;
|
|
begin
|
|
if inSelection then
|
|
GetSelection(RichEditWnd, st, res)
|
|
else
|
|
res:=GetTextLength(RichEditWnd);
|
|
|
|
if res>0 then begin
|
|
SetLength(w, res);
|
|
FillChar(t, sizeof(t), 0);
|
|
t.cb:=(length(w)+1)*sizeof(WideChar);
|
|
t.flags:=GT_DEFAULT;
|
|
if inSelection then t.flags:=t.flags or GT_SELECTION;
|
|
t.codepage:=CP_WINUNICODE;
|
|
res:=SendMessageW(RichEditWnd, EM_GETTEXTEX, WPARAM(@t), LPARAM(@w[1]));
|
|
Result:=w;
|
|
end else
|
|
Result:='';
|
|
end;
|
|
|
|
class function TRichEditManager.GetTextA(RichEditWnd: Handle;
|
|
inSelection: Boolean): AnsiString;
|
|
var
|
|
t : GETTEXTEX;
|
|
res : Integer;
|
|
s : AnsiString;
|
|
st : Integer;
|
|
begin
|
|
if inSelection then
|
|
GetSelection(RichEditWnd, st, res)
|
|
else
|
|
res:=GetTextLength(RichEditWnd);
|
|
|
|
if res>0 then begin
|
|
SetLength(s, res);
|
|
FillChar(t, sizeof(t), 0);
|
|
t.cb:=length(s)+1;
|
|
t.flags:=GT_DEFAULT;
|
|
t.codepage:=CP_ACP;
|
|
res:=SendMessageA(RichEditWnd, EM_GETTEXTEX, WPARAM(@t), LPARAM(@s[1]));
|
|
Result:=s;
|
|
end else
|
|
Result:='';
|
|
end;
|
|
|
|
class function TRichEditManager.GetTextUtf8(RichEditWnd: Handle;
|
|
inSelection: Boolean): string;
|
|
begin
|
|
if UnicodeEnabledOS then
|
|
Result:=UTF8Encode(GetTextW(RichEditWnd, inSelection))
|
|
else
|
|
Result:=AnsiToUtf8(GetTextA(RichEditWnd, inSelection));
|
|
end;
|
|
|
|
class procedure TRichEditManager.GetPara2(RichEditWnd: Handle; TextStart: Integer;
|
|
var para: PARAFORMAT2);
|
|
var
|
|
s, l : Integer;
|
|
begin
|
|
GetSelection(RichEditWnd, s, l);
|
|
|
|
SetSelection(RichEditWnd, TextStart, 0);
|
|
|
|
FillChar(para, sizeof(para), 0);
|
|
para.cbSize:=sizeof(para);
|
|
SendMessagea(RichEditWnd, EM_GETPARAFORMAT, 0, LPARAM(@para));
|
|
|
|
SetSelection(RichEditWnd, s, l);
|
|
end;
|
|
|
|
class procedure TRichEditManager.SetPara2(RichEditWnd: Handle;
|
|
TextStart, TextLen: Integer; const para: PARAFORMAT2);
|
|
var
|
|
s, l : Integer;
|
|
begin
|
|
GetSelection(RichEditWnd, s, l);
|
|
SetSelection(RichEditWnd, TextStart, TextLen);
|
|
SendMessagea(RichEditWnd, EM_SETPARAFORMAT, 0, LPARAM(@para));
|
|
SetSelection(RichEditWnd, s, l);
|
|
end;
|
|
|
|
class function TRichEditManager.Find(RichEditWnd: THandle; const ANiddle: WideString; const ASearch: TIntSearchOpt): Integer; overload;
|
|
var
|
|
l : integer;
|
|
begin
|
|
Result:=Find(RichEDitWnd, ANiddle, ASearch, l);
|
|
end;
|
|
|
|
class function TRichEditManager.Find(RichEditWnd: THandle;
|
|
const ANiddle: WideString; const ASearch: TIntSearchOpt; var TextLen: Integer): Integer;
|
|
var
|
|
fw: TFINDTEXTEXW;
|
|
fa: TFINDTEXTEXA;
|
|
opt: WParam;
|
|
txt: string;
|
|
mn, mx : Integer;
|
|
begin
|
|
if ANiddle='' then begin
|
|
Result:=-1;
|
|
Exit;
|
|
end;
|
|
opt:=0;
|
|
if not (soBackward in ASearch.Options) then opt:=FR_DOWN; // if not set, then search is backward
|
|
if soMatchCase in ASearch.Options then opt := opt or FR_MATCHCASE;
|
|
if soWholeWord in ASearch.Options then opt := opt or FR_WHOLEWORD;
|
|
mn := ASearch.start;
|
|
if soBackward in ASearch.Options then begin
|
|
if ASearch.len<0 then mx := 0
|
|
else begin
|
|
mx := ASearch.start-ASearch.len;
|
|
if mx < 0 then mx:=0;
|
|
end;
|
|
end else begin
|
|
if ASearch.len<0 then fw.chrg.cpMax := -1
|
|
else begin
|
|
mx := ASearch.start+ASearch.len;
|
|
if mx < 0 then mx:=-1;
|
|
end;
|
|
end;
|
|
|
|
if UnicodeEnabledOS then begin
|
|
fw.chrg.cpMin := mn;
|
|
fw.chrg.cpMax := mx;
|
|
fw.lpstrText := PWideChar(@ANiddle[1]);
|
|
Result := SendMessage(RichEditWnd, EM_FINDTEXTEXW, opt, LParam(@fw));
|
|
if Result>=0 then TextLen:=fw.chrgText.cpMax-fw.chrgText.cpMin;
|
|
end else begin
|
|
fa.chrg.cpMin := mn;
|
|
fa.chrg.cpMax := mx;
|
|
txt:=ANiddle;
|
|
fa.lpstrText := PAnsiChar(@txt[1]);
|
|
Result := SendMessage(RichEditWnd, EM_FINDTEXTEX, opt, LParam(@fa));
|
|
if Result>=0 then TextLen:=fa.chrgText.cpMax-fa.chrgText.cpMin;
|
|
end;
|
|
end;
|
|
|
|
class procedure TRichEditManager.GetParaRange(RichEditWnd: Handle; TextStart: integer;
|
|
var para: TParaRange);
|
|
var
|
|
line: Integer;
|
|
//txtlen: Integer;
|
|
st: Integer;
|
|
ln: Integer;
|
|
toend: Integer;
|
|
tost: Integer;
|
|
buf : string[16];
|
|
rng : TTEXTRANGEA;
|
|
res : Integer;
|
|
begin
|
|
//txtlen:=GetTextLength(RichEditWnd);
|
|
// lines are NOT paragraphs, but wordwrapped lines
|
|
line:=SendMessage(RichEditWnd, EM_EXLINEFROMCHAR, 0, TextStart);
|
|
st:=SendMessage(RichEditWnd, EM_LINEINDEX, line, 0);
|
|
tost:=st;
|
|
toend:=0;
|
|
|
|
while tost>0 do begin
|
|
rng.lpstrText:=@buf[1];
|
|
rng.chrg.cpMin:=tost-1;
|
|
rng.chrg.cpMax:=tost;
|
|
buf[1]:=#0;
|
|
res:=SendMessageA(RichEditWnd, EM_GETTEXTRANGE, 0, LPARAM(@rng));
|
|
if (buf[1]=HardBreak) then
|
|
Break // found the beggining of the paragraph
|
|
else begin
|
|
line:=SendMessage(RichEditWnd, EM_EXLINEFROMCHAR, 0, tost-2); // getting the line before the linebreak
|
|
tost:=SendMessage(RichEditWnd, EM_LINEINDEX, line, 0);
|
|
inc(toend, SendMessage(RichEditWnd, EM_LINELENGTH, line, 0));
|
|
end;
|
|
end;
|
|
|
|
repeat
|
|
ln:=SendMessage(RichEditWnd, EM_LINELENGTH, st, 0);
|
|
inc(toend, ln);
|
|
inc(st, ln);
|
|
rng.lpstrText:=@buf[1];
|
|
rng.chrg.cpMin:=st;
|
|
rng.chrg.cpMax:=st+1;
|
|
buf[1]:=#0;
|
|
res:=SendMessage(RichEditWnd, EM_GETTEXTRANGE, 0, LPARAM(@rng));
|
|
until (res=0) or (buf[1] = HardBreak);
|
|
|
|
para.start:=tost;
|
|
para.lengthNoBr:=toend;
|
|
if res>0 then inc(toend); // there's a line break character - add it to the range
|
|
para.length:=toend;
|
|
end;
|
|
|
|
function WinInsertImageFromFile (const ARichMemo: TCustomRichMemo; APos: Integer;
|
|
const FileNameUTF8: string;
|
|
const AImgSize: TSize): Boolean;
|
|
var
|
|
hnd : THandle;
|
|
rch : IRichEditOle;
|
|
Fmt : FORMATETC;
|
|
FN : WideString;
|
|
LockBytes: ILockBytes;
|
|
ClientSite: IOleClientSite;
|
|
Storage: IStorage;
|
|
Image: IOleObject;
|
|
Obj: TREOBJECT;
|
|
|
|
sl, ss: Integer;
|
|
const
|
|
PointSize = 72.0;
|
|
RtfSizeToInch = 2.54 * 1000.0;
|
|
SizeFactor = 1 / PointSize * RtfSizeToInch;
|
|
begin
|
|
Result:=false;
|
|
if not Assigned(ARichMemo) then Exit;
|
|
if not ARichMemo.HandleAllocated then begin
|
|
ARichMemo.HandleNeeded;
|
|
if not ARichMemo.HandleAllocated then Exit;
|
|
end;
|
|
if (FileNameUTF8 ='') then Exit;
|
|
|
|
ss:=ARichMemo.SelStart;
|
|
sl:=ARichMemo.SelLength;
|
|
try
|
|
hnd:= THandle(ARichMemo.Handle);
|
|
SendMessage(hnd, EM_GETOLEINTERFACE, 0, LPARAM(@rch));
|
|
|
|
FillChar(Fmt, sizeoF(Fmt), 0);
|
|
Fmt.dwAspect:=DVASPECT_CONTENT;
|
|
Fmt.lindex:=-1;
|
|
|
|
CreateILockBytesOnHGlobal(0, True, LockBytes);
|
|
StgCreateDocfileOnILockBytes(LockBytes, STGM_SHARE_EXCLUSIVE or STGM_CREATE or STGM_READWRITE, 0, Storage);
|
|
rch.GetClientSite(ClientSite);
|
|
|
|
FN := UTF8Decode( FileNameUTF8 );
|
|
OleCreateFromFile(CLSID_NULL, @FN[1], IOleObject
|
|
, OLERENDER_DRAW, @Fmt, ClientSite, Storage, Image);
|
|
OleSetContainedObject(Image, True);
|
|
|
|
FillChar(Obj, sizeof(Obj),0);
|
|
Obj.cbStruct := SizeOf(Obj);
|
|
Obj.cp := REO_CP_SELECTION;
|
|
Image.GetUserClassID(Obj.clsid);
|
|
Obj.poleobj := Image;
|
|
Obj.pstg := Storage;
|
|
Obj.polesite := ClientSite;
|
|
Obj.dvaspect := DVASPECT_CONTENT;
|
|
if (AImgSize.cx<>0) or (AImgSize.cy<>0) then begin
|
|
//http://msdn.microsoft.com/en-us/library/windows/desktop/bb787946%28v=vs.85%29.aspx
|
|
//The size of the object. The unit of measure is 0.01 millimeters, which is a HIMETRIC measurement.
|
|
Obj.sizel.cx:=round(AImgSize.cx * SizeFactor);
|
|
Obj.sizel.cy:=round(AImgSize.cy * SizeFactor);
|
|
end;
|
|
|
|
ARichMemo.SelStart:=APos;
|
|
ARichMemo.SelLength:=0;
|
|
Result:= Succeeded(rch.InsertObject(obj));
|
|
finally
|
|
ARichMemo.SelStart:=ss;
|
|
ARichMemo.SelLength:=sl;
|
|
end;
|
|
end;
|
|
|
|
initialization
|
|
InsertImageFromFile := @WinInsertImageFromFile;
|
|
|
|
end.
|
|
|