Difference between revisions of "RichMemo/WorkArounds"

From Lazarus wiki
Jump to navigationJump to search
(Start a page about how to work around some bugs in RichMemo)
 
Line 9: Line 9:
 
The function works fine under Linux.
 
The function works fine under Linux.
  
Documented at RichMemo#GetTextAttributes
+
Documented at [[RichMemo#GetTextAttributes]]
  
 
Logged https://bugs.freepascal.org/view.php?id=32296
 
Logged https://bugs.freepascal.org/view.php?id=32296
Line 26: Line 26:
  
 
Its unnecessary under linux but does no harm, possibly even returns false a touch faster under Linux ?  I have tested under Win10 and GTK2 based Linux.
 
Its unnecessary under linux but does no harm, possibly even returns false a touch faster under Linux ?  I have tested under Win10 and GTK2 based Linux.
 +
 +
==== Search() fails under Linux ====
 +
 +
'''The Problem''' : Search():boolean fails under Linux. About line 1080 in Richmemo.pas you find Search(..):boolean implemented. (Don't confuse with Search(..):integer). This function does not set the Result at the start and always returns true under Linux. Even when the GTK system has set ATextStart to -1.
 +
 +
Logged : https://bugs.freepascal.org/view.php?id=32297
 +
 +
Documented at [[RichMemo#Search]]
 +
 +
'''The Test''' :
 +
<syntaxhighlight>var
 +
    Start, len : longint;
 +
begin
 +
    RichMemo1.Clear;
 +
    RichMemo1.Append('This is some text to search');
 +
    if Richmemo1.Search('xxxx', 1, 25, [], Start, Len) then
 +
        showmessage('found ' + inttostr(Start) + ' ' + inttostr(Len))
 +
    else showmessage('Failed to find');
 +
end;</syntaxhighlight>
 +
 +
Tells us it found xxxx at -1 !
 +
 +
'''The Fix''' : Easily fixed. Has been tested under Win10 and Linux GTK2. Add two lines to make the function Search():boolean to make it look like this -
 +
 +
<syntaxhighlight>begin
 +
  Result := false;                                        // Add this line !!!!!!!!
 +
  if not HandleAllocated then HandleNeeded;
 +
  if HandleAllocated then begin
 +
    so.len:=Len;
 +
    so.start:=Start;
 +
    so.options:=SearchOpt;
 +
    if not TWSCustomRichMemoClass(WidgetSetClass).isSearchEx then begin
 +
      ATextStart:=TWSCustomRichMemoClass(WidgetSetClass).Search(Self, ANiddle, so);
 +
      // not recommended. The text found coulbe longer than Niddle
 +
      // depending on the language and search options (to be done)
 +
      // mostly for Arabi and Hebrew languages
 +
      ATextLength:=UTF8Length(ANiddle);
 +
      if ATextStart >= 0 then Result := true;                  // and Add this line !!!!!!!
 +
    end else begin
 +
      Result:=TWSCustomRichMemoClass(WidgetSetClass).SearchEx(Self, ANiddle, so, ATextStart, ATextLength);
 +
    end;
 +
  end else
 +
    Result:=false;
 +
end; </syntaxhighlight>
 +
 +
==== OnLinkAction does not set LinkStart and LinkLen on Win10 ====
 +
 +
'''Problem''' : OnLinkAction does not set LinkStart and LinkLen on Win10. A call back procedure, eg Form1.RichMemo1LinkAction(..) has those two parameters to indicate where the link the user clicked is. However, on Win10 (at least) they are set randomly.
 +
 +
'''The Fix''' : Fortunately, SelStart  is set to where the user clicked, must be somewhere on the link.
 +
 +
<syntaxhighlight>procedure TEditBoxForm.RichMemo1LinkAction(Sender: TObject;
 +
    ALinkAction: TLinkAction; const info: TLinkMouseInfo; LinkStart,
 +
    LinkLen: Integer);
 +
var
 +
    Index, Len : longint;
 +
begin
 +
    Index := RichMemo1.SelStart;
 +
    Len := 0;
 +
    while RichMemo1.IsLink(Index) do dec(Index);
 +
    inc(Index);
 +
    While RichMemo1.isLink(Index + Len) do inc(Len);
 +
    Showmessage('Link clicked was ' + RichMemo1.GetText(Index, Len));
 +
end;</syntaxhighlight> 
 +
 +
==== I Cannot determine the system default or application font. ====
 +
 +
'''The Problem''' : Cannot determine the system default or application font. Maybe someone else can suggest a more elegant way to find out this information, I cannot !  And it turns out that we need to know that font because a call to SetLink() changes the in use font to that one !
 +
 +
'''The Fix''' : Well, this is tacky but works. Make a call to SetLink() from OnShow(..), record the font and then clear it. Should work on all systems.
 +
 +
<syntaxhighlight>procedure TEditBoxForm.FormShow(Sender: TObject);
 +
var
 +
  FP :  TFontParams;
 +
begin
 +
    RichMemo1.Clear;                   
 +
    RichMemo1.Append('a line of text');
 +
    Richmemo1.SetLink(2, 4, true);     
 +
    RichMemo1.GetTextAttributes(4, FP);
 +
    Showmessage('Looks like we are using ' + inttostr(FP.Size) + ' ' + FP.Name);
 +
    RichMemo1.Clear;
 +
end;</syntaxhighlight>
 +
 +
The Problem : SetLink() Fails on Linux.
 +
 +
Reported : http://forum.lazarus.freepascal.org/index.php/topic,37850.0.html
 +
 +
Seems that if there is an existing tag covering the spot where we want to put a link, it fails. But if we clear any tags in that area first, all good.
 +
 +
==== SetLink() sometimes changes the font ====
 +
 +
'''The Problem''' : SetLink() sometimes changes the font. This happens when, for example, your code has detected that the user has just typed in a link address, SetLink() is called but as the user continues to type the font may look different. Its because after the call, the in use font is changed to be the system or application font. But before that call, it was what ever font RichMemwas using.
 +
 +
'''The Fix''' : One solution to this problem is to make sure we are using the System Font all along. See above item on this page ....
 +
 +
==== GetStyleRange(..) does not count changes in background colour ====
 +
 +
'''The Problem''' : GetStyleRange(..) does not count changes in background colour when determining range boundaries under Windows. This function is documented at [[RichMemo#GetStyleRange]]
 +
 +
'''The Fix''' : Its trivial to make a new version of SetStyleRange(..) to replace the one built into RichMemo as long as you have already patched  RichMemo1.GetTextAttributes(Index, FPr), as detailed above.
 +
 +
<syntaxhighlight>function Form1.FPisEqual(const FP, FPRef : TFontParams) : boolean;
 +
begin
 +
  Result := false;
 +
  if FP.name  FPRef.name then exit();
 +
  if FP.size  FPRef.size then exit();
 +
  if FP.bkColor  FPRef.bkcolor then exit();
 +
  if FP.style  FPRef.style then exit();
 +
  if FP.HasBkClr  FPRef.HasBkClr then exit();
 +
  Result := true
 +
end;
 +
 +
function Form1.GetStyleRange(const Index : longint; out Start, Len : longint) : boolean;
 +
var
 +
        FP, FPr :  TFontParams;
 +
begin
 +
    Result := false;
 +
    Start := Index;
 +
    if not RichMemo1.GetTextAttributes(Index, FPr) then exit();
 +
    repeat
 +
    dec(Start);
 +
        if not RichMemo1.GetTextAttributes(Start, FP) then break;
 +
    until not FPisEqual(FP, FPr);
 +
    inc(Start);
 +
    Len := 0;
 +
    repeat
 +
        inc(Len);
 +
        if not RichMemo1.GetTextAttributes(Start + Len, FP) then break;
 +
    until not FPisEqual(FP, FPr);
 +
    Result := True;
 +
end;</syntaxhighlight>
 +
 +
So, instead of calling RichMemo1.GetStyleRange(..)  just  use  GetStyleRange(..).
 +
 +
==== Sometimes newly typed characters are not made visible, Windows ====
 +
 +
'''The Problem''' : Newly typed characters are not made visible, Windows. Seems to happen for exmple when you scan over the document after each keypress by hooking into the OnChange(..) event. Or you write something into the RichEdit from the form's OnShow event. Wiping over with the mouse or moving the control makes the newly written text visible again. Strange.
 +
 +
'''The Fix''' : (I warned you some of thes are pretty ugly) I found that a call to SetLink(..) will refresh things ! So, add that towards the end of your Change(..) call back procedure. Passing it 'false' says turn the link off, there is not one there anyway so all good.  This is not necessary under Linux but it does no real harm, technically its a waste of cycles so you may want to use an $IFDEF
 +
 +
<syntaxhighlight>procedure TEditBoxForm.RichMemo1Change(Sender: TObject);
 +
var
 +
    Point : TPoint;
 +
    Start, Len : longint;
 +
begin
 +
    // do what ever you need do here.....
 +
    RichMemo1.SetLink(RichMem1.SelStart-1, 1, false);
 +
end;</syntaxhighlight>

Revision as of 10:26, 28 August 2017

RichMemo is a great application (thank you Dmitry!) but its not finished. This page lists some work arounds for some incomplete RichMemo functions. Its important to note that the things mentioned here are NOT official characteristics of RichMemo. They are things found to work in the current release. Its likely they will change in the future, please don't assume that they will continue to work or even that they will even continue to be needed.

If you use any of these work arounds, mark your code accordingly and test carefully when you update RichMemo ! If you don't need a work around, get rid of it, some of these here are quite ugly !

GetTextAttributes()

The problem : RichMemo.GetTextAttributes() does not return false when first param is out of range. First Param to this function points to a char in the Memo that it should report on. The function is described as returning False if the indicated char is somehow invalid. However, passing -1 or a number greater than RichMemo.GetTexLen() returns True. The function works fine under Linux.

Documented at RichMemo#GetTextAttributes

Logged https://bugs.freepascal.org/view.php?id=32296

The Test

if Richmemo1.GetTextAttributes(-1, FP) then showmessage('Failed on -1');
if Richmemo1.GetTextAttributes(30000, FP) then showmessage('Failed on 30000');


The Fix An easy fix appears to be to add a test at entry into the function, insert into line 677 of RichMemo.pas -

677 Result := False;
678 if (textStart = GetTextLen) then exit();

Its unnecessary under linux but does no harm, possibly even returns false a touch faster under Linux ? I have tested under Win10 and GTK2 based Linux.

Search() fails under Linux

The Problem : Search():boolean fails under Linux. About line 1080 in Richmemo.pas you find Search(..):boolean implemented. (Don't confuse with Search(..):integer). This function does not set the Result at the start and always returns true under Linux. Even when the GTK system has set ATextStart to -1.

Logged : https://bugs.freepascal.org/view.php?id=32297

Documented at RichMemo#Search

The Test :

var
    Start, len : longint;
begin
    RichMemo1.Clear;
    RichMemo1.Append('This is some text to search');
    if Richmemo1.Search('xxxx', 1, 25, [], Start, Len) then
        showmessage('found ' + inttostr(Start) + ' ' + inttostr(Len))
    else showmessage('Failed to find');
end;

Tells us it found xxxx at -1 !

The Fix : Easily fixed. Has been tested under Win10 and Linux GTK2. Add two lines to make the function Search():boolean to make it look like this -

begin
  Result := false;                                        // Add this line !!!!!!!!
  if not HandleAllocated then HandleNeeded;
  if HandleAllocated then begin
    so.len:=Len;
    so.start:=Start;
    so.options:=SearchOpt;
    if not TWSCustomRichMemoClass(WidgetSetClass).isSearchEx then begin
      ATextStart:=TWSCustomRichMemoClass(WidgetSetClass).Search(Self, ANiddle, so);
      // not recommended. The text found coulbe longer than Niddle
      // depending on the language and search options (to be done)
      // mostly for Arabi and Hebrew languages
      ATextLength:=UTF8Length(ANiddle);
      if ATextStart >= 0 then Result := true;                  // and Add this line !!!!!!!
    end else begin
      Result:=TWSCustomRichMemoClass(WidgetSetClass).SearchEx(Self, ANiddle, so, ATextStart, ATextLength);
    end;
  end else
    Result:=false;
end;

OnLinkAction does not set LinkStart and LinkLen on Win10

Problem : OnLinkAction does not set LinkStart and LinkLen on Win10. A call back procedure, eg Form1.RichMemo1LinkAction(..) has those two parameters to indicate where the link the user clicked is. However, on Win10 (at least) they are set randomly.

The Fix : Fortunately, SelStart is set to where the user clicked, must be somewhere on the link.

procedure TEditBoxForm.RichMemo1LinkAction(Sender: TObject;
    ALinkAction: TLinkAction; const info: TLinkMouseInfo; LinkStart,
    LinkLen: Integer);
var
     Index, Len : longint;
begin
    Index := RichMemo1.SelStart;
    Len := 0;
    while RichMemo1.IsLink(Index) do dec(Index);
    inc(Index);
    While RichMemo1.isLink(Index + Len) do inc(Len);
    Showmessage('Link clicked was ' + RichMemo1.GetText(Index, Len));
end;

I Cannot determine the system default or application font.

The Problem : Cannot determine the system default or application font. Maybe someone else can suggest a more elegant way to find out this information, I cannot ! And it turns out that we need to know that font because a call to SetLink() changes the in use font to that one !

The Fix : Well, this is tacky but works. Make a call to SetLink() from OnShow(..), record the font and then clear it. Should work on all systems.

procedure TEditBoxForm.FormShow(Sender: TObject);
var
  FP :  TFontParams;
begin
    RichMemo1.Clear;                     
    RichMemo1.Append('a line of text');
    Richmemo1.SetLink(2, 4, true);       
    RichMemo1.GetTextAttributes(4, FP);
    Showmessage('Looks like we are using ' + inttostr(FP.Size) + ' ' + FP.Name);
    RichMemo1.Clear;
 end;

The Problem : SetLink() Fails on Linux.

Reported : http://forum.lazarus.freepascal.org/index.php/topic,37850.0.html

Seems that if there is an existing tag covering the spot where we want to put a link, it fails. But if we clear any tags in that area first, all good.

SetLink() sometimes changes the font

The Problem : SetLink() sometimes changes the font. This happens when, for example, your code has detected that the user has just typed in a link address, SetLink() is called but as the user continues to type the font may look different. Its because after the call, the in use font is changed to be the system or application font. But before that call, it was what ever font RichMemwas using.

The Fix : One solution to this problem is to make sure we are using the System Font all along. See above item on this page ....

GetStyleRange(..) does not count changes in background colour

The Problem : GetStyleRange(..) does not count changes in background colour when determining range boundaries under Windows. This function is documented at RichMemo#GetStyleRange

The Fix : Its trivial to make a new version of SetStyleRange(..) to replace the one built into RichMemo as long as you have already patched RichMemo1.GetTextAttributes(Index, FPr), as detailed above.

function Form1.FPisEqual(const FP, FPRef : TFontParams) : boolean;
begin
  Result := false;
  if FP.name  FPRef.name then exit();
  if FP.size  FPRef.size then exit();
  if FP.bkColor  FPRef.bkcolor then exit();
  if FP.style  FPRef.style then exit();
  if FP.HasBkClr  FPRef.HasBkClr then exit();
  Result := true
end;

function Form1.GetStyleRange(const Index : longint; out Start, Len : longint) : boolean;
var
        FP, FPr :  TFontParams;
begin
    Result := false;
    Start := Index;
    if not RichMemo1.GetTextAttributes(Index, FPr) then exit();
    repeat
    	dec(Start);
        if not RichMemo1.GetTextAttributes(Start, FP) then break;
    until not FPisEqual(FP, FPr);
    inc(Start);
    Len := 0;
    repeat
        inc(Len);
        if not RichMemo1.GetTextAttributes(Start + Len, FP) then break;
    until not FPisEqual(FP, FPr);
    Result := True;
end;

So, instead of calling RichMemo1.GetStyleRange(..) just use GetStyleRange(..).

Sometimes newly typed characters are not made visible, Windows

The Problem : Newly typed characters are not made visible, Windows. Seems to happen for exmple when you scan over the document after each keypress by hooking into the OnChange(..) event. Or you write something into the RichEdit from the form's OnShow event. Wiping over with the mouse or moving the control makes the newly written text visible again. Strange.

The Fix : (I warned you some of thes are pretty ugly) I found that a call to SetLink(..) will refresh things ! So, add that towards the end of your Change(..) call back procedure. Passing it 'false' says turn the link off, there is not one there anyway so all good. This is not necessary under Linux but it does no real harm, technically its a waste of cycles so you may want to use an $IFDEF

procedure TEditBoxForm.RichMemo1Change(Sender: TObject);
var
     Point : TPoint;
     Start, Len : longint;
begin
    // do what ever you need do here.....
    RichMemo1.SetLink(RichMem1.SelStart-1, 1, false);	
end;