Wiki copymirror

This is a mirror of the wiki at pascal.chat. Editing, history and sign-in are there.

RichMemo

English · Polski · Русский

Introduction

RichMemo is a package that includes a component to replace the Delphi TRichEdit component. It's designed in cross-platform way, so implementation is possible for the following platforms: Win32, MacOSX and Linux. Since, being cross-platform is the primary target, the native RichMemo API can be extended to make it Delphi's RichEdit compatible.

Its main characteristics are

Planned: (patches are welcomed)

The download contains the component, an installation package and a demo application, that illustrates the features of the component along with some instrumentation for evaluating the chart on a given system.

Please add your bugreports, feature requests, pull request on GitHub.

Licensing

Author: Dmitry 'skalogryz' Boyarintsev

License: modified LGPL (the same as the FPC RTL and the Lazarus LCL). You can contact the author if the modified LGPL doesn't work with your project licensing.

Download

The easiest way to install the stable version of the component is via the Online Package Manager included in current Lazarus versions.

The latest trunk is available on GitHub: https://github.com/skalogryz/richmemo/ . The latest sources can be downloaded by this GitHub link.

Installation

Originally richmemopackage was a single file, containing IDE specific and run-time needed classes. However, that meant that IDEIntf package would be a requirement for any project using RichMemo. In order to mitigate that the package was split into to separate packages:

Font Parameters

Font parameters are generally represented by TFontParams record. The type is used to describe rich attributes of the font. Some attributes goes beyond the scope of TFont class, thus an extra type was necessary. However, most of the TFont related data types are reused (i.e. TFontStyles).

The actual vertical offset of super/sub scripts depends on OS implementation and currently cannot be controlled.

You can assign FontParams from TFont structure. For that you'll need to use GetFontParams(afont: TFont) function. Note, most of LCL controls have TFont set to "default" values. These are not actual values, but rather an indication that control's default font should be used (similar to crDefault for TColor).

GetFontParams function resolves "Default" font name and returns the actual font family name.

Methods

SetTextAttributes

delphi
procedure SetTextAttributes(TextStart, TextLen: Integer; AFont: TFont);
delphi
procedure SetTextAttributes(TextStart, TextLen: Integer; const TextParams: TFontParams);

SetTextureAttributes methods are changing specified text range style. Font parameters is passed in both methods, by AFont parameter (LCL TFont object) or by TFontParams (declared at RichMemo).

Setting text attributes does not change current selection. If it's necessary to modify the style of currently selected text, you should SelStart and SelLength as a text range values:

delphi
RichMemo1.SetTextAttributes(RichMemo1.SelStart, RichMemo1.SelLength, FontDialog1.Font);

GetTextAttributes

delphi
function GetTextAttributes(TextStart: Integer; var TextParams: TFontParams): Boolean; virtual;

Fill font params of the character, at TextStart position. Method returns True if textstart is valid character position, and False overwise.

GetStyleRange

delphi
function GetStyleRange(CharPos: Integer; var RangeStart, RangeLen: Integer): Boolean; virtual;

Returns a range of characters that share the same font parameters, i.e. all characters in the range has the same font name, size, color and styles.

The method returns true if successed. the method returns false, if CharPos is incorrect value - more than available characters or some other error.

CharAtPos

delphi
function CharAtPos(x,y: Integer): Integer; virtual;

Returns zero-based character offset (not UTF8 position). Returns -1 if fails.

The method is aware of scrolling state of the control. Thus two calls to CharAtPos(0,0) might return different values if scroll positions is changed between calls. See "examples/hittest" for the sample of usage.

If specified x,y would be out of the content of RichMemo the return value is undefined. It is left up to widgetset to either return -1 or closes character available.

SetRangeColor

delphi
procedure SetRangeColor(TextStart, TextLength: Integer; FontColor: TColor);

The method sets color of characters in the specified range to the FontColor. Other font parameters (name, size, styles) are left unchanged.

SetRangeParams

delphi
    procedure SetRangeParams(TextStart, TextLength: Integer; ModifyMask: TTextModifyMask;
      const fnt: TFontParams; AddFontStyle, RemoveFontStyle: TFontStyles); overload;

The method changes font parameters in the specified range.

delphi
procedure SetRangeParams(TextStart, TextLength: Integer;
    ModifyMask: TTextModifyMask; const FontName: String; 
    FontSize: Integer; FontColor: TColor; 
    AddFontStyle, RemoveFontStyle: TFontStyles);

The overloaded version of the method. It's just a wrapper around the parameter using TFontParams structure. The method doesn't support changing of background color, you should use the TFontParams version

For example, if the following code is used

delphi
  RichMemo1.SetRangeParams ( 
     RichMemo1.SelStart, RichMemo1.SelLength,
     [tmm_Styles, tmm_Color], // changing Color and Styles only
     '',  // this is font name - it's not used, thus we can leave it empty
     0,  // this is font size - it's font size, we can leave it empty
     clGreen, // making all the text in the selected region green color
     [fsBold, fsItalic],  // adding Bold and Italic Styles
     []
  );

GetParaAlignment

Gets paragraph alignment

delphi
function GetParaAlignment(TextStart: Integer; var AAlign: TParaAlignment): Boolean;

Note:

SetParaAlignment

Sets paragraph alignent

delphi
procedure SetParaAlignment(TextStart, TextLen: Integer; AAlign: TParaAlignment);

Note:

GetParaMetric

Returns paragraph metrics for a given paragraph

Note

delphi
function GetParaMetric(TextStart: Integer; var AMetric: TParaMetric): Boolean;

procedure SetParaMetric(TextStart, TextLen: Integer; const AMetric: TParaMetric);

It's recommended to use InitParaMetric function in order to initialize TParaMetric structure. Otherwise zeroing structure out is by it size (i.e. FillChar(m, sizeof(TParaMetric), 0) is sufficient. If TParaMetric values are received from GetParaMetric call, then the structure is considered to be initialized properly.

GetParaRange

Returns character range for a paragraph. The paragraph is identified by a character.

delphi
  function GetParaRange(CharOfs: Integer; var ParaRange: TParaRange): Boolean; 
  function GetParaRange(CharOfs: Integer; var TextStart, TextLength: Integer): Boolean;

CharOfs - is a character that belongs to a paragraph which range is returned. TParaRange - is a structure that returns 3 fields

SetParaTabs

Sets the set of tab stops to paragraphs in the specified length.

delphi
  procedure SetParaTabs(TextStart, TextLen: Integer; const AStopList: TTabStopList);

Notes

Renamefest
Prior to r4140 TTabAlignment enum values were taLeft, taCenter, taRight, taDecimal,but it did collide with Classes.TAlignment declarations. Thus the suffix was changed.

win32
allows no more that 32 tabs

gtk2
only tabLeft align is supported

carbon
not implemented

GetParaTabs

Gets the tab stops array. If no specific tabs were specified (widget default tab stops) AStopList count would be set to 0.

delphi
 function GetParaTabs(CharOfs: Integer; var AStopList: TTabStopList): Boolean;

SetParaNumbering

Sets paragraphs automatic numbering and styles, such as bullets

delphi
procedure SetParaNumbering(TextStart, TextLen: Integer; const ANumber: TParaNumbering); virtual;

:* style - the style of numbering

Note: for cross-platform compatibility, it's recommended to use either pnNone, pnBullet, pnNumber or pnCustomChar. RTF provides more support for numbering, yet not every platform rich-text widgetset supports them

for initializing the structure you can use InitParaNumbering() or InitParaNumber() functions. Also using Default() would work too

SetRangeParaParams

Sets paragraph metrics selectively

delphi
procedure SetRangeParaParams(TextStart, TextLength: Integer; ModifyMask: TParaModifyMask; const ParaMetric: TParaMetric);

LoadRichText

delphi
function LoadRichText(Source: TStream): Boolean; virtual;

The method loads RTF enocded data from the specified stream. Returns true if success, and false otherwise. If source is nil, the method returns false

The content of TRichMemo is completely replaced by the content on the source stream. Current text selection is reset.

SaveRichText

delphi
function SaveRichText(Dest: TStream): Boolean; virtual;

The method saves RTF enocded data to the specified stream. Returns true if success, and false otherwise. If source is nil, the method returns false

Current state of TRichMemo is unchanged after the method returns

Search

delphi
function Search(const ANiddle: string; Start, Len: Integer; const SearchOpt: TSearchOptions): Integer;

The method returns the character position of the found sub-string. The position is suitable for use in SelStart property. However, might not be usable for the direct Copy operation, if Unicode characters are present in the code. Instead UTF8Copy should be used.

If the ANiddle was not found in the text, -1 is returned

delphi
function Search(const ANiddle: string; Start, Len: Integer; const SearchOpt: TSearchOptions;
  var TextStart, TextLength: Integer): Boolean;

(The method is introduced with r5115)

The method returns the true if searched sub-string is found, false otherwise.

The returned ATextStart and ATextLength values are suitable for use in SelStart and SelLength properties.

Note: for complex scripts, the found text might be exactly the same as the sub-string searched. The search behavior (on complex scripts) is widgetset specific. Thus searching for the same string on different widgetsets (i.e. win32 or gtk2) might produce different results. If you need the same results and don't get them, please create a bug report.

If you need to extract the found text, you should use GetText() method (passing ATextStart and ATextLength values).

GetText, GetUText

delphi
    function GetText(TextStart, TextLength: Integer): String;
    function GetUText(TextStart, TextLength: Integer): UnicodeString;

GetText() returns UTF8 sub-string

GetUText() returns UTF16 sub-string

Current selection would not be affected by the operation. (If you see the selection affected, please report as an issue).

You should not consider the efficiency of either method. For example, WinAPI internally operates with UTF16 characters, thus GetUText() might be more efficient for it. While Gtk2 operates UTF8 and calling GetText() might be more efficient for it. Instead of thinking about underlying system, you should be considering the needs of your task.

Redo

delphi
    procedure Redo;

The method redos the last change undone previously.

You can always call CanRedo to check, if there're any actions can be redone.

Properties

ZoomFactor

delphi
property ZoomFactor: double

Read/Write property. Controls zooming of the RichMemo content. 1.0 - is no-zoom. Less than < 1.0 - decrease zoom, more than 1.0 - increasing zooming. If 0 is set - value defaults to 1.0 zoom factor (no zoom).

HideSelection

delphi
property HideSelection: Boolean default false

Read/Write property. If True RichMemo selection is hidden if the control is not focused. If False, the selection is shown all the time.

CanRedo

delphi
property CanRedo: Boolean

If True, there're actions in Undo queue that can be redone. If False, there are no actions in Undo queue that can be redone. Calling to Redo won't have any effect.

Transparent

delphi
property Transparent: Boolean

If True, the background of richmemo is not filled with any color, leaving any underlying controls or windows to be visible. (Suggested to be used with TImage that would provide the background) If False, (default) the background is filled with a solid color

Note that the property might not affect "border" style of the the richmemo. You need to set BorderStyle to bsNone in order to disable it. For macOS this also hides the focus ring (when the control is focused)

Rtf

delphi
property Rtf: String

Read/Write property that allows to read or write Rich-Text Formatted (RTF) text to the whole control. The intent of the property is to have an ability to set Rich-Text in design time, however the property would work in run-time as well.

If then property value is an empty string then Lines property is used as a plain text to initialize control value in load-time.

Note: The property is available in TRichMemo class only, it's not available (or even implemented in anyway) in TCustomRichMemo class. It is recommended to use LoadRichText/SaveRichText in order to modify the content of the page, just because they could consume less memory, than copying the whole rich-text into the memory.

Cross-platform and Forward Compatibility Note: - currently the property is implemented based on the widgetset providede Rich-text read/write routines. It is very likely that in near future it would be changed to used RichMemo read/write RTF stream code only. The issue is with older systems might not support the latest version of RTF.

So right now, you might run into an issue, If you create a project in Gtk2 and save the richmemo is design time containing Unicode characters. Then if you try to load it on the XP machine and would use widgetset native loader, you might see characters missing or rendered improperly. Changing to the usage of RichMemo RTF loader/saver will prevent this issue from happening. Currently you can also avoid it by registering RichMemo loaders (call RegisterRTFLoader, RegisterRTFSaver procedures in project initialization, before any RichMemo is loaded)

Events

OnSelectionChange

delphi
property OnSelectionChange: TNotifyEvent
TNotifyEvent = procedure (Sender: TObject) of object;

The event is fired whenever, selection is changed within RichMemo, either programmatically or due to the user action.

TRichEditForMemo

The class helper that implements RichEdit programmatic interface. The helper is declared at RichEditHelpers unit, so you have to add it to the uses section. Helpers are available in FPC 2.6.0 or later.

Methods

FindText

Searches a given range in the text for a target string

Properties

SelAttributes

Reads/Modifies character attributes of the current selection.

Paragraph

Reads/Modifies paragraph attributes of the current selection.

RichMemoUtils

The unit is introduced to add some useful OS-specific features for handling working with RichMemo

InsertImageFromFile

delphi
function InsertImageFromFile (const ARichMemo: TCustomRichMemo; APos: Integer;
     const FileNameUTF8: string;
     const AImgSize: TSize
 ): Boolean = nil;

Disclaimer: the function would insert an image file into RichMemo (if implemented by the widgetset) But in a very inefficient way. The image would be read again and the memory would be re-allocated for the image every time. So please, don't use it for smileys in your chat instant messaging. A better API (with data caching) is considered. (That's why this method is not part of TCustomRichMemo class).

There's also an utility function InsertImageFromFileNoResize that calls InsertImageFromFile, passing size as 0.

An example of use

delphi
 procedure TForm1.Button1Click(Sender: TObject);
 begin
  // running a dialog to select a picture to insert
  if OpenDialog1.Execute then 
    // inserting the picture at the current RichMemo cursor position
    InsertImageFromFileNoResize(RichMemo1, RichMemo1.SelStart, OpenDialog1.FileName);
 end;

InsertStyledText,InsertColorStyledText,InsertFontText

The set of functions that simplify appending and replacing of styled text. The specified style of the text is applied on the insertion. The common rule for all functions, if InsPos (insertion position) is negative - the text is appended to the end. In order to insert the text in the beginning of the test InsPos should set to 0.

The inserted text is not forced to be on a new line or add a new line. You might want to add LineFeed character to TestUTF8 parameter, if you want the inserted text to cause a new line to be started.

delphi
procedure InsertStyledText(
  const ARichMemo: TCustomRichMemo; 
  const TextUTF8: String; 
  AStyle: TFontStyles;
  InsPos : Integer = -1 )

InsertStyledText inserts the styled text at the specified position.

delphi
procedure InsertColorStyledText(
  const ARichMemo: TCustomRichMemo; 
  const TextUTF8: String; 
  AColor: TColor; 
  AStyle: TFontStyles;
  InsPos : Integer = -1 )

InsertColorStyledText inserts the text with specified style and color at the specified position. to the end of the RichMemo

delphi
procedure InsertFontText(
  const ARichMemo: TCustomRichMemo; 
  const TextUTF8: String; 
  const prms: TFontParams;     
  InsPos : Integer = -1 )

InsertFontText inserts the text and applies specified FontParams to it.

You might want to create a class helper to implement these functions as methods for RichMemo. Beware, if you're using Delphi-compatibility helper - you might right into a conflict.

Frequently Asked Questions

Using RichMemo in Shared Libraries

Issue #17412 If you need to use the component in a shared library, you might need to add -fPIC key to the compiler option of "the package" and the "project".

(Delphi) RichEdit like interface

Issue #14632 A typical problem is porting an existing code that's using RichEdit from Delphi. RichMemo interface doesn't match RichEdit in many ways. But there're two ways to handle that:

pascal
uses ... RichMemo, RichMemoHelpers;

TForm = class
  RichMemo1 : TRichMemo;
   
  // SelAttributes property is not available in the base class
  // but added by a helper defined at RichMemoHelpers unit
RichMemo1.SelAttributes.Name := 'Courier New';

Append mixed color text at the end of the RichMemo

If you just need simple coloring then here is an example that will each time add a new line with random color (tested on Windows):

delphi
  procedure TForm1.Button1Click(Sender: TObject);
  var
    i, j: integer;
    str: string;
  begin
    with Richmemo1 do
    begin
      str := 'Newline text';
      i := Length(Lines.Text) - Lines.Count; // cr as #10#13 is counted only once so subtract it once
      j := Length(str) + 1;                  // +1 to make the cr the same format
      Lines.Add(str);
      SetRangeColor(i, j, Round(random * $FFFFFF));
    end;
  end;
text
 <span style="color:#0140FF">Newline text</span>
 <span style="color:#507FAF">NewLine text</span>
 <span style="color:#C04A90">NewLine text</span>

Alternative example for simple coloring (tested on Windows):

delphi
  procedure TForm1.Button2Click(Sender: TObject);
  begin
    with RichMemo1 do
    begin
      Lines.Add('Line in red');
      SetRangeColor(Length(Lines.Text) - Length(Lines[Lines.Count - 1]) - Lines.Count - 1, Length(Lines[Lines.Count - 1]), clRed);
  
      Lines.Add('Line in blue');
      SetRangeColor(Length(Lines.Text) - Length(Lines[Lines.Count - 1]) - Lines.Count - 1, Length(Lines[Lines.Count - 1]), clBlue);
  
      Lines.Add('Normal line.');
      Lines.Add('Normal line.');
  
      Lines.Add('Line in green ');
      SetRangeColor(Length(Lines.Text) - Length(Lines[Lines.Count - 1]) - Lines.Count - 1, Length(Lines[Lines.Count - 1]), clGreen);
    end;
  end;
text
 <span style="color:#FF0000">Line in red</span>
 <span style="color:#0000FF">Line in blue</span>
 <span style="color:#000000">Normal line.</span>
 <span style="color:#000000">Normal line.</span>
 <span style="color:#008000">Line in green</span>

If you need mixed coloring then this is an example that will add a new line with several different colored words (tested on Windows):

delphi
procedure TForm1.Button3Click(Sender: TObject);
    procedure AddColorStr(s: string; const col: TColor = clBlack; const NewLine: boolean = true);
    begin
      with RichMemo1 do
      begin
        if NewLine then
        begin
          Lines.Add('');
          Lines.Delete(Lines.Count - 1); // avoid double line spacing
        end;

        SelStart  := Length(Text);
        SelText   := s;
        SelLength := Length(s);
        SetRangeColor(SelStart, SelLength, col);

        // deselect inserted string and position cursor at the end of the text
        SelStart  := Length(Text);
        SelText   := '';
      end;
    end;
  begin
    AddColorStr('Black, ');
    AddColorStr('Green, ', clGReen, false);
    AddColorStr('Blue, ', clBlue, false);
    AddColorStr('Red', clRed, false);
  end;
text
 <span style="color:#000000">Black, </span><span style="color:#008000">Green, </span><span style="color:#0000FF">Blue, </span><span style="color:#FF0000">Red</span>
 <span style="color:#000000">Black, </span><span style="color:#008000">Green, </span><span style="color:#0000FF">Blue, </span><span style="color:#FF0000">Red</span>
 <span style="color:#000000">Black, </span><span style="color:#008000">Green, </span><span style="color:#0000FF">Blue, </span><span style="color:#FF0000">Red</span>

Markup Language Parsing

mlparse example The native rich-edit controls do not support a markup-language parsing.

You'll need to write it yourself as shown in mlparse example

Internals

Platform Support Matrix

Values in the matrix are not expected to be static (except for "carbon" column), each system is planned to support all features declared by RichMemo.

Note that the table contains more features than published above. That means that the APIs are already available in RichMemo, but since they're unstable (on some platforms) it's considered "experimental" rather than promised.

Feature Win32 Gtk2 Qt Cocoa Carbon
Font color and style selection Yes Yes Yes Yes Yes
Font background color Yes Yes Yes Yes No
Subscript,Superscript Yes Yes No No No
GetStyleRange Yes Yes No Yes Yes
Paragraph Alignment Yes Yes Yes Yes Almost Impossible
Paragraph Metrics Yes Yes No Yes Almost Impossible
Paragraph Tabs Yes Yes No Yes No
Zoom Yes Incomplete No Yes No
Printing Yes No No No No
RTF Load/Save OS RichMemo No OS OS
Insertables Yes Yes No No No
Transparent Yes No Yes Yes No

Win32

RichMemo without Theme patch RichMemo with Theme

Win32RichMemo provides a global variable NCPaint. It's a handler of non-client area drawing. By default, it attempts to draw themed border (see Win32RichMemo.ThemedNCPaint for implementation). It provides good results on Windows XP themes, but later themes (where animations are used), the results are not so great and should be updated.

In order to let system do NCPaint only (i.e. LCL implementation is causing issues or new windows updated RichEdit to draw borders properly), you can change NCPaint value at runtime, resetting it to nil

pascal
uses
  RichMemo, ..{$ifdef WINDOWS}Win32RichMemo{$endif}

initialization
  {$ifdef WINDOWS}
  Win32RichMemo.NCPaint:=nil;
  {$endif}

You can also provide your own implementation of NCPaint. However, if you implement proper animated theme drawing, please provide patch.

The behavior is Windows implementation specific and should not (and will not be) part of RichMemo interface.

There's additional information about Windows RICHEDIT that can be found at:

Gtk2

2.1.0 (trunk/later)

Gtk2 internal functions got changes. Specifically function GetWidgetInfo was modified, making RichMemo to fail on compilation.

in order to adapt the change RMLCLTRUNK must be defined. It can be easily specified using richmemopackage compiler options.

Gtk3

The APIs between Gtk2 and Gtk3 are not really different. The major difference is in painting. For Gtk3 Cairo canvas is heavily used. The only area it affects is "Internals". IF any one wants to contribute Gtk2 implementation - please create separate gtk3richmemoXXX units. No {$ifdefs} should be created in gtk2 units.

Cocoa

Lazarus 1.8 and earlier

You need to add a define RMLCL18 for the package compilation.

Qt

See also


From the Free Pascal wiki, page "RichMemo", as it stood on 2022-09-29 after a change by Skalogryz, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's Licensing page. If you have concerns about this material, message the admin.

Revision 4, last changed by WikiArchive .