{-----------------------------------------------------------------------------
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/MPL-1.1.html

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.

The Original Code is: JvRichEd.PAS, released on 2002-07-04.

The Initial Developers of the Original Code are: Fedor Koshevnikov, Igor Pavluk and Serge Korolev
Copyright (c) 1997, 1998 Fedor Koshevnikov, Igor Pavluk and Serge Korolev
Copyright (c) 2001,2002 SGB Software
Portions created by Sébastien Buysse are Copyright (C) 2001 Sébastien Buysse.
All Rights Reserved.

Contributor(s):
  Polaris Software
  Sébastien Buysse [sbuysse att buypin dott com] (original code in JvRichEdit.pas)
  Michael Beck [mbeck att bigfoot dott com] (contributor to JvRichEdit.pas)
  Roman Kovbasiouk [roko att users dott sourceforge dott net] (merging JvRichEdit.pas)
  Remko Bonte [remkobonte att myrealbox dott com] (insert image procedures, MS Text converters)
  Jacob Boerema [jgboerema att hotmail dott com] (indentation style, zoom, tab styles)

You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
located at http://jvcl.sourceforge.net

Known Issues:
-----------------------------------------------------------------------------}
// $Id: JvRichEdit.pas 11282 2007-05-14 19:11:20Z remkobonte $

unit JvRichEdit;

{$I jvcl.inc}
{$I windowsonly.inc}

{$RANGECHECKS OFF}

interface

{$HPPEMIT '#define CHARFORMAT2A Richedit::CHARFORMAT2A'}

uses
  {$IFDEF UNITVERSIONING}
  JclUnitVersioning,
  {$ENDIF UNITVERSIONING}
  Windows, ActiveX, ComObj, CommCtrl, Messages, SysUtils, Classes, Controls,
  OleCtnrs,
  Forms, Graphics, StdCtrls, Dialogs, RichEdit, Menus, ComCtrls, SyncObjs,
  JVCLVer, JvExStdCtrls;

type
  TJvCustomRichEdit = class;

  TJvAttributeType = (atDefaultText, atSelected, atWord);
  TJvConsistentAttribute = (caBold, caColor, caFace, caItalic, caSize,
    caStrikeOut, caUnderline, caProtected, caOffset, caHidden, caCharset,
    caLink, caBackColor, caDisabled, caWeight, caSubscript, caRevAuthor);
  TJvConsistentAttributes = set of TJvConsistentAttribute;
  TSubscriptStyle = (ssNone, ssSubscript, ssSuperscript);
  TUnderlineType = (utNone, utSolid, utWord, utDouble, utDotted, utDash,
    utDashDot, utDashDotDot, utWave, utThick);
  TUnderlineColor = (ucBlack, ucBlue, ucAqua, ucLime, ucFuchsia, ucRed,
    ucYellow, ucWhite, ucNavy, ucTeal, ucGreen, ucPurple, ucMaroon, ucOlive,
    ucGray, ucSilver);

  TJvTextAttributes = class(TPersistent)
  private
    FRichEdit: TJvCustomRichEdit;
    FType: TJvAttributeType;
    procedure AssignFont(Font: TFont);
    procedure GetAttributes(var Format: TCharFormat2);
    procedure SetAttributes(var Format: RichEdit.TCharFormat2);
    function GetAttribute(const Flag: Integer): Boolean;
    function GetBackColor: TColor;
    function GetCharset: TFontCharset;
    function GetColor: TColor;
    function GetConsistentAttributes: TJvConsistentAttributes;
    function GetDisabled: Boolean;
    function GetHeight: Integer;
    function GetHidden: Boolean;
    function GetLink: Boolean;
    function GetName: TFontName;
    function GetOffset: Integer;
    function GetPitch: TFontPitch;
    function GetProtected: Boolean;
    function GetRevAuthorIndex: Byte;
    function GetSize: Integer;
    function GetStyle: TFontStyles;
    function GetSubscriptStyle: TSubscriptStyle;
    function GetUnderlineColor: TUnderlineColor;
    function GetUnderlineType: TUnderlineType;
    procedure SetAttribute(const Flag: Integer; const Value: Boolean);
    procedure SetBackColor(Value: TColor);
    procedure SetCharset(Value: TFontCharset);
    procedure SetColor(Value: TColor);
    procedure SetDisabled(Value: Boolean);
    procedure SetHeight(Value: Integer);
    procedure SetHidden(Value: Boolean);
    procedure SetLink(Value: Boolean);
    procedure SetName(Value: TFontName);
    procedure SetOffset(Value: Integer);
    procedure SetPitch(Value: TFontPitch);
    procedure SetProtected(Value: Boolean);
    procedure SetRevAuthorIndex(Value: Byte);
    procedure SetSize(Value: Integer);
    procedure SetStyle(Value: TFontStyles);
    procedure SetSubscriptStyle(Value: TSubscriptStyle);
    procedure SetUnderlineColor(const Value: TUnderlineColor);
    procedure SetUnderlineType(Value: TUnderlineType);
  protected
    procedure InitFormat(var Format: RichEdit.TCharFormat2);
    procedure AssignTo(Dest: TPersistent); override;
  public
    constructor Create(AOwner: TJvCustomRichEdit; AttributeType: TJvAttributeType);
    procedure Assign(Source: TPersistent); override;
    property BackColor: TColor read GetBackColor write SetBackColor;
    property Charset: TFontCharset read GetCharset write SetCharset;
    property Color: TColor read GetColor write SetColor;
    property ConsistentAttributes: TJvConsistentAttributes read GetConsistentAttributes;
    property Disabled: Boolean read GetDisabled write SetDisabled;
    property Height: Integer read GetHeight write SetHeight;
    property Hidden: Boolean read GetHidden write SetHidden;
    property Link: Boolean read GetLink write SetLink;
    property Name: TFontName read GetName write SetName;
    property Offset: Integer read GetOffset write SetOffset;
    property Pitch: TFontPitch read GetPitch write SetPitch;
    property Protected: Boolean read GetProtected write SetProtected;
    property RevAuthorIndex: Byte read GetRevAuthorIndex write SetRevAuthorIndex;
    property Size: Integer read GetSize write SetSize;
    property Style: TFontStyles read GetStyle write SetStyle;
    property SubscriptStyle: TSubscriptStyle read GetSubscriptStyle write SetSubscriptStyle;
    property UnderlineColor: TUnderlineColor read GetUnderlineColor write SetUnderlineColor;
    property UnderlineType: TUnderlineType read GetUnderlineType write SetUnderlineType;
    property Bold: Boolean index CFE_BOLD read GetAttribute write SetAttribute;
    property Italic: Boolean index CFE_ITALIC read GetAttribute write SetAttribute;
    property Underline: Boolean index CFE_UNDERLINE read GetAttribute write SetAttribute;
    property StrikeOut: Boolean index CFE_STRIKEOUT read GetAttribute write SetAttribute;
  end;

  TJvNumbering = (nsNone, nsBullet, nsArabicNumbers, nsLoCaseLetter,
    nsUpCaseLetter, nsLoCaseRoman, nsUpCaseRoman);
  TJvNumberingStyle = (nsParenthesis, nsPeriod, nsEnclosed, nsSimple);
  TParaAlignment = (paLeftJustify, paRightJustify, paCenter, paJustify);
  TLineSpacingRule = (lsSingle, lsOneAndHalf, lsDouble, lsSpecifiedOrMore,
    lsSpecified, lsMultiple);
  THeadingStyle = 0..9;
  TParaTableStyle = (tsNone, tsTableRow, tsTableCellEnd, tsTableCell);

  TJvIndentationStyle = (isRichEdit, isOffice); // added by J.G. Boerema
  // TJvIndentationStyle: default is isRichEdit
  // - isRichEdit: LefIndent relative to FirstIndent
  // - isOffice: FirstIndent relative to LeftIndent (like MsWord and WordPad)
  // For example when FirstIndent=2 and LeftIndent=1 the effect is:
  // isRichEdit: first line starts at 2 and following lines at 3
  // isOffice: first line starts at 3 and following lines at 1

  //  From Msdn PARAFORMAT info:
{
   Rich Edit 2.0: For compatibility with TOM interfaces, you can use the eight
   high-order bits to store additional information about each tab stop.

   Bits 24-27 can specify one of the following values to indicate the tab alignment.
   These bits do not affect the rich edit control display for versions earlier
   than Rich Edit 3.0. [Note J.G.Boerema: This information is incorrect! At
   least, my version of Rich Edit 3 shows all tabs as ordinary tabs.]
   0    Ordinary tab
   1    Center tab
   2    Right-aligned tab
   3    Decimal tab
   4    Word bar tab (vertical bar)

   Bits 28-31 can specify one of the following values to indicate the type of tab leader.
   These bits do not affect the rich edit control display.
   0    No leader
   1    Dotted leader
   2    Dashed leader
   3    Underlined leader
   4    Thick line leader
   5    Double line leader
}

  TJvTabAlignment =
    (taOrdinary, taCenter, taRight, taDecimal, taVertical); // added by J.G. Boerema
  // Note: if taVertical then tableader should be disabled according to Word
  TJvTabLeader =
    (tlNone, tlDotted, tlDashed, tlUnderlined, tlThick, tlDouble); // added by J.G. Boerema

  TJvParaAttributes = class(TPersistent)
  private
    FRichEdit: TJvCustomRichEdit;
    FIndentationStyle: TJvIndentationStyle; // added by J.G. Boerema
    procedure GetAttributes(var Paragraph: TParaFormat2);
    function GetAlignment: TParaAlignment;
    function GetFirstIndent: Longint;
    function GetHeadingStyle: THeadingStyle;
    function GetLeftIndent: Longint;
    function GetLineSpacing: Longint;
    function GetLineSpacingRule: TLineSpacingRule;
    function GetNumbering: TJvNumbering;
    function GetNumberingStart: Integer;
    function GetNumberingStyle: TJvNumberingStyle;
    function GetNumberingTab: Word;
    function GetRightIndent: Longint;
    function GetSpaceAfter: Longint;
    function GetSpaceBefore: Longint;
    function GetTab(Index: Byte): Longint;
    function GetTabCount: Integer;
    function GetTableStyle: TParaTableStyle;
    function GetTabAlignment(Index: Byte): TJvTabAlignment;
    function GetTabLeader(Index: Byte): TJvTabLeader;
    procedure SetAlignment(Value: TParaAlignment);
    procedure SetAttributes(var Paragraph: TParaFormat2);
    procedure SetFirstIndent(Value: Longint);
    procedure SetHeadingStyle(Value: THeadingStyle);
    procedure SetLeftIndent(Value: Longint);
    procedure SetLineSpacing(Value: Longint);
    procedure SetLineSpacingRule(Value: TLineSpacingRule);
    procedure SetNumbering(Value: TJvNumbering);
    procedure SetNumberingStart(const Value: Integer);
    procedure SetNumberingStyle(Value: TJvNumberingStyle);
    procedure SetNumberingTab(Value: Word);
    procedure SetRightIndent(Value: Longint);
    procedure SetSpaceAfter(Value: Longint);
    procedure SetSpaceBefore(Value: Longint);
    procedure SetTab(Index: Byte; Value: Longint);
    procedure SetTabCount(Value: Integer);
    procedure SetTableStyle(Value: TParaTableStyle);
    procedure SetTabAlignment(Index: Byte; Value: TJvTabAlignment);
    procedure SetTabLeader(Index: Byte; Value: TJvTabLeader);
  protected
    procedure InitPara(var Paragraph: TParaFormat2);
    procedure AssignTo(Dest: TPersistent); override;
  public
    constructor Create(AOwner: TJvCustomRichEdit);
    procedure Assign(Source: TPersistent); override;
    property Alignment: TParaAlignment read GetAlignment write SetAlignment;
    property FirstIndent: Longint read GetFirstIndent write SetFirstIndent;
    property HeadingStyle: THeadingStyle read GetHeadingStyle write SetHeadingStyle;
    property IndentationStyle: TJvIndentationStyle read FIndentationStyle
      write FIndentationStyle; // added by J.G. Boerema
    property LeftIndent: Longint read GetLeftIndent write SetLeftIndent;
    property LineSpacing: Longint read GetLineSpacing write SetLineSpacing;
    property LineSpacingRule: TLineSpacingRule read GetLineSpacingRule write SetLineSpacingRule;
    property Numbering: TJvNumbering read GetNumbering write SetNumbering;
    property NumberingStart: Integer read GetNumberingStart write SetNumberingStart;
    property NumberingStyle: TJvNumberingStyle read GetNumberingStyle write SetNumberingStyle;
    property NumberingTab: Word read GetNumberingTab write SetNumberingTab;
    property RightIndent: Longint read GetRightIndent write SetRightIndent;
    property SpaceAfter: Longint read GetSpaceAfter write SetSpaceAfter;
    property SpaceBefore: Longint read GetSpaceBefore write SetSpaceBefore;
    property Tab[Index: Byte]: Longint read GetTab write SetTab;
    property TabCount: Integer read GetTabCount write SetTabCount;
    property TableStyle: TParaTableStyle read GetTableStyle write SetTableStyle;
    property TabAlignment[Index: Byte]: TJvTabAlignment read GetTabAlignment write SetTabAlignment;
    property TabLeader[Index: Byte]: TJvTabLeader read GetTabLeader write SetTabLeader;
  end;

  TJvConversionKind = (ckImport, ckExport);
  TJvConversionTextKind = (ctkText, ctkRTF, ctkBothPreferText, ctkBothPreferRTF);

  { (rb) Name TJvConverter is already taken, thus: }
  TJvConversion = class(TObject)
  private
    FOnProgress: TNotifyEvent;
    FParentWindow: THandle;
  protected
    FPercentDone: Integer;
    procedure DoProgress(APercentDone: Integer);
  public
    function CanHandle(const AExtension: string; const AKind: TJvConversionKind): Boolean; overload; virtual;
    function CanHandle(const AKind: TJvConversionKind): Boolean; overload; virtual;
    function Filter: string; virtual;
    function TextKind: TJvConversionTextKind; virtual;
    function IsFormatCorrect(const AFileName: string): Boolean; overload; virtual;
    function IsFormatCorrect(AStream: TStream): Boolean; overload; virtual;

    function Open(const AFileName: string; const AKind: TJvConversionKind): Boolean; overload; virtual;
    function Open(Stream: TStream; const AKind: TJvConversionKind): Boolean; overload; virtual;
    procedure Init(AParentWindow: THandle); virtual;
    procedure Done; virtual;
    function Retry: Boolean; virtual;

    function ConvertRead(Buffer: PChar; BufSize: Integer): Integer; virtual;
    function ConvertWrite(Buffer: PChar; BufSize: Integer): Integer; virtual;

    function UserCancel: Boolean; virtual;
    function Error: Boolean; virtual;
    function ErrorStr: string; virtual;

    property OnProgress: TNotifyEvent read FOnProgress write FOnProgress;
    property PercentDone: Integer read FPercentDone;
    property ParentWindow: THandle read FParentWindow;
  end;

  TJvStreamConversion = class(TJvConversion)
  private
    FStream: TStream;
    FSavedPosition: Int64;
    FStreamSize: Integer;
    FFreeStream: Boolean;
    FBytesConverted: Integer;
  public
    function Open(const AFileName: string; const AKind: TJvConversionKind): Boolean; override;
    function Open(Stream: TStream; const AKind: TJvConversionKind): Boolean; override;
    procedure Done; override;
    function Retry: Boolean; override;
    function ConvertRead(Buffer: PChar; BufSize: Integer): Integer; override;
    function ConvertWrite(Buffer: PChar; BufSize: Integer): Integer; override;
    property Stream: TStream read FStream;
  end;

  TJvTextConversion = class(TJvStreamConversion)
  public
    function CanHandle(const AExtension: string; const AKind: TJvConversionKind): Boolean; override;
    function Filter: string; override;
    function TextKind: TJvConversionTextKind; override;
  end;

  TJvRTFConversion = class(TJvStreamConversion)
  public
    function CanHandle(const AExtension: string; const AKind: TJvConversionKind): Boolean; override;
    function Filter: string; override;
    function TextKind: TJvConversionTextKind; override;
    function IsFormatCorrect(const AFileName: string): Boolean; override;
    function IsFormatCorrect(AStream: TStream): Boolean; override;
  end;

  TJvOEMConversion = class(TJvStreamConversion)
  public
    function ConvertRead(Buffer: PChar; BufSize: Integer): Integer; override;
    function ConvertWrite(Buffer: PChar; BufSize: Integer): Integer; override;
    function TextKind: TJvConversionTextKind; override;
  end;

  FCE = Smallint; // File Conversion Error

  { typedef long (PASCAL *PFN_RTF)(long, long); }
  PFN_RTF = function(I1, I2: Longint): Longint; stdcall;
  { long PASCAL InitConverter32(HANDLE hWnd, char *szModule); }
  TInitConverter32 = function(hWnd: THandle; szModule: PChar): LongBool; stdcall;
  { void PASCAL UninitConverter(void); }
  TUninitConverter = procedure; stdcall;
  { void PASCAL GetReadNames(HANDLE haszClass, HANDLE haszDescrip, HANDLE haszExt); }
  TGetReadNames = procedure(haszClass, haszDescrip, haszExt: THandle); stdcall;
  { void PASCAL GetWriteNames(HANDLE haszClass, HANDLE haszDescrip, HANDLE haszExt); }
  TGetWriteNames = procedure(haszClass, haszDescrip, haszExt: THandle); stdcall;
  { HGLOBAL PASCAL RegisterApp(unsigned long lFlags, void FAR *lpFuture); }
  TRegisterApp = function(lFlags: DWORD; lpFuture: Pointer): HGLOBAL; stdcall;
  { FCE  PASCAL IsFormatCorrect32(HANDLE ghszFile, HANDLE ghszClass); }
  TIsFormatCorrect32 = function(ghszFile, ghszClass: THandle): FCE; stdcall;
  { FCE  PASCAL ForeignToRtf32(HANDLE ghszFile, void *pstgForeign, HANDLE ghBuff, HANDLE ghszClass, HANDLE ghszSubset, PFN_RTF lpfnOut); }
  TForeignToRtf32 = function(ghszFile: THandle; pstgForeign: Pointer; ghBuff, ghszClass, ghszSubset: THandle;
    lpfnOut: PFN_RTF): FCE; stdcall;
  { FCE  PASCAL RtfToForeign32(HANDLE ghszFile, void *pstgForeign, HANDLE ghBuff, HANDLE ghshClass, PFN_RTF lpfnIn); }
  TRtfToForeign32 = function(ghszFile: THandle; pstgForeign: Pointer; ghBuff, ghshClass: THandle;
    lpfnIn: PFN_RTF): FCE; stdcall;
  { long PASCAL CchFetchLpszError(long fce, char FAR *lpszError, long cb); }
  TCchFetchLpszError = function(fce: Longint; lpszError: PChar; cb: Longint): Longint; stdcall;
  { long PASCAL FRegisterConverter(HANDLE hkeyRoot); }
  TFRegisterConverter = function(hkeyRoot: THandle): Longint; stdcall;

  TJvMSTextConversion = class(TJvConversion)
  private
    FConverterFileName: string;
    FExtensions: TStringList;
    FDescription: string;
    FConverterKind: TJvConversionKind;

    FConverter: HMODULE;
    FInitConverter32: TInitConverter32;
    FUninitConverter: TUninitConverter;
    FIsFormatCorrect32: TIsFormatCorrect32;
    FForeignToRtf32: TForeignToRtf32;
    FRtfToForeign32: TRtfToForeign32;
    FCchFetchLpszError: TCchFetchLpszError;

    { Indicates whether the thread is done }
    FThreadDone: Boolean;
    { Indicates whether the conversion process has been cancelled by the
      main thread }
    FCancel: Boolean;

    FBytesAvailable: Integer;
    { Buffer accessable by the converter dll }
    FBuffer: HGLOBAL;
    FBufferPtr: PChar;
    FTempProgress: Integer;

    { Thread synchronization based on the source of Wordpad, see
      http://cvs.wndtabs.com/cgi-bin/viewcvs/viewcvs.cgi/BCG/WordPad/

      Import works as follows

      Thread                            RichEdit
      ------                            --------
      loop:                             loop:
        @@ Converter converts buffer1     @@ Copy buffer1 to buffer2
                                          richedit processes buffer2

      The @@ parts may not happen simultaneously, thus this is converted to:

      Thread                            RichEdit
      ------                            --------
      loop:                             loop:
        @@ Converter converts buffer1     [wait until thread ready]
        [thread ready]                    @@ Copy buffer1 to buffer2
        [wait until richedit ready]       [richedit ready]
                                          richedit retrieves data from buffer2

      Export works as follows:

      Thread                            RichEdit
      ------                            --------
      loop:                             loop:
        @@ Converter converts buffer1     richedit puts data in buffer2
                                          @@ Copy buffer2 to buffer1

      The @@ parts may not happen simultaneously, thus this is converted to:

      Thread                            RichEdit
      ------                            --------
      loop:                             loop:
        [thread ready]                    richedit puts data in buffer2
        [wait until richedit ready]       [wait until thread ready]
        @@ Converter converts buffer1     @@ Copy buffer2 to buffer1
                                          [richedit ready]

      - buffer1 is FBuffer
      - buffer2 is the Buffer param from ConvertRead or ConvertWrite

    }

    FRichEditReady: TEvent;
    FThreadReady: TEvent;
    FConversionError: FCE;
    FFileName: HMODULE;
    FInitDone: Boolean;
  protected
    procedure LoadConverter;
    procedure FreeConverter;
    procedure Check(Result: FCE);
    procedure DoError(ErrorCode: FCE);

    { Handled in the context of the thread: }
    procedure DoConversion;
    function HandleExportCallback(cchBuff, nPercent: Longint): Longint;
    function HandleImportCallback(cchBuff, nPercent: Longint): Longint;
    procedure WaitUntilThreadReady;
    procedure WaitUntilRichEditReady;

    procedure Lock;
    procedure Unlock;

    procedure InitConverter;
  public
    constructor Create(const AConverterFileName, AExtensions, ADescription: string;
      const AKind: TJvConversionKind); virtual;
    destructor Destroy; override;

    function CanHandle(const AExtension: string; const AKind: TJvConversionKind): Boolean; override;
    function CanHandle(const AKind: TJvConversionKind): Boolean; override;

    function Open(const AFileName: string; const AKind: TJvConversionKind): Boolean; override;

    procedure Done; override;

    function TextKind: TJvConversionTextKind; override;
    function Filter: string; override;
    function IsFormatCorrect(const AFileName: string): Boolean; override;
    function TranslateError(ErrorCode: FCE): string;

    function ConvertRead(Buffer: PChar; BufSize: Integer): Integer; override;
    function ConvertWrite(Buffer: PChar; BufSize: Integer): Integer; override;

    function UserCancel: Boolean; override;
    function Error: Boolean; override;
    function ErrorStr: string; override;
  end;

  TUndoName = (unUnknown, unTyping, unDelete, unDragDrop, unCut, unPaste);
  TRichSearchType = (stWholeWord, stMatchCase, stBackward, stSetSelection);
  TRichSearchTypes = set of TRichSearchType;
  TRichSelection = (stText, stObject, stMultiChar, stMultiObject);
  TRichSelectionType = set of TRichSelection;
  TRichLangOption = (rlAutoKeyboard, rlAutoFont, rlImeCancelComplete,
    rlImeAlwaysSendNotify);
  TRichLangOptions = set of TRichLangOption;
  TRichStreamFormat = (sfDefault, sfRichText, sfPlainText);
  TRichStreamMode = (smSelection, smPlainRtf, smNoObjects, smUnicode);
  TRichStreamModes = set of TRichStreamMode;
  TRichEditURLClickEvent = procedure(Sender: TObject; const URLText: string;
    Button: TMouseButton) of object;
  TRichEditProtectChangeEx = procedure(Sender: TObject; const Msg: TMessage;
    StartPos, EndPos: Integer; var AllowChange: Boolean) of object;
  TRichEditFindErrorEvent = procedure(Sender: TObject; const FindText: string) of object;
  TRichEditFindCloseEvent = procedure(Sender: TObject; Dialog: TFindDialog) of object;
  TRichEditProgressEvent = procedure(Sender: TObject; PercentDone: Integer) of object;

  TJvCustomRichEdit = class(TJvExCustomMemo)
  private
    FHideScrollBars: Boolean;
    FSelectionBar: Boolean;
    FAutoURLDetect: Boolean;
    FWordSelection: Boolean;
    FPlainText: Boolean;
    FSelAttributes: TJvTextAttributes;
    FDefAttributes: TJvTextAttributes;
    FWordAttributes: TJvTextAttributes;
    FParagraph: TJvParaAttributes;
    FOldParaAlignment: TParaAlignment;
    FScreenLogPixels: Integer;
    FUndoLimit: Integer;
    FLines: TStrings;
    FState: TObject;
    FHideSelection: Boolean;
    FLangOptions: TRichLangOptions;
    FLinesUpdating: Boolean;
    FPageRect: TRect;
    FClickRange: TCharRange;
    FClickBtn: TMouseButton;
    FFindDialog: TFindDialog;
    FReplaceDialog: TReplaceDialog;
    FLastFind: TFindDialog;
    FAllowObjects: Boolean;
    FCallback: TObject;
    FRichEditOle: IUnknown;
    FPopupVerbMenu: TPopupMenu;
    FTitle: string;
    FAutoVerbMenu: Boolean;
    FAllowInPlace: Boolean;
    FDefaultConverter: TJvConversion;
    FImageRect: TRect;
    FAutoAdvancedTypography: Boolean;
    FAdvancedTypography: Boolean;
    FOLEDragDrop: Boolean;
    FOnSelChange: TNotifyEvent;
    FOnResizeRequest: TRichEditResizeEvent;
    FOnProtectChange: TRichEditProtectChange;
    FOnProtectChangeEx: TRichEditProtectChangeEx;
    FOnSaveClipboard: TRichEditSaveClipboard;
    FOnURLClick: TRichEditURLClickEvent;
    FOnTextNotFound: TRichEditFindErrorEvent;
    FOnCloseFindDialog: TRichEditFindCloseEvent;
    // From JvRichEdit.pas by Sébastien Buysse
    FOnHorizontalScroll: TNotifyEvent;
    FOnVerticalScroll: TNotifyEvent;
    FOnConversionProgress: TRichEditProgressEvent;
    FForceUndo: Boolean;
    FUseFixedPopup: Boolean;
    // From CCR
    FOnInPlaceActivate: TNotifyEvent;
    FOnInPlaceDeactivate: TNotifyEvent;

    function GetAdvancedTypography: Boolean;
    function GetAutoURLDetect: Boolean;
    function GetWordSelection: Boolean;
    function GetLangOptions: TRichLangOptions;
    function GetCanRedo: Boolean;
    function GetCanPaste: Boolean;
    function GetRedoName: TUndoName;
    function GetUndoName: TUndoName;
    function GetStreamFormat: TRichStreamFormat;
    function GetStreamMode: TRichStreamModes;
    function GetSelectionType: TRichSelectionType;
    function GetZoom: Integer; // Added by J.G. Boerema
    function IsAdvancedTypographyStored: Boolean;
    procedure PopupVerbClick(Sender: TObject);
    procedure ObjectPropsClick(Sender: TObject);
    procedure CloseObjects;
    procedure UpdateHostNames;
    procedure SetAdvancedTypography(const Value: Boolean);
    procedure SetAllowObjects(Value: Boolean);
    procedure SetStreamFormat(Value: TRichStreamFormat);
    procedure SetStreamMode(Value: TRichStreamModes);
    procedure SetAutoURLDetect(Value: Boolean);
    procedure SetWordSelection(Value: Boolean);
    procedure SetHideScrollBars(Value: Boolean);
    procedure SetHideSelection(Value: Boolean);
    procedure SetTitle(const Value: string);
    procedure SetLangOptions(Value: TRichLangOptions);
    procedure SetRichEditStrings(Value: TStrings);
    procedure SetDefAttributes(Value: TJvTextAttributes);
    procedure SetSelAttributes(Value: TJvTextAttributes);
    procedure SetWordAttributes(Value: TJvTextAttributes);
    procedure SetSelectionBar(Value: Boolean);
    procedure SetOLEDragDrop(const Value: Boolean);
    procedure SetUndoLimit(Value: Integer);
    procedure SetZoom(Value: Integer); // Added by J.G. Boerema
    procedure UpdateTextModes(Plain: Boolean);
    procedure UpdateTypographyOptions(const Advanced: Boolean);
    procedure AdjustFindDialogPosition(Dialog: TFindDialog);
    procedure SetupFindDialog(Dialog: TFindDialog; const SearchStr,
      ReplaceStr: string);
    function FindEditText(Dialog: TFindDialog; AdjustPos, Events: Boolean): Boolean;
    function GetCanFindNext: Boolean;
    procedure FindDialogFind(Sender: TObject);
    procedure NeedAdvancedTypography;
    procedure ReplaceDialogReplace(Sender: TObject);
    procedure SetSelText(const Value: string);
    procedure FindDialogClose(Sender: TObject);
    procedure SetUIActive(Active: Boolean);
    procedure CMBiDiModeChanged(var Msg: TMessage); message CM_BIDIMODECHANGED;
    procedure CMDocWindowActivate(var Msg: TMessage); message CM_DOCWINDOWACTIVATE;
    procedure CMUIDeactivate(var Msg: TMessage); message CM_UIDEACTIVATE;
    procedure CNNotify(var Msg: TWMNotify); message CN_NOTIFY;
    procedure EMReplaceSel(var Msg: TMessage); message EM_REPLACESEL;
    procedure WMDestroy(var Msg: TWMDestroy); message WM_DESTROY;
    procedure WMMouseMove(var Msg: TMessage); message WM_MOUSEMOVE;
    procedure WMPaint(var Msg: TWMPaint); message WM_PAINT;
    procedure WMRButtonUp(var Msg: TMessage); message WM_RBUTTONUP;
    procedure WMSetCursor(var Msg: TWMSetCursor); message WM_SETCURSOR;
    procedure WMSetFont(var Msg: TWMSetFont); message WM_SETFONT;
    // From JvRichEdit.pas by Sébastien Buysse
    procedure WMHScroll(var Msg: TWMHScroll); message WM_HSCROLL;
    procedure WMVScroll(var Msg: TWMVScroll); message WM_VSCROLL;
    function GetFlat: Boolean;
    procedure SetFlat(const Value: Boolean);
    function GetParentFlat: Boolean;
    procedure SetParentFlat(const Value: Boolean);
  protected
    procedure ColorChanged; override;
    procedure FontChanged; override;

    function GetConverter(const AFileName: string; const Kind: TJvConversionKind): TJvConversion; overload;
    function GetConverter(AStream: TStream; const Kind: TJvConversionKind): TJvConversion; overload;

    procedure CreateParams(var Params: TCreateParams); override;
    procedure CreateWindowHandle(const Params: TCreateParams); override;
    procedure CreateWnd; override;
    procedure DestroyWnd; override;
    function GetPopupMenu: TPopupMenu; override;
    procedure TextNotFound(Dialog: TFindDialog); virtual;
    procedure RequestSize(const Rect: TRect); virtual;
    procedure SelectionChange; dynamic;
    function ProtectChange(const Msg: TMessage; StartPos,
      EndPos: Integer): Boolean; dynamic;
    function SaveClipboard(NumObj, NumChars: Integer): Boolean; dynamic;
    procedure URLClick(const URLText: string; Button: TMouseButton); dynamic;
    procedure SetPlainText(Value: Boolean); virtual;
    procedure CloseFindDialog(Dialog: TFindDialog); virtual;
    procedure DoSetMaxLength(Value: Integer); override;
    procedure DoConversionProgress(const AProgress: Integer);
    function GetSelLength: Integer; override;
    function GetSelStart: Integer; override;
    function GetSelText: string; override;
    procedure SetSelLength(Value: Integer); override;
    procedure SetSelStart(Value: Integer); override;
    property AllowInPlace: Boolean read FAllowInPlace write FAllowInPlace default True;
    property AutoAdvancedTypography: Boolean read FAutoAdvancedTypography write FAutoAdvancedTypography default True;
    property AdvancedTypography: Boolean read GetAdvancedTypography write SetAdvancedTypography stored
      IsAdvancedTypographyStored;
    property AllowObjects: Boolean read FAllowObjects write SetAllowObjects default True;
    property AutoURLDetect: Boolean read GetAutoURLDetect write SetAutoURLDetect default True;
    property AutoVerbMenu: Boolean read FAutoVerbMenu write FAutoVerbMenu default True;
    property HideSelection: Boolean read FHideSelection write SetHideSelection default True;
    property HideScrollBars: Boolean read FHideScrollBars
      write SetHideScrollBars default True;
    property Title: string read FTitle write SetTitle;
    property LangOptions: TRichLangOptions read GetLangOptions write SetLangOptions default [rlAutoFont];
    property Lines: TStrings read FLines write SetRichEditStrings;
    property OLEDragDrop: Boolean read FOLEDragDrop write SetOLEDragDrop default True;
    property PlainText: Boolean read FPlainText write SetPlainText default False;
    property SelectionBar: Boolean read FSelectionBar write SetSelectionBar default True;
    property StreamFormat: TRichStreamFormat read GetStreamFormat write SetStreamFormat default sfDefault;
    property StreamMode: TRichStreamModes read GetStreamMode write SetStreamMode default [];
    property UndoLimit: Integer read FUndoLimit write SetUndoLimit default 100;
    property WordSelection: Boolean read GetWordSelection write SetWordSelection default True;
    property ScrollBars default ssBoth;
    property TabStop default True;
    property SelText: string read GetSelText write SetSelText;
    // Zoom: zoom in/out percentage (100=normal) note: no need to set default (100) in constructor.
    property Zoom: Integer read GetZoom write SetZoom default 100;
    property OnSaveClipboard: TRichEditSaveClipboard read FOnSaveClipboard
      write FOnSaveClipboard;
    property OnSelectionChange: TNotifyEvent read FOnSelChange write FOnSelChange;
    property OnProtectChange: TRichEditProtectChange read FOnProtectChange
      write FOnProtectChange; { obsolete }
    property OnProtectChangeEx: TRichEditProtectChangeEx read FOnProtectChangeEx
      write FOnProtectChangeEx;
    property OnResizeRequest: TRichEditResizeEvent read FOnResizeRequest
      write FOnResizeRequest;
    property OnURLClick: TRichEditURLClickEvent read FOnURLClick write FOnURLClick;
    property OnTextNotFound: TRichEditFindErrorEvent read FOnTextNotFound write FOnTextNotFound;
    property OnCloseFindDialog: TRichEditFindCloseEvent read FOnCloseFindDialog
      write FOnCloseFindDialog;
    property OnConversionProgress: TRichEditProgressEvent read FOnConversionProgress write FOnConversionProgress;
    // From JvRichEdit.pas by Sébastien Buysse
    property OnVerticalScroll: TNotifyEvent read FOnVerticalScroll write FOnVerticalScroll;
    property OnHorizontalScroll: TNotifyEvent read FOnHorizontalScroll write FOnHorizontalScroll;
    property ForceUndo: Boolean read FForceUndo write FForceUndo default True;
    property UseFixedPopup: Boolean read FUseFixedPopup write FUseFixedPopup default True;
    // from CCR
    property OnInPlaceActivate: TNotifyEvent read FOnInPlaceActivate write FOnInPlaceActivate;
    property OnInPlaceDeactivate: TNotifyEvent read FOnInPlaceDeactivate write FOnInPlaceDeactivate;
    property Flat: Boolean read GetFlat write SetFlat default False;
    property ParentFlat: Boolean read GetParentFlat write SetParentFlat default True;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Clear; override;
    procedure SaveToImage(Picture: TPicture);

    procedure InsertGraphic(AGraphic: TGraphic; const Sizeable: Boolean);
    { Same interface as TOleContainer }
    procedure InsertLinkToFile(const FileName: string; Iconic: Boolean);
    procedure InsertObject(const OleClassName: string; Iconic: Boolean);
    procedure InsertObjectFromFile(const FileName: string; Iconic: Boolean);
    procedure InsertObjectFromInfo(const Info: TCreateInfo);
    // InsertFormatText inserts formatted text at the cursor position given by Index.
    // If Index < 0, the text is inserted at the current SelStart position.
    // S is the string to insert
    // AFont is the font to use. If AFont = nil, then the current attributes at the insertion point are used.
    // NOTE: this procedure does not reset the attributes after the call, i.e if you change the text color
    // it will remain that color until you change it again.
    procedure InsertFormatText(Index: Integer; const S: string; const AFont: TFont = nil); overload;
    procedure InsertFormatText(Index: Integer; const S: string;
      FontStyle: TFontStyles; const FontName: string = ''; const FontColor: TColor = clDefault; FontHeight: Integer = 0); overload;

    // AddFormatText works just like InsertFormatText but always moves the insertion
    // point to the end of the available text
    procedure AddFormatText(const S: string; const AFont: TFont = nil); overload;
    procedure AddFormatText(const S: string; FontStyle: TFontStyles; const FontName: string = ''; const FontColor: TColor = clDefault; FontHeight: Integer = 0); overload;

    procedure SetSelection(StartPos, EndPos: Longint; ScrollCaret: Boolean);
    function GetSelection: TCharRange;
    function GetTextRange(StartPos, EndPos: Longint): string;
    function LineFromChar(CharIndex: Integer): Integer;
    function GetLineIndex(LineNo: Integer): Integer;
    function GetLineLength(CharIndex: Integer): Integer;
    function WordAtCursor: string;
    function FindText(const SearchStr: string;
      StartPos, Length: Integer; Options: TRichSearchTypes): Integer;
    function GetSelTextBuf(Buffer: PChar; BufSize: Integer): Integer; override;
    function GetCaretPos: TPoint; override;
    function GetCharPos(CharIndex: Integer): TPoint;
    function InsertObjectDialog: Boolean;
    function ObjectPropertiesDialog: Boolean;
    function PasteSpecialDialog: Boolean;
    function FindDialog(const SearchStr: string): TFindDialog;
    function ReplaceDialog(const SearchStr, ReplaceStr: string): TReplaceDialog;
    function FindNext: Boolean;
    procedure Print(const Caption: string); virtual;
    class procedure RegisterConversionFormat(AConverter: TJvConversion);
    class procedure RegisterMSTextConverters;
    class function Filter(const AKind: TJvConversionKind): string;
    procedure ClearUndo;
    procedure Redo;
    procedure StopGroupTyping;
    procedure CloseActiveObject; // from CCR

    property CanFindNext: Boolean read GetCanFindNext;
    property CanRedo: Boolean read GetCanRedo;
    property CanPaste: Boolean read GetCanPaste;
    property RedoName: TUndoName read GetRedoName;
    property UndoName: TUndoName read GetUndoName;
    property DefaultConverter: TJvConversion read FDefaultConverter write FDefaultConverter;
    property DefAttributes: TJvTextAttributes read FDefAttributes write SetDefAttributes;
    property SelAttributes: TJvTextAttributes read FSelAttributes write SetSelAttributes;
    property WordAttributes: TJvTextAttributes read FWordAttributes write SetWordAttributes;
    property PageRect: TRect read FPageRect write FPageRect;
    property Paragraph: TJvParaAttributes read FParagraph;
    property SelectionType: TRichSelectionType read GetSelectionType;
  end;

  TJvRichEdit = class(TJvCustomRichEdit)
  published
    property AdvancedTypography;
    property Align;
    property Alignment;
    property AutoAdvancedTypography;
    property AutoSize;
    property AutoURLDetect;
    property AutoVerbMenu;
    property AllowObjects;
    property AllowInPlace;
    property Anchors;
    {$IFDEF COMPILER6_UP}
    property BevelEdges;
    property BevelInner;
    property BevelKind default bkNone;
    property BevelOuter;
    {$ENDIF COMPILER6_UP}
    property BiDiMode;
    property BorderWidth;
    property DragKind;
    property BorderStyle;
    property ClipboardCommands;
    property Color;
    property DragCursor;
    property DragMode;
    property Enabled;
    property Flat;
    property Font;
    property ForceUndo;
    property HideSelection;
    property HideScrollBars;
    property HintColor;
    property Title;
    property ImeMode;
    property ImeName;
    property Constraints;
    property ParentBiDiMode;
    property LangOptions;
    property Lines;
    property MaxLength;
    property OLEDragDrop;
    property ParentColor;
    property ParentFlat;
    property ParentFont;
    property ParentShowHint;
    property PlainText;
    property PopupMenu;
    property ReadOnly;
    property ScrollBars;
    property SelectionBar;
    property SelText;
    property ShowHint;
    property StreamFormat;
    property StreamMode;
    property TabOrder;
    property TabStop;
    property UndoLimit;
    property UseFixedPopup;
    property Visible;
    property WantTabs;
    property WantReturns;
    property WordSelection;
    property WordWrap;
    property Zoom; // added by J.G. Boerema
    property OnChange;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnContextPopup;
    property OnConversionProgress;
    property OnEndDock;
    property OnStartDock;
    property OnEndDrag;
    property OnEnter;
    property OnExit;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
    property OnMouseWheel;
    property OnMouseWheelDown;
    property OnMouseWheelUp;
    property OnProtectChange; { obsolete }
    property OnProtectChangeEx;
    property OnResizeRequest;
    property OnSaveClipboard;
    property OnSelectionChange;
    property OnStartDrag;
    property OnTextNotFound;
    property OnCloseFindDialog;
    property OnURLClick;
    property OnMouseEnter;
    property OnMouseLeave;
    property OnParentColorChange;
    property OnVerticalScroll;
    property OnHorizontalScroll;
    // From CCR
    property OnInPlaceActivate;
    property OnInPlaceDeactivate;
  end;

var
  RichEditVersion: Integer;

  { Two procedures to construct RTF from a bitmap. You can use this to
    insert bitmaps in the rich edit control, for example:

      Stream := TMemoryStream.Create;
      try
        BitmapToRTF(SomeBitmap, Stream);
        Stream.Position := 0;

        JvRichEdit1.StreamFormat := sfRichText;
        JvRichEdit1.StreamMode := [smSelection, smPlainRtf];
        JvRichEdit1.Lines.LoadFromStream(Stream);
      finally
        Stream.Free;
      end;

    But:

    * if you stream out the RTF content of the rich edit control, the bitmaps
      are *not* included. Use TJvRichEdit.InsertGraphic if you want the bitmaps
      to be included in the RTF.
    * TJvRichEdit.AllowObjects must be set to True.
    * BitmapToRTF is the fastest, TJvRichEdit.InsertGraphic the slowest.
  }

{ uses the \dibitmap identifier }
procedure BitmapToRTF(ABitmap: TBitmap; AStream: TStream);
{ uses the \wmetafile identifier }
function BitmapToRTF2(ABitmap: TBitmap; AStream: TStream): Boolean;

{$IFDEF UNITVERSIONING}
const
  UnitVersioning: TUnitVersionInfo = (
    RCSfile: '$URL: https://jvcl.svn.sourceforge.net/svnroot/jvcl/tags/JVCL3_32/run/JvRichEdit.pas $';
    Revision: '$Revision: 11282 $';
    Date: '$Date: 2007-05-14 21:11:20 +0200 (lun., 14 mai 2007) $';
    LogPath: 'JVCL\run'
  );
{$ENDIF UNITVERSIONING}

implementation

uses
  Printers, ComStrs, OleConst, OleDlg, Math, Registry, Contnrs,
  JvThemes, JvConsts, JvResources, JvFixedEditPopUp;

type
  PENLink = ^TENLink;
  PENOleOpFailed = ^TENOleOpFailed;
  TFindTextEx = TFindTextExA;
  TTextRangeA = record
    chrg: TCharRange;
    lpstrText: PAnsiChar;
  end;

  TTextRangeW = record
    chrg: TCharRange;
    lpstrText: PWideChar;
  end;

  TTextRange = TTextRangeA;

  { OLE Extensions to the Rich Text Editor }
  { Converted from RICHOLE.H               }
  { Structure passed to GetObject and InsertObject }

  _ReObject = record
    cbStruct: DWORD; { Size of structure                }
    cp: ULONG; { Character position of object     }
    clsid: TCLSID; { Class ID of object               }
    poleobj: IOleObject; { OLE object interface             }
    pstg: IStorage; { Associated storage interface     }
    polesite: IOleClientSite; { Associated client site interface }
    sizel: TSize; { Size of object (may be 0,0)      }
    dvAspect: Longint; { Display aspect to use            }
    dwFlags: DWORD; { Object status flags              }
    dwUser: DWORD; { DWORD for user's use             }
  end;
  TReObject = _ReObject;

  EMSTextConversionError = class(Exception)
  private
    FErrorCode: FCE;
  public
    constructor Create(const Msg: string; AErrorCode: FCE = 0);
    property ErrorCode: FCE read FErrorCode write FErrorCode;
  end;

  (*  make Delphi 5 compiler happy // andreas
    { RichEdit GUIDs }
    IID_IRichEditOle: TGUID = (
      D1: $00020D00; D2: $0000; D3: $0000; D4: ($C0, $00, $00, $00, $00, $00, $00, $46));
    IID_IRichEditOleCallback: TGUID = (
      D1: $00020D03; D2: $0000; D3: $0000; D4: ($C0, $00, $00, $00, $00, $00, $00, $46));
  *)

  {
   *  IRichEditOle
   *
   *  Purpose:
   *    Interface used by the client of RichEdit to perform OLE-related
   *    operations.
   *
   *    The methods herein may just want to be regular Windows messages.
  }

  IRichEditOle = interface(IUnknown)
    ['{00020d00-0000-0000-c000-000000000046}']
    function GetClientSite(out clientSite: IOleClientSite): HRESULT; stdcall;
    function GetObjectCount: HRESULT; stdcall;
    function GetLinkCount: HRESULT; stdcall;
    function GetObject(iob: Longint; out ReObject: TReObject;
      dwFlags: DWORD): HRESULT; stdcall;
    function InsertObject(var ReObject: TReObject): HRESULT; stdcall;
    function ConvertObject(iob: Longint; rclsidNew: TIID;
      lpstrUserTypeNew: LPCSTR): HRESULT; stdcall;
    function ActivateAs(rclsid: TIID; rclsidAs: TIID): 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(var chrg: TCharRange; reco: DWORD;
      out dataObj: IDataObject): HRESULT; stdcall;
    function ImportDataObject(dataObj: IDataObject; cf: TClipFormat;
      hMetaPict: HGLOBAL): HRESULT; stdcall;
  end;

  {
   *  IRichEditOleCallback
   *
   *  Purpose:
   *    Interface used by the RichEdit to get OLE-related stuff from the
   *    application using RichEdit.
  }

  IRichEditOleCallback = interface(IUnknown)
    ['{00020d03-0000-0000-c000-000000000046}']
    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; const oleobj: IOleObject;
      const chrg: TCharRange; out Menu: HMENU): HRESULT; stdcall;
  end;

  TConversionFormatList = class(TObjectList)
  private
    FRTFConvIndex: Integer;
    FTextConvIndex: Integer;
    function GetItem(Index: Integer): TJvConversion;
  public
    constructor Create; virtual;
    { GetConverter implicitly calls Result.Init, thus caller must call Result.Done }
    function GetConverter(AParentWindow: THandle; const AFileName: string;
      const Kind: TJvConversionKind): TJvConversion; overload;
    function GetConverter(AParentWindow: THandle; AStream: TStream;
      const Kind: TJvConversionKind): TJvConversion; overload;
    function GetFilter(const AKind: TJvConversionKind): string;
    function DefaultConverter: TJvConversion;
    property Items[Index: Integer]: TJvConversion read GetItem {write SetItem}; default;
  end;

  TImageDataObject = class(TInterfacedObject, IDataObject)
  private
    FGraphic: TGraphic;
  public
    constructor Create(AGraphic: TGraphic); virtual;
    { IDataObject }
    function GetData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium):
      HRESULT; stdcall;
    function GetDataHere(const FormatEtc: TFormatEtc; out Medium: TStgMedium):
      HRESULT; stdcall;
    function QueryGetData(const FormatEtc: TFormatEtc): HRESULT;
      stdcall;
    function GetCanonicalFormatEtc(const FormatEtc: TFormatEtc;
      out FormatEtcOut: TFormatEtc): HRESULT; stdcall;
    function SetData(const FormatEtc: TFormatEtc; var Medium: TStgMedium;
      fRelease: BOOL): HRESULT; stdcall;
    function EnumFormatEtc(dwDirection: Longint; out EnumFormatEtc:
      IEnumFormatEtc): HRESULT; stdcall;
    function DAdvise(const FormatEtc: TFormatEtc; advf: Longint;
      const advSink: IAdviseSink; out dwConnection: Longint): HRESULT; stdcall;
    function DUnadvise(dwConnection: Longint): HRESULT; stdcall;
    function EnumDAdvise(out enumAdvise: IEnumStatData): HRESULT;
      stdcall;
  end;

  TJvRichEditState = class(TObject)
  private
    FOrigFormat: TRichStreamFormat;
    FOrigMode: TRichStreamModes;
    FStreamFormat: TRichStreamFormat;
    FStreamMode: TRichStreamModes;
    FSelStart: Integer;
    FSelLength: Integer;
    FModified: Boolean;
    FForcePlainText: Boolean;
    FStream: TMemoryStream;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Store(RichEdit: TJvCustomRichEdit);
    procedure Restore(RichEdit: TJvCustomRichEdit);
    property ForcePlainText: Boolean read FForcePlainText write FForcePlainText;
  end;

  TJvRichEditStrings = class(TStrings)
  private
    FRichEdit: TJvCustomRichEdit;
    FFormat: TRichStreamFormat;
    FMode: TRichStreamModes;
    procedure EnableChange(const Value: Boolean);
  protected
    procedure ProgressCallback(Sender: TObject);
    function Get(Index: Integer): string; override;
    function GetCount: Integer; override;
    procedure Put(Index: Integer; const S: string); override;
    procedure SetUpdateState(Updating: Boolean); override;
    procedure SetTextStr(const Value: string); override;

    procedure DoImport(AConverter: TJvConversion);
    procedure DoExport(AConverter: TJvConversion);
  public
    procedure Clear; override;
    procedure AddStrings(Strings: TStrings); override;
    procedure Delete(Index: Integer); override;
    procedure Insert(Index: Integer; const S: string); override;
    procedure LoadFromFile(const FileName: string); override;
    procedure LoadFromStream(Stream: TStream); override;
    procedure SaveToFile(const FileName: string); override;
    procedure SaveToStream(Stream: TStream); override;
    property Format: TRichStreamFormat read FFormat write FFormat;
    property Mode: TRichStreamModes read FMode write FMode;
  end;

  TMSTextConversionThread = class(TThread)
  protected
    procedure Execute; override;
  public
    constructor Create; virtual;
  end;

  { TOleUILinkInfo - helper interface for Object Properties dialog }

  TOleUILinkInfo = class(TInterfacedObject, IOleUILinkInfo)
  private
    FReObject: TReObject;
    FRichEdit: TJvCustomRichEdit;
    FOleLink: IOleLink;
  public
    constructor Create(ARichEdit: TJvCustomRichEdit; ReObject: TReObject);
    function GetNextLink(dwLink: Longint): Longint; stdcall;
    function SetLinkUpdateOptions(dwLink: Longint;
      dwUpdateOpt: Longint): HRESULT; stdcall;
    function GetLinkUpdateOptions(dwLink: Longint;
      var dwUpdateOpt: Longint): HRESULT; stdcall;
    function SetLinkSource(dwLink: Longint; pszDisplayName: PChar;
      lenFileName: Longint; var chEaten: Longint;
      fValidateSource: BOOL): HRESULT; stdcall;
    function GetLinkSource(dwLink: Longint; var pszDisplayName: PChar;
      var lenFileName: Longint; var pszFullLinkType: PChar;
      var pszShortLinkType: PChar; var fSourceAvailable: BOOL;
      var fIsSelected: BOOL): HRESULT; stdcall;
    function OpenLinkSource(dwLink: Longint): HRESULT; stdcall;
    function UpdateLink(dwLink: Longint; fErrorMessage: BOOL;
      fErrorAction: BOOL): HRESULT; stdcall;
    function CancelLink(dwLink: Longint): HRESULT; stdcall;
    function GetLastUpdate(dwLink: Longint;
      var LastUpdate: TFileTime): HRESULT; stdcall;
  end;

  { TOleUIObjInfo - helper interface for Object Properties dialog }

  TOleUIObjInfo = class(TInterfacedObject, IOleUIObjInfo)
  private
    FRichEdit: TJvCustomRichEdit;
    FReObject: TReObject;
  public
    constructor Create(ARichEdit: TJvCustomRichEdit; ReObject: TReObject);
    function GetObjectInfo(dwObject: Longint;
      var dwObjSize: Longint; var lpszLabel: PChar;
      var lpszType: PChar; var lpszShortType: PChar;
      var lpszLocation: PChar): HRESULT; stdcall;
    function GetConvertInfo(dwObject: Longint; var ClassID: TCLSID;
      var wFormat: Word; var ConvertDefaultClassID: TCLSID;
      var lpClsidExclude: PCLSID; var cClsidExclude: Longint): HRESULT; stdcall;
    function ConvertObject(dwObject: Longint;
      const clsidNew: TCLSID): HRESULT; stdcall;
    function GetViewInfo(dwObject: Longint; var hMetaPict: HGLOBAL;
      var dvAspect: Longint; var nCurrentScale: Integer): HRESULT; stdcall;
    function SetViewInfo(dwObject: Longint; hMetaPict: HGLOBAL;
      dvAspect: Longint; nCurrentScale: Integer;
      bRelativeToOrig: BOOL): HRESULT; stdcall;
  end;

  TRichEditOleCallback = class(TObject, IUnknown, IRichEditOleCallback)
  private
    FDocForm: IVCLFrameForm;
    FFrameForm: IVCLFrameForm;
    FAccelTable: HACCEL;
    FAccelCount: Integer;
    FAutoScroll: Boolean;
    procedure CreateAccelTable;
    procedure DestroyAccelTable;
    procedure AssignFrame;
  private
    FRefCount: Longint;
    FRichEdit: TJvCustomRichEdit;
  public
    constructor Create(ARichEdit: TJvCustomRichEdit);
    destructor Destroy; override;
    function QueryInterface(const iid: TGUID; out Obj): HRESULT; stdcall;
    function _AddRef: Longint; stdcall;
    function _Release: Longint; stdcall;
    function GetNewStorage(out stg: IStorage): HRESULT; stdcall;
    function GetInPlaceContext(out Frame: IOleInPlaceFrame;
      out Doc: IOleInPlaceUIWindow;
      lpFrameInfo: POleInPlaceFrameInfo): HRESULT; stdcall;
    function GetClipboardData(const chrg: TCharRange; reco: DWORD;
      out dataObj: IDataObject): HRESULT; stdcall;
    function GetContextMenu(seltype: Word; const oleobj: IOleObject;
      const chrg: TCharRange; out Menu: HMENU): 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 GetDragDropEffect(fDrag: BOOL; grfKeyState: DWORD;
      var dwEffect: DWORD): HRESULT; stdcall;
  end;

  TBiDiOptions = record
    cbSize: UINT;
    wMask: WORD;
    wEffects: WORD;
  end;

const
  { File Conversion Errors }
  fceTrue = FCE(1); // IsFormatCorrect32: recognized the input file.
  fceNoErr = FCE(0); // IsFormatCorrect32: Did not recognize the input file.
  // Operation completed successfully for other APIs
  fceOpenInFileErr = FCE(-1); // could not open input file
  fceReadErr = FCE(-2); // error during read
  fceOpenConvErr = FCE(-3); // error opening conversion file (obsolete)
  fceWriteErr = FCE(-4); // error during write
  fceInvalidFile = FCE(-5); // invalid data in conversion file
  fceOpenExceptErr = FCE(-6); // error opening exception file (obsolete)
  fceWriteExceptErr = FCE(-7); // error writing exception file (obsolete)
  fceNoMemory = FCE(-8); // out of memory
  fceInvalidDoc = FCE(-9); // invalid document (obsolete)
  fceDiskFull = FCE(-10); // out of space on output (obsolete)
  fceDocTooLarge = FCE(-11); // conversion document too large for target (obsolete)
  fceOpenOutFileErr = FCE(-12); // could not open output file
  fceUserCancel = FCE(-13); // conversion cancelled by user
  fceWrongFileType = FCE(-14); // wrong file type for this converter

  CTwipsPerInch = 1440;
  CTwipsPerPoint = 20;
  CHundredthMMPerInch = 2540;
  CPointsPerInch = 72;

  RichEdit10ModuleName = 'RICHED32.DLL';
  RichEdit20ModuleName = 'RICHED20.DLL';

  FT_DOWN = 1;

  // PARAFORMAT2 wNumberingStyle options
  PFNS_PAREN = $0000; // default, e.g., 1)
  PFNS_PARENS = $0100; // tomListParentheses/256, e.g., (1)
  PFNS_PERIOD = $0200; // tomListPeriod/256, e.g., 1.
  PFNS_PLAIN = $0300; // tomListPlain/256, e.g., 1
  PFNS_NONUMBER = $0400; // Used for continuation w/o number
  PFNS_NEWNUMBER = $8000; // Start new number with wNumberingStart

  // (can be combined with other PFNS_xxx)

  EM_GETBIDIOPTIONS = (WM_USER + 200);
  EM_SETBIDIOPTIONS = (WM_USER + 201);
  EM_SETTYPOGRAPHYOPTIONS = (WM_USER + 202);
  EM_GETTYPOGRAPHYOPTIONS = (WM_USER + 203);

  // Options for EM_SETTYPOGRAPHYOPTIONS

  TO_ADVANCEDTYPOGRAPHY = 1;
  TO_SIMPLELINEBREAK = 2;
  TO_DISABLECUSTOMTEXTOUT = 4;
  TO_ADVANCEDLAYOUT = 8;

  // Options for EM_GET/EM_SET TYPOGRAPHYOPTIONS
  BOM_DEFPARADIR        = $0001;   // Default paragraph direction (implies alignment) (obsolete)
  BOM_PLAINTEXT         = $0002;   // Use plain text layout (obsolete)
  BOM_NEUTRALOVERRIDE   = $0004;   // Override neutral layout (obsolete)
  BOM_CONTEXTREADING    = $0008;   // Context reading order
  BOM_CONTEXTALIGNMENT  = $0010;   // Context alignment

  BOE_RTLDIR            = $0001;   // Default paragraph direction (implies alignment) (obsolete)
  BOE_PLAINTEXT         = $0002;   // Use plain text layout (obsolete)
  BOE_NEUTRALOVERRIDE   = $0004;   // Override neutral layout (obsolete)
  BOE_CONTEXTREADING    = $0008;   // Context reading order
  BOE_CONTEXTALIGNMENT  = $0010;   // Context alignment 



  // Underline types. RE 1.0 displays only CFU_UNDERLINE
  CFU_CF1UNDERLINE = $FF; // Map charformat's bit underline to CF2
  CFU_INVERT = $FE; // For IME composition fake a selection
  CFU_UNDERLINETHICKLONGDASH = 18; // (*) display as dash
  CFU_UNDERLINETHICKDOTTED = 17; // (*) display as dot
  CFU_UNDERLINETHICKDASHDOTDOT = 16; // (*) display as dash dot dot
  CFU_UNDERLINETHICKDASHDOT = 15; // (*) display as dash dot
  CFU_UNDERLINETHICKDASH = 14; // (*) display as dash
  CFU_UNDERLINELONGDASH = 13; // (*) display as dash
  CFU_UNDERLINEHEAVYWAVE = 12; // (*) display as wave
  CFU_UNDERLINEDOUBLEWAVE = 11; // (*) display as wave
  CFU_UNDERLINEHAIRLINE = 10; // (*) display as single
  CFU_UNDERLINETHICK = 9;
  CFU_UNDERLINEWAVE = 8;
  CFU_UNDERLINEDASHDOTDOT = 7;
  CFU_UNDERLINEDASHDOT = 6;
  CFU_UNDERLINEDASH = 5;
  CFU_UNDERLINEDOTTED = 4;
  CFU_UNDERLINEDOUBLE = 3; // (*) display as single
  CFU_UNDERLINEWORD = 2; // (*) display as single
  CFU_UNDERLINE = 1;
  CFU_UNDERLINENONE = 0;

  AttrFlags: array[TJvAttributeType] of Word =
  (0, SCF_SELECTION, SCF_WORD or SCF_SELECTION);

  CF_EMBEDDEDOBJECT = 'Embedded Object';
  CF_LINKSOURCE = 'Link Source';

  EM_GETZOOM = (WM_USER + 224);
  EM_SETZOOM = (WM_USER + 225);

  // Some masks for tab alignment and leader handling
  // Note: not the official names which I don't know

  TA_ALIGNMENT = $0F000000; // Bits 24-27
  TA_LEADER = $F0000000; // Bits 28-31
  //TA_ALL       = $FF000000; // Bits 24-31
  TA_TAB = $00FFFFFF; // Tab: bits 0-23
  TA_TAB_LEADER = (TA_TAB or TA_LEADER);
  TA_TAB_ALIGNMENT = (TA_TAB or TA_ALIGNMENT);

  { Flags to specify which interfaces should be returned in the structure above }

  REO_GETOBJ_NO_INTERFACES = $00000000;
  REO_GETOBJ_POLEOBJ = $00000001;
  REO_GETOBJ_PSTG = $00000002;
  REO_GETOBJ_POLESITE = $00000004;
  REO_GETOBJ_ALL_INTERFACES = $00000007;

  { Place object at selection }

  REO_CP_SELECTION = ULONG(-1);

  { Use character position to specify object instead of index }

  REO_IOB_SELECTION = ULONG(-1);
  REO_IOB_USE_CP = ULONG(-2);

  { Object flags }

  REO_NULL = $00000000; { No flags                         }
  REO_READWRITEMASK = $0000003F; { Mask out RO bits                 }
  REO_DONTNEEDPALETTE = $00000020; { Object doesn't need palette      }
  REO_BLANK = $00000010; { Object is blank                  }
  REO_DYNAMICSIZE = $00000008; { Object defines size always       }
  REO_INVERTEDSELECT = $00000004; { Object drawn all inverted if sel }
  REO_BELOWBASELINE = $00000002; { Object sits below the baseline   }
  REO_RESIZABLE = $00000001; { Object may be resized            }
  REO_LINK = $80000000; { Object is a link (RO)            }
  REO_STATIC = $40000000; { Object is static (RO)            }
  REO_SELECTED = $08000000; { Object selected (RO)             }
  REO_OPEN = $04000000; { Object open in its server (RO)   }
  REO_INPLACEACTIVE = $02000000; { Object in place active (RO)      }
  REO_HILITED = $01000000; { Object is to be hilited (RO)     }
  REO_LINKAVAILABLE = $00800000; { Link believed available (RO)     }
  REO_GETMETAFILE = $00400000; { Object requires metafile (RO)    }

  { Flags for IRichEditOle.GetClipboardData,   }
  { IRichEditOleCallback.GetClipboardData and  }
  { IRichEditOleCallback.QueryAcceptData       }

  RECO_PASTE = $00000000; { paste from clipboard  }
  RECO_DROP = $00000001; { drop                  }
  RECO_COPY = $00000002; { copy to the clipboard }
  RECO_CUT = $00000003; { cut to the clipboard  }
  RECO_DRAG = $00000004; { drag                  }

  ReadError = $0001;
  WriteError = $0002;
  NoError = $0000;

  RichLangOptions: array[TRichLangOption] of DWORD = (IMF_AUTOKEYBOARD,
    IMF_AUTOFONT, IMF_IMECANCELCOMPLETE, IMF_IMEALWAYSSENDNOTIFY);

  CHex: array[0..$F] of Char =
  ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    'A', 'B', 'C', 'D', 'E', 'F');

  { Converter API names }

  ForeignToRtf32Name = 'ForeignToRtf32';
  InitConverter32Name = 'InitConverter32';
  IsFormatCorrect32Name = 'IsFormatCorrect32';
  RtfToForeign32Name = 'RtfToForeign32';
  UninitConverterName = 'UninitConverter';
  CchFetchLpszErrorName = 'CchFetchLpszError';

  CConvertBufferSize = $1004;

var
  { Clipboard formats }
  CFEmbeddedObject: Integer;
  CFLinkSource: Integer;
  CFRtf: Integer;
  CFRtfNoObjs: Integer;

  { Global converter vars }
  GlobalConversionFormatList: TConversionFormatList = nil;
  GCurrentConverter: TJvMSTextConversion = nil;
  GMSTextConvertersRegistered: Boolean;

  Painting: Boolean = False;

//=== Local procedures =======================================================

function GConversionFormatList: TConversionFormatList;
begin
  if not Assigned(GlobalConversionFormatList) then
    GlobalConversionFormatList := TConversionFormatList.Create;
  Result := GlobalConversionFormatList;
end;

function GetParentWindow(Control: TControl): THandle;
begin
  if Control <> nil then
    Control := GetParentForm(Control);

  if Control is TWinControl then
    Result := TWinControl(Control).Handle
  else
    Result := Application.Handle;
end;

{ OLE utility routines }

function WStrLen(Str: PWideChar): Integer;
begin
  Result := 0;
  while Str[Result] <> #0 do
    Inc(Result);
end;

procedure ReleaseObject(var Obj);
begin
  if IUnknown(Obj) <> nil then
  begin
    IUnknown(Obj) := nil;
  end;
end;

procedure CreateStorage(var Storage: IStorage);
var
  LockBytes: ILockBytes;
begin
  OleCheck(CreateILockBytesOnHGlobal(0, True, LockBytes));
  try
    OleCheck(StgCreateDocfileOnILockBytes(LockBytes,
      STGM_READWRITE or STGM_SHARE_EXCLUSIVE or STGM_CREATE, 0, Storage));
  finally
    ReleaseObject(LockBytes);
  end;
end;

procedure DestroyMetaPict(MetaPict: HGLOBAL);
begin
  if MetaPict <> 0 then
  begin
    DeleteMetaFile(PMetafilePict(GlobalLock(MetaPict))^.hMF);
    GlobalUnlock(MetaPict);
    GlobalFree(MetaPict);
  end;
end;

function OleSetDrawAspect(OleObject: IOleObject; Iconic: Boolean;
  IconMetaPict: HGLOBAL; var DrawAspect: Longint): HRESULT;
var
  OleCache: IOleCache;
  EnumStatData: IEnumStatData;
  OldAspect, AdviseFlags, Connection: Longint;
  TempMetaPict: HGLOBAL;
  FormatEtc: TFormatEtc;
  Medium: TStgMedium;
  ClassID: TCLSID;
  StatData: TStatData;
begin
  Result := S_OK;
  OldAspect := DrawAspect;
  if Iconic then
  begin
    DrawAspect := DVASPECT_ICON;
    AdviseFlags := ADVF_NODATA;
  end
  else
  begin
    DrawAspect := DVASPECT_CONTENT;
    AdviseFlags := ADVF_PRIMEFIRST;
  end;
  if (DrawAspect <> OldAspect) or (DrawAspect = DVASPECT_ICON) then
  begin
    Result := OleObject.QueryInterface(IOleCache, OleCache);
    if Succeeded(Result) then
    try
      if DrawAspect <> OldAspect then
      begin
        { Setup new cache with the new aspect }
        FillChar(FormatEtc, SizeOf(FormatEtc), 0);
        FormatEtc.dwAspect := DrawAspect;
        FormatEtc.lindex := -1;
        Result := OleCache.Cache(FormatEtc, AdviseFlags, Connection);
      end;
      if Succeeded(Result) and (DrawAspect = DVASPECT_ICON) then
      begin
        TempMetaPict := 0;
        if IconMetaPict = 0 then
        begin
          if Succeeded(OleObject.GetUserClassID(ClassID)) then
          begin
            TempMetaPict := OleGetIconOfClass(ClassID, nil, True);
            IconMetaPict := TempMetaPict;
          end;
        end;
        try
          FormatEtc.cfFormat := CF_METAFILEPICT;
          FormatEtc.ptd := nil;
          FormatEtc.dwAspect := DVASPECT_ICON;
          FormatEtc.lindex := -1;
          FormatEtc.tymed := TYMED_MFPICT;
          Medium.tymed := TYMED_MFPICT;
          Medium.hMetaFilePict := IconMetaPict;
          Medium.unkForRelease := nil;
          Result := OleCache.SetData(FormatEtc, Medium, False);
        finally
          DestroyMetaPict(TempMetaPict);
        end;
      end;
      if Succeeded(Result) and (DrawAspect <> OldAspect) then
      begin
        { remove any existing caches that are set up for the old display aspect }
        OleCache.EnumCache(EnumStatData);
        if EnumStatData <> nil then
        try
          while EnumStatData.Next(1, StatData, nil) = 0 do
            if StatData.FormatEtc.dwAspect = OldAspect then
              OleCache.Uncache(StatData.dwConnection);
        finally
          ReleaseObject(EnumStatData);
        end;
      end;
    finally
      ReleaseObject(OleCache);
    end;
    if Succeeded(Result) and (DrawAspect <> DVASPECT_ICON) then
      OleObject.Update;
  end;
end;

function GetIconMetaPict(OleObject: IOleObject; DrawAspect: Longint): HGLOBAL;
var
  DataObject: IDataObject;
  FormatEtc: TFormatEtc;
  Medium: TStgMedium;
  ClassID: TCLSID;
begin
  Result := 0;
  if DrawAspect = DVASPECT_ICON then
  begin
    OleObject.QueryInterface(IDataObject, DataObject);
    if DataObject <> nil then
    begin
      FormatEtc.cfFormat := CF_METAFILEPICT;
      FormatEtc.ptd := nil;
      FormatEtc.dwAspect := DVASPECT_ICON;
      FormatEtc.lindex := -1;
      FormatEtc.tymed := TYMED_MFPICT;
      if Succeeded(DataObject.GetData(FormatEtc, Medium)) then
        Result := Medium.hMetaFilePict;
      ReleaseObject(DataObject);
    end;
  end;
  if Result = 0 then
  begin
    OleCheck(OleObject.GetUserClassID(ClassID));
    Result := OleGetIconOfClass(ClassID, nil, True);
  end;
end;

{ Return the first piece of a moniker }

function OleStdGetFirstMoniker(Moniker: IMoniker): IMoniker;
var
  Mksys: Longint;
  EnumMoniker: IEnumMoniker;
begin
  Result := nil;
  if Moniker <> nil then
  begin
    if (Moniker.IsSystemMoniker(Mksys) = 0) and
      (Mksys = MKSYS_GENERICCOMPOSITE) then
    begin
      if Moniker.Enum(True, EnumMoniker) <> 0 then
        Exit;
      EnumMoniker.Next(1, Result, nil);
      ReleaseObject(EnumMoniker);
    end
    else
    begin
      Result := Moniker;
    end;
  end;
end;

{ Return length of file moniker piece of the given moniker }

function OleStdGetLenFilePrefixOfMoniker(Moniker: IMoniker): Integer;
var
  MkFirst: IMoniker;
  BindCtx: IBindCtx;
  Mksys: Longint;
  P: PWideChar;
begin
  Result := 0;
  if Moniker <> nil then
  begin
    MkFirst := OleStdGetFirstMoniker(Moniker);
    if MkFirst <> nil then
    begin
      if (MkFirst.IsSystemMoniker(Mksys) = 0) and
        (Mksys = MKSYS_FILEMONIKER) then
      begin
        if CreateBindCtx(0, BindCtx) = 0 then
        begin
          if (MkFirst.GetDisplayName(BindCtx, nil, P) = 0) and (P <> nil) then
          begin
            Result := WStrLen(P);
            CoTaskMemFree(P);
          end;
          ReleaseObject(BindCtx);
        end;
      end;
      ReleaseObject(MkFirst);
    end;
  end;
end;

function CoAllocCStr(const S: string): PChar;
begin
  Result := StrCopy(CoTaskMemAlloc(Length(S) + 1), PChar(S));
end;

function WStrToString(P: PWideChar): string;
begin
  Result := '';
  if P <> nil then
  begin
    Result := WideCharToString(P);
    CoTaskMemFree(P);
  end;
end;

function GetFullNameStr(OleObject: IOleObject): string;
var
  P: PWideChar;
begin
  OleObject.GetUserType(USERCLASSTYPE_FULL, P);
  Result := WStrToString(P);
end;

function GetShortNameStr(OleObject: IOleObject): string;
var
  P: PWideChar;
begin
  OleObject.GetUserType(USERCLASSTYPE_SHORT, P);
  Result := WStrToString(P);
end;

function GetDisplayNameStr(OleLink: IOleLink): string;
var
  P: PWideChar;
begin
  OleLink.GetSourceDisplayName(P);
  Result := WStrToString(P);
end;

function GetVCLFrameForm(Form: TCustomForm): IVCLFrameForm;
begin
  if Form.OleFormObject = nil then
    TOleForm.Create(Form);
  Result := Form.OleFormObject as IVCLFrameForm;
end;

function IsFormMDIChild(Form: TCustomForm): Boolean;
begin
  Result := (Form is TForm) and (TForm(Form).FormStyle = fsMDIChild);
end;

procedure LinkError(const Ident: string);
begin
  Application.MessageBox(PChar(Ident), PChar(SLinkProperties),
    MB_OK or MB_ICONSTOP);
end;

{ Get RichEdit OLE interface }

function GetRichEditOle(Wnd: HWND; var RichEditOle): Boolean;
begin
  Result := SendMessage(Wnd, EM_GETOLEINTERFACE, 0, Longint(@RichEditOle)) <> 0;
end;

function StreamSave(dwCookie: Longint; pbBuff: PByte;
  cb: Longint; var pcb: Longint): Longint; stdcall;
var
  Converter: TJvConversion;
begin
  Result := NoError;
  Converter := TJvConversion(dwCookie);
  try
    pcb := 0;
    if Converter <> nil then
      pcb := Converter.ConvertWrite(PChar(pbBuff), cb);
  except
    Result := WriteError;
  end;
end;

type
  TCookie = class
  private
    FConverter: TJvConversion;
    FSkipLf, FSkipCr: Boolean;
    // SourceLength is number of characters
    function AdjustLineBreaks(Dest, Source: PChar; SourceLength: Integer): Integer;
    function AdjustLineBreaksW(Dest, Source: PWideChar; SourceLength: Integer): Integer;
  public
    constructor Create(AConverter: TJvConversion);
    // BufferSize is the size of the Buffer in bytes
    function Load(Buffer: PByte; BufferSize: Longint): Longint;
    function LoadW(Buffer: PByte; BufferSize: Longint): Longint;
    property Converter: TJvConversion read FConverter;
  end;

{ AdjustLineBreaks adjusts all line breaks in the given string S to be true
  #13/#10 sequences. The function changes any #13 characters not followed by a #10
  and any #10 characters not preceded by a #13 into #13/#10 pairs. It also
  converts #10/#13 pairs to #13/#10 pairs. The #10/#13 pair is common in Unix text
  files. (SysUtils)
}

function TCookie.AdjustLineBreaks(Dest, Source: PChar; SourceLength: Integer): Integer;
var
  SourceEnd: PChar;
  DestStart: PChar;
begin
  SourceEnd := Source + SourceLength;
  DestStart := Dest;
  while Source < SourceEnd do
  begin
    case Source^ of
      Lf:
        if FSkipLf then
          FSkipLf := False
        else
        begin
          Dest^ := Cr;
          Inc(Dest);
          Dest^ := Lf;
          Inc(Dest);
          FSkipCr := True;
        end;
      Cr:
        if FSkipCr then
          FSkipCr := False
        else
        begin
          Dest^ := Cr;
          Inc(Dest);
          Dest^ := Lf;
          Inc(Dest);
          FSkipLf := True;
        end;
    else
      FSkipCr := False;
      FSkipLf := False;
      Dest^ := Source^;
      Inc(Dest);
    end;
    Inc(Source);
  end;
  Result := Dest - DestStart;
end;

function TCookie.AdjustLineBreaksW(Dest, Source: PWideChar;
  SourceLength: Integer): Integer;
var
  SourceEnd: PWideChar;
  DestStart: PWideChar;
begin
  SourceEnd := Source + SourceLength;
  DestStart := Dest;
  while Source < SourceEnd do
  begin
    case Source^ of
      #10:
        if FSkipLf then
          FSkipLf := False
        else
        begin
          Dest^ := #13;
          Inc(Dest);
          Dest^ := #10;
          Inc(Dest);
          FSkipCr := True;
        end;
      #13:
        if FSkipCr then
          FSkipCr := False
        else
        begin
          Dest^ := #13;
          Inc(Dest);
          Dest^ := #10;
          Inc(Dest);
          FSkipLf := True;
        end;
    else
      FSkipCr := False;
      FSkipLf := False;
      Dest^ := Source^;
      Inc(Dest);
    end;
    Inc(Source);
  end;
  Result := Dest - DestStart;
end;

constructor TCookie.Create(AConverter: TJvConversion);
begin
  inherited Create;
  FConverter := AConverter;
end;

function TCookie.Load(Buffer: PByte; BufferSize: Longint): Longint;
var
  pBuff: PChar;
begin
  BufferSize := BufferSize div 2;
  Result := 0;
  pBuff := PChar(Buffer) + BufferSize;
  if Converter <> nil then
    Result := Converter.ConvertRead(pBuff, BufferSize);
  if Result > 0 then
    Result := AdjustLineBreaks(PChar(Buffer), pBuff, Result);
end;

function TCookie.LoadW(Buffer: PByte; BufferSize: Integer): Longint;
var
  pBuff: PWideChar;
begin
  // AdjustLineBreaksW can double the needed buffer size; so tell the converter
  // to use only half the buffer and (Mantis #4129) ensure BufferSize is even.
  BufferSize := (BufferSize div 4) * 2;
  Result := 0;
  pBuff := PWideChar(Buffer) + BufferSize div 2;
  if Converter <> nil then
    Result := Converter.ConvertRead(PChar(pBuff), BufferSize);
  if Result > 0 then
    Result := 2 * AdjustLineBreaksW(PWideChar(Buffer), PWideChar(pBuff), Result div 2);
end;

function StreamLoad(dwCookie: Longint; pbBuff: PByte;
  cb: Longint; var pcb: Longint): Longint; stdcall;
begin
  Result := NoError;
  try
    pcb := TCookie(dwCookie).Load(pbBuff, cb);
  except
    Result := ReadError;
  end;
end;

function StreamLoadW(dwCookie: Longint; pbBuff: PByte;
  cb: Longint; var pcb: Longint): Longint; stdcall;
begin
  Result := NoError;
  try
    pcb := TCookie(dwCookie).LoadW(pbBuff, cb);
  except
    Result := ReadError;
  end;
end;

function FileNameToHGLOBAL(const AFileName: string): HGLOBAL;
var
  DataPtr: Pointer;
  Buffer: array[0..MAX_PATH] of Char;
begin
  // DOC : Each entry point that accepts file names should expect all file name
  //       arguments from Word to be in the OEM character set (unless the character
  //       set is explicitly negotiated using RegisterApp).
  //
  //  For example: CharToOem will translate the copyright (c) symbol (=1 char)
  //  to C¸ (or something). Not doing so will result in errors.

  StrCopy(Buffer, PChar(AFileName));
  CharToOem(Buffer, Buffer);

  Result := GlobalAlloc(GHND, StrLen(Buffer) + 1); // with last #0, thus + 1
  try
    DataPtr := GlobalLock(Result);
    try
      StrCopy(DataPtr, Buffer);
    finally
      GlobalUnlock(Result);
    end;
  except
    GlobalFree(Result);
    raise;
  end;
end;

function StringToHGLOBAL(const S: string): HGLOBAL;
var
  DataPtr: Pointer;
begin
  Result := GlobalAlloc(GHND, Length(S) + 1); // with last #0, thus + 1
  try
    DataPtr := GlobalLock(Result);
    try
      Move(PChar(S)^, DataPtr^, Length(S));
    finally
      GlobalUnlock(Result);
    end;
  except
    GlobalFree(Result);
    raise;
  end;
end;

function ExportCallback(cchBuff, nPercent: Longint): Longint; stdcall;
begin
  Result := GCurrentConverter.HandleExportCallback(cchBuff, nPercent);
end;

function ImportCallback(cchBuff, nPercent: Longint): Longint; stdcall;
begin
  Result := GCurrentConverter.HandleImportCallback(cchBuff, nPercent);
end;

function FCEToString(AErrorCode: FCE): string;
begin
  case AErrorCode of
    fceOpenInFileErr: Result := RsEOpenInFileErr;
    fceReadErr: Result := RsEReadErr;
    fceOpenConvErr: Result := RsEOpenConvErr;
    fceWriteErr: Result := RsEWriteErr;
    fceInvalidFile: Result := RsEInvalidFile;
    fceOpenExceptErr: Result := RsEOpenExceptErr;
    fceWriteExceptErr: Result := RsEWriteExceptErr;
    fceNoMemory: Result := RsENoMemory;
    fceInvalidDoc: Result := RsEInvalidDoc;
    fceDiskFull: Result := RsEDiskFull;
    fceDocTooLarge: Result := RsEDocTooLarge;
    fceOpenOutFileErr: Result := RsEOpenOutFileErr;
    fceUserCancel: Result := RsEUserCancel;
    fceWrongFileType: Result := RsEWrongFileType;
  else
    Result := '';
  end;
end;

//=== Global procedures ======================================================

procedure BitmapToRTF(ABitmap: TBitmap; AStream: TStream);
const
  CPrefix = '{\rtf1 {\pict\picw%d\pich%d\dibitmap0 ';
  CPostfix = ' }}';
var
  Header, Bits: PChar;
  HeaderSize, BitsSize: DWORD;
  P, Q: PChar;
  S: string;
begin
  GetDIBSizes(ABitmap.Handle, HeaderSize, BitsSize);
  GetMem(Header, 2 * (HeaderSize + BitsSize));
  try
    Bits := Header + HeaderSize;
    GetDIB(ABitmap.Handle, ABitmap.Palette, Header^, Bits^);

    { Example :

      HeaderSize = 2, BitsSize = 2

      Header = $AB, $00, $DE, $F8, ?? , ?? , ?? , ??
      ->
      Header = 'A', 'B', '0', '0', 'D', 'E', 'F', '8'
    }
    Q := Header + HeaderSize + BitsSize - 1;
    //P := Header + 2 * (HeaderSize + BitsSize) - 1;
    P := Q + HeaderSize + BitsSize;
    while Q >= Header do
    begin
      P^ := CHex[Byte(Q^) mod 16];
      Dec(P);
      P^ := CHex[Byte(Q^) div 16];
      Dec(P);
      Dec(Q);
    end;
    S := Format(CPrefix, [ABitmap.Width, ABitmap.Height]);
    AStream.Write(PChar(S)^, Length(S));
    AStream.Write(Header^, (HeaderSize + BitsSize) * 2);
    AStream.Write(CPostfix, Length(CPostfix));
  finally
    FreeMem(Header);
  end;
end;

function BitmapToRTF2(ABitmap: TBitmap; AStream: TStream): Boolean;

{

  \wmetafileN  - Source of the picture is a Windows metafile. The N argument
                 identifies the metafile type (the default type is 1).
  \picwN       - xExt field if the picture is a Windows metafile; picture
                 width in pixels if the picture is a bitmap or from QuickDraw.
                 The N argument is a long integer.
  \pichN       - yExt field if the picture is a Windows metafile; picture
                 height in pixels if the picture is a bitmap or from QuickDraw.
                 The N argument is a long integer.
  \picwgoalN   - Desired width of the picture in twips. The N argument is a
                 long integer.
  \pichgoalN   - Desired height of the picture in twips. The N argument is a
                 long integer.
}

const
  CPrefix = '{\rtf1 {\pict\wmetafile8\picw%d\pich%d\picwgoal%d\pichgoal%d ';
  CPostfix = ' }}';
var
  P, Q: PChar;
  S: string;
  DC: HDC;
  MetafileHandle: HMETAFILE;
  Size: TPoint;
  BitsLength: UINT;
  Bits: PChar;
begin
  Result := False;

  // Retrieve Extent
  Size.X := MulDiv(ABitmap.Width, CHundredthMMPerInch, Screen.PixelsPerInch);
  Size.Y := MulDiv(ABitmap.Height, CHundredthMMPerInch, Screen.PixelsPerInch);

  // Create Metafile DC and set it up
  DC := CreateMetafile(nil);

  SetWindowOrgEx(DC, 0, 0, nil);
  SetWindowExtEx(DC, Size.X, Size.Y, nil);

  StretchBlt(DC, 0, 0, Size.X, Size.Y,
    ABitmap.Canvas.Handle, 0, 0, ABitmap.Width, ABitmap.Height, SRCCOPY);

  MetafileHandle := CloseMetaFile(DC);

  if MetafileHandle = 0 then
    Exit;

  try
    BitsLength := GetMetaFileBitsEx(MetafileHandle, 0, nil);
    GetMem(Bits, BitsLength * 2);
    try
      if GetMetaFileBitsEx(MetafileHandle, BitsLength, Bits) < BitsLength then
        Exit;

      Q := Bits + BitsLength - 1;
      //P := Bits + 2 * BitsLength - 1;
      P := Q + BitsLength;
      while Q >= Bits do
      begin
        P^ := CHex[Byte(Q^) mod 16];
        Dec(P);
        P^ := CHex[Byte(Q^) div 16];
        Dec(P);
        Dec(Q);
      end;

      S := Format(CPrefix, [Size.X, Size.Y,
        MulDiv(ABitmap.Width, CTwipsPerInch, Screen.PixelsPerInch),
          MulDiv(ABitmap.Height, CTwipsPerInch, Screen.PixelsPerInch)]);
      AStream.Write(PChar(S)^, Length(S));
      AStream.Write(Bits^, BitsLength * 2);
      AStream.Write(CPostfix, Length(CPostfix));

      Result := True;
    finally
      FreeMem(Bits, BitsLength * 2);
    end;
  finally
    DeleteMetaFile(MetafileHandle);
  end;
end;

//=== { EMSTextConversionError } =============================================

constructor EMSTextConversionError.Create(const Msg: string; AErrorCode: FCE);
var
  S: string;
begin
  S := Msg;
  if S = '' then
  begin
    S := FCEToString(AErrorCode);
    if S = '' then
      S := Format(RsEConversionError, [AErrorCode]);
  end;
  inherited Create(S);
  FErrorCode := AErrorCode;
end;

//=== { TConversionFormatList } ==============================================

constructor TConversionFormatList.Create;
begin
  inherited Create;
  FRTFConvIndex := Add(TJvRTFConversion.Create);
  FTextConvIndex := Add(TJvTextConversion.Create);
end;

function TConversionFormatList.DefaultConverter: TJvConversion;
begin
  Result := Items[FRTFConvIndex];
end;

function TConversionFormatList.GetConverter(
  AParentWindow: THandle; AStream: TStream;
  const Kind: TJvConversionKind): TJvConversion;
begin
  { Return either the RTF converter or the text converter }
  Result := Items[FRTFConvIndex];
  Result.Init(AParentWindow);
  if Result.CanHandle(Kind) and
    ((Kind <> ckImport) or Result.IsFormatCorrect(AStream)) then
    { Caller must call Done }
    Exit;
  Result.Done;

  Result := Items[FTextConvIndex];
  Result.Init(AParentWindow);
end;

function TConversionFormatList.GetConverter(AParentWindow: THandle;
  const AFileName: string; const Kind: TJvConversionKind): TJvConversion;
var
  Ext: string;
  I: Integer;
begin
  Ext := AnsiLowerCaseFileName(ExtractFileExt(AFileName));
  System.Delete(Ext, 1, 1);

  for I := 0 to Count - 1 do
  begin
    Result := Items[I];
    Result.Init(AParentWindow);
    if Result.CanHandle(Ext, Kind) and
      ((Kind <> ckImport) or Result.IsFormatCorrect(AFileName)) then
      { Caller must call Done }
      Exit;
    Result.Done;
  end;
  Result := nil;
end;

function TConversionFormatList.GetFilter(const AKind: TJvConversionKind): string;
var
  I: Integer;
begin
  Result := '';

  for I := 0 to Count - 1 do
    if Items[I].CanHandle(AKind) then
      Result := Result + Items[I].Filter + '|';

  if Result > '' then
    System.Delete(Result, Length(Result), 1);
end;

function TConversionFormatList.GetItem(Index: Integer): TJvConversion;
begin
  Result := inherited Items[Index] as TJvConversion;
end;

//=== { TImageDataObject } ===================================================

constructor TImageDataObject.Create(AGraphic: TGraphic);
begin
  inherited Create;
  FGraphic := AGraphic;
end;

function TImageDataObject.DAdvise(const FormatEtc: TFormatEtc;
  advf: Integer; const advSink: IAdviseSink;
  out dwConnection: Integer): HRESULT;
begin
  Result := OLE_E_ADVISENOTSUPPORTED;
end;

function TImageDataObject.DUnadvise(dwConnection: Integer): HRESULT;
begin
  Result := OLE_E_ADVISENOTSUPPORTED;
end;

function TImageDataObject.EnumDAdvise(
  out enumAdvise: IEnumStatData): HRESULT;
begin
  Result := OLE_E_ADVISENOTSUPPORTED;
end;

function TImageDataObject.EnumFormatEtc(dwDirection: Integer;
  out EnumFormatEtc: IEnumFormatEtc): HRESULT;
begin
  EnumFormatEtc := nil;
  Result := E_NOTIMPL;
end;

function TImageDataObject.GetCanonicalFormatEtc(const FormatEtc: TFormatEtc;
  out FormatEtcOut: TFormatEtc): HRESULT;
begin
  FormatEtcOut.ptd := nil;
  Result := E_NOTIMPL;
end;

function TImageDataObject.GetData(const FormatEtcIn: TFormatEtc;
  out Medium: TStgMedium): HRESULT;
var
  SizeMetric: TPoint;
  Buffer: Pointer;
  Length: UINT;
  DC: HDC;
  hMF: HMETAFILE;
  hMem: THandle;
  pMFP: PMetafilePict;
begin
  // Handle only MetaFile
  if (FormatEtcIn.tymed and TYMED_MFPICT) = 0 then
  begin
    Result := DV_E_FORMATETC;
    Exit;
  end;
  if FGraphic is TMetafile then //Get a Win3x-style HMETAFILE handle
    with TMetafile(FGraphic) do //from a HENHMETAFILE one.
    begin
      SizeMetric.X := MMWidth;
      SizeMetric.Y := MMHeight;
      Buffer := nil;
      Length := 0;
      DC := GetDC(0);
      try
        Length := GetWinMetaFileBits(Handle, 0, nil, MM_ANISOTROPIC, DC);
        GetMem(Buffer, Length);
        if GetWinMetaFileBits(Handle, Length, Buffer,
             MM_ANISOTROPIC, DC) = Length then
          hMF := SetMetaFileBitsEx(Length, Buffer)
        else
          hMF := 0;
      finally
        if Buffer <> nil then
          FreeMem(Buffer, Length);
        ReleaseDC(0, DC);
      end;
    end
  else
  begin
    // convert pixels to mm
    SizeMetric.X := MulDiv(FGraphic.Width,
      cHundredthMMPerInch, Screen.PixelsPerInch);
    SizeMetric.Y := MulDiv(FGraphic.Height,
      cHundredthMMPerInch, Screen.PixelsPerInch);
    // Create Metafile DC and set it up
    DC := CreateMetafile(nil);
    SetWindowOrgEx(DC, 0, 0, nil);
    SetWindowExtEx(DC, SizeMetric.X, SizeMetric.Y, nil);

    if FGraphic.ClassType = TIcon then
      DrawIconEx(DC, 0, 0, TIcon(FGraphic).Handle, SizeMetric.X, SizeMetric.Y,
        0, 0, DI_NORMAL)
    else
      with TCanvas.Create do
      try
        Handle := DC;
        StretchDraw(Rect(0, 0, SizeMetric.X, SizeMetric.Y), FGraphic);
      finally
        Free;
      end;
    hMF := CloseMetaFile(DC);
  end;
  if hMF = 0 then
  begin
    Result := E_UNEXPECTED;
    Exit;
  end;

  // Get memory handle
  hMem := GlobalAlloc(GMEM_SHARE or GMEM_MOVEABLE, SizeOf(METAFILEPICT));
  if hMem = 0 then
  begin
    DeleteMetaFile(hMF);
    Result := STG_E_MEDIUMFULL;
    Exit;
  end;
  pMFP := PMetafilePict(GlobalLock(hMem));
  pMFP^.hMF := hMF;
  pMFP^.mm := MM_ANISOTROPIC;
  pMFP^.xExt := SizeMetric.X;
  pMFP^.yExt := SizeMetric.Y;
  GlobalUnlock(hMem);

  Medium.tymed := TYMED_MFPICT;
  Medium.hGlobal := hMem;
  Medium.unkForRelease := nil;

  Result := S_OK;
end;

function TImageDataObject.GetDataHere(const FormatEtc: TFormatEtc;
  out Medium: TStgMedium): HRESULT;
begin
  Result := E_NOTIMPL;
end;

function TImageDataObject.QueryGetData(const FormatEtc: TFormatEtc): HRESULT;
begin
  Result := E_NOTIMPL;
end;

function TImageDataObject.SetData(const FormatEtc: TFormatEtc;
  var Medium: TStgMedium; fRelease: BOOL): HRESULT;
begin
  Result := E_NOTIMPL;
end;

//=== { TJvConversion } ======================================================

function TJvConversion.CanHandle(const AKind: TJvConversionKind): Boolean;
begin
  Result := True;
end;

function TJvConversion.CanHandle(const AExtension: string;
  const AKind: TJvConversionKind): Boolean;
begin
  Result := True;
end;

function TJvConversion.ConvertRead(Buffer: PChar;
  BufSize: Integer): Integer;
begin
  Result := -1;
end;

function TJvConversion.ConvertWrite(Buffer: PChar;
  BufSize: Integer): Integer;
begin
  Result := -1;
end;

procedure TJvConversion.Done;
begin
  FParentWindow := 0;
end;

procedure TJvConversion.DoProgress(APercentDone: Integer);
begin
  if APercentDone < 0 then
    APercentDone := 0
  else
  if APercentDone > 100 then
    APercentDone := 100;
  if APercentDone <> FPercentDone then
  begin
    FPercentDone := APercentDone;
    if Assigned(FOnProgress) then
      FOnProgress(Self);
  end;
end;

function TJvConversion.Error: Boolean;
begin
  Result := False;
end;

function TJvConversion.ErrorStr: string;
begin
  Result := '';
end;

function TJvConversion.Filter: string;
begin
  Result := '';
end;

procedure TJvConversion.Init(AParentWindow: THandle);
begin
  FParentWindow := AParentWindow;
end;

function TJvConversion.IsFormatCorrect(const AFileName: string): Boolean;
begin
  Result := True;
end;

function TJvConversion.IsFormatCorrect(AStream: TStream): Boolean;
begin
  Result := True;
end;

function TJvConversion.Open(const AFileName: string;
  const AKind: TJvConversionKind): Boolean;
begin
  Result := False;
end;

function TJvConversion.Open(Stream: TStream;
  const AKind: TJvConversionKind): Boolean;
begin
  Result := False;
end;

function TJvConversion.Retry: Boolean;
begin
  Result := False;
end;

function TJvConversion.TextKind: TJvConversionTextKind;
begin
  Result := ctkRTF;
end;

function TJvConversion.UserCancel: Boolean;
begin
  Result := False;
end;

//=== { TJvCustomRichEdit } ==================================================

constructor TJvCustomRichEdit.Create(AOwner: TComponent);
var
  DC: HDC;
begin
  inherited Create(AOwner);
  { If you create a TJvRichEdit control at design-time the Text of the control
    will NOT be set to its Name because csSetCaption is excluded }
  // ControlStyle := ControlStyle + [csAcceptsControls] - [csSetCaption];
  ControlStyle := ControlStyle - [csSetCaption];
  IncludeThemeStyle(Self, [csNeedsBorderPaint]);
  FSelAttributes := TJvTextAttributes.Create(Self, atSelected);
  FDefAttributes := TJvTextAttributes.Create(Self, atDefaultText);
  FWordAttributes := TJvTextAttributes.Create(Self, atWord);
  FParagraph := TJvParaAttributes.Create(Self);
  FLines := TJvRichEditStrings.Create;
  TJvRichEditStrings(FLines).FRichEdit := Self;
  TabStop := True;
  Width := 185;
  Height := 89;
  AutoSize := False;
  DoubleBuffered := False;
  FAllowObjects := True;
  FAllowInPlace := True;
  FAutoVerbMenu := True;
  FHideSelection := True;
  FHideScrollBars := True;
  ScrollBars := ssBoth;
  FSelectionBar := True;
  FAutoAdvancedTypography := True;
  FOLEDragDrop := True;
  FLangOptions := [rlAutoFont];
  DC := GetDC(HWND_DESKTOP);
  FScreenLogPixels := GetDeviceCaps(DC, LOGPIXELSY);
  ReleaseDC(HWND_DESKTOP, DC);
  DefaultConverter := nil;
  FOldParaAlignment := TParaAlignment(Alignment);
  FUndoLimit := 100;
  FAutoURLDetect := True;
  FWordSelection := True;
  with FClickRange do
  begin
    cpMin := -1;
    cpMax := -1;
  end;
  FForceUndo := True;
  FCallback := TRichEditOleCallback.Create(Self);
  FUseFixedPopup := True;
  Perform(CM_PARENTBIDIMODECHANGED, 0, 0);
end;

destructor TJvCustomRichEdit.Destroy;
begin
  FLastFind := nil;
  FSelAttributes.Free;
  FDefAttributes.Free;
  FWordAttributes.Free;
  FParagraph.Free;
  FLines.Free;
  FState.Free;
  FPopupVerbMenu.Free;
  FFindDialog.Free;
  FReplaceDialog.Free;
  inherited Destroy;
  { be sure that callback object is destroyed after inherited Destroy }
  TRichEditOleCallback(FCallback).Free;
end;

procedure TJvCustomRichEdit.AddFormatText(const S: string; const AFont: TFont);
begin
  InsertFormatText(GetTextLen, S, AFont);
end;

procedure TJvCustomRichEdit.AddFormatText(const S: string;
  FontStyle: TFontStyles; const FontName: string; const FontColor: TColor;
  FontHeight: Integer);
begin
  InsertFormatText(GetTextLen, S, FontStyle, FontName, FontColor, FontHeight);
end;

procedure TJvCustomRichEdit.AdjustFindDialogPosition(Dialog: TFindDialog);
var
  TextRect, R: TRect;
begin
  if Dialog.Handle = 0 then
    Exit;
  with TextRect do
  begin
    TopLeft := ClientToScreen(GetCharPos(SelStart));
    BottomRight := ClientToScreen(GetCharPos(SelStart + SelLength));
    Inc(Bottom, 20);
  end;
  with Dialog do
  begin
    GetWindowRect(Handle, R);
    if PtInRect(R, TextRect.TopLeft) or PtInRect(R, TextRect.BottomRight) then
    begin
      if TextRect.Top > R.Bottom - R.Top + 20 then
        OffsetRect(R, 0, TextRect.Top - R.Bottom - 20)
      else
      begin
        if TextRect.Top + R.Bottom - R.Top < GetSystemMetrics(SM_CYSCREEN) then
          OffsetRect(R, 0, 40 + TextRect.Top - R.Top);
      end;
      Position := R.TopLeft;
    end;
  end;
end;

procedure TJvCustomRichEdit.Clear;
begin
  CloseObjects;
  inherited Clear;
  Modified := False;
end;

procedure TJvCustomRichEdit.ClearUndo;
begin
  SendMessage(Handle, EM_EMPTYUNDOBUFFER, 0, 0);
end;

procedure TJvCustomRichEdit.CloseActiveObject;
begin
  if FRichEditOle <> nil then
    IRichEditOle(FRichEditOle).InPlaceDeactivate;
end;

procedure TJvCustomRichEdit.CloseFindDialog(Dialog: TFindDialog);
begin
  if Assigned(FOnCloseFindDialog) then
    FOnCloseFindDialog(Self, Dialog);
end;

procedure TJvCustomRichEdit.CloseObjects;
var
  I: Integer;
  ReObject: TReObject;
begin
  if Assigned(FRichEditOle) then
  begin
    FillChar(ReObject, SizeOf(ReObject), 0);
    ReObject.cbStruct := SizeOf(ReObject);
    with IRichEditOle(FRichEditOle) do
    begin
      for I := GetObjectCount - 1 downto 0 do
        if Succeeded(GetObject(I, ReObject, REO_GETOBJ_POLEOBJ)) then
        begin
          if ReObject.dwFlags and REO_INPLACEACTIVE <> 0 then
            IRichEditOle(FRichEditOle).InPlaceDeactivate;
          ReObject.poleobj.Close(OLECLOSE_NOSAVE);
          ReleaseObject(ReObject.poleobj);
        end;
    end;
  end;
end;

procedure TJvCustomRichEdit.CMBiDiModeChanged(var Msg: TMessage);
var
  AParagraph: TParaFormat2;
  BiDiOptions: TBiDiOptions;
begin
  HandleNeeded; { we REALLY need the handle for BiDi }
  inherited;

  BiDiOptions.cbSize := sizeof(BiDiOptions);
  BiDiOptions.wMask := BOM_NEUTRALOVERRIDE or BOM_CONTEXTREADING or BOM_CONTEXTALIGNMENT;
  BiDiOptions.wEffects := BOE_NEUTRALOVERRIDE or BOE_CONTEXTREADING or BOE_CONTEXTALIGNMENT;
  SendMessage(Handle, EM_SETBIDIOPTIONS, 0, Integer(@BiDiOptions));

  Paragraph.GetAttributes(AParagraph);
  AParagraph.dwMask := PFM_ALIGNMENT;
  AParagraph.wAlignment := Ord(Alignment) + 1;
  Paragraph.SetAttributes(AParagraph);
end;

// From JvRichEdit.pas by Sébastien Buysse

procedure TJvCustomRichEdit.CMDocWindowActivate(var Msg: TMessage);
begin
  if Assigned(FCallback) then
    with TRichEditOleCallback(FCallback) do
      if Assigned(FDocForm) and IsFormMDIChild(FDocForm.Form) then
      begin
        if Msg.WParam = 0 then
        begin
          FFrameForm.SetMenu(0, 0, 0);
          FFrameForm.ClearBorderSpace;
        end;
      end;
end;

procedure TJvCustomRichEdit.CMUIDeactivate(var Msg: TMessage);
begin
  if (GetParentForm(Self) <> nil) and Assigned(FRichEditOle) and
    (GetParentForm(Self).ActiveOleControl = Self) then
    {IRichEditOle(FRichEditOle).InPlaceDeactivate};
end;

procedure TJvCustomRichEdit.CNNotify(var Msg: TWMNotify);
var
  AMsg: TMessage;
begin
  with Msg do
    case NMHdr^.code of
      EN_SELCHANGE:
        SelectionChange;
      EN_REQUESTRESIZE:
        RequestSize(PReqSize(NMHdr)^.rc);
      EN_SAVECLIPBOARD:
        with PENSaveClipboard(NMHdr)^ do
          if not SaveClipboard(cObjectCount, cch) then
            Result := 1;
      EN_PROTECTED:
        with PENProtected(NMHdr)^ do
        begin
          AMsg.Msg := Msg;
          AMsg.WParam := WParam;
          AMsg.LParam := LParam;
          AMsg.Result := 0;
          if not ProtectChange(AMsg, chrg.cpMin, chrg.cpMax) then
            Result := 1;
        end;
      EN_LINK:
        with PENLink(NMHdr)^ do
        begin
          case Msg of
            WM_RBUTTONDOWN:
              begin
                FClickRange := chrg;
                FClickBtn := mbRight;
              end;
            WM_RBUTTONUP:
              begin
                if (FClickBtn = mbRight) and (FClickRange.cpMin = chrg.cpMin) and
                  (FClickRange.cpMax = chrg.cpMax) then
                  URLClick(GetTextRange(chrg.cpMin, chrg.cpMax), mbRight);
                with FClickRange do
                begin
                  cpMin := -1;
                  cpMax := -1;
                end;
              end;
            WM_LBUTTONDOWN:
              begin
                FClickRange := chrg;
                FClickBtn := mbLeft;
              end;
            WM_LBUTTONUP:
              begin
                if (FClickBtn = mbLeft) and (FClickRange.cpMin = chrg.cpMin) and
                  (FClickRange.cpMax = chrg.cpMax) then
                  URLClick(GetTextRange(chrg.cpMin, chrg.cpMax), mbLeft);
                with FClickRange do
                begin
                  cpMin := -1;
                  cpMax := -1;
                end;
              end;
          end;
        end;
      EN_STOPNOUNDO:
        begin
          { cannot allocate enough memory to maintain the undo state }
        end;
    end;
end;

procedure TJvCustomRichEdit.ColorChanged;
begin
  inherited ColorChanged;
  SendMessage(Handle, EM_SETBKGNDCOLOR, 0, ColorToRGB(Color))
end;

procedure TJvCustomRichEdit.CreateParams(var Params: TCreateParams);
const
  HideScrollBars: array[Boolean] of DWORD = (ES_DISABLENOSCROLL, 0);
  HideSelections: array[Boolean] of DWORD = (ES_NOHIDESEL, 0);
  WordWraps: array[Boolean] of DWORD = (0, ES_AUTOHSCROLL);
  SelectionBars: array[Boolean] of DWORD = (0, ES_SELECTIONBAR);
  OLEDragDrops: array[Boolean] of DWORD = (ES_NOOLEDRAGDROP, 0);
begin
  inherited CreateParams(Params);
  case RichEditVersion of
    1:
      CreateSubClass(Params, RICHEDIT_CLASS10A);
  else
    CreateSubClass(Params, RICHEDIT_CLASS);
  end;
  with Params do
  begin
    Style := (Style and not (WS_HSCROLL or WS_VSCROLL)) or ES_SAVESEL or
      (WS_CLIPSIBLINGS or WS_CLIPCHILDREN);
    { NOTE: WS_CLIPCHILDREN and WS_CLIPSIBLINGS are essential otherwise }
    { once the object is inserted you see some painting problems.       }
    Style := Style and not (WS_HSCROLL or WS_VSCROLL);
    if ScrollBars in [ssVertical, ssBoth] then
      Style := Style or WS_VSCROLL;
    if (ScrollBars in [ssHorizontal, ssBoth]) and not WordWrap then
      Style := Style or WS_HSCROLL;
    Style := Style or OLEDragDrops[FOLEDragDrop] or HideScrollBars[FHideScrollBars] or
      SelectionBars[FSelectionBar] or HideSelections[FHideSelection] and
      not WordWraps[WordWrap];
    WindowClass.Style := WindowClass.Style and not (CS_HREDRAW or CS_VREDRAW);
  end;
end;

procedure TJvCustomRichEdit.CreateWindowHandle(const Params: TCreateParams);
var
  Bounds: TRect;
begin
  Bounds := BoundsRect;
  inherited CreateWindowHandle(Params);
  if HandleAllocated then
    BoundsRect := Bounds;
end;

procedure TJvCustomRichEdit.CreateWnd;
var
  SavedAdvancedTypography: Boolean;
  SavedModified: Boolean;
  Mask: Longint;
begin
  SavedAdvancedTypography := AdvancedTypography;
  inherited CreateWnd;
  if (SysLocale.FarEast) and not (SysLocale.PriLangID = LANG_JAPANESE) then
    Font.Charset := GetDefFontCharSet;
  Mask := ENM_CHANGE or ENM_SELCHANGE or ENM_REQUESTRESIZE or ENM_PROTECTED;
  if RichEditVersion >= 2 then
    Mask := Mask or ENM_LINK;
  SendMessage(Handle, EM_SETEVENTMASK, 0, Mask);
  SendMessage(Handle, EM_SETBKGNDCOLOR, 0, ColorToRGB(Color));
  DoSetMaxLength(MaxLength);
  SetWordSelection(FWordSelection);
  if RichEditVersion >= 2 then
  begin
    SendMessage(Handle, EM_AUTOURLDETECT, Longint(FAutoURLDetect), 0);
    FUndoLimit := SendMessage(Handle, EM_SETUNDOLIMIT, FUndoLimit, 0);
    UpdateTextModes(PlainText);
    // GetAdvancedTypography returns now always false, because the handle
    // is recreated, so can't use property AdvancedTypography
    FAdvancedTypography := SavedAdvancedTypography;
    UpdateTypographyOptions(FAdvancedTypography);
    SetLangOptions(FLangOptions);
  end;
  if FAllowObjects then
  begin
    SendMessage(Handle, EM_SETOLECALLBACK, 0,
      LParam(TRichEditOleCallback(FCallback) as IRichEditOleCallback));
    GetRichEditOle(Handle, FRichEditOle);
    UpdateHostNames;
  end;

  if FState is TJvRichEditState then
    TJvRichEditState(FState).Restore(Self);
  FState.Free;
  FState := nil;
  if RichEditVersion < 2 then
  begin
    { (rb) This code is probably unnecessary; it only assigns Font to
      FDefAttributes, see WM_SETFONT handler; but that is also done in
      TWinControl.CreateWnd }
    SavedModified := Modified;
    { This changes the Modified property }
    SendMessage(Handle, WM_SETFONT, 0, 0);
    Modified := SavedModified;
  end;
end;

procedure TJvCustomRichEdit.DestroyWnd;
begin
  {$IFDEF DELPHI10_UP}
  if csRecreating in ControlState then
  begin
  {$ENDIF DELPHI10_UP}
    FState := TJvRichEditState.Create;
    TJvRichEditState(FState).ForcePlainText := csDesigning in ComponentState;
    TJvRichEditState(FState).Store(Self);
  {$IFDEF DELPHI10_UP}
  end;
  {$ENDIF DELPHI10_UP}
  inherited DestroyWnd;
end;

procedure TJvCustomRichEdit.DoConversionProgress(const AProgress: Integer);
begin
  if Assigned(FOnConversionProgress) then
    FOnConversionProgress(Self, AProgress);
end;

procedure TJvCustomRichEdit.DoSetMaxLength(Value: Integer);
begin
  { The rich edit control's default maximum amount of text is 32K }
  { Let's set it at 16M by default }
  if Value = 0 then
    Value := $FFFFFF;
  SendMessage(Handle, EM_EXLIMITTEXT, 0, Value);
end;

procedure TJvCustomRichEdit.EMReplaceSel(var Msg: TMessage);
var
  CharRange: TCharRange;
begin
  Perform(EM_EXGETSEL, 0, Longint(@CharRange));
  with CharRange do
    cpMax := cpMin + Integer(StrLen(PChar(Msg.LParam)));
  if (FUndoLimit > 1) and (RichEditVersion >= 2) and (not FLinesUpdating or ForceUndo) then
    Msg.WParam := 1; { allow Undo }
  inherited;
  if FLinesUpdating then
    CharRange.cpMin := CharRange.cpMax;
  Perform(EM_EXSETSEL, 0, Longint(@CharRange));
  Perform(Messages.EM_SCROLLCARET, 0, 0);
end;

class function TJvCustomRichEdit.Filter(
  const AKind: TJvConversionKind): string;
begin
  Result := GConversionFormatList.GetFilter(AKind);
end;

function TJvCustomRichEdit.FindDialog(const SearchStr: string): TFindDialog;
begin
  if FFindDialog = nil then
  begin
    FFindDialog := TFindDialog.Create(Self);
    if FReplaceDialog <> nil then
      FFindDialog.FindText := FReplaceDialog.FindText;
  end;
  Result := FFindDialog;
  SetupFindDialog(FFindDialog, SearchStr, '');
  FFindDialog.Execute;
end;

procedure TJvCustomRichEdit.FindDialogClose(Sender: TObject);
begin
  CloseFindDialog(Sender as TFindDialog);
end;

procedure TJvCustomRichEdit.FindDialogFind(Sender: TObject);
begin
  FindEditText(TFindDialog(Sender), True, True);
end;

function TJvCustomRichEdit.FindEditText(Dialog: TFindDialog; AdjustPos, Events: Boolean): Boolean;
var
  Length, StartPos: Integer;
  SrchOptions: TRichSearchTypes;
begin
  with TFindDialog(Dialog) do
  begin
    SrchOptions := [stSetSelection];
    if frDown in Options then
    begin
      StartPos := Max(SelStart, SelStart + SelLength);
      Length := System.Length(Text) - StartPos + 1;
    end
    else
    begin
      SrchOptions := SrchOptions + [stBackward];
      StartPos := Min(SelStart, SelStart + SelLength);
      Length := StartPos + 1;
    end;
    if frMatchCase in Options then
      SrchOptions := SrchOptions + [stMatchCase];
    if frWholeWord in Options then
      SrchOptions := SrchOptions + [stWholeWord];
    Result := Self.FindText(FindText, StartPos, Length, SrchOptions) >= 0;
    if FindText <> '' then
      FLastFind := Dialog;
    if Result then
    begin
      if AdjustPos then
        AdjustFindDialogPosition(Dialog);
    end
    else
    if Events then
      TextNotFound(Dialog);
  end;
end;

function TJvCustomRichEdit.FindNext: Boolean;
begin
  if CanFindNext then
    Result := FindEditText(FLastFind, False, True)
  else
    Result := False;
end;

function TJvCustomRichEdit.FindText(const SearchStr: string;
  StartPos, Length: Integer; Options: TRichSearchTypes): Integer;
var
  Find: TFindTextEx;
  Flags: Integer;
begin
  with Find.chrg do
  begin
    cpMin := StartPos;
    cpMax := cpMin + Abs(Length);
  end;
  if RichEditVersion >= 2 then
  begin
    if not (stBackward in Options) then
      Flags := FT_DOWN
    else
      Flags := 0;
  end
  else
  begin
    Options := Options - [stBackward];
    Flags := 0;
  end;
  if stWholeWord in Options then
    Flags := Flags or FT_WHOLEWORD;
  if stMatchCase in Options then
    Flags := Flags or FT_MATCHCASE;
  Find.lpstrText := PChar(SearchStr);
  Result := SendMessage(Handle, EM_FINDTEXTEX, Flags, Longint(@Find));
  if (Result >= 0) and (stSetSelection in Options) then
  begin
    SendMessage(Handle, EM_EXSETSEL, 0, Longint(@Find.chrgText));
    SendMessage(Handle, Messages.EM_SCROLLCARET, 0, 0);
  end;
end;

procedure TJvCustomRichEdit.FontChanged;
begin
  inherited FontChanged;
  FDefAttributes.Assign(Font);
end;

function TJvCustomRichEdit.GetAdvancedTypography: Boolean;
begin
  // Advanced and normal line breaking may also be turned on automatically by
  // the rich edit control if it is needed for certain languages. So don't
  // rely on FAdvancedTypography alone.
  if HandleAllocated and not (csDesigning in ComponentState) then
  begin
    if RichEditVersion >= 3 then
      FAdvancedTypography := SendMessage(Handle, EM_GETTYPOGRAPHYOPTIONS, 0, 0) and TO_ADVANCEDTYPOGRAPHY =
        TO_ADVANCEDTYPOGRAPHY;
  end;
  Result := FAdvancedTypography;
end;

function TJvCustomRichEdit.GetAutoURLDetect: Boolean;
begin
  Result := FAutoURLDetect;
  if HandleAllocated and not (csDesigning in ComponentState) then
  begin
    if RichEditVersion >= 2 then
      Result := Boolean(SendMessage(Handle, EM_GETAUTOURLDETECT, 0, 0));
  end;
end;

function TJvCustomRichEdit.GetCanFindNext: Boolean;
begin
  Result := HandleAllocated and (FLastFind <> nil) and
    (FLastFind.FindText <> '');
end;

function TJvCustomRichEdit.GetCanPaste: Boolean;
begin
  Result := False;
  if HandleAllocated then
    Result := SendMessage(Handle, EM_CANPASTE, 0, 0) <> 0;
end;

function TJvCustomRichEdit.GetCanRedo: Boolean;
begin
  Result := False;
  if HandleAllocated and (RichEditVersion >= 2) then
    Result := SendMessage(Handle, EM_CANREDO, 0, 0) <> 0;
end;

function TJvCustomRichEdit.GetCaretPos: TPoint;
var
  CharRange: TCharRange;
begin
  SendMessage(Handle, EM_EXGETSEL, 0, Longint(@CharRange));
  Result.X := CharRange.cpMax;
  Result.Y := LineFromChar(Result.X);
  Dec(Result.X, GetLineIndex(-1));
end;

function TJvCustomRichEdit.GetCharPos(CharIndex: Integer): TPoint;
var
  Res: Longint;
begin
  Result.X := 0;
  Result.Y := 0;
  //  FillChar(Result, SizeOf(Result), 0);
  if HandleAllocated then
  begin
    if RichEditVersion = 2 then
    begin
      Res := SendMessage(Handle, EM_POSFROMCHAR, CharIndex, 0);
      Result.X := LoWord(Res);
      Result.Y := HiWord(Res);
    end
    else { RichEdit 1.0 and 3.0 }
      SendMessage(Handle, EM_POSFROMCHAR, WParam(@Result), CharIndex);
  end;
end;

function TJvCustomRichEdit.GetConverter(AStream: TStream;
  const Kind: TJvConversionKind): TJvConversion;
begin
  Result := DefaultConverter;
  if Result = nil then
    Result := GConversionFormatList.GetConverter(
      GetParentWindow(Self), AStream, Kind)
  else
    Result.Init(GetParentWindow(Self));
end;

function TJvCustomRichEdit.GetConverter(const AFileName: string;
  const Kind: TJvConversionKind): TJvConversion;
begin
  { Note: First AFileName determines the converter, if not found then we pick
          the default converter. Same behaviour as TRichEdit }
  Result := GConversionFormatList.GetConverter(
    GetParentWindow(Self), AFileName, Kind);
  if Result = nil then
  begin
    Result := DefaultConverter;
    if Result = nil then
      Result := GConversionFormatList.DefaultConverter;
    Result.Init(GetParentWindow(Self));
  end;
end;

function TJvCustomRichEdit.GetFlat: Boolean;
begin
  Result := not Ctl3D;
end;

function TJvCustomRichEdit.GetLangOptions: TRichLangOptions;
var
  Flags: Longint;
  I: TRichLangOption;
begin
  Result := FLangOptions;
  if HandleAllocated and not (csDesigning in ComponentState) and
    (RichEditVersion >= 2) then
  begin
    Result := [];
    Flags := SendMessage(Handle, EM_GETLANGOPTIONS, 0, 0);
    for I := Low(TRichLangOption) to High(TRichLangOption) do
      if Flags and RichLangOptions[I] <> 0 then
        Include(Result, I);
  end;
end;

function TJvCustomRichEdit.GetLineIndex(LineNo: Integer): Integer;
begin
  Result := SendMessage(Handle, EM_LINEINDEX, LineNo, 0);
end;

function TJvCustomRichEdit.GetLineLength(CharIndex: Integer): Integer;
begin
  Result := SendMessage(Handle, EM_LINELENGTH, CharIndex, 0);
end;

function TJvCustomRichEdit.GetParentFlat: Boolean;
begin
  Result := ParentCtl3D;
end;

function TJvCustomRichEdit.GetPopupMenu: TPopupMenu;
var
  EnumOleVerb: IEnumOleVerb;
  OleVerb: TOleVerb;
  Item: TMenuItem;
  ReObject: TReObject;
begin
  FPopupVerbMenu.Free;
  FPopupVerbMenu := nil;
  Result := inherited GetPopupMenu;
  if FAutoVerbMenu and (SelectionType = [stObject]) and
    Assigned(FRichEditOle) then
  begin
    FillChar(ReObject, SizeOf(ReObject), 0);
    ReObject.cbStruct := SizeOf(ReObject);
    if Succeeded(IRichEditOle(FRichEditOle).GetObject(
      Longint(REO_IOB_SELECTION), ReObject, REO_GETOBJ_POLEOBJ)) then
    try
      if Assigned(ReObject.poleobj) and
        (ReObject.dwFlags and REO_INPLACEACTIVE = 0) then
      begin
        FPopupVerbMenu := TPopupMenu.Create(Self);
        if ReObject.poleobj.EnumVerbs(EnumOleVerb) = 0 then
        try
          while (EnumOleVerb.Next(1, OleVerb, nil) = 0) and
            (OleVerb.lVerb >= 0) and
            (OleVerb.grfAttribs and OLEVERBATTRIB_ONCONTAINERMENU <> 0) do
          begin
            Item := TMenuItem.Create(FPopupVerbMenu);
            Item.Caption := WideCharToString(OleVerb.lpszVerbName);
            Item.Tag := OleVerb.lVerb;
            Item.Default := (OleVerb.lVerb = OLEIVERB_PRIMARY);
            Item.OnClick := PopupVerbClick;
            FPopupVerbMenu.Items.Add(Item);
          end;
        finally
          ReleaseObject(EnumOleVerb);
        end;
        if (Result <> nil) and (Result.Items.Count > 0) then
        begin
          Item := TMenuItem.Create(FPopupVerbMenu);
          Item.Caption := '-';
          Result.Items.Add(Item);
          Item := TMenuItem.Create(FPopupVerbMenu);
          Item.Caption := Format(SPropDlgCaption, [GetFullNameStr(ReObject.poleobj)]);
          Item.OnClick := ObjectPropsClick;
          Result.Items.Add(Item);
          if FPopupVerbMenu.Items.Count > 0 then
          begin
            FPopupVerbMenu.Items.Caption := GetFullNameStr(ReObject.poleobj);
            Result.Items.Add(FPopupVerbMenu.Items);
          end;
        end
        else
        if FPopupVerbMenu.Items.Count > 0 then
        begin
          Item := TMenuItem.Create(FPopupVerbMenu);
          Item.Caption := Format(SPropDlgCaption, [GetFullNameStr(ReObject.poleobj)]);
          Item.OnClick := ObjectPropsClick;
          FPopupVerbMenu.Items.Insert(0, Item);
          Result := FPopupVerbMenu;
        end;
      end;
    finally
      ReleaseObject(ReObject.poleobj);
    end;
  end
  else
  if (Result = nil) and UseFixedPopup then
    Result := FixedDefaultEditPopUp(Self);
end;

function TJvCustomRichEdit.GetRedoName: TUndoName;
begin
  Result := unUnknown;
  if (RichEditVersion >= 2) and HandleAllocated then
    Result := TUndoName(SendMessage(Handle, EM_GETREDONAME, 0, 0));
end;

function TJvCustomRichEdit.GetSelection: TCharRange;
begin
  SendMessage(Handle, EM_EXGETSEL, 0, Longint(@Result));
end;

function TJvCustomRichEdit.GetSelectionType: TRichSelectionType;
const
  SelTypes: array[TRichSelection] of Integer =
  (SEL_TEXT, SEL_OBJECT, SEL_MULTICHAR, SEL_MULTIOBJECT);
var
  Selection: Integer;
  I: TRichSelection;
begin
  Result := [];
  if HandleAllocated then
  begin
    Selection := SendMessage(Handle, EM_SELECTIONTYPE, 0, 0);
    for I := Low(TRichSelection) to High(TRichSelection) do
      if SelTypes[I] and Selection <> 0 then
        Include(Result, I);
  end;
end;

function TJvCustomRichEdit.GetSelLength: Integer;
begin
  with GetSelection do
    Result := cpMax - cpMin;
end;

function TJvCustomRichEdit.GetSelStart: Integer;
begin
  Result := GetSelection.cpMin;
end;

function TJvCustomRichEdit.GetSelText: string;
begin
  with GetSelection do
    Result := GetTextRange(cpMin, cpMax);
end;

function TJvCustomRichEdit.GetSelTextBuf(Buffer: PChar; BufSize: Integer): Integer;
var
  S: string;
begin
  S := SelText;
  Result := Length(S);
  if BufSize < Length(S) then
    Result := BufSize;
  StrPLCopy(Buffer, S, Result);
end;

function TJvCustomRichEdit.GetStreamFormat: TRichStreamFormat;
begin
  Result := TJvRichEditStrings(Lines).Format;
end;

function TJvCustomRichEdit.GetStreamMode: TRichStreamModes;
begin
  Result := TJvRichEditStrings(Lines).Mode;
end;

function TJvCustomRichEdit.GetTextRange(StartPos, EndPos: Longint): string;
var
  TextRange: TTextRange;
begin
  SetLength(Result, EndPos - StartPos + 1);
  TextRange.chrg.cpMin := StartPos;
  TextRange.chrg.cpMax := EndPos;
  TextRange.lpstrText := PAnsiChar(Result);
  SetLength(Result, SendMessage(Handle, EM_GETTEXTRANGE, 0, Longint(@TextRange)));
end;

function TJvCustomRichEdit.GetUndoName: TUndoName;
begin
  Result := unUnknown;
  if (RichEditVersion >= 2) and HandleAllocated then
    Result := TUndoName(SendMessage(Handle, EM_GETUNDONAME, 0, 0));
end;

function TJvCustomRichEdit.GetWordSelection: Boolean;
begin
  Result := FWordSelection;
  if HandleAllocated then
    Result := (SendMessage(Handle, EM_GETOPTIONS, 0, 0) and
      ECO_AUTOWORDSELECTION) <> 0;
end;

function TJvCustomRichEdit.GetZoom: Integer; // Added by J.G. Boerema
var
  WP, LP: Integer;
begin
  Result := 100;
  if (RichEditVersion >= 3) and HandleAllocated then
  begin
    SendMessage(Handle, EM_GETZOOM, Integer(@WP), Integer(@LP));
    if (LP > 0) then
      Result := MulDiv(100, WP, LP);
  end;
end;

procedure TJvCustomRichEdit.InsertGraphic(AGraphic: TGraphic; const Sizeable: Boolean);
var
  OleClientSite: IOleClientSite;
  Storage: IStorage;
  OleObject: IOleObject;
  ReObject: TReObject;
  DataObject: IDataObject;
  Selection: TCharRange;
  FormatEtc: TFormatEtc;
begin
  if HandleAllocated and Assigned(FRichEditOle) then
  begin
    DataObject := TImageDataObject.Create(AGraphic);

    FillChar(ReObject, SizeOf(TReObject), 0);
    IRichEditOle(FRichEditOle).GetClientSite(OleClientSite);
    Storage := nil;
    OleObject := nil;
    try
      CreateStorage(Storage);

      FormatEtc.cfFormat := CF_METAFILEPICT;
      FormatEtc.ptd := nil;
      FormatEtc.dwAspect := DVASPECT_CONTENT;
      FormatEtc.lindex := -1;
      FormatEtc.tymed := TYMED_MFPICT;

      OleCheck(OleCreateStaticFromData(DataObject, IOleObject, OLERENDER_FORMAT,
        @FormatEtc, OleClientSite, Storage, OleObject));
      OleSetContainedObject(OleObject, True);
      try
        FillChar(ReObject, SizeOf(TReObject), #0);
        with ReObject do
        begin
          cbStruct := SizeOf(TReObject);
          cp := REO_CP_SELECTION;
          poleobj := OleObject;
          OleObject.GetUserClassID(clsid);
          pstg := Storage;
          polesite := OleClientSite;
          dvAspect := DVASPECT_CONTENT;
          if Sizeable then
            dwFlags := REO_RESIZABLE;
          //OleCheck(OleSetDrawAspect(OleObject,
          //  Data.dwFlags and PSF_CHECKDISPLAYASICON <> 0,
          //  Data.hMetaPict, dvAspect));
        end;
        SendMessage(Handle, EM_EXGETSEL, 0, Longint(@Selection));
        Selection.cpMax := Selection.cpMin + 1;
        OleCheck(IRichEditOle(FRichEditOle).InsertObject(ReObject));
        SendMessage(Handle, EM_EXSETSEL, 0, Longint(@Selection));
        IRichEditOle(FRichEditOle).SetDvaspect(
          Longint(REO_IOB_SELECTION), ReObject.dvAspect);
      finally
        ReleaseObject(OleObject);
      end;
    finally
      ReleaseObject(OleClientSite);
      ReleaseObject(Storage);
    end;
  end;
end;

procedure TJvCustomRichEdit.InsertFormatText(Index: Integer; const S: string; FontStyle: TFontStyles;
  const FontName: string; const FontColor: TColor; FontHeight: Integer);
var
  AFont: TFont;
begin
  if S = '' then
    Exit;
  AFont := TFont.Create;
  try
    AFont.Assign(SelAttributes);
    AFont.Style := FontStyle;
    if FontName <> '' then
      AFont.Name := FontName;
    if FontColor <> clDefault then
      AFont.Color := FontColor;
    if FontHeight <> 0 then
      AFont.Height := FontHeight;
    InsertFormatText(Index, S, AFont);

  finally
    AFont.Free;
  end;
end;

procedure TJvCustomRichEdit.InsertFormatText(Index: Integer; const S: string; const AFont: TFont = nil);
var
  ASelStart, ASelLength: Integer;
begin
  if S = '' then
    Exit;
  ASelStart := SelStart;
  ASelLength := SelLength;
  try
    if Index > -1 then
      SelStart := Index;
    SelLength := 0;
    if AFont <> nil then
      SelAttributes.Assign(AFont);
    SelText := S;
  finally
    SelStart := ASelStart;
    SelLength := ASelLength;
  end;
end;

procedure TJvCustomRichEdit.InsertLinkToFile(const FileName: string;
  Iconic: Boolean);
var
  Info: TCreateInfo;
begin
  Info.CreateType := ctLinkToFile;
  Info.ShowAsIcon := Iconic;
  Info.IconMetaPict := 0;
  Info.FileName := FileName;
  InsertObjectFromInfo(Info);
end;

procedure TJvCustomRichEdit.InsertObject(const OleClassName: string;
  Iconic: Boolean);
var
  Info: TCreateInfo;
begin
  Info.CreateType := ctNewObject;
  Info.ShowAsIcon := Iconic;
  Info.IconMetaPict := 0;
  Info.ClassID := ProgIDToClassID(OleClassName);
  InsertObjectFromInfo(Info);
end;

function TJvCustomRichEdit.InsertObjectDialog: Boolean;
var
  Data: TOleUIInsertObject;
  NameBuffer: array[0..255] of Char;
  OleClientSite: IOleClientSite;
  Storage: IStorage;
  OleObject: IOleObject;
  ReObject: TReObject;
  IsNewObject: Boolean;
  Selection: TCharRange;
begin
  FillChar(Data, SizeOf(Data), 0);
  FillChar(NameBuffer, SizeOf(NameBuffer), 0);
  FillChar(ReObject, SizeOf(TReObject), 0);
  if Assigned(FRichEditOle) then
  begin
    IRichEditOle(FRichEditOle).GetClientSite(OleClientSite);
    Storage := nil;
    try
      CreateStorage(Storage);
      with Data do
      begin
        cbStruct := SizeOf(Data);
        dwFlags := IOF_SELECTCREATENEW or IOF_VERIFYSERVERSEXIST or
          IOF_CREATENEWOBJECT or IOF_CREATEFILEOBJECT or IOF_CREATELINKOBJECT;
        hWndOwner := Handle;
        lpszFile := NameBuffer;
        cchFile := SizeOf(NameBuffer);
        iid := IOleObject;
        oleRender := OLERENDER_DRAW;
        lpIOleClientSite := OleClientSite;
        lpIStorage := Storage;
        ppvObj := @OleObject;
      end;
      try
        Result := OleUIInsertObject(Data) = OLEUI_OK;
        if Result then
        try
          IsNewObject := Data.dwFlags and IOF_SELECTCREATENEW = IOF_SELECTCREATENEW;
          with ReObject do
          begin
            cbStruct := SizeOf(TReObject);
            cp := REO_CP_SELECTION;
            clsid := Data.clsid;
            poleobj := OleObject;
            pstg := Storage;
            polesite := OleClientSite;
            dvAspect := DVASPECT_CONTENT;
            dwFlags := REO_RESIZABLE;
            if IsNewObject then
              dwFlags := dwFlags or REO_BLANK;
          end;
          OleCheck(OleSetDrawAspect(OleObject,
            Data.dwFlags and IOF_CHECKDISPLAYASICON <> 0,
            Data.hMetaPict, ReObject.dvAspect));
          SendMessage(Handle, EM_EXGETSEL, 0, Longint(@Selection));
          Selection.cpMax := Selection.cpMin + 1;
          OleCheck(IRichEditOle(FRichEditOle).InsertObject(ReObject));
          SendMessage(Handle, EM_EXSETSEL, 0, Longint(@Selection));
          SendMessage(Handle, Messages.EM_SCROLLCARET, 0, 0);
          IRichEditOle(FRichEditOle).SetDvaspect(
            Longint(REO_IOB_SELECTION), ReObject.dvAspect);
          if IsNewObject then
            OleObject.DoVerb(OLEIVERB_SHOW, nil,
              OleClientSite, 0, Handle, ClientRect);
        finally
          ReleaseObject(OleObject);
        end;
      finally
        DestroyMetaPict(Data.hMetaPict);
      end;
    finally
      ReleaseObject(OleClientSite);
      ReleaseObject(Storage);
    end;
  end
  else
    Result := False;
end;

procedure TJvCustomRichEdit.InsertObjectFromFile(const FileName: string;
  Iconic: Boolean);
var
  Info: TCreateInfo;
begin
  Info.CreateType := ctFromFile;
  Info.ShowAsIcon := Iconic;
  Info.IconMetaPict := 0;
  Info.FileName := FileName;
  InsertObjectFromInfo(Info);
end;

procedure TJvCustomRichEdit.InsertObjectFromInfo(
  const Info: TCreateInfo);
var
  OleClientSite: IOleClientSite;
  Storage: IStorage;
  OleObject: IOleObject;
  ReObject: TReObject;
  Selection: TCharRange;
begin
  if not Assigned(FRichEditOle) then
    Exit;

  IRichEditOle(FRichEditOle).GetClientSite(OleClientSite);
  Storage := nil;
  OleObject := nil;
  try
    CreateStorage(Storage);
    with Info do
    begin
      case CreateType of
        ctNewObject:
          OleCheck(OleCreate(ClassID, IOleObject, OLERENDER_DRAW, nil,
            OleClientSite, Storage, OleObject));
        ctFromFile:
          OleCheck(OleCreateFromFile(GUID_NULL, PWideChar(FileName), IOleObject,
            OLERENDER_DRAW, nil, OleClientSite, Storage, OleObject));
        ctLinkToFile:
          OleCheck(OleCreateLinkToFile(PWideChar(FileName), IOleObject,
            OLERENDER_DRAW, nil, OleClientSite, Storage, OleObject));
        ctFromData:
          OleCheck(OleCreateFromData(DataObject, IOleObject,
            OLERENDER_DRAW, nil, OleClientSite, Storage, OleObject));
        ctLinkFromData:
          OleCheck(OleCreateLinkFromData(DataObject, IOleObject,
            OLERENDER_DRAW, nil, OleClientSite, Storage, OleObject));
      end;
      try
        if CreateType = ctNewObject then
          OleSetContainedObject(OleObject, True);
        FillChar(ReObject, SizeOf(TReObject), 0);
        with ReObject do
        begin
          cbStruct := SizeOf(TReObject);
          cp := REO_CP_SELECTION;
          poleobj := OleObject;
          OleObject.GetUserClassID(clsid);
          pstg := Storage;
          polesite := OleClientSite;
          dvAspect := DVASPECT_CONTENT;
          dwFlags := REO_RESIZABLE;
          if CreateType = ctNewObject then
            dwFlags := dwFlags or REO_BLANK;
        end;
        OleCheck(OleSetDrawAspect(OleObject, ShowAsIcon,
          IconMetaPict, ReObject.dvAspect));
        SendMessage(Handle, EM_EXGETSEL, 0, Longint(@Selection));
        Selection.cpMax := Selection.cpMin + 1;
        OleCheck(IRichEditOle(FRichEditOle).InsertObject(ReObject));
        SendMessage(Handle, EM_EXSETSEL, 0, Longint(@Selection));
        SendMessage(Handle, Messages.EM_SCROLLCARET, 0, 0);
        IRichEditOle(FRichEditOle).SetDvaspect(
          Longint(REO_IOB_SELECTION), ReObject.dvAspect);
        if CreateType = ctNewObject then
          OleObject.DoVerb(OLEIVERB_SHOW, nil,
            OleClientSite, 0, Handle, ClientRect);
      finally
        ReleaseObject(OleObject);
      end;
    end;
  finally
    ReleaseObject(OleClientSite);
    ReleaseObject(Storage);
  end;
end;

function TJvCustomRichEdit.IsAdvancedTypographyStored: Boolean;
begin
  Result := not AutoAdvancedTypography;
end;

function TJvCustomRichEdit.LineFromChar(CharIndex: Integer): Integer;
begin
  Result := SendMessage(Handle, EM_EXLINEFROMCHAR, 0, CharIndex);
end;

procedure TJvCustomRichEdit.NeedAdvancedTypography;
begin
  if AutoAdvancedTypography and (RichEditVersion >= 3) then
  begin
    HandleNeeded;
    AdvancedTypography := True;
    // setting AdvancedTypography will set AutoAdvancedTypography to False, so:
    AutoAdvancedTypography := True;
  end;
end;

function TJvCustomRichEdit.ObjectPropertiesDialog: Boolean;
var
  ObjectProps: TOleUIObjectProps;
  PropSheet: TPropSheetHeader;
  GeneralProps: TOleUIGnrlProps;
  ViewProps: TOleUIViewProps;
  LinkProps: TOleUILinkProps;
  DialogCaption: string;
  ReObject: TReObject;
begin
  Result := False;
  if not Assigned(FRichEditOle) or (SelectionType <> [stObject]) then
    Exit;
  FillChar(ObjectProps, SizeOf(ObjectProps), 0);
  FillChar(PropSheet, SizeOf(PropSheet), 0);
  FillChar(GeneralProps, SizeOf(GeneralProps), 0);
  FillChar(ViewProps, SizeOf(ViewProps), 0);
  FillChar(LinkProps, SizeOf(LinkProps), 0);
  FillChar(ReObject, SizeOf(ReObject), 0);
  ReObject.cbStruct := SizeOf(ReObject);
  if Succeeded(IRichEditOle(FRichEditOle).GetObject(Longint(REO_IOB_SELECTION),
    ReObject, REO_GETOBJ_POLEOBJ or REO_GETOBJ_POLESITE)) then
  try
    if ReObject.dwFlags and REO_INPLACEACTIVE = 0 then
    begin
      ObjectProps.cbStruct := SizeOf(ObjectProps);
      ObjectProps.dwFlags := OPF_DISABLECONVERT;
      ObjectProps.lpPS := @PropSheet;
      ObjectProps.lpObjInfo := TOleUIObjInfo.Create(Self, ReObject);
      if (ReObject.dwFlags and REO_LINK) <> 0 then
      begin
        ObjectProps.dwFlags := ObjectProps.dwFlags or OPF_OBJECTISLINK;
        ObjectProps.lpLinkInfo := TOleUILinkInfo.Create(Self, ReObject);
      end;
      ObjectProps.lpGP := @GeneralProps;
      ObjectProps.lpVP := @ViewProps;
      ObjectProps.lpLP := @LinkProps;
      PropSheet.dwSize := SizeOf(PropSheet);
      PropSheet.hWndParent := Handle;
      PropSheet.HInstance := MainInstance;
      DialogCaption := Format(SPropDlgCaption, [GetFullNameStr(ReObject.poleobj)]);
      PropSheet.pszCaption := PChar(DialogCaption);
      GeneralProps.cbStruct := SizeOf(GeneralProps);
      ViewProps.cbStruct := SizeOf(ViewProps);
      ViewProps.dwFlags := VPF_DISABLESCALE;
      LinkProps.cbStruct := SizeOf(LinkProps);
      LinkProps.dwFlags := ELF_DISABLECANCELLINK;
      Result := OleUIObjectProperties(ObjectProps) = OLEUI_OK;
    end;
  finally
  end;
end;

procedure TJvCustomRichEdit.ObjectPropsClick(Sender: TObject);
begin
  ObjectPropertiesDialog;
end;

function TJvCustomRichEdit.PasteSpecialDialog: Boolean;

  procedure SetPasteEntry(var Entry: TOleUIPasteEntry; Format: TClipFormat;
    tymed: DWORD; const FormatName, ResultText: string; Flags: DWORD);
  begin
    with Entry do
    begin
      fmtetc.cfFormat := Format;
      fmtetc.dwAspect := DVASPECT_CONTENT;
      fmtetc.lindex := -1;
      fmtetc.tymed := tymed;
      if FormatName <> '' then
        lpstrFormatName := PChar(FormatName)
      else
        lpstrFormatName := '%s';
      if ResultText <> '' then
        lpstrResultText := PChar(ResultText)
      else
        lpstrResultText := '%s';
      dwFlags := Flags;
    end;
  end;

const
  PasteFormatCount = 6;
var
  Data: TOleUIPasteSpecial;
  PasteFormats: array[0..PasteFormatCount - 1] of TOleUIPasteEntry;
  Format: Integer;
  Info: TCreateInfo;
begin
  Result := False;
  if not CanPaste or not Assigned(FRichEditOle) then
    Exit;
  FillChar(Data, SizeOf(Data), 0);
  FillChar(PasteFormats, SizeOf(PasteFormats), 0);
  with Data do
  begin
    cbStruct := SizeOf(Data);
    hWndOwner := Handle;
    arrPasteEntries := @PasteFormats[0];
    cPasteEntries := PasteFormatCount;
    arrLinkTypes := @CFLinkSource;
    cLinkTypes := 1;
    dwFlags := PSF_SELECTPASTE;
  end;
  SetPasteEntry(PasteFormats[0], CFEmbeddedObject, TYMED_ISTORAGE, '', '',
    OLEUIPASTE_PASTE or OLEUIPASTE_ENABLEICON);
  SetPasteEntry(PasteFormats[1], CFLinkSource, TYMED_ISTREAM, '', '',
    OLEUIPASTE_LINKTYPE1 or OLEUIPASTE_ENABLEICON);
  SetPasteEntry(PasteFormats[2], CFRtf, TYMED_ISTORAGE,
    CF_RTF, CF_RTF, OLEUIPASTE_PASTE);
  SetPasteEntry(PasteFormats[3], CFRtfNoObjs, TYMED_ISTORAGE,
    CF_RTFNOOBJS, CF_RTFNOOBJS, OLEUIPASTE_PASTE);
  SetPasteEntry(PasteFormats[4], CF_TEXT, TYMED_HGLOBAL,
    'Unformatted text', 'text without any formatting', OLEUIPASTE_PASTE);
  SetPasteEntry(PasteFormats[5], CF_BITMAP, TYMED_GDI,
    'Windows Bitmap', 'bitmap image', OLEUIPASTE_PASTE);
  try
    if OleUIPasteSpecial(Data) = OLEUI_OK then
    begin
      Result := True;
      if Data.nSelectedIndex in [0, 1] then
      begin
        case Data.nSelectedIndex of
          0: Info.CreateType := ctFromData;
          1: Info.CreateType := ctLinkFromData;
        end;
        Info.DataObject := Data.lpSrcDataObj;
        Info.ShowAsIcon := Data.dwFlags and PSF_CHECKDISPLAYASICON <> 0;
        Info.IconMetaPict := Data.hMetaPict;

        InsertObjectFromInfo(Info);
      end
      else
      begin
        Format := PasteFormats[Data.nSelectedIndex].fmtetc.cfFormat;
        OleCheck(IRichEditOle(FRichEditOle).ImportDataObject(
          Data.lpSrcDataObj, Format, Data.hMetaPict));
        SendMessage(Handle, Messages.EM_SCROLLCARET, 0, 0);
      end;
    end;
  finally
    DestroyMetaPict(Data.hMetaPict);
    ReleaseObject(Data.lpSrcDataObj);
  end;
end;

procedure TJvCustomRichEdit.PopupVerbClick(Sender: TObject);
var
  ReObject: TReObject;
begin
  if Assigned(FRichEditOle) then
  begin
    FillChar(ReObject, SizeOf(ReObject), 0);
    ReObject.cbStruct := SizeOf(ReObject);
    if Succeeded(IRichEditOle(FRichEditOle).GetObject(
      Longint(REO_IOB_SELECTION), ReObject, REO_GETOBJ_POLEOBJ or
      REO_GETOBJ_POLESITE)) then
    try
      if ReObject.dwFlags and REO_INPLACEACTIVE = 0 then
        OleCheck(ReObject.poleobj.DoVerb((Sender as TMenuItem).Tag, nil,
          ReObject.polesite, 0, Handle, ClientRect));
    finally
      ReleaseObject(ReObject.polesite);
      ReleaseObject(ReObject.poleobj);
    end;
  end;
end;

procedure TJvCustomRichEdit.Print(const Caption: string);
var
  Range: TFormatRange;
  LastChar, MaxLen, LogX, LogY, OldMap: Integer;
  SaveRect: TRect;
  TextLenEx: TGetTextLengthEx;
begin
  FillChar(Range, SizeOf(TFormatRange), 0);
  with Printer, Range do
  begin
    Title := Caption;
    BeginDoc;
    HDC := Handle;
    hdcTarget := HDC;
    LogX := GetDeviceCaps(Handle, LOGPIXELSX);
    LogY := GetDeviceCaps(Handle, LOGPIXELSY);
    if IsRectEmpty(PageRect) then
    begin
      rc.Right := PageWidth * CTwipsPerInch div LogX;
      rc.Bottom := PageHeight * CTwipsPerInch div LogY;
    end
    else
    begin
      rc.Left := PageRect.Left * CTwipsPerInch div LogX;
      rc.Top := PageRect.Top * CTwipsPerInch div LogY;
      rc.Right := PageRect.Right * CTwipsPerInch div LogX;
      rc.Bottom := PageRect.Bottom * CTwipsPerInch div LogY;
    end;
    rcPage := rc;
    SaveRect := rc;
    LastChar := 0;
    if RichEditVersion >= 2 then
    begin
      with TextLenEx do
      begin
        Flags := GTL_DEFAULT;
        codepage := CP_ACP;
      end;
      MaxLen := Perform(EM_GETTEXTLENGTHEX, WParam(@TextLenEx), 0);
    end
    else
      MaxLen := GetTextLen;
    chrg.cpMax := -1;
    { ensure printer DC is in text map mode }
    OldMap := SetMapMode(HDC, MM_TEXT);
    SendMessage(Self.Handle, EM_FORMATRANGE, 0, 0); { flush buffer }
    try
      repeat
        rc := SaveRect;
        chrg.cpMin := LastChar;
        LastChar := SendMessage(Self.Handle, EM_FORMATRANGE, 1, Longint(@Range));
        if (LastChar < MaxLen) and (LastChar <> -1) then
          NewPage;
      until (LastChar >= MaxLen) or (LastChar = -1);
      EndDoc;
    finally
      SendMessage(Self.Handle, EM_FORMATRANGE, 0, 0); { flush buffer }
      SetMapMode(HDC, OldMap); { restore previous map mode }
    end;
  end;
end;

function TJvCustomRichEdit.ProtectChange(const Msg: TMessage; StartPos,
  EndPos: Integer): Boolean;
begin
  Result := False;
  if Assigned(OnProtectChangeEx) then
    OnProtectChangeEx(Self, Msg, StartPos, EndPos, Result)
  else
  if Assigned(OnProtectChange) then
    OnProtectChange(Self, StartPos, EndPos, Result);
end;

procedure TJvCustomRichEdit.Redo;
begin
  SendMessage(Handle, EM_REDO, 0, 0);
end;

class procedure TJvCustomRichEdit.RegisterConversionFormat(
  AConverter: TJvConversion);
begin
  if Assigned(AConverter) then
    GConversionFormatList.Add(AConverter);
end;

class procedure TJvCustomRichEdit.RegisterMSTextConverters;
{ http://support.microsoft.com/support/kb/articles/q212/2/65.asp
  http://www.microsoft.com/office/ork/2003/tools/BoxA07.htm
}
const
  SKey = '\Software\Microsoft\Shared Tools\Text Converters\';
  SImportExportKey: array[TJvConversionKind] of string = ('Import\', 'Export\');
var
  KeyNames: TStringList;
  Registry: TRegistry;

  procedure RegisterConverters(const AKind: TJvConversionKind);
  var
    I: Integer;
  begin
    with Registry do
    begin
      if not OpenKey(SKey + SImportExportKey[AKind], False) then
        Exit;

      GetKeyNames(KeyNames);
      for I := 0 to KeyNames.Count - 1 do
        if OpenKey(SKey + SImportExportKey[AKind] + KeyNames[I], False) then
        begin
          RegisterConversionFormat(TJvMSTextConversion.Create(
            ReadString('Path'),
            ReadString('Extensions'),
            ReadString('Name'),
            AKind
            ));
        end;
    end;
  end;

begin
  if GMSTextConvertersRegistered then
    Exit;
  GMSTextConvertersRegistered := True;

  Registry := TRegistry.Create(KEY_READ);
  try
    Registry.RootKey := HKEY_LOCAL_MACHINE;

    KeyNames := TStringList.Create;
    try
      RegisterConverters(ckImport);
      RegisterConverters(ckExport);
    finally
      KeyNames.Free;
    end;
  finally
    Registry.Free;
  end;
end;

function TJvCustomRichEdit.ReplaceDialog(const SearchStr,
  ReplaceStr: string): TReplaceDialog;
begin
  if FReplaceDialog = nil then
  begin
    FReplaceDialog := TReplaceDialog.Create(Self);
    if FFindDialog <> nil then
      FReplaceDialog.FindText := FFindDialog.FindText;
  end;
  Result := FReplaceDialog;
  SetupFindDialog(FReplaceDialog, SearchStr, ReplaceStr);
  FReplaceDialog.Execute;
end;

procedure TJvCustomRichEdit.ReplaceDialogReplace(Sender: TObject);
var
  Cnt: Integer;
  SaveSelChange: TNotifyEvent;

  function MatchesText(const FindText, FoundText: string; Options: TFindOptions): Boolean;
  begin
    if frWholeWord in Options then
    begin
      if frMatchCase in Options then
        Result := AnsiSameStr(FindText, FoundText)
      else
        Result := AnsiSameText(FindText, FoundText);
    end
    else
    begin
      if frMatchCase in Options then
        Result := Pos(FoundText, FindText) > 0
      else
        Result := Pos(AnsiLowerCase(FindText), AnsiLowerCase(FoundText)) > 0;
    end;
  end;
begin
  with TReplaceDialog(Sender) do
  begin
    if frReplaceAll in Options then
    begin
      Cnt := 0;
      SaveSelChange := FOnSelChange;
      TJvRichEditStrings(Lines).EnableChange(False);
      try
        FOnSelChange := nil;
        while FindEditText(TFindDialog(Sender), False, False) do
        begin
          SelText := ReplaceText;
          Inc(Cnt);
        end;
        if Cnt = 0 then
          TextNotFound(TFindDialog(Sender))
        else
          AdjustFindDialogPosition(TFindDialog(Sender));
      finally
        TJvRichEditStrings(Lines).EnableChange(True);
        FOnSelChange := SaveSelChange;
        if Cnt > 0 then
        begin
          Change;
          SelectionChange;
        end;
      end;
    end
    else
    if frReplace in Options then
    begin
      if MatchesText(SelText, FindText, Options) then
        SelText := ReplaceText;
      FindEditText(TFindDialog(Sender), True, True);
    end;
  end;
end;

procedure TJvCustomRichEdit.RequestSize(const Rect: TRect);
begin
  if Assigned(OnResizeRequest) then
    OnResizeRequest(Self, Rect);
  FImageRect := Rect;
end;

function TJvCustomRichEdit.SaveClipboard(NumObj, NumChars: Integer): Boolean;
begin
  Result := True;
  if Assigned(OnSaveClipboard) then
    OnSaveClipboard(Self, NumObj, NumChars, Result);
end;

procedure TJvCustomRichEdit.SaveToImage(Picture: TPicture);
const
  cSelectionBarWidth = 9;
var
  ABmp: TBitmap;
  Range: TFormatRange;
  R: TRect;
begin
  if (Picture = nil) or (ClientWidth = 0) or (ClientHeight = 0) or not HandleAllocated then
    Exit;
  ABmp := TBitmap.Create;
  try
    if IsRectEmpty(FImageRect) then
    begin
      FImageRect.Right := ClientWidth;
      FImageRect.Bottom := ClientHeight;
    end;
    // Determine draw width ("formatting rectangle"), FImageRect is control width
    SendMessage(Handle, EM_GETRECT, 0, Integer(@R));
    // According to MSDN the selection bar is not included in the formatting
    // rectangle, but this seems to be NOT true
    if SelectionBar then
      Dec(R.Right, cSelectionBarWidth);
    ABmp.Width := R.Right - R.Left;
    ABmp.Height := FImageRect.Bottom;
    R.Top := 0;
    R.Left := 0;
    // R must be in twips:
    // pixels * (twips/inch) / (pixels/inch) = twips
    R.Right := MulDiv(ABmp.Width, cTwipsPerInch, Screen.PixelsPerInch);
    R.Bottom := MulDiv(ABmp.Height, cTwipsPerInch, Screen.PixelsPerInch);
    Range.hdc := ABmp.Canvas.Handle;
    Range.hdcTarget := ABmp.Canvas.Handle;
    Range.rc := R;
    Range.rcPage := R;
    Range.chrg.cpMin := 0;
    Range.chrg.cpMax := -1;
    SendMessage(Handle, EM_FORMATRANGE, 1, Integer(@Range));
    SendMessage(Handle, EM_FORMATRANGE, 0, 0); { flush buffer }
    Picture.Assign(ABmp);
  finally
    ABmp.Free;
  end;
end;

procedure TJvCustomRichEdit.SelectionChange;
begin
  if Assigned(OnSelectionChange) then
    OnSelectionChange(Self);
end;

procedure TJvCustomRichEdit.SetAdvancedTypography(const Value: Boolean);
begin
  FAdvancedTypography := Value;
  if FAdvancedTypography then
    AutoAdvancedTypography := False;
  UpdateTypographyOptions(FAdvancedTypography);
end;

procedure TJvCustomRichEdit.SetAllowObjects(Value: Boolean);
begin
  if FAllowObjects <> Value then
  begin
    FAllowObjects := Value;
    RecreateWnd;
  end;
end;

procedure TJvCustomRichEdit.SetAutoURLDetect(Value: Boolean);
begin
  if Value <> FAutoURLDetect then
  begin
    FAutoURLDetect := Value;
    if HandleAllocated and (RichEditVersion >= 2) then
      SendMessage(Handle, EM_AUTOURLDETECT, Longint(FAutoURLDetect), 0);
  end;
end;

procedure TJvCustomRichEdit.SetDefAttributes(Value: TJvTextAttributes);
begin
  FDefAttributes.Assign(Value);
end;

procedure TJvCustomRichEdit.SetFlat(const Value: Boolean);
begin
  Ctl3D := not Value;
end;

procedure TJvCustomRichEdit.SetHideScrollBars(Value: Boolean);
begin
  if HideScrollBars <> Value then
  begin
    FHideScrollBars := Value;
    RecreateWnd;
  end;
end;

procedure TJvCustomRichEdit.SetHideSelection(Value: Boolean);
begin
  if HideSelection <> Value then
  begin
    FHideSelection := Value;
    SendMessage(Handle, EM_HIDESELECTION, Ord(HideSelection), LParam(True));
  end;
end;

procedure TJvCustomRichEdit.SetLangOptions(Value: TRichLangOptions);
var
  Flags: DWORD;
  I: TRichLangOption;
begin
  FLangOptions := Value;
  if HandleAllocated and (RichEditVersion >= 2) then
  begin
    Flags := 0;
    for I := Low(TRichLangOption) to High(TRichLangOption) do
      if I in Value then
        Flags := Flags or RichLangOptions[I];
    SendMessage(Handle, EM_SETLANGOPTIONS, 0, LParam(Flags));
  end;
end;

procedure TJvCustomRichEdit.SetOLEDragDrop(const Value: Boolean);
begin
  if FOLEDragDrop <> Value then
  begin
    FOLEDragDrop := Value;
    RecreateWnd;
  end;
end;

procedure TJvCustomRichEdit.SetParentFlat(const Value: Boolean);
begin
  ParentCtl3D := Value;
end;

procedure TJvCustomRichEdit.SetPlainText(Value: Boolean);
var
  State: TJvRichEditState;
begin
  if PlainText <> Value then
  begin
    if HandleAllocated and (RichEditVersion >= 2) then
    begin
      State := TJvRichEditState.Create;
      try
        State.ForcePlainText := (csDesigning in ComponentState) or Value;
        State.Store(Self);

        TJvRichEditStrings(Lines).EnableChange(False);
        try
          SendMessage(Handle, WM_SETTEXT, 0, 0);
          UpdateTextModes(Value);
          FPlainText := Value;
        finally
          TJvRichEditStrings(Lines).EnableChange(True);
        end;

        State.Restore(Self);
      finally
        State.Free;
      end;
    end;
    FPlainText := Value;
  end;
end;

procedure TJvCustomRichEdit.SetRichEditStrings(Value: TStrings);
begin
  FLines.Assign(Value);
end;

procedure TJvCustomRichEdit.SetSelAttributes(Value: TJvTextAttributes);
begin
  FSelAttributes.Assign(Value);
end;

procedure TJvCustomRichEdit.SetSelection(StartPos, EndPos: Longint;
  ScrollCaret: Boolean);
var
  CharRange: TCharRange;
begin
  with CharRange do
  begin
    cpMin := StartPos;
    cpMax := EndPos;
  end;
  SendMessage(Handle, EM_EXSETSEL, 0, Longint(@CharRange));
  if ScrollCaret then
    SendMessage(Handle, Messages.EM_SCROLLCARET, 0, 0);
end;

procedure TJvCustomRichEdit.SetSelectionBar(Value: Boolean);
begin
  if FSelectionBar <> Value then
  begin
    FSelectionBar := Value;
    RecreateWnd;
  end;
end;

procedure TJvCustomRichEdit.SetSelLength(Value: Integer);
begin
  with GetSelection do
    SetSelection(cpMin, cpMin + Value, True);
end;

procedure TJvCustomRichEdit.SetSelStart(Value: Integer);
begin
  SetSelection(Value, Value, False);
end;

procedure TJvCustomRichEdit.SetSelText(const Value: string);
begin
  FLinesUpdating := True;
  inherited SelText := Value;
  FLinesUpdating := False;
end;

procedure TJvCustomRichEdit.SetStreamFormat(Value: TRichStreamFormat);
begin
  TJvRichEditStrings(Lines).Format := Value;
end;

procedure TJvCustomRichEdit.SetStreamMode(Value: TRichStreamModes);
begin
  TJvRichEditStrings(Lines).Mode := Value;
end;

procedure TJvCustomRichEdit.SetTitle(const Value: string);
begin
  if FTitle <> Value then
  begin
    FTitle := Value;
    UpdateHostNames;
  end;
end;

procedure TJvCustomRichEdit.SetUIActive(Active: Boolean);
var
  Form: TCustomForm;
begin
  try
    Form := GetParentForm(Self);
    if Form <> nil then
      if Active then
      begin
        if (Form.ActiveOleControl <> nil) and
          (Form.ActiveOleControl <> Self) then
          Form.ActiveOleControl.Perform(CM_UIDEACTIVATE, 0, 0);
        Form.ActiveOleControl := Self;
        if AllowInPlace and CanFocus then
          SetFocus;
        if Assigned(FOnInPlaceActivate) then
          FOnInPlaceActivate(Self); // CCR
      end
      else
      begin
        if Form.ActiveOleControl = Self then
          Form.ActiveOleControl := nil;
        if (Form.ActiveControl = Self) and AllowInPlace then
        begin
          Windows.SetFocus(Handle);
          SelectionChange;
        end;
        if Assigned(FOnInPlaceDeactivate) then
          FOnInPlaceDeactivate(Self); //  CCR
      end;
  except
    Application.HandleException(Self);
  end;
end;

procedure TJvCustomRichEdit.SetUndoLimit(Value: Integer);
begin
  if Value <> FUndoLimit then
  begin
    FUndoLimit := Value;
    if (RichEditVersion >= 2) and HandleAllocated then
      FUndoLimit := SendMessage(Handle, EM_SETUNDOLIMIT, Value, 0);
  end;
end;

{ Find & Replace Dialogs }

procedure TJvCustomRichEdit.SetupFindDialog(Dialog: TFindDialog;
  const SearchStr, ReplaceStr: string);
begin
  with Dialog do
  begin
    if SearchStr <> '' then
      FindText := SearchStr;
    if RichEditVersion = 1 then
      Options := Options + [frHideUpDown, frDown];
    OnFind := FindDialogFind;
    OnClose := FindDialogClose;
  end;
  if Dialog is TReplaceDialog then
    with TReplaceDialog(Dialog) do
    begin
      if ReplaceStr <> '' then
        ReplaceText := ReplaceStr;
      OnReplace := ReplaceDialogReplace;
    end;
end;

procedure TJvCustomRichEdit.SetWordAttributes(Value: TJvTextAttributes);
begin
  FWordAttributes.Assign(Value);
end;

procedure TJvCustomRichEdit.SetWordSelection(Value: Boolean);
var
  Options: LParam;
begin
  FWordSelection := Value;
  if HandleAllocated then
  begin
    Options := SendMessage(Handle, EM_GETOPTIONS, 0, 0);
    if Value then
      Options := Options or ECO_AUTOWORDSELECTION
    else
      Options := Options and not ECO_AUTOWORDSELECTION;
    SendMessage(Handle, EM_SETOPTIONS, ECOOP_SET, Options);
  end;
end;

procedure TJvCustomRichEdit.SetZoom(Value: Integer);
begin
  if (RichEditVersion >= 3) and HandleAllocated then
    if Value = 0 then
      SendMessage(Handle, EM_SETZOOM, 0, 0)
    else
      SendMessage(Handle, EM_SETZOOM, Value, 100);
end;

procedure TJvCustomRichEdit.StopGroupTyping;
begin
  if (RichEditVersion >= 2) and HandleAllocated then
    SendMessage(Handle, EM_STOPGROUPTYPING, 0, 0);
end;

procedure TJvCustomRichEdit.TextNotFound(Dialog: TFindDialog);
begin
  with Dialog do
    if Assigned(FOnTextNotFound) then
      FOnTextNotFound(Self, FindText);
end;

procedure TJvCustomRichEdit.UpdateHostNames;
var
  AppName: string;
begin
  if HandleAllocated and Assigned(FRichEditOle) then
  begin
    AppName := Application.Title;
    if Trim(AppName) = '' then
      AppName := ExtractFileName(Application.ExeName);
    if Trim(Title) = '' then
      IRichEditOle(FRichEditOle).SetHostNames(PChar(AppName), PChar(AppName))
    else
      IRichEditOle(FRichEditOle).SetHostNames(PChar(AppName), PChar(Title));
  end;
end;

procedure TJvCustomRichEdit.UpdateTextModes(Plain: Boolean);
const
  TextModes: array[Boolean] of DWORD = (TM_RICHTEXT, TM_PLAINTEXT);
  UndoModes: array[Boolean] of DWORD = (TM_SINGLELEVELUNDO, TM_MULTILEVELUNDO);
begin
  if (RichEditVersion >= 2) and HandleAllocated then
  begin
    SendMessage(Handle, EM_SETTEXTMODE, TextModes[Plain] or
      UndoModes[FUndoLimit > 1], 0);
  end;
end;

procedure TJvCustomRichEdit.UpdateTypographyOptions(const Advanced: Boolean);
const
  AdvancedModes: array[Boolean] of DWORD = (0, TO_ADVANCEDTYPOGRAPHY);
begin
  if HandleAllocated and (RichEditVersion >= 3) then
  begin
    SendMessage(Handle, EM_SETTYPOGRAPHYOPTIONS, AdvancedModes[Advanced],
      TO_ADVANCEDTYPOGRAPHY);
  end;
end;

procedure TJvCustomRichEdit.URLClick(const URLText: string; Button: TMouseButton);
begin
  if Assigned(OnURLClick) then
    OnURLClick(Self, URLText, Button);
end;

procedure TJvCustomRichEdit.WMDestroy(var Msg: TWMDestroy);
begin
  CloseObjects;
  ReleaseObject(FRichEditOle);
  inherited;
end;

procedure TJvCustomRichEdit.WMHScroll(var Msg: TWMHScroll);
begin
  inherited;
  if Assigned(FOnHorizontalScroll) then
    FOnHorizontalScroll(Self);
end;

procedure TJvCustomRichEdit.WMMouseMove(var Msg: TMessage);
begin
  inherited;
end;

procedure TJvCustomRichEdit.WMPaint(var Msg: TWMPaint);
var
  R, R1: TRect;
begin
  if RichEditVersion >= 2 then
    inherited
  else
  begin
    if GetUpdateRect(Handle, R, True) then
    begin
      with ClientRect do
        R1 := Rect(Right - 3, Top, Right, Bottom);
      if IntersectRect(R, R, R1) then
        InvalidateRect(Handle, @R1, True);
    end;
    if Painting then
      Invalidate
    else
    begin
      Painting := True;
      try
        inherited;
      finally
        Painting := False;
      end;
    end;
  end;
end;

procedure TJvCustomRichEdit.WMRButtonUp(var Msg: TMessage);
begin
  { RichEd20 does not pass the WM_RBUTTONUP message to defwndproc, }
  { so we get no WM_CONTEXTMENU message. Simulate message here.    }
  if ((RichEditVersion <> 1) or (Win32MajorVersion < 5)) and AllowObjects then
    Perform(WM_CONTEXTMENU, Handle, LParam(PointToSmallPoint(
      ClientToScreen(SmallPointToPoint(TWMMouse(Msg).Pos)))));
  inherited;
end;

procedure TJvCustomRichEdit.WMSetCursor(var Msg: TWMSetCursor);
begin
  inherited;
end;

procedure TJvCustomRichEdit.WMSetFont(var Msg: TWMSetFont);
begin
  FDefAttributes.Assign(Font);
end;

procedure TJvCustomRichEdit.WMVScroll(var Msg: TWMVScroll);
begin
  inherited;
  if Assigned(FOnVerticalScroll) then
    FOnVerticalScroll(Self);
end;

function TJvCustomRichEdit.WordAtCursor: string;
var
  Range: TCharRange;
begin
  Result := '';
  if HandleAllocated then
  begin
    Range.cpMax := SelStart;
    if Range.cpMax = 0 then
      Range.cpMin := 0
    else
    if SendMessage(Handle, EM_FINDWORDBREAK, WB_ISDELIMITER, Range.cpMax) <> 0 then
      Range.cpMin := SendMessage(Handle, EM_FINDWORDBREAK, WB_MOVEWORDLEFT, Range.cpMax)
    else
      Range.cpMin := SendMessage(Handle, EM_FINDWORDBREAK, WB_LEFT, Range.cpMax);
    while SendMessage(Handle, EM_FINDWORDBREAK, WB_ISDELIMITER, Range.cpMin) <> 0 do
      Inc(Range.cpMin);
    Range.cpMax := SendMessage(Handle, EM_FINDWORDBREAK, WB_RIGHTBREAK, Range.cpMax);
    Result := Trim(GetTextRange(Range.cpMin, Range.cpMax));
  end;
end;

//=== { TJvMSTextConversion } ================================================

constructor TJvMSTextConversion.Create(const AConverterFileName, AExtensions,
  ADescription: string; const AKind: TJvConversionKind);

  {$IFDEF COMPILER5}
  procedure StrTokenize(const S: string; Delimiter: Char; Strings: TStrings);
  var
    BufStart, BufEnd: PChar;
    Store: Char;
  begin
    BufStart := PChar(S);
    BufEnd := BufStart;
    while (BufEnd <> nil) and (BufEnd^ <> #0) do
    begin
      if BufEnd^ = Delimiter then
      begin
        Store := BufEnd^;
        BufEnd^ := #0;
        Strings.Add(BufStart);
        BufEnd^ := Store;
        BufStart := BufEnd;
        Inc(BufStart);
      end;
      Inc(BufEnd);
    end;
    if (BufStart <> nil) and (BufStart^ <> #0) then
      Strings.Add(BufStart);
  end;
  {$ENDIF COMPILER5}

begin
  inherited Create;
  FExtensions := TStringList.Create;
  {$IFDEF COMPILER5}
  StrTokenize(AExtensions, ' ', FExtensions);
  {$ELSE}
  FExtensions.Delimiter := ' ';
  FExtensions.DelimitedText := AExtensions;
  {$ENDIF COMPILER5}
  FConverterFileName := AConverterFileName;
  FDescription := ADescription;
  FConverterKind := AKind;
  FThreadDone := True;
  FCancel := False;
end;

destructor TJvMSTextConversion.Destroy;
begin
  Done;
  FExtensions.Free;
  inherited Destroy;
end;

function TJvMSTextConversion.CanHandle(const AExtension: string;
  const AKind: TJvConversionKind): Boolean;
var
  I: Integer;
begin
  Result := CanHandle(AKind);
  if not Result then
    Exit;

  for I := 0 to FExtensions.Count - 1 do
    if (FExtensions[I] = '*') or (FExtensions[I] = AExtension) then
    begin
      Result := True;
      Exit;
    end;

  Result := False;
end;

function TJvMSTextConversion.CanHandle(
  const AKind: TJvConversionKind): Boolean;
begin
  Result := AKind = FConverterKind;
end;

procedure TJvMSTextConversion.Check(Result: FCE);
begin
  if Result <> fceNoErr then
    DoError(Result);
end;

function TJvMSTextConversion.ConvertRead(Buffer: PChar;
  BufSize: Integer): Integer;
var
  AvailableBufferSize: Integer;
  DestBufferPtr: PChar;
  ByteCount: Integer;
begin
  { Fill Buffer with BufSize bytes data from FBuffer }

  if not Assigned(FForeignToRtf32) then
    DoError(fceReadErr);

  AvailableBufferSize := BufSize;
  DestBufferPtr := Buffer;

  repeat
    if FBytesAvailable = 0 then
    begin
      Unlock;
      FRichEditReady.SetEvent;

      WaitUntilThreadReady;
      FThreadReady.ResetEvent;
      { Thread can have set FConversionError & FThreadDone so check those: }

      if FConversionError <> fceNoErr then
        DoError(FConversionError);

      if FThreadDone then
      begin
        Result := BufSize - AvailableBufferSize;
        Exit;
      end;
    end;

    Lock;

    ByteCount := Min(AvailableBufferSize, FBytesAvailable);
    Move(FBufferPtr^, DestBufferPtr^, ByteCount);
    Inc(DestBufferPtr, ByteCount);
    Inc(FBufferPtr, ByteCount);
    Dec(FBytesAvailable, ByteCount);
    Dec(AvailableBufferSize, ByteCount);

    DoProgress(FTempProgress);
  until AvailableBufferSize = 0;

  Result := BufSize;
end;

function TJvMSTextConversion.ConvertWrite(Buffer: PChar;
  BufSize: Integer): Integer;
var
  DestBufferPtr: PChar;
begin
  if not Assigned(FForeignToRtf32) then
    DoError(fceWriteErr);

  { Result = bytes actually written }
  Result := BufSize;

  while BufSize <> 0 do
  begin
    { wait until thread is ready to export more data.. }
    WaitUntilThreadReady;
    FThreadReady.ResetEvent;

    if FConversionError <> fceNoErr then
      DoError(FConversionError);

    { FBytesAvailable indicates here how many bytes of data are available for
      the converter dll to convert. }
    FBytesAvailable := Min(BufSize, CConvertBufferSize);
    Dec(BufSize, FBytesAvailable);

    DestBufferPtr := GlobalLock(FBuffer);
    if not Assigned(DestBufferPtr) then
      DoError(fceNoMemory);
    Move(Buffer^, DestBufferPtr^, FBytesAvailable);
    GlobalUnlock(FBuffer);

    DoProgress(FTempProgress);

    { Signal that data is ready to be exported }
    FRichEditReady.SetEvent;
  end;
end;

procedure TJvMSTextConversion.DoConversion;
{ This procedure is called in the context of the thread }
var
  hDesc: HGLOBAL;
  hSubset: HGLOBAL;
  LConversionError: FCE;
begin
  { insanity check }
  if (FBuffer = 0) or (GCurrentConverter <> Self) then
  begin
    FConversionError := fceNoMemory;
    FThreadDone := True;
    FThreadReady.SetEvent;

    Exit;
  end;

  hDesc := StringToHGLOBAL('');
  hSubset := StringToHGLOBAL('');

  if FConverterKind = ckImport then
  begin
    WaitUntilRichEditReady;
    FRichEditReady.ResetEvent;

    LConversionError := FForeignToRtf32(FFileName, nil, FBuffer,
      hDesc, hSubset, ImportCallback);

    { This ensures that the ConvertRead picks up the last bytes before FThreadDone is set }
    FThreadReady.SetEvent;
    WaitUntilRichEditReady;
  end
  else
    LConversionError := FRtfToForeign32(FFileName, nil, FBuffer,
      hDesc, ExportCallback);

  GlobalFree(hDesc);
  GlobalFree(hSubset);

  if (FConversionError = fceNoErr) and not FCancel then
    FConversionError := LConversionError;

  FThreadDone := True;
  FThreadReady.SetEvent;
end;

procedure TJvMSTextConversion.DoError(ErrorCode: FCE);
begin
  FConversionError := ErrorCode;
  raise EMSTextConversionError.Create(TranslateError(ErrorCode), ErrorCode);
end;

procedure TJvMSTextConversion.Done;
begin
  if Error then
    FCancel := True;

  while not FThreadDone do
  begin
    FRichEditReady.SetEvent;
    WaitUntilThreadReady;

    FBytesAvailable := 0;

    FThreadReady.ResetEvent;
  end;

  Unlock;
  if FBuffer <> 0 then
    GlobalFree(FBuffer);
  FBuffer := 0;

  FreeAndNil(FThreadReady);
  FreeAndNil(FRichEditReady);

  if Assigned(FUninitConverter) then
    FUninitConverter;

  FreeConverter;

  if FFileName <> 0 then
    GlobalFree(FFileName);
  FFileName := 0;

  if GCurrentConverter = Self then
    GCurrentConverter := nil;
  FInitDone := False;

  inherited Done;
end;

function TJvMSTextConversion.Error: Boolean;
begin
  Result := (FConversionError <> fceNoErr) and
    (FConversionError <> fceUserCancel);
end;

function TJvMSTextConversion.ErrorStr: string;
begin
  if not Error then
  begin
    Result := '';
    Exit;
  end;

  Result := TranslateError(FConversionError);
  if Result = '' then
  begin
    Result := FCEToString(FConversionError);
    if Result = '' then
      Result := Format(RsEConversionError, [FConversionError]);
  end;
end;

function TJvMSTextConversion.Filter: string;
var
  I: Integer;
  LFilter: string;
begin
  //'Text files (*.txt)|*.TXT'
  //'Description (*.htm; *.html)|*.HTM;*.HTML'

  LFilter := '';
  Result := '';
  for I := 0 to FExtensions.Count - 1 do
  begin
    Result := Result + '*.' + FExtensions[I] + '; ';
    LFilter := LFilter + '*.' + FExtensions[I] + ';';
  end;
  if Result > '' then
    Delete(Result, Length(Result) - 1, 2);
  if LFilter > '' then
    Delete(LFilter, Length(LFilter), 1);
  if Result > '' then
    Result := FDescription + ' (' + Result + ')|' + LFilter
  else
    Result := FDescription;
end;

procedure TJvMSTextConversion.FreeConverter;
begin
  if FConverter <> 0 then
    FreeLibrary(FConverter);

  FConverter := 0;

  FInitConverter32 := nil;
  FIsFormatCorrect32 := nil;
  FForeignToRtf32 := nil;
  FRtfToForeign32 := nil;
  FUninitConverter := nil;
  FCchFetchLpszError := nil;
end;

function TJvMSTextConversion.HandleExportCallback(cchBuff,
  nPercent: Integer): Longint;
begin
  if FBuffer = 0 then
  begin
    Result := fceNoMemory;
    Exit;
  end;

  FTempProgress := nPercent;

  { Signal that we're ready to convert data.. }
  FThreadReady.SetEvent;
  { ..and wait until the richedit has data available to convert }
  WaitUntilRichEditReady;
  FRichEditReady.ResetEvent;

  { Result = 0 indicates that we're done
    Result < 0 indicates error or user cancel
    Result > 0 indicates # of bytes put in FBuffer
  }
  if FCancel then
    Result := fceUserCancel
  else
    Result := FBytesAvailable;
end;

function TJvMSTextConversion.HandleImportCallback(cchBuff,
  nPercent: Integer): Longint;
begin
  // cchBuff = a count of the bytes of RTF data that the converter has placed in
  //           ghBuff.
  // nPercent can range between 0 and 100, representing the estimate made by
  // the converter of how much of the conversion process has been completed.

  if FBuffer = 0 then
  begin
    Result := fceNoMemory;
    Exit;
  end;

  FTempProgress := nPercent;
  FBytesAvailable := cchBuff;

  { Signal that data is ready.. }
  FThreadReady.SetEvent;
  { ..and wait until additional data is wanted }
  WaitUntilRichEditReady;
  FRichEditReady.ResetEvent;

  { Result = 0 indicates that we're done
    Result < 0 indicates error or user cancel
    Result > 0 indicates # of bytes put in FBuffer
  }

  if FCancel then
    Result := fceUserCancel
  else
    { FBytesAvailable should be 0 by now }
    Result := FBytesAvailable;
end;

procedure TJvMSTextConversion.InitConverter;
begin
  if FInitDone then
    Exit;
  FInitDone := True;

  LoadConverter;
  if not Assigned(FInitConverter32) or
    not FInitConverter32(ParentWindow, PChar(AnsiUpperCaseFileName(Application.ExeName))) then

    raise EMSTextConversionError.CreateRes(@RsECouldNotInitConverter);
end;

function TJvMSTextConversion.IsFormatCorrect(
  const AFileName: string): Boolean;
var
  hFile: THandle;
  hClass: THandle;
begin
  InitConverter;

  Result := Assigned(FIsFormatCorrect32);
  if not Result then
    Exit;

  hFile := FileNameToHGLOBAL(AFileName);
  hClass := StringToHGLOBAL('');
  try
    Result := FIsFormatCorrect32(hFile, hClass) = fceTrue;
  finally
    GlobalFree(hClass);
    GlobalFree(hFile);
  end;
end;

procedure TJvMSTextConversion.LoadConverter;
begin
  if FConverter <> 0 then
    Exit;

  FConverter := LoadLibrary(PChar(FConverterFileName));
  if FConverter <> 0 then
  begin
    @FInitConverter32 := GetProcAddress(FConverter, InitConverter32Name);
    @FIsFormatCorrect32 := GetProcAddress(FConverter, IsFormatCorrect32Name);
    @FForeignToRtf32 := GetProcAddress(FConverter, ForeignToRtf32Name);
    @FRtfToForeign32 := GetProcAddress(FConverter, RtfToForeign32Name);
    @FUninitConverter := GetProcAddress(FConverter, UninitConverterName);
    @FCchFetchLpszError := GetProcAddress(FConverter, CchFetchLpszErrorName);
  end;
end;

procedure TJvMSTextConversion.Lock;
begin
  if FBufferPtr = nil then
    FBufferPtr := GlobalLock(FBuffer);

  if FBufferPtr = nil then
    DoError(fceNoMemory);
end;

function TJvMSTextConversion.Open(const AFileName: string;
  const AKind: TJvConversionKind): Boolean;
var
  Sa: TSecurityAttributes;
begin
  { Note: cleanup is done in method Done; method Done is always called
          after Open is called }

  Result := (AKind <> ckImport) or FileExists(AFileName);
  if not Result then
    Exit;

  if GCurrentConverter <> nil then
    raise EMSTextConversionError.CreateRes(@RsEConversionBusy);
  GCurrentConverter := Self;

  InitConverter;

  FFileName := FileNameToHGLOBAL(AFileName);
  if FFileName = 0 then
    DoError(fceNoMemory);

  FBuffer := GlobalAlloc(GHND, CConvertBufferSize);
  if FBuffer = 0 then
    DoError(fceNoMemory);

  Sa.nLength := SizeOf(TSecurityAttributes);
  Sa.lpSecurityDescriptor := nil;
  Sa.bInheritHandle := True;

  FThreadReady := TEvent.Create(@Sa, True, False, '');
  FRichEditReady := TEvent.Create(@Sa, True, False, '');

  FConversionError := fceNoErr;
  FThreadDone := False;
  FCancel := False;
  FBufferPtr := nil;

  FPercentDone := -1;
  DoProgress(0);

  TMSTextConversionThread.Create;

  Result := True;
end;

function TJvMSTextConversion.TextKind: TJvConversionTextKind;
begin
  Result := ctkRTF;
end;

function TJvMSTextConversion.TranslateError(ErrorCode: FCE): string;
const
  CMaxErrorStrSize = 1024; { arbitrary value }
var
  Data: THandle;
  DataPtr: PChar;
  Size: Longint;
begin
  InitConverter;

  if not Assigned(FCchFetchLpszError) then
  begin
    Result := FCEToString(ErrorCode);
    Exit;
  end;

  Data := GlobalAlloc(GHND, CMaxErrorStrSize + 1); // with last #0, thus + 1
  try
    DataPtr := GlobalLock(Data);
    try
      Size := FCchFetchLpszError(ErrorCode, DataPtr, CMaxErrorStrSize);
      if Size > 0 then
        SetString(Result, DataPtr, Size)
      else
        Result := '';
    finally
      GlobalUnlock(Data);
    end;
  finally
    GlobalFree(Data);
  end;
end;

procedure TJvMSTextConversion.Unlock;
begin
  if FBufferPtr <> nil then
    GlobalUnlock(FBuffer);
  FBufferPtr := nil;
end;

function TJvMSTextConversion.UserCancel: Boolean;
begin
  Result := FConversionError = fceUserCancel;
end;

procedure TJvMSTextConversion.WaitUntilRichEditReady;
var
  Msg: TMsg;
  H: THandle;
begin
  H := FRichEditReady.Handle;

  while MsgWaitForMultipleObjects(1, H, False, INFINITE,
    QS_SENDMESSAGE) <> WAIT_OBJECT_0 do
  begin
    PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE);
  end;
end;

procedure TJvMSTextConversion.WaitUntilThreadReady;
var
  Msg: TMsg;
  H: THandle;
begin
  H := FThreadReady.Handle;

  while MsgWaitForMultipleObjects(1, H, False, INFINITE,
    QS_SENDMESSAGE) <> WAIT_OBJECT_0 do
  begin
    if PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) then
      Application.HandleMessage;
  end;
end;

//=== { TJvOEMConversion } ===================================================

function TJvOEMConversion.ConvertRead(Buffer: PChar;
  BufSize: Integer): Integer;
var
  Mem: TMemoryStream;
begin
  Mem := TMemoryStream.Create;
  try
    Mem.SetSize(BufSize);
    Result := inherited ConvertRead(PChar(Mem.Memory), BufSize);
    OemToCharBuff(PChar(Mem.Memory), Buffer, Result);
  finally
    Mem.Free;
  end;
end;

function TJvOEMConversion.ConvertWrite(Buffer: PChar;
  BufSize: Integer): Integer;
var
  Mem: TMemoryStream;
begin
  Mem := TMemoryStream.Create;
  try
    Mem.SetSize(BufSize);
    CharToOemBuff(Buffer, PChar(Mem.Memory), BufSize);
    Result := inherited ConvertWrite(PChar(Mem.Memory), BufSize);
  finally
    Mem.Free;
  end;
end;

function TJvOEMConversion.TextKind: TJvConversionTextKind;
begin
  Result := ctkBothPreferRTF;
end;

//=== { TJvParaAttributes } ==================================================

constructor TJvParaAttributes.Create(AOwner: TJvCustomRichEdit);
begin
  inherited Create;
  FRichEdit := AOwner;
  // FIndentationStyle := isRichEdit; // = 0 so not needed; added by J.G. Boerema
end;

procedure TJvParaAttributes.Assign(Source: TPersistent);
var
  I: Integer;
  Paragraph: TParaFormat2;
begin
  if Source is TParaAttributes then
  begin
    Alignment := TParaAlignment(TParaAttributes(Source).Alignment);
    FirstIndent := TParaAttributes(Source).FirstIndent;
    LeftIndent := TParaAttributes(Source).LeftIndent;
    RightIndent := TParaAttributes(Source).RightIndent;
    Numbering := TJvNumbering(TParaAttributes(Source).Numbering);
    for I := 0 to MAX_TAB_STOPS - 1 do
      Tab[I] := TParaAttributes(Source).Tab[I];
  end
  else
  if Source is TJvParaAttributes then
  begin
    TJvParaAttributes(Source).GetAttributes(Paragraph);
    SetAttributes(Paragraph);
  end
  else
    inherited Assign(Source);
end;

procedure TJvParaAttributes.AssignTo(Dest: TPersistent);
var
  I: Integer;
begin
  if Dest is TParaAttributes then
  begin
    with TParaAttributes(Dest) do
    begin
      if Self.Alignment = paJustify then
        Alignment := taLeftJustify
      else
        Alignment := TAlignment(Self.Alignment);
      FirstIndent := Self.FirstIndent;
      LeftIndent := Self.LeftIndent;
      RightIndent := Self.RightIndent;
      if Self.Numbering <> nsNone then
        Numbering := TNumberingStyle(nsBullet)
      else
        Numbering := TNumberingStyle(nsNone);
      for I := 0 to MAX_TAB_STOPS - 1 do
        Tab[I] := Self.Tab[I];
    end;
  end
  else
    inherited AssignTo(Dest);
end;

function TJvParaAttributes.GetAlignment: TParaAlignment;
var
  Paragraph: TParaFormat2;
begin
  GetAttributes(Paragraph);
  Result := TParaAlignment(Paragraph.wAlignment - 1);
end;

procedure TJvParaAttributes.GetAttributes(var Paragraph: TParaFormat2);
begin
  InitPara(Paragraph);
  if FRichEdit.HandleAllocated then
    SendMessage(FRichEdit.Handle, EM_GETPARAFORMAT, 0, LParam(@Paragraph));
end;

function TJvParaAttributes.GetFirstIndent: Longint;
var
  Paragraph: TParaFormat2;
begin
  GetAttributes(Paragraph);
  if IndentationStyle = isRichEdit then
    Result := Paragraph.dxStartIndent div CTwipsPerPoint
  else // isOffice
    Result := -Paragraph.dxOffset div CTwipsPerPoint;
end;

function TJvParaAttributes.GetHeadingStyle: THeadingStyle;
var
  Paragraph: TParaFormat2;
begin
  if RichEditVersion < 3 then
    Result := 0
  else
  begin
    { See MSDN, ITextPara.GetStyle documentation:

      -1  : StyleNormal
      -2  : StyleHeading1
      -3  : StyleHeading2
      ..
      -10 : StyleHeading9

    }
    GetAttributes(Paragraph);
    Paragraph.sStyle := -(Paragraph.sStyle + 1);
    if (Paragraph.sStyle >= Low(THeadingStyle)) and (Paragraph.sStyle <= High(THeadingStyle)) then
      Result := THeadingStyle(Paragraph.sStyle)
    else
      Result := 0;
  end;
end;

function TJvParaAttributes.GetLeftIndent: Longint;
var
  Paragraph: TParaFormat2;
begin
  GetAttributes(Paragraph);
  if IndentationStyle = isRichEdit then
    Result := Paragraph.dxOffset div CTwipsPerPoint
  else // isOffice
    Result := (Paragraph.dxStartIndent + Paragraph.dxOffset) div CTwipsPerPoint;
end;

function TJvParaAttributes.GetLineSpacing: Longint;
var
  Paragraph: TParaFormat2;
begin
  GetAttributes(Paragraph);
  Result := Paragraph.dyLineSpacing div CTwipsPerPoint;
end;

function TJvParaAttributes.GetLineSpacingRule: TLineSpacingRule;
var
  Paragraph: TParaFormat2;
begin
  GetAttributes(Paragraph);
  Result := TLineSpacingRule(Paragraph.bLineSpacingRule);
end;

function TJvParaAttributes.GetNumbering: TJvNumbering;
var
  Paragraph: TParaFormat2;
begin
  GetAttributes(Paragraph);
  Result := TJvNumbering(Paragraph.wNumbering);
  if RichEditVersion = 1 then
    if Result <> nsNone then
      Result := nsBullet;
end;

function TJvParaAttributes.GetNumberingStart: Integer;
var
  Paragraph: TParaFormat2;
begin
  GetAttributes(Paragraph);
  Result := Paragraph.wNumberingStart;
end;

function TJvParaAttributes.GetNumberingStyle: TJvNumberingStyle;
var
  Paragraph: TParaFormat2;
begin
  if RichEditVersion < 2 then
    Result := nsSimple
  else
  begin
    GetAttributes(Paragraph);
    case Paragraph.wNumberingStyle of
      PFNS_PERIOD: Result := nsPeriod;
      PFNS_PARENS: Result := nsEnclosed;
      PFNS_PLAIN: Result := nsSimple;
    else
      Result := nsParenthesis;
    end;
  end;
end;

function TJvParaAttributes.GetNumberingTab: Word;
var
  Paragraph: TParaFormat2;
begin
  GetAttributes(Paragraph);
  Result := Paragraph.wNumberingTab div CTwipsPerPoint;
end;

function TJvParaAttributes.GetRightIndent: Longint;
var
  Paragraph: TParaFormat2;
begin
  GetAttributes(Paragraph);
  Result := Paragraph.dxRightIndent div CTwipsPerPoint;
end;

function TJvParaAttributes.GetSpaceAfter: Longint;
var
  Paragraph: TParaFormat2;
begin
  GetAttributes(Paragraph);
  Result := Paragraph.dySpaceAfter div CTwipsPerPoint;
end;

function TJvParaAttributes.GetSpaceBefore: Longint;
var
  Paragraph: TParaFormat2;
begin
  GetAttributes(Paragraph);
  Result := Paragraph.dySpaceBefore div CTwipsPerPoint;
end;

function TJvParaAttributes.GetTab(Index: Byte): Longint;
var
  Paragraph: TParaFormat2;
begin
  GetAttributes(Paragraph);
  Result := (Paragraph.rgxTabs[Index] and TA_TAB) div CTwipsPerPoint;
end;

function TJvParaAttributes.GetTabAlignment(Index: Byte): TJvTabAlignment;
var
  Paragraph: TParaFormat2;
  Temp: Integer;
begin
  if Index >= MAX_TAB_STOPS - 1 then
  begin
    Result := TJvTabAlignment(0);
    Exit;
  end;
  GetAttributes(Paragraph);
  {Result := TJvTabAlignment((Paragraph.rgxTabs[Index] and TA_ALIGNMENT) shr 24);}
  // D6 doesnt want to do it in one step so:
  Temp := (Paragraph.rgxTabs[Index] and TA_ALIGNMENT) shr 24;
  Result := TJvTabAlignment(Temp);
end;

function TJvParaAttributes.GetTabCount: Integer;
var
  Paragraph: TParaFormat2;
begin
  GetAttributes(Paragraph);
  Result := Paragraph.cTabCount;
end;

function TJvParaAttributes.GetTabLeader(Index: Byte): TJvTabLeader;
var
  Paragraph: TParaFormat2;
  Temp: Integer;
begin
  if Index >= MAX_TAB_STOPS - 1 then
  begin
    Result := TJvTabLeader(0);
    Exit;
  end;
  GetAttributes(Paragraph);
  {Result := TJvTabAlignment((Paragraph.rgxTabs[Index] and TA_LEADER) shr 28);}
  // D6 doesnt want to do it in one step so:
  // Note: and TA_LEADER not necessary: those bits get shifted out anyway
  Temp := (Paragraph.rgxTabs[Index] {and TA_LEADER}) shr 28;
  Result := TJvTabLeader(Temp);
end;

function TJvParaAttributes.GetTableStyle: TParaTableStyle;
var
  Paragraph: TParaFormat2;
begin
  Result := tsNone;
  if RichEditVersion < 2 then
    Exit;
  GetAttributes(Paragraph);
  with Paragraph do
  begin
    if (wReserved and PFE_TABLEROW) <> 0 then
      Result := tsTableRow
    else
    if (wReserved and PFE_TABLECELLEND) <> 0 then
      Result := tsTableCellEnd
    else
    if (wReserved and PFE_TABLECELL) <> 0 then
      Result := tsTableCell;
  end;
end;

procedure TJvParaAttributes.InitPara(var Paragraph: TParaFormat2);
begin
  FillChar(Paragraph, SizeOf(Paragraph), 0);
  if RichEditVersion >= 2 then
    Paragraph.cbSize := SizeOf(Paragraph)
  else
    Paragraph.cbSize := SizeOf(TParaFormat);
end;

procedure TJvParaAttributes.SetAlignment(Value: TParaAlignment);
var
  Paragraph: TParaFormat2;
begin
  InitPara(Paragraph);
  if Value = paJustify then
    FRichEdit.NeedAdvancedTypography;
  with Paragraph do
  begin
    dwMask := PFM_ALIGNMENT;
    wAlignment := Ord(Value) + 1;
  end;
  SetAttributes(Paragraph);
end;

procedure TJvParaAttributes.SetAttributes(var Paragraph: TParaFormat2);
begin
  FRichEdit.HandleNeeded; { we REALLY need the handle for BiDi }
  if FRichEdit.HandleAllocated then
  begin
    if FRichEdit.UseRightToLeftAlignment then
      if Paragraph.wAlignment = PFA_LEFT then
        Paragraph.wAlignment := PFA_RIGHT
      else
      if Paragraph.wAlignment = PFA_RIGHT then
        Paragraph.wAlignment := PFA_LEFT;
    SendMessage(FRichEdit.Handle, EM_SETPARAFORMAT, 0, LParam(@Paragraph));
  end;
end;

procedure TJvParaAttributes.SetFirstIndent(Value: Longint);
var
  Paragraph: TParaFormat2;
begin
  InitPara(Paragraph);
  with Paragraph do
  begin
    if IndentationStyle = isRichEdit then
    begin
      dwMask := PFM_STARTINDENT;
      dxStartIndent := Value * CTwipsPerPoint;
    end
    else // isOffice
    begin
      dwMask := PFM_STARTINDENT + PFM_OFFSET;
      dxStartIndent := (Value + LeftIndent) * CTwipsPerPoint;
      dxOffset := (LeftIndent * CTwipsPerPoint) - dxStartIndent;
    end;
  end;
  SetAttributes(Paragraph);
end;

procedure TJvParaAttributes.SetHeadingStyle(Value: THeadingStyle);
var
  Paragraph: TParaFormat2;
begin
  if RichEditVersion < 3 then
    Exit;
  InitPara(Paragraph);
  with Paragraph do
  begin
    dwMask := PFM_STYLE;
    sStyle := -Value - 1;
  end;
  SetAttributes(Paragraph);
end;

procedure TJvParaAttributes.SetLeftIndent(Value: Longint);
var
  Paragraph: TParaFormat2;
begin
  InitPara(Paragraph);
  with Paragraph do
  begin
    if IndentationStyle = isRichEdit then
    begin
      dwMask := PFM_OFFSET;
      dxOffset := Value * CTwipsPerPoint;
    end
    else // isOffice
    begin
      dwMask := PFM_STARTINDENT + PFM_OFFSET;
      dxStartIndent := (FirstIndent + Value) * CTwipsPerPoint;
      dxOffset := (Value * CTwipsPerPoint) - dxStartIndent;
    end;
  end;
  SetAttributes(Paragraph);
end;

procedure TJvParaAttributes.SetLineSpacing(Value: Longint);
var
  Paragraph: TParaFormat2;
begin
  if RichEditVersion < 2 then
    Exit;
  GetAttributes(Paragraph);
  with Paragraph do
  begin
    dwMask := PFM_LINESPACING;
    dyLineSpacing := Value * CTwipsPerPoint;
  end;
  SetAttributes(Paragraph);
end;

procedure TJvParaAttributes.SetLineSpacingRule(Value: TLineSpacingRule);
var
  Paragraph: TParaFormat2;
begin
  if RichEditVersion < 2 then
    Exit;
  GetAttributes(Paragraph);
  with Paragraph do
  begin
    dwMask := PFM_LINESPACING;
    bLineSpacingRule := Ord(Value);
  end;
  SetAttributes(Paragraph);
end;

procedure TJvParaAttributes.SetNumbering(Value: TJvNumbering);
var
  Paragraph: TParaFormat2;
begin
  if RichEditVersion = 1 then
    if Value <> nsNone then
      Value := TJvNumbering(PFN_BULLET);
  case Value of
    nsNone:
      LeftIndent := 0;
  else
    if LeftIndent < 10 then
      LeftIndent := 10;
  end;
  InitPara(Paragraph);
  with Paragraph do
  begin
    dwMask := PFM_NUMBERING;
    wNumbering := Ord(Value);
  end;
  SetAttributes(Paragraph);
end;

procedure TJvParaAttributes.SetNumberingStart(const Value: Integer);
var
  Paragraph: TParaFormat2;
begin
  InitPara(Paragraph);
  with Paragraph do
  begin
    dwMask := PFM_NUMBERINGSTART;
    wNumberingStart := Value
  end;
  SetAttributes(Paragraph);
end;

procedure TJvParaAttributes.SetNumberingStyle(Value: TJvNumberingStyle);
const
  CNumberingStyle: array[TJvNumberingStyle] of Word = (PFNS_PAREN, PFNS_PERIOD, PFNS_PARENS, PFNS_PLAIN);
var
  Paragraph: TParaFormat2;
begin
  if RichEditVersion < 2 then
    Exit;
  InitPara(Paragraph);
  with Paragraph do
  begin
    dwMask := PFM_NUMBERINGSTYLE;
    wNumberingStyle := CNumberingStyle[Value];
  end;
  SetAttributes(Paragraph);
end;

procedure TJvParaAttributes.SetNumberingTab(Value: Word);
var
  Paragraph: TParaFormat2;
begin
  if RichEditVersion < 2 then
    Exit;
  InitPara(Paragraph);
  with Paragraph do
  begin
    dwMask := PFM_NUMBERINGTAB;
    wNumberingTab := Value * CTwipsPerPoint;
  end;
  SetAttributes(Paragraph);
end;

procedure TJvParaAttributes.SetRightIndent(Value: Longint);
var
  Paragraph: TParaFormat2;
begin
  InitPara(Paragraph);
  with Paragraph do
  begin
    dwMask := PFM_RIGHTINDENT;
    dxRightIndent := Value * CTwipsPerPoint;
  end;
  SetAttributes(Paragraph);
end;

procedure TJvParaAttributes.SetSpaceAfter(Value: Longint);
var
  Paragraph: TParaFormat2;
begin
  if RichEditVersion < 2 then
    Exit;
  InitPara(Paragraph);
  with Paragraph do
  begin
    dwMask := PFM_SPACEAFTER;
    dySpaceAfter := Value * CTwipsPerPoint;
  end;
  SetAttributes(Paragraph);
end;

procedure TJvParaAttributes.SetSpaceBefore(Value: Longint);
var
  Paragraph: TParaFormat2;
begin
  if RichEditVersion < 2 then
    Exit;
  InitPara(Paragraph);
  with Paragraph do
  begin
    dwMask := PFM_SPACEBEFORE;
    dySpaceBefore := Value * CTwipsPerPoint;
  end;
  SetAttributes(Paragraph);
end;

procedure TJvParaAttributes.SetTab(Index: Byte; Value: Longint);
var
  Paragraph: TParaFormat2;
begin
  // Added a check for max tab (J.G. Boerema)
  if Index >= MAX_TAB_STOPS - 1 then
    Exit;
  GetAttributes(Paragraph);
  with Paragraph do
  begin
    // Note: the first part is a bugfix
    if cTabCount <= Index then
    begin
      cTabCount := Index + 1;
      rgxTabs[Index] := 0; // is this necessary?
    end;
    // Replace the TAB value with the new one but
    // remember the alignment and leader values
    rgxTabs[Index] := (rgxTabs[Index] and
      Longint(TA_ALIGNMENT or TA_LEADER)) or (Value * CTwipsPerPoint);
    dwMask := PFM_TABSTOPS;
    SetAttributes(Paragraph);
  end;
end;

procedure TJvParaAttributes.SetTabAlignment(Index: Byte; Value: TJvTabAlignment);
var
  Paragraph: TParaFormat2;
begin
  if RichEditVersion < 2 then
    Exit;
  if Index >= MAX_TAB_STOPS - 1 then
    Exit;
  if Value <> taOrdinary then
    FRichEdit.NeedAdvancedTypography;

  GetAttributes(Paragraph);
  with Paragraph do
  begin
    if cTabCount <= Index then
    begin
      cTabCount := Index + 1;
      rgxTabs[Index] := 0; // is this necessary?
    end;
    // Replace the old alignment value with the new one but
    // remember the tab and leader values
    rgxTabs[Index] := Longint(rgxTabs[Index] and TA_TAB_LEADER) or (Ord(Value) shl 24);
    dwMask := PFM_TABSTOPS;
    SetAttributes(Paragraph);
  end;
end;

procedure TJvParaAttributes.SetTabCount(Value: Integer);
var
  Paragraph: TParaFormat2;
begin
  GetAttributes(Paragraph);
  with Paragraph do
  begin
    dwMask := PFM_TABSTOPS;
    cTabCount := Value;
    SetAttributes(Paragraph);
  end;
end;

procedure TJvParaAttributes.SetTabLeader(Index: Byte; Value: TJvTabLeader);
var
  Paragraph: TParaFormat2;
begin
  if RichEditVersion < 2 then
    Exit;
  if Index >= MAX_TAB_STOPS - 1 then
    Exit;
  if Value <> tlNone then
    FRichEdit.NeedAdvancedTypography;

  GetAttributes(Paragraph);
  with Paragraph do
  begin
    if cTabCount <= Index then
    begin
      cTabCount := Index + 1;
      rgxTabs[Index] := 0; // is this necessary?
    end;
    // Replace the old leader value with the new one but
    // remember the tab and alignment values
    rgxTabs[Index] := (rgxTabs[Index] and TA_TAB_ALIGNMENT) or (Ord(Value) shl 28);
    dwMask := PFM_TABSTOPS;
    SetAttributes(Paragraph);
  end;
end;

procedure TJvParaAttributes.SetTableStyle(Value: TParaTableStyle);
var
  Paragraph: TParaFormat2;
begin
  if RichEditVersion < 2 then
    Exit;
  InitPara(Paragraph);
  with Paragraph do
  begin
    dwMask := PFM_TABLE;
    case Value of
      tsTableRow:
        wReserved := PFE_TABLEROW;
      tsTableCellEnd:
        wReserved := PFE_TABLECELLEND;
      tsTableCell:
        wReserved := PFE_TABLECELL;
    end;
  end;
  SetAttributes(Paragraph);
end;

//=== { TJvRichEditState } ===================================================

constructor TJvRichEditState.Create;
begin
  inherited Create;
  FStream := TMemoryStream.Create;
end;

destructor TJvRichEditState.Destroy;
begin
  FStream.Free;
  inherited Destroy;
end;

procedure TJvRichEditState.Restore(RichEdit: TJvCustomRichEdit);
begin
  TJvRichEditStrings(RichEdit.Lines).Format := FStreamFormat;
  TJvRichEditStrings(RichEdit.Lines).Mode := FStreamMode;

  FStream.Position := 0;
  RichEdit.Lines.LoadFromStream(FStream);

  TJvRichEditStrings(RichEdit.Lines).Format := FOrigFormat;
  TJvRichEditStrings(RichEdit.Lines).Mode := FOrigMode;

  RichEdit.SelStart := FSelStart;
  RichEdit.SelLength := FSelLength;
  RichEdit.Modified := FModified;
end;

procedure TJvRichEditState.Store(RichEdit: TJvCustomRichEdit);
begin
  FModified := RichEdit.Modified;

  FSelStart := RichEdit.SelStart;
  FSelLength := RichEdit.SelLength;

  FOrigFormat := TJvRichEditStrings(RichEdit.Lines).Format;
  FOrigMode := TJvRichEditStrings(RichEdit.Lines).Mode;

  if RichEdit.PlainText or ForcePlainText then
    TJvRichEditStrings(RichEdit.Lines).Format := sfPlainText
  else
    TJvRichEditStrings(RichEdit.Lines).Format := sfRichText;
  TJvRichEditStrings(RichEdit.Lines).Mode := [smUnicode];

  FStreamFormat := TJvRichEditStrings(RichEdit.Lines).Format;
  FStreamMode := TJvRichEditStrings(RichEdit.Lines).Mode;

  RichEdit.Lines.SaveToStream(FStream);

  TJvRichEditStrings(RichEdit.Lines).Format := FOrigFormat;
  TJvRichEditStrings(RichEdit.Lines).Mode := FOrigMode;
end;

//=== { TJvRichEditStrings } =================================================

procedure TJvRichEditStrings.AddStrings(Strings: TStrings);
var
  SelChange: TNotifyEvent;
begin
  SelChange := FRichEdit.OnSelectionChange;
  FRichEdit.OnSelectionChange := nil;
  try
    inherited AddStrings(Strings);
  finally
    FRichEdit.OnSelectionChange := SelChange;
  end;
end;

procedure TJvRichEditStrings.Clear;
begin
  FRichEdit.Clear;
end;

procedure TJvRichEditStrings.Delete(Index: Integer);
const
  Empty: PChar = '';
var
  Selection: TCharRange;
begin
  if Index < 0 then
    Exit;
  Selection.cpMin := FRichEdit.GetLineIndex(Index);
  if Selection.cpMin <> -1 then
  begin
    Selection.cpMax := FRichEdit.GetLineIndex(Index + 1);
    if Selection.cpMax = -1 then
      Selection.cpMax := Selection.cpMin +
        FRichEdit.GetLineLength(Selection.cpMin);
    SendMessage(FRichEdit.Handle, EM_EXSETSEL, 0, Longint(@Selection));
    FRichEdit.FLinesUpdating := True;
    try
      SendMessage(FRichEdit.Handle, EM_REPLACESEL, 0, Longint(Empty));
    finally
      FRichEdit.FLinesUpdating := False;
    end;
  end;
end;

procedure TJvRichEditStrings.DoExport(AConverter: TJvConversion);
var
  EditStream: TEditStream;
  TextType: Longint;
begin
  with EditStream do
  begin
    dwCookie := Longint(AConverter);
    pfnCallBack := StreamSave;
    dwError := 0;
  end;
  case FFormat of
    sfDefault:
      if FRichEdit.PlainText then
        TextType := SF_TEXT
      else
        TextType := SF_RTF;
    sfRichText:
      TextType := SF_RTF;
  else {sfPlainText}
    TextType := SF_TEXT;
  end;
  if TextType = SF_RTF then
  begin
    if smNoObjects in Mode then
      TextType := SF_RTFNOOBJS;
    if smPlainRtf in Mode then
      TextType := TextType or SFF_PLAINRTF;
  end
  else
  if TextType = SF_TEXT then
  begin
    if (smUnicode in Mode) and (RichEditVersion > 1) then
      TextType := TextType or SF_UNICODE;
  end;
  if smSelection in Mode then
    TextType := TextType or SFF_SELECTION;
  SendMessage(FRichEdit.Handle, EM_STREAMOUT, TextType, Longint(@EditStream));

  if not AConverter.UserCancel then
  begin
    if AConverter.Error then
      raise EOutOfResources.Create(AConverter.ErrorStr)
    else
    if EditStream.dwError <> 0 then
      raise EOutOfResources.CreateRes(@sRichEditSaveFail);
  end;
end;

procedure TJvRichEditStrings.DoImport(AConverter: TJvConversion);
var
  EditStream: TEditStream;
  TextType: Longint;
  Cookie: TCookie;
begin
  Cookie := TCookie.Create(AConverter);
  try
    with EditStream do
    begin
      dwCookie := Longint(Cookie);
      pfnCallBack := StreamLoad;
      dwError := 0;
    end;
    case FFormat of
      sfDefault:
        if FRichEdit.PlainText then
          TextType := SF_TEXT
        else
          TextType := SF_RTF;
      sfRichText:
        TextType := SF_RTF;
    else {sfPlainText}
      TextType := SF_TEXT;
    end;
    if TextType = SF_RTF then
    begin
      if smPlainRtf in Mode then
        TextType := TextType or SFF_PLAINRTF;
    end;
    if TextType = SF_TEXT then
    begin
      if (smUnicode in Mode) and (RichEditVersion > 1) then
      begin
        TextType := TextType or SF_UNICODE;
        EditStream.pfnCallback := StreamLoadW;
      end;
    end;
    if smSelection in Mode then
      TextType := TextType or SFF_SELECTION;
    SendMessage(FRichEdit.Handle, EM_STREAMIN, TextType, Longint(@EditStream));

    if not AConverter.UserCancel then
    begin
      if (EditStream.dwError <> 0) and AConverter.Retry then
      begin
        if (TextType and SF_RTF) = SF_RTF then
        begin
          TextType := SF_TEXT;
          if (smUnicode in Mode) and (RichEditVersion > 1) then
          begin
            TextType := TextType or SF_UNICODE;
            EditStream.pfnCallback := StreamLoadW;
          end;
        end
        else
        begin
          TextType := SF_RTF;
          if smPlainRtf in Mode then
            TextType := TextType or SFF_PLAINRTF;
          EditStream.pfnCallback := StreamLoad;
        end;
        SendMessage(FRichEdit.Handle, EM_STREAMIN, TextType, Longint(@EditStream));
      end;

      if AConverter.Error then
        raise EOutOfResources.Create(AConverter.ErrorStr)
      else
      if EditStream.dwError <> 0 then
        raise EOutOfResources.CreateRes(@sRichEditLoadFail);
    end;

    if not (smSelection in Mode) then // Mantis 2591: do not change the selection if there is one
      FRichEdit.SetSelection(0, 0, True);
  finally
    Cookie.Free;
  end;
end;

procedure TJvRichEditStrings.EnableChange(const Value: Boolean);
var
  EventMask: Longint;
begin
  with FRichEdit do
  begin
    EventMask := SendMessage(Handle, EM_GETEVENTMASK, 0, 0);
    if Value then
      EventMask := EventMask or ENM_CHANGE
    else
      EventMask := EventMask and not ENM_CHANGE;
    SendMessage(Handle, EM_SETEVENTMASK, 0, EventMask);
  end;
end;

function TJvRichEditStrings.Get(Index: Integer): string;
var
  Text: array[0..4095] of Char;
  L: Integer;
  W: Word;
begin
  // (rom) reimplemented as Move
  W := SizeOf(Text);
  System.Move(W, Text[0], SizeOf(Word));
  L := SendMessage(FRichEdit.Handle, EM_GETLINE, Index, Longint(@Text));
  if (Text[L - 2] = Cr) and (Text[L - 1] = Lf) then
    Dec(L, 2)
  else
  if (RichEditVersion >= 2) and (Text[L - 1] = Cr) then
    Dec(L);
  SetString(Result, Text, L);
end;

function TJvRichEditStrings.GetCount: Integer;
begin
  with FRichEdit do
  begin
    Result := SendMessage(Handle, EM_GETLINECOUNT, 0, 0);
    if GetLineLength(GetLineIndex(Result - 1)) = 0 then
      Dec(Result);
  end;
end;

procedure TJvRichEditStrings.Insert(Index: Integer; const S: string);
var
  L: Integer;
  Selection: TCharRange;
  Fmt: PChar;
  Str: string;
begin
  if Index >= 0 then
  begin
    Selection.cpMin := FRichEdit.GetLineIndex(Index);
    if Selection.cpMin >= 0 then
    begin
      if RichEditVersion = 1 then
        Fmt := '%s' + sLineBreak
      else
        Fmt := '%s' + Cr;
    end
    else
    begin
      Selection.cpMin := FRichEdit.GetLineIndex(Index - 1);
      if Selection.cpMin < 0 then
      begin
        Selection.cpMin :=
          SendMessage(FRichEdit.Handle, EM_LINEINDEX, Index - 1, 0);
        if Selection.cpMin < 0 then
          Exit;
        L := SendMessage(FRichEdit.Handle, EM_LINELENGTH, Selection.cpMin, 0);
        if L = 0 then
          Exit;
        Inc(Selection.cpMin, L);
        if RichEditVersion = 1 then
          Fmt := sLineBreak + '%s'
        else
          Fmt := Cr + '%s';
      end
      else
      begin
        L := FRichEdit.GetLineLength(Selection.cpMin);
        if L = 0 then
          Exit;
        Inc(Selection.cpMin, L);
        if RichEditVersion = 1 then
          Fmt := '%s' + sLineBreak
        else
          Fmt := '%s' + Cr;
      end;
    end;
    Selection.cpMax := Selection.cpMin;
    SendMessage(FRichEdit.Handle, EM_EXSETSEL, 0, Longint(@Selection));
    Str := SysUtils.Format(Fmt, [S]);
    FRichEdit.FLinesUpdating := True;
    try
      SendMessage(FRichEdit.Handle, EM_REPLACESEL, 0, Longint(PChar(Str)));
    finally
      FRichEdit.FLinesUpdating := False;
    end;
    if RichEditVersion = 1 then
      if FRichEdit.SelStart <> (Selection.cpMax + Length(Str)) then
        raise EOutOfResources.CreateRes(@sRichEditInsertError);
  end;
end;

procedure TJvRichEditStrings.LoadFromFile(const FileName: string);
var
  SaveFormat: TRichStreamFormat;
  Converter: TJvConversion;
begin
  Converter := FRichEdit.GetConverter(FileName, ckImport);
  try
    Converter.OnProgress := ProgressCallback;
    SaveFormat := Format;
    try
      if FRichEdit.PlainText then
        { When PlainText is set, the control does not accept RTF }
        FFormat := sfPlainText
      else
      if FFormat = sfDefault then
        case Converter.TextKind of
          ctkText, ctkBothPreferText:
            FFormat := sfPlainText;
          ctkRTF, ctkBothPreferRTF:
            FFormat := sfRichText;
        end;

      if not Converter.Open(FileName, ckImport) then
        raise EOutOfResources.CreateRes(@sRichEditLoadFail);

      DoImport(Converter);
    finally
      FFormat := SaveFormat;
    end;
  finally
    Converter.Done;
    Converter.OnProgress := nil;
  end;
end;

procedure TJvRichEditStrings.LoadFromStream(Stream: TStream);
var
  SaveFormat: TRichStreamFormat;
  Converter: TJvConversion;
begin
  FRichEdit.HandleNeeded;
  if FRichEdit.HandleAllocated then
  begin
    Converter := FRichEdit.GetConverter(Stream, ckImport);
    try
      Converter.OnProgress := ProgressCallback;
      SaveFormat := Format;
      try
        if FRichEdit.PlainText then
          { When PlainText is set, the control does not accept RTF }
          FFormat := sfPlainText
        else
        if FFormat = sfDefault then
          case Converter.TextKind of
            ctkText, ctkBothPreferText:
              FFormat := sfPlainText;
            ctkRTF, ctkBothPreferRTF:
              FFormat := sfRichText;
          end;

        if not Converter.Open(Stream, ckImport) then
          raise EOutOfResources.CreateRes(@sRichEditLoadFail);

        DoImport(Converter)
      finally
        FFormat := SaveFormat;
      end;
    finally
      Converter.Done;
      Converter.OnProgress := nil;
    end;
  end;
end;

procedure TJvRichEditStrings.ProgressCallback(Sender: TObject);
begin
  if Sender is TJvConversion then
    FRichEdit.DoConversionProgress(TJvConversion(Sender).PercentDone);
end;

procedure TJvRichEditStrings.Put(Index: Integer; const S: string);
var
  Selection: TCharRange;
begin
  if Index >= 0 then
  begin
    Selection.cpMin := FRichEdit.GetLineIndex(Index);
    if Selection.cpMin <> -1 then
    begin
      Selection.cpMax := Selection.cpMin +
        FRichEdit.GetLineLength(Selection.cpMin);
      SendMessage(FRichEdit.Handle, EM_EXSETSEL, 0, Longint(@Selection));
      FRichEdit.FLinesUpdating := True;
      try
        SendMessage(FRichEdit.Handle, EM_REPLACESEL, 0, Longint(PChar(S)));
      finally
        FRichEdit.FLinesUpdating := False;
      end;
    end;
  end;
end;

procedure TJvRichEditStrings.SaveToFile(const FileName: string);
var
  SaveFormat: TRichStreamFormat;
  Converter: TJvConversion;
begin
  Converter := FRichEdit.GetConverter(FileName, ckExport);
  try
    Converter.OnProgress := ProgressCallback;
    SaveFormat := Format;
    try
      if FRichEdit.PlainText then
        { When PlainText is set, the control does not accept RTF }
        FFormat := sfPlainText
      else
      if FFormat = sfDefault then
        case Converter.TextKind of
          ctkText, ctkBothPreferText:
            FFormat := sfPlainText;
          ctkRTF, ctkBothPreferRTF:
            FFormat := sfRichText;
        end;

      if not Converter.Open(FileName, ckExport) then
        raise EOutOfResources.CreateRes(@sRichEditSaveFail);

      DoExport(Converter)
    finally
      FFormat := SaveFormat;
    end;
  finally
    Converter.Done;
    Converter.OnProgress := nil;
  end;
end;

procedure TJvRichEditStrings.SaveToStream(Stream: TStream);
var
  SaveFormat: TRichStreamFormat;
  Converter: TJvConversion;
begin
  FRichEdit.HandleNeeded;
  if FRichEdit.HandleAllocated then
  begin
    Converter := FRichEdit.GetConverter(Stream, ckExport);
    try
      Converter.OnProgress := ProgressCallback;
      SaveFormat := Format;
      try
        if FRichEdit.PlainText then
          { When PlainText is set, the control does not accept RTF }
          FFormat := sfPlainText
        else
        if FFormat = sfDefault then
          case Converter.TextKind of
            ctkText, ctkBothPreferText:
              FFormat := sfPlainText;
            ctkRTF, ctkBothPreferRTF:
              FFormat := sfRichText;
          end;

        if not Converter.Open(Stream, ckExport) then
          raise EOutOfResources.CreateRes(@sRichEditSaveFail);

        DoExport(Converter)
      finally
        FFormat := SaveFormat;
      end;
    finally
      Converter.Done;
      Converter.OnProgress := nil;
    end;
  end;
end;

procedure TJvRichEditStrings.SetTextStr(const Value: string);
begin
  EnableChange(False);
  try
    inherited SetTextStr(Value);
  finally
    EnableChange(True);
  end;
end;

procedure TJvRichEditStrings.SetUpdateState(Updating: Boolean);
begin
  if FRichEdit.Showing then
    SendMessage(FRichEdit.Handle, WM_SETREDRAW, Ord(not Updating), 0);
  if not Updating then
  begin
    FRichEdit.Refresh;
    FRichEdit.Perform(CM_TEXTCHANGED, 0, 0);
  end;
end;

//=== { TJvRTFConversion } ===================================================

function TJvRTFConversion.CanHandle(const AExtension: string;
  const AKind: TJvConversionKind): Boolean;
begin
  Result := AExtension = 'rtf';
end;

function TJvRTFConversion.Filter: string;
begin
  Result := RsRTFFilter;
end;

function TJvRTFConversion.IsFormatCorrect(AStream: TStream): Boolean;
const
  CRTFHeader = '{\rtf';
  CRTFHeaderSize = Length(CRTFHeader);
var
  SavedPosition: Int64;
  Buffer: array[0..CRTFHeaderSize] of Char; // + #0
begin
  SavedPosition := AStream.Position;
  try
    Buffer[CRTFHeaderSize] := #0;

    Result :=
      (AStream.Read(Buffer, CRTFHeaderSize) = CRTFHeaderSize) and
      (StrIComp(PChar(CRTFHeader), Buffer) = 0);
  finally
    AStream.Position := SavedPosition;
  end;
end;

function TJvRTFConversion.IsFormatCorrect(
  const AFileName: string): Boolean;
var
  LStream: TStream;
begin
  Result := FileExists(AFileName);
  if not Result then
    Exit;

  LStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite);
  try
    Result := IsFormatCorrect(LStream);
  finally
    LStream.Free;
  end;
end;

function TJvRTFConversion.TextKind: TJvConversionTextKind;
begin
  Result := ctkBothPreferRTF;
end;

//=== { TJvStreamConversion } ================================================

function TJvStreamConversion.ConvertRead(Buffer: PChar;
  BufSize: Integer): Integer;
begin
  Result := FStream.Read(Buffer^, BufSize);
  if FStreamSize > 0 then
  begin
    Inc(FBytesConverted, Result);
    DoProgress((FBytesConverted * 100 + FStreamSize div 2) div FStreamSize);
  end;
end;

function TJvStreamConversion.ConvertWrite(Buffer: PChar;
  BufSize: Integer): Integer;
begin
  Result := FStream.Write(Buffer^, BufSize);
  if FStreamSize > 0 then
  begin
    Inc(FBytesConverted, Result);
    DoProgress((FBytesConverted * 100 + FStreamSize div 2) div FStreamSize);
  end;
end;

procedure TJvStreamConversion.Done;
begin
  if FFreeStream then
    FStream.Free;
  FStream := nil;
  inherited Done;
end;

function TJvStreamConversion.Open(Stream: TStream;
  const AKind: TJvConversionKind): Boolean;
begin
  FFreeStream := False;
  FStream := Stream;

  FSavedPosition := FStream.Seek(0, soFromCurrent);
  FStreamSize := FStream.Seek(0, soFromEnd);
  FStream.Seek(FSavedPosition, soFromBeginning);
  FBytesConverted := 0;

  Result := True;
end;

function TJvStreamConversion.Open(const AFileName: string;
  const AKind: TJvConversionKind): Boolean;
begin
  FFreeStream := True;
  if AKind = ckImport then
    FStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite)
  else
    FStream := TFileStream.Create(AFileName, fmCreate);

  FSavedPosition := 0;
  FStreamSize := FStream.Size;
  FBytesConverted := 0;

  Result := True;
end;

function TJvStreamConversion.Retry: Boolean;
begin
  Result := TextKind in [ctkBothPreferText, ctkBothPreferRTF];
  if Result then
  begin
    FStream.Position := FSavedPosition;
    FBytesConverted := 0;
  end;
end;

//=== { TJvTextAttributes } ==================================================

constructor TJvTextAttributes.Create(AOwner: TJvCustomRichEdit;
  AttributeType: TJvAttributeType);
begin
  inherited Create;
  FRichEdit := AOwner;
  FType := AttributeType;
end;

procedure TJvTextAttributes.Assign(Source: TPersistent);
var
  Format: TCharFormat2;
begin
  if Source is TFont then
    AssignFont(TFont(Source))
  else
  if Source is TTextAttributes then
  begin
    Name := TTextAttributes(Source).Name;
    Charset := TTextAttributes(Source).Charset;
    Style := TTextAttributes(Source).Style;
    Pitch := TTextAttributes(Source).Pitch;
    Color := TTextAttributes(Source).Color;
  end
  else
  if Source is TJvTextAttributes then
  begin
    TJvTextAttributes(Source).GetAttributes(Format);
    SetAttributes(Format);
  end
  else
    inherited Assign(Source);
end;

procedure TJvTextAttributes.AssignFont(Font: TFont);
var
  LogFont: TLogFont;
  Format: TCharFormat2;
begin
  InitFormat(Format);
  with Format do
  begin
    case Font.Pitch of
      fpVariable:
        bPitchAndFamily := VARIABLE_PITCH;
      fpFixed:
        bPitchAndFamily := FIXED_PITCH;
    else
      bPitchAndFamily := DEFAULT_PITCH;
    end;
    dwMask := dwMask or CFM_SIZE or CFM_BOLD or CFM_ITALIC or
      CFM_UNDERLINE or CFM_STRIKEOUT or CFM_FACE or CFM_COLOR;
    { Font.Size is in points; yHeight is in twips }
    yHeight := Font.Size * CTwipsPerPoint;
    if (Font.Color = clWindowText) or (Font.Color = clDefault) then
      dwEffects := dwEffects or CFE_AUTOCOLOR
    else
      crTextColor := ColorToRGB(Font.Color);
    if fsBold in Font.Style then
      dwEffects := dwEffects or CFE_BOLD;
    if fsItalic in Font.Style then
      dwEffects := dwEffects or CFE_ITALIC;
    if fsUnderline in Font.Style then
      dwEffects := dwEffects or CFE_UNDERLINE;
    if fsStrikeOut in Font.Style then
      dwEffects := dwEffects or CFE_STRIKEOUT;
    StrPLCopy(szFaceName, Font.Name, SizeOf(szFaceName));
    dwMask := dwMask or CFM_CHARSET;
    bCharSet := Font.Charset;
    if GetObject(Font.Handle, SizeOf(LogFont), @LogFont) <> 0 then
    begin
      dwMask := dwMask or DWORD(CFM_WEIGHT);
      wWeight := Word(LogFont.lfWeight);
    end;
  end;
  SetAttributes(Format);
end;

procedure TJvTextAttributes.AssignTo(Dest: TPersistent);
begin
  if Dest is TFont then
  begin
    TFont(Dest).Color := Color;
    TFont(Dest).Name := Name;
    TFont(Dest).Charset := Charset;
    TFont(Dest).Style := Style;
    TFont(Dest).Size := Size;
    TFont(Dest).Pitch := Pitch;
  end
  else
  if Dest is TTextAttributes then
  begin
    TTextAttributes(Dest).Color := Color;
    TTextAttributes(Dest).Name := Name;
    TTextAttributes(Dest).Charset := Charset;
    TTextAttributes(Dest).Style := Style;
    TTextAttributes(Dest).Pitch := Pitch;
  end
  else
    inherited AssignTo(Dest);
end;

function TJvTextAttributes.GetAttribute(const Flag: Integer): Boolean;
var
  Format: TCharFormat2;
begin
  GetAttributes(Format);
  Result := Format.dwEffects and Flag <> 0;
end;

procedure TJvTextAttributes.GetAttributes(var Format: TCharFormat2);
begin
  InitFormat(Format);
  if FRichEdit.HandleAllocated then
    SendMessage(FRichEdit.Handle, EM_GETCHARFORMAT, AttrFlags[FType],
      LParam(@Format));
end;

function TJvTextAttributes.GetBackColor: TColor;
var
  Format: TCharFormat2;
begin
  if RichEditVersion < 2 then
  begin
    Result := clWindow;
    Exit;
  end;
  GetAttributes(Format);
  with Format do
    if (dwEffects and CFE_AUTOBACKCOLOR) <> 0 then
      Result := clWindow
    else
      Result := crBackColor;
end;

function TJvTextAttributes.GetCharset: TFontCharset;
var
  Format: TCharFormat2;
begin
  GetAttributes(Format);
  Result := Format.bCharSet;
end;

function TJvTextAttributes.GetColor: TColor;
var
  Format: TCharFormat2;
begin
  GetAttributes(Format);
  with Format do
    if (dwEffects and CFE_AUTOCOLOR) <> 0 then
      Result := clWindowText
    else
      Result := crTextColor;
end;

function TJvTextAttributes.GetConsistentAttributes: TJvConsistentAttributes;
var
  Format: TCharFormat2;
begin
  Result := [];
  if FRichEdit.HandleAllocated and (FType <> atDefaultText) then
  begin
    InitFormat(Format);
    SendMessage(FRichEdit.Handle, EM_GETCHARFORMAT,
      AttrFlags[FType], LParam(@Format));
    with Format do
    begin
      if (dwMask and CFM_BOLD) <> 0 then
        Include(Result, caBold);
      if (dwMask and CFM_COLOR) <> 0 then
        Include(Result, caColor);
      if (dwMask and CFM_FACE) <> 0 then
        Include(Result, caFace);
      if (dwMask and CFM_ITALIC) <> 0 then
        Include(Result, caItalic);
      if (dwMask and CFM_SIZE) <> 0 then
        Include(Result, caSize);
      if (dwMask and CFM_STRIKEOUT) <> 0 then
        Include(Result, caStrikeOut);
      if (dwMask and CFM_UNDERLINE) <> 0 then
        Include(Result, caUnderline);
      if (dwMask and CFM_PROTECTED) <> 0 then
        Include(Result, caProtected);
      if (dwMask and CFM_OFFSET) <> 0 then
        Include(Result, caOffset);
      if (dwMask and CFM_HIDDEN) <> 0 then
        Include(Result, caHidden);
      if (dwMask and CFM_CHARSET) <> 0 then
        Include(Result, caCharset);
      if RichEditVersion >= 2 then
      begin
        if (dwMask and CFM_LINK) <> 0 then
          Include(Result, caLink);
        if (dwMask and CFM_BACKCOLOR) <> 0 then
          Include(Result, caBackColor);
        if (dwMask and CFM_DISABLED) <> 0 then
          Include(Result, caDisabled);
        if (dwMask and CFM_WEIGHT) <> 0 then
          Include(Result, caWeight);
        if (dwMask and CFM_SUBSCRIPT) <> 0 then
          Include(Result, caSubscript);
        if (dwMask and CFM_REVAUTHOR) <> 0 then
          Include(Result, caRevAuthor);
      end;
    end;
  end;
end;

function TJvTextAttributes.GetDisabled: Boolean;
var
  Format: TCharFormat2;
begin
  Result := False;
  if RichEditVersion < 2 then
    Exit;
  GetAttributes(Format);
  Result := Format.dwEffects and CFE_DISABLED <> 0;
end;

function TJvTextAttributes.GetHeight: Integer;
begin
  { Points -> Logical pixels }
  Result := MulDiv(Size, FRichEdit.FScreenLogPixels, CPointsPerInch);
end;

function TJvTextAttributes.GetHidden: Boolean;
var
  Format: TCharFormat2;
begin
  Result := False;
  if RichEditVersion < 2 then
    Exit;
  GetAttributes(Format);
  Result := Format.dwEffects and CFE_HIDDEN <> 0;
end;

function TJvTextAttributes.GetLink: Boolean;
var
  Format: TCharFormat2;
begin
  Result := False;
  if RichEditVersion < 2 then
    Exit;
  GetAttributes(Format);
  with Format do
    Result := (dwEffects and CFE_LINK) <> 0;
end;

function TJvTextAttributes.GetName: TFontName;
var
  Format: TCharFormat2;
begin
  GetAttributes(Format);
  Result := Format.szFaceName;
end;

function TJvTextAttributes.GetOffset: Integer;
var
  Format: TCharFormat2;
begin
  GetAttributes(Format);
  Result := Format.yOffset div CTwipsPerPoint;
end;

function TJvTextAttributes.GetPitch: TFontPitch;
var
  Format: TCharFormat2;
begin
  GetAttributes(Format);
  case Format.bPitchAndFamily and $03 of
    DEFAULT_PITCH:
      Result := fpDefault;
    VARIABLE_PITCH:
      Result := fpVariable;
    FIXED_PITCH:
      Result := fpFixed;
  else
    Result := fpDefault;
  end;
end;

function TJvTextAttributes.GetProtected: Boolean;
var
  Format: TCharFormat2;
begin
  GetAttributes(Format);
  with Format do
    Result := (dwEffects and CFE_PROTECTED) <> 0;
end;

function TJvTextAttributes.GetRevAuthorIndex: Byte;
var
  Format: TCharFormat2;
begin
  GetAttributes(Format);
  Result := Format.bRevAuthor;
end;

function TJvTextAttributes.GetSize: Integer;
var
  Format: TCharFormat2;
begin
  GetAttributes(Format);
  Result := Format.yHeight div CTwipsPerPoint;
end;

function TJvTextAttributes.GetStyle: TFontStyles;
var
  Format: TCharFormat2;
begin
  Result := [];
  GetAttributes(Format);
  with Format do
  begin
    if (dwEffects and CFE_BOLD) <> 0 then
      Include(Result, fsBold);
    if (dwEffects and CFE_ITALIC) <> 0 then
      Include(Result, fsItalic);
    if (dwEffects and CFE_UNDERLINE) <> 0 then
      Include(Result, fsUnderline);
    if (dwEffects and CFE_STRIKEOUT) <> 0 then
      Include(Result, fsStrikeOut);
  end;
end;

function TJvTextAttributes.GetSubscriptStyle: TSubscriptStyle;
var
  Format: TCharFormat2;
begin
  Result := ssNone;
  if RichEditVersion < 2 then
    Exit;
  GetAttributes(Format);
  with Format do
  begin
    if (dwEffects and CFE_SUBSCRIPT) <> 0 then
      Result := ssSubscript
    else
    if (dwEffects and CFE_SUPERSCRIPT) <> 0 then
      Result := ssSuperscript;
  end;
end;

function TJvTextAttributes.GetUnderlineColor: TUnderlineColor;
var
  Format: TCharFormat2;
begin
  Result := ucBlack;
  if RichEditVersion < 3 then
    Exit;
  GetAttributes(Format);
  with Format do
  begin
    if (dwEffects and CFE_UNDERLINE <> 0) and
      (dwMask and CFM_UNDERLINETYPE = CFM_UNDERLINETYPE) then
      Result := TUnderlineColor(bUnderlineType div $10);
  end;
end;

function TJvTextAttributes.GetUnderlineType: TUnderlineType;
var
  Format: TCharFormat2;
begin
  Result := utNone;
  if RichEditVersion < 2 then
    Exit;
  GetAttributes(Format);
  with Format do
  begin
    if (dwEffects and CFE_UNDERLINE <> 0) and
      (dwMask and CFM_UNDERLINETYPE = CFM_UNDERLINETYPE) then
      Result := TUnderlineType(bUnderlineType mod $10);
  end;
end;

procedure TJvTextAttributes.InitFormat(var Format: TCharFormat2);
begin
  FillChar(Format, SizeOf(Format), 0);
  if RichEditVersion >= 2 then
    Format.cbSize := SizeOf(Format)
  else
    Format.cbSize := SizeOf(TCharFormat);
end;

procedure TJvTextAttributes.SetAttribute(const Flag: Integer; const Value: Boolean);
var
  Format: TCharFormat2;
begin
  InitFormat(Format);
  { Assume Mask value is same as Flag, this is correct for CFE_BOLD, CFE_ITALIC,
    CFE_UNDERLINE and CFE_STRIKEOUT }
  Format.dwMask := Flag;
  if Value then
    Format.dwEffects := Flag;
  SetAttributes(Format);
end;

procedure TJvTextAttributes.SetAttributes(var Format: TCharFormat2);
begin
  if FRichEdit.HandleAllocated then
    SendMessage(FRichEdit.Handle, EM_SETCHARFORMAT, AttrFlags[FType],
      LParam(@Format));
end;

procedure TJvTextAttributes.SetBackColor(Value: TColor);
var
  Format: TCharFormat2;
begin
  if RichEditVersion < 2 then
    Exit;
  InitFormat(Format);
  with Format do
  begin
    dwMask := CFM_BACKCOLOR;
    if (Value = clWindow) or (Value = clDefault) then
      dwEffects := CFE_AUTOBACKCOLOR
    else
      crBackColor := ColorToRGB(Value);
  end;
  SetAttributes(Format);
end;

procedure TJvTextAttributes.SetCharset(Value: TFontCharset);
var
  Format: TCharFormat2;
begin
  InitFormat(Format);
  with Format do
  begin
    dwMask := CFM_CHARSET;
    bCharSet := Value;
  end;
  SetAttributes(Format);
end;

procedure TJvTextAttributes.SetColor(Value: TColor);
var
  Format: TCharFormat2;
begin
  InitFormat(Format);
  with Format do
  begin
    dwMask := CFM_COLOR;
    if (Value = clWindowText) or (Value = clDefault) then
      dwEffects := CFE_AUTOCOLOR
    else
      crTextColor := ColorToRGB(Value);
  end;
  SetAttributes(Format);
end;

procedure TJvTextAttributes.SetDisabled(Value: Boolean);
var
  Format: TCharFormat2;
begin
  if RichEditVersion < 2 then
    Exit;
  InitFormat(Format);
  with Format do
  begin
    dwMask := CFM_DISABLED;
    if Value then
      dwEffects := CFE_DISABLED;
  end;
  SetAttributes(Format);
end;

procedure TJvTextAttributes.SetHeight(Value: Integer);
begin
  { Logical pixels -> Points }
  Size := MulDiv(Value, CPointsPerInch, FRichEdit.FScreenLogPixels);
end;

procedure TJvTextAttributes.SetHidden(Value: Boolean);
var
  Format: TCharFormat2;
begin
  if RichEditVersion < 2 then
    Exit;
  InitFormat(Format);
  with Format do
  begin
    dwMask := CFM_HIDDEN;
    if Value then
      dwEffects := CFE_HIDDEN;
  end;
  SetAttributes(Format);
end;

procedure TJvTextAttributes.SetLink(Value: Boolean);
var
  Format: TCharFormat2;
begin
  if RichEditVersion < 2 then
    Exit;
  InitFormat(Format);
  with Format do
  begin
    dwMask := CFM_LINK;
    if Value then
      dwEffects := CFE_LINK;
  end;
  SetAttributes(Format);
end;

procedure TJvTextAttributes.SetName(Value: TFontName);
var
  Format: TCharFormat2;
begin
  InitFormat(Format);
  with Format do
  begin
    dwMask := CFM_FACE;
    StrPLCopy(szFaceName, Value, SizeOf(szFaceName));
  end;
  SetAttributes(Format);
end;

procedure TJvTextAttributes.SetOffset(Value: Integer);
var
  Format: TCharFormat2;
begin
  InitFormat(Format);
  with Format do
  begin
    dwMask := DWORD(CFM_OFFSET);
    yOffset := Value * CTwipsPerPoint;
  end;
  SetAttributes(Format);
end;

procedure TJvTextAttributes.SetPitch(Value: TFontPitch);
var
  Format: TCharFormat2;
begin
  InitFormat(Format);
  with Format do
  begin
    case Value of
      fpVariable:
        bPitchAndFamily := VARIABLE_PITCH;
      fpFixed:
        bPitchAndFamily := FIXED_PITCH;
    else
      bPitchAndFamily := DEFAULT_PITCH;
    end;
  end;
  SetAttributes(Format);
end;

procedure TJvTextAttributes.SetProtected(Value: Boolean);
var
  Format: TCharFormat2;
begin
  InitFormat(Format);
  with Format do
  begin
    dwMask := CFM_PROTECTED;
    if Value then
      dwEffects := CFE_PROTECTED;
  end;
  SetAttributes(Format);
end;

procedure TJvTextAttributes.SetRevAuthorIndex(Value: Byte);
var
  Format: TCharFormat2;
begin
  if RichEditVersion < 2 then
    Exit;
  InitFormat(Format);
  with Format do
  begin
    dwMask := CFM_REVAUTHOR;
    bRevAuthor := Value;
  end;
  SetAttributes(Format);
end;

procedure TJvTextAttributes.SetSize(Value: Integer);
var
  Format: TCharFormat2;
begin
  InitFormat(Format);
  with Format do
  begin
    dwMask := DWORD(CFM_SIZE);
    yHeight := Value * CTwipsPerPoint;
  end;
  SetAttributes(Format);
end;

procedure TJvTextAttributes.SetStyle(Value: TFontStyles);
var
  Format: TCharFormat2;
begin
  InitFormat(Format);
  with Format do
  begin
    dwMask := CFM_BOLD or CFM_ITALIC or CFM_UNDERLINE or CFM_STRIKEOUT;
    if fsBold in Value then
      dwEffects := dwEffects or CFE_BOLD;
    if fsItalic in Value then
      dwEffects := dwEffects or CFE_ITALIC;
    if fsUnderline in Value then
      dwEffects := dwEffects or CFE_UNDERLINE;
    if fsStrikeOut in Value then
      dwEffects := dwEffects or CFE_STRIKEOUT;
  end;
  SetAttributes(Format);
end;

procedure TJvTextAttributes.SetSubscriptStyle(Value: TSubscriptStyle);
var
  Format: TCharFormat2;
begin
  if RichEditVersion < 2 then
    Exit;
  InitFormat(Format);
  with Format do
  begin
    dwMask := DWORD(CFM_SUBSCRIPT);
    case Value of
      ssSubscript:
        dwEffects := CFE_SUBSCRIPT;
      ssSuperscript:
        dwEffects := CFE_SUPERSCRIPT;
    end;
  end;
  SetAttributes(Format);
end;

procedure TJvTextAttributes.SetUnderlineColor(
  const Value: TUnderlineColor);
var
  Format: TCharFormat2;
  LUnderlineType: TUnderlineType;
begin
  if RichEditVersion < 3 then
    Exit;

  LUnderlineType := UnderlineType;
  if LUnderlineType = utNone then
    Exit;

  InitFormat(Format);
  with Format do
  begin
    dwMask := CFM_UNDERLINETYPE or CFM_UNDERLINE;
    bUnderlineType := Ord(LUnderlineType) + $10 * Ord(Value);
    dwEffects := dwEffects or CFE_UNDERLINE;
  end;
  SetAttributes(Format);
end;

procedure TJvTextAttributes.SetUnderlineType(Value: TUnderlineType);
var
  Format: TCharFormat2;
begin
  if RichEditVersion < 2 then
    Exit;
  InitFormat(Format);
  with Format do
  begin
    dwMask := CFM_UNDERLINETYPE or CFM_UNDERLINE;
    bUnderlineType := Ord(Value);
    if Value <> utNone then
    begin
      Inc(bUnderlineType, $10 * Ord(UnderlineColor));
      dwEffects := dwEffects or CFE_UNDERLINE;
    end;
  end;
  SetAttributes(Format);
end;

//=== { TJvTextConversion } ==================================================

function TJvTextConversion.CanHandle(const AExtension: string;
  const AKind: TJvConversionKind): Boolean;
begin
  Result := AExtension = 'txt';
end;

function TJvTextConversion.Filter: string;
begin
  Result := RsTextFilter;
end;

function TJvTextConversion.TextKind: TJvConversionTextKind;
begin
  Result := ctkBothPreferText;
end;

//=== { TMSTextConversionThread } ============================================

constructor TMSTextConversionThread.Create;
begin
  FreeOnTerminate := True;
  inherited Create(False);
end;

procedure TMSTextConversionThread.Execute;
begin
  if GCurrentConverter <> nil then
    GCurrentConverter.DoConversion;
end;

//=== { TOleUILinkInfo } =====================================================

constructor TOleUILinkInfo.Create(ARichEdit: TJvCustomRichEdit;
  ReObject: TReObject);
begin
  inherited Create;
  FReObject := ReObject;
  FRichEdit := ARichEdit;
  OleCheck(FReObject.poleobj.QueryInterface(IOleLink, FOleLink));
end;

function TOleUILinkInfo.CancelLink(dwLink: Longint): HRESULT;
begin
  LinkError(SCannotBreakLink);
  Result := E_NOTIMPL;
end;

function TOleUILinkInfo.GetLastUpdate(dwLink: Longint;
  var LastUpdate: TFileTime): HRESULT;
begin
  Result := S_OK;
end;

function TOleUILinkInfo.GetLinkSource(dwLink: Longint; var pszDisplayName: PChar;
  var lenFileName: Longint; var pszFullLinkType: PChar;
  var pszShortLinkType: PChar; var fSourceAvailable: BOOL;
  var fIsSelected: BOOL): HRESULT;
var
  Moniker: IMoniker;
begin
  if @pszDisplayName <> nil then
    pszDisplayName := CoAllocCStr(GetDisplayNameStr(FOleLink));
  if @lenFileName <> nil then
  begin
    lenFileName := 0;
    FOleLink.GetSourceMoniker(Moniker);
    if Moniker <> nil then
    begin
      lenFileName := OleStdGetLenFilePrefixOfMoniker(Moniker);
      ReleaseObject(Moniker);
    end;
  end;
  if @pszFullLinkType <> nil then
    pszFullLinkType := CoAllocCStr(GetFullNameStr(FReObject.poleobj));
  if @pszShortLinkType <> nil then
    pszShortLinkType := CoAllocCStr(GetShortNameStr(FReObject.poleobj));
  Result := S_OK;
end;

function TOleUILinkInfo.GetLinkUpdateOptions(dwLink: Longint;
  var dwUpdateOpt: Longint): HRESULT;
begin
  Result := FOleLink.GetUpdateOptions(dwUpdateOpt);
end;

function TOleUILinkInfo.GetNextLink(dwLink: Longint): Longint;
begin
  if dwLink = 0 then
    Result := Longint(FRichEdit)
  else
    Result := 0;
end;

function TOleUILinkInfo.OpenLinkSource(dwLink: Longint): HRESULT;
begin
  try
    OleCheck(FReObject.poleobj.DoVerb(OLEIVERB_SHOW, nil, FReObject.polesite,
      0, FRichEdit.Handle, FRichEdit.ClientRect));
  except
    Application.HandleException(FRichEdit);
  end;
  Result := S_OK;
end;

function TOleUILinkInfo.SetLinkSource(dwLink: Longint; pszDisplayName: PChar;
  lenFileName: Longint; var chEaten: Longint;
  fValidateSource: BOOL): HRESULT;
var
  DisplayName: string;
  Buffer: array[0..255] of WideChar;
begin
  Result := E_FAIL;
  if fValidateSource then
  begin
    DisplayName := pszDisplayName;
    if Succeeded(FOleLink.SetSourceDisplayName(StringToWideChar(DisplayName,
      Buffer, SizeOf(Buffer) div 2))) then
    begin
      chEaten := Length(DisplayName);
      try
        OleCheck(FReObject.poleobj.Update);
      except
        Application.HandleException(FRichEdit);
      end;
      Result := S_OK;
    end;
  end
  else
    LinkError(SInvalidLinkSource);
end;

function TOleUILinkInfo.SetLinkUpdateOptions(dwLink: Longint;
  dwUpdateOpt: Longint): HRESULT;
begin
  Result := FOleLink.SetUpdateOptions(dwUpdateOpt);
  if Succeeded(Result) then
    FRichEdit.Modified := True;
end;

function TOleUILinkInfo.UpdateLink(dwLink: Longint; fErrorMessage: BOOL;
  fErrorAction: BOOL): HRESULT;
begin
  try
    OleCheck(FReObject.poleobj.Update);
  except
    Application.HandleException(FRichEdit);
  end;
  Result := S_OK;
end;

//=== { TOleUIObjInfo } ======================================================

constructor TOleUIObjInfo.Create(ARichEdit: TJvCustomRichEdit;
  ReObject: TReObject);
begin
  inherited Create;
  FRichEdit := ARichEdit;
  FReObject := ReObject;
end;

function TOleUIObjInfo.ConvertObject(dwObject: Longint;
  const clsidNew: TCLSID): HRESULT;
begin
  Result := E_NOTIMPL;
end;

function TOleUIObjInfo.GetConvertInfo(dwObject: Longint; var ClassID: TCLSID;
  var wFormat: Word; var ConvertDefaultClassID: TCLSID;
  var lpClsidExclude: PCLSID; var cClsidExclude: Longint): HRESULT;
begin
  FReObject.poleobj.GetUserClassID(ClassID);
  Result := S_OK;
end;

function TOleUIObjInfo.GetObjectInfo(dwObject: Longint;
  var dwObjSize: Longint; var lpszLabel: PChar;
  var lpszType: PChar; var lpszShortType: PChar;
  var lpszLocation: PChar): HRESULT;
begin
  if @dwObjSize <> nil then
    dwObjSize := -1; { Unknown size }
  if @lpszLabel <> nil then
    lpszLabel := CoAllocCStr(GetFullNameStr(FReObject.poleobj));
  if @lpszType <> nil then
    lpszType := CoAllocCStr(GetFullNameStr(FReObject.poleobj));
  if @lpszShortType <> nil then
    lpszShortType := CoAllocCStr(GetShortNameStr(FReObject.poleobj));
  if @lpszLocation <> nil then
  begin
    if Trim(FRichEdit.Title) <> '' then
      lpszLocation := CoAllocCStr(Format('%s - %s',
        [FRichEdit.Title, Application.Title]))
    else
      lpszLocation := CoAllocCStr(Application.Title);
  end;
  Result := S_OK;
end;

function TOleUIObjInfo.GetViewInfo(dwObject: Longint; var hMetaPict: HGLOBAL;
  var dvAspect: Longint; var nCurrentScale: Integer): HRESULT;
begin
  if @hMetaPict <> nil then
    hMetaPict := GetIconMetaPict(FReObject.poleobj, FReObject.dvAspect);
  if @dvAspect <> nil then
    dvAspect := FReObject.dvAspect;
  if @nCurrentScale <> nil then
    nCurrentScale := 0;
  Result := S_OK;
end;

function TOleUIObjInfo.SetViewInfo(dwObject: Longint; hMetaPict: HGLOBAL;
  dvAspect: Longint; nCurrentScale: Integer;
  bRelativeToOrig: BOOL): HRESULT;
var
  Iconic: Boolean;
begin
  if Assigned(FRichEdit.FRichEditOle) then
  begin
    case dvAspect of
      DVASPECT_CONTENT:
        Iconic := False;
      DVASPECT_ICON:
        Iconic := True;
    else
      Iconic := FReObject.dvAspect = DVASPECT_ICON;
    end;
    IRichEditOle(FRichEdit.FRichEditOle).InPlaceDeactivate;
    Result := OleSetDrawAspect(FReObject.poleobj, Iconic, hMetaPict,
      FReObject.dvAspect);
    if Succeeded(Result) then
      IRichEditOle(FRichEdit.FRichEditOle).SetDvaspect(
        Longint(REO_IOB_SELECTION), FReObject.dvAspect);
  end
  else
    Result := E_NOTIMPL;
end;

//=== { TRichEditOleCallback } ===============================================

constructor TRichEditOleCallback.Create(ARichEdit: TJvCustomRichEdit);
begin
  inherited Create;
  FRichEdit := ARichEdit;
end;

destructor TRichEditOleCallback.Destroy;
begin
  DestroyAccelTable;
  FFrameForm := nil;
  FDocForm := nil;
  inherited Destroy;
end;

procedure TRichEditOleCallback.AssignFrame;
begin
  if (GetParentForm(FRichEdit) <> nil) and not Assigned(FFrameForm) and
    FRichEdit.AllowInPlace then
  begin
    FDocForm := GetVCLFrameForm(ValidParentForm(FRichEdit));
    FFrameForm := FDocForm;
    if IsFormMDIChild(FDocForm.Form) then
      FFrameForm := GetVCLFrameForm(Application.MainForm);
  end;
end;

function TRichEditOleCallback.ContextSensitiveHelp(fEnterMode: BOOL): HRESULT;
begin
  Result := NoError;
end;

procedure TRichEditOleCallback.CreateAccelTable;
var
  Menu: TMainMenu;
begin
  if (FAccelTable = 0) and Assigned(FFrameForm) then
  begin
    Menu := FFrameForm.Form.Menu;
    if Menu <> nil then
      Menu.GetOle2AcceleratorTable(FAccelTable, FAccelCount, [0, 2, 4]);
  end;
end;

function TRichEditOleCallback.DeleteObject(const oleobj: IOleObject): HRESULT;
begin
  if Assigned(oleobj) then
    oleobj.Close(OLECLOSE_NOSAVE);
  Result := NoError;
end;

procedure TRichEditOleCallback.DestroyAccelTable;
begin
  if FAccelTable <> 0 then
  begin
    DestroyAcceleratorTable(FAccelTable);
    FAccelTable := 0;
    FAccelCount := 0;
  end;
end;

function TRichEditOleCallback.GetClipboardData(const chrg: TCharRange; reco: DWORD;
  out dataObj: IDataObject): HRESULT;
begin
  Result := E_NOTIMPL;
end;

function TRichEditOleCallback.GetContextMenu(seltype: Word;
  const oleobj: IOleObject; const chrg: TCharRange; out Menu: HMENU): HRESULT;
begin
  Result := E_NOTIMPL;
end;

function TRichEditOleCallback.GetDragDropEffect(fDrag: BOOL; grfKeyState: DWORD;
  var dwEffect: DWORD): HRESULT;
begin
  Result := E_NOTIMPL;
end;

function TRichEditOleCallback.GetInPlaceContext(out Frame: IOleInPlaceFrame; out Doc: IOleInPlaceUIWindow;
  lpFrameInfo: POleInPlaceFrameInfo): HRESULT;
begin
  AssignFrame;
  if Assigned(FFrameForm) and FRichEdit.AllowInPlace then
  begin
    Frame := FFrameForm;
    Doc := FDocForm;
    CreateAccelTable;
    with lpFrameInfo^ do
    begin
      fMDIApp := False;
      FFrameForm.GetWindow(hwndFrame);
      haccel := FAccelTable;
      cAccelEntries := FAccelCount;
    end;
    Result := S_OK;
  end
  else
    Result := E_NOTIMPL;
end;

function TRichEditOleCallback.GetNewStorage(out stg: IStorage): HRESULT;
begin
  try
    CreateStorage(stg);
    Result := S_OK;
  except
    Result := E_OUTOFMEMORY;
  end;
end;

function TRichEditOleCallback.QueryAcceptData(const dataObj: IDataObject;
  var cfFormat: TClipFormat; reco: DWORD; fReally: BOOL;
  hMetaPict: HGLOBAL): HRESULT;
begin
  Result := S_OK;
end;

function TRichEditOleCallback.QueryInsertObject(const clsid: TCLSID; const stg: IStorage;
  cp: Longint): HRESULT;
begin
  Result := NoError;
end;

function TRichEditOleCallback.QueryInterface(const iid: TGUID; out Obj): HRESULT;
begin
  if GetInterface(iid, Obj) then
    Result := S_OK
  else
    Result := E_NOINTERFACE;
end;

function TRichEditOleCallback.ShowContainerUI(fShow: BOOL): HRESULT;
begin
  if not fShow then
    AssignFrame;
  if Assigned(FFrameForm) then
  begin
    if fShow then
    begin
      FFrameForm.SetMenu(0, 0, 0);
      FFrameForm.ClearBorderSpace;
      FRichEdit.SetUIActive(False);
      DestroyAccelTable;
      TForm(FFrameForm.Form).AutoScroll := FAutoScroll;
      FFrameForm := nil;
      FDocForm := nil;
    end
    else
    begin
      FAutoScroll := TForm(FFrameForm.Form).AutoScroll;
      TForm(FFrameForm.Form).AutoScroll := False;
      FRichEdit.SetUIActive(True);
    end;
    Result := S_OK;
  end
  else
    Result := E_NOTIMPL;
end;

function TRichEditOleCallback._AddRef: Longint;
begin
  Inc(FRefCount);
  Result := FRefCount;
end;

function TRichEditOleCallback._Release: Longint;
begin
  Dec(FRefCount);
  Result := FRefCount;
end;

{ Initialization part }

var
  GLibHandle: THandle = 0;

{$IFDEF COMPILER5}
{ copied from JclFileUtils.pas }
function GetModulePath(const Module: HMODULE): string;
var
  L: Integer;
begin
  L := MAX_PATH + 1;
  SetLength(Result, L);
  L := Windows.GetModuleFileName(Module, Pointer(Result), L);
  SetLength(Result, L);
end;
{$ENDIF COMPILER5}

procedure InitRichEditDll;
var
  OldError: Longint;
  FileName: string;
  InfoSize, Wnd: DWORD;
  VerBuf: Pointer;
  FI: PVSFixedFileInfo;
  VerSize: DWORD;
begin
  RichEditVersion := 1;
  OldError := SetErrorMode(SEM_NOOPENFILEERRORBOX);
  try
    GLibHandle := LoadLibrary(RichEdit20ModuleName);
    if (GLibHandle > 0) and (GLibHandle < HINSTANCE_ERROR) then
      GLibHandle := 0;
    if GLibHandle = 0 then
    begin
      GLibHandle := LoadLibrary(RichEdit10ModuleName);
      if (GLibHandle > 0) and (GLibHandle < HINSTANCE_ERROR) then
        GLibHandle := 0;
    end
    else
    begin
      RichEditVersion := 2;

      {$IFDEF COMPILER5}
      FileName := GetModulePath(GLibHandle);
      {$ELSE}
      FileName := GetModuleName(GLibHandle);
      {$ENDIF COMPILER5}
      InfoSize := GetFileVersionInfoSize(PChar(FileName), Wnd);
      if InfoSize <> 0 then
      begin
        GetMem(VerBuf, InfoSize);
        try
          if GetFileVersionInfo(PChar(FileName), Wnd, InfoSize, VerBuf) then
            if VerQueryValue(VerBuf, '\', Pointer(FI), VerSize) then
            begin
              if FI.dwFileVersionMS and $FFFF0000 = $00050000 then
                RichEditVersion := (FI.dwFileVersionMS and $FFFF) div 10;
              if RichEditVersion = 0 then
                RichEditVersion := 2;
            end;
        finally
          FreeMem(VerBuf);
        end;
      end;
    end;
  finally
    SetErrorMode(OldError);
  end;
end;

procedure FinalRichEditDll;
begin
  if GLibHandle > 0 then
  begin
    FreeLibrary(GLibHandle);
    GLibHandle := 0;
  end;
end;

initialization
  {$IFDEF UNITVERSIONING}
  RegisterUnitVersion(HInstance, UnitVersioning);
  {$ENDIF UNITVERSIONING}
  InitRichEditDll;
  CFEmbeddedObject := RegisterClipboardFormat(CF_EMBEDDEDOBJECT);
  CFLinkSource := RegisterClipboardFormat(CF_LINKSOURCE);
  CFRtf := RegisterClipboardFormat(CF_RTF);
  CFRtfNoObjs := RegisterClipboardFormat(CF_RTFNOOBJS);

finalization
  FreeAndNil(GlobalConversionFormatList);
  FinalRichEditDll;
  {$IFDEF UNITVERSIONING}
  UnregisterUnitVersion(HInstance);
  {$ENDIF UNITVERSIONING}

end.

