Optimum use of RichView Action List Editor

General TRichView support forum. Please post your questions here
Post Reply
jfleming
Posts: 19
Joined: Tue May 25, 2010 4:58 pm

Optimum use of RichView Action List Editor

Post by jfleming »

Using V15.3.2 in Delphi XE6.

For my application using RTF text saved in a database BLOB field, I copied the Action List Editor and changed its Save method to return the modified RTF to the caller. I also modified the editor's OnShow event to load the caller-supplied initial RTF into the editor.

The editor is called in my application when the user double-clicks a RichEdit embedded in a grid linked to the database. Grid and RichEdit are from WolltoWoll (InfoPower).

My code works correctly, but several doubts linger:

1: Are there unnecessary encoding conversions being done ?? Unicode to ansi string or whatever.

2: Are there points where conversions are missing or could mess up my text ?? The application only handles English, Spanish and French text.

3: Is the code suppressing the extra paragraphs TRichView adds still required, and correct ?? Is there some better way to do this, or suppress the addition of paragraphs ??

I would be very grateful if someone would review my code and make any comments they think appropriate. If you have any questions I will, of course, answer them as best I can.

JF

Code: Select all

Test Program:
=============

procedure TRichViewEditorTest.MyDBRichEditDblClick (Sender: TObject);
var
  Local_RTF_Text:   string;
begin
  RichViewEditor_15_3_2 := TRichViewEditor_15_3_2.Create (RichViewEditorTest);
  try
    RichViewEditor_15_3_2.Text_In_RTF := AnsiString(MyDBRichEdit.GetRTFText);

    RichViewEditor_15_3_2.PopupParent := RichViewEditorTest;

    RichViewEditor_15_3_2.ShowModal;

    if (RichViewEditor_15_3_2.Text_Changed) then
      begin
        if ((MyQuery.State <> dsEdit) and
            (MyQuery.State <> dsInsert)) then
          MyQuery.Edit;

        MyDBRichEdit.Clear;
        MyDBRichEdit.SetRTFText(string(RichViewEditor_15_3_2.Text_In_RTF));

        MyDBRichEdit.CopyRichEdittoBlob(MyQueryCLOB_FIELD);

        Local_RTF_Text := MyQueryCLOB_FIELD.AsString;      // These 3 lines remove the extra 
        Strip_Redundant_Final_Paragraph (Local_RTF_Text);  // paragraph appended when text is
        MyQueryCLOB_FIELD.AsString := Local_RTF_Text;      // saved.

        MyQuery.Post;
      end;
  finally
    RichViewEditor_15_3_2.Free;
  end;
end;

################################################################

FormRichViewEditor_15_3_2:
==========================

  public
    Text_In_RTF:           AnsiString;
    Text_Changed:          Boolean;


// Load the text to be edited into the RichViewEdit object.

procedure TRichViewEditor_15_3_2.FormShow (Sender: TObject);
var
  Text_Memory_Stream:      TMemoryStream;
begin
  Text_Memory_Stream := TMemoryStream.Create;
  try
    Text_Memory_Stream.WriteBuffer(PRVAnsiChar(Text_In_RTF)^, Length(Text_In_RTF));

    Text_Memory_Stream.Position := 0;

    RichViewEdit.Clear;
    RichViewEdit.LoadRTFFromStream(Text_Memory_Stream);
    RichViewEdit.Format;
  finally
    Text_Memory_Stream.Free;
  end;

  Text_Changed := False;
end;

################################################################

procedure TRichViewEditor_15_3_2.rvActionSave1Execute (Sender: TObject);
var
  My_Memory_Stream:      TMemoryStream;
begin
            // Get the RTF text into the output AnsiString variable "Text_In_RTF".
  My_Memory_Stream := TMemoryStream.Create;
  try
    RichViewEdit.SaveRTFToStream (My_Memory_Stream, False);

    Text_In_RTF := '';
    SetLength (Text_In_RTF, My_Memory_Stream.Size);

    My_Memory_Stream.Position := 0;
    My_Memory_Stream.ReadBuffer(PRVAnsiChar(Text_In_RTF)^, Length(Text_In_RTF));

            // Change all instances of the "\chcbpat<N>" keyword to "\highlight<N>".
    Text_In_RTF := System.AnsiStrings.AnsiReplaceStr (Text_In_RTF, '\chcbpat', '\highlight');
  finally
    Defn_Text_Memory_Stream.Free;
  end;

  Text_Changed := True;
end;

################################################################

{--------------------------------------------------------------------------------------------------}
{ The end of an RTF text created by the RichView editor includes the last three lines:             }
{   <maybe some text or directives>\par                                                            }
{   \pard\lang3082<maybe other directives>\par                                                     }
{   <right curly bracket>                                                                          }
{ An extra paragraph was introduced in TRichView for compatibility with Word but causes an extra   }
{ blank line to be added at the end of a Rich Text on screen and when printed using FastReport.    }
{ The problem on screen is mostly cosmetic. On the printed report, however, it means all single-   }
{ line texts become two (or more) lines, one extra being added each time the data is edited and    }
{ the changes saved. This is a massive waste in the vertical dimension.                            }
{ To solve this problem, this routine should be called immediately following the line which copies }
{ the Rich Text to the database CLOB field on returning from the editor:                           }
{     <MyDBRichEdit>.CopyRichEdittoBlob(<MyQuery><MyCLOBField>);                                   }
{                                                                                                  }
{     Local_RTF_Text := <MyQuery><MyCLOBField>.AsString;                                           }
{     Strip_Redundant_Final_Paragraph (Local_RTF_Text);                                            }
{     <MyQuery><MyCLOBField>.AsString := Local_RTF_Text;                                           }
{                                                                                                  }
{--------------------------------------------------------------------------------------------------}
procedure Strip_Redundant_Final_Paragraph (var RTF_String: string);
var
  Local_Length:     Integer;
  Line_To_Modify:   string;
begin
  Printable_RTF := TStringList.Create;
  try
    Printable_RTF.Text := RTF_String;
    if (Printable_RTF.Count >= 2) then
      begin
        Local_Length := Length(Printable_RTF.Strings[Printable_RTF.Count-3]);
        if (Local_Length >= 4) then
          begin
            Line_To_Modify := Printable_RTF.Strings[Printable_RTF.Count-3];

            if (CompareText(Copy(Line_To_Modify, Local_Length-3, 4), '\par') = 0) then
              begin
                Line_To_Modify := Copy(Line_To_Modify, 1, Local_Length-4);
                if (Line_To_Modify <> '') then
                  Printable_RTF.Strings[Printable_RTF.Count-3] := Line_To_Modify
                else
                  Printable_RTF.Delete(Printable_RTF.Count-3);

                RTF_String := Printable_RTF.Text;
              end;
          end;
      end;
  finally
    Printable_RTF.Free;
  end;
end;

Sergey Tkachenko
Site Admin
Posts: 17557
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Re: Optimum use of RichView Action List Editor

Post by Sergey Tkachenko »

1. It's hard to tell if this is an optimal code, because it calls many procedures implemented elsewhere: GetRTFText, SetRTFText, CopyRichEdittoBlob. Also, BLOB type is also important: is it binary field, or ANSI memo, or Unicode memo?

2. No, it must be ok, because all characters in RTF have codes less than 128.
But make sure that rvrtfSavePicturesBinary is NOT included in RTFOptions property, otherwise conversion to/from Unicode may damage RTF (and RTF with binary images cannot be saved in memo fields).

3. I believe all end-of-paragraph keywords saved to RTF are necessary. Without a final end-of-paragraph keyword, MS Word ignores the last paragraph attributes. But as far as I remember, when reading RTF in TRichView, a new final paragraph is not added.
jfleming
Posts: 19
Joined: Tue May 25, 2010 4:58 pm

Re: Optimum use of RichView Action List Editor

Post by jfleming »

Thanks for responding to my queries. Maybe I can remove some of the unknowns in my original post and code.

1: <RichEdit>.GetRTFText obtains the raw RTF as a string: function GetRTFText string;

<RichEdit>.SetRTFText inserts raw RTF into the control: procedure SetRTFText (InsertText: string);

<RichEdit>.CopyRichEdittoBlob (Field: TField);
<RichEdit>.CopyRichEditFromBlob (Field: TField);

CLOB (Oracle data type) stores single-byte and multibyte character data. Supports both fixed-width and variable-width character sets.

From attempts to extract the CLOB field contents from the database as single-byte characters, it is safe to assume the data in the CLOB is unicode 16-bits (most likely UTF-16).

2: rvrtfSavePicturesBinary is False in the editor. Thanks for the heads-up. There are no images in the RTF data, at least for now. There are images handled by the application, but that's a different area altogether and not mixed with the text.

3: I understand the need to add an end-of-paragraph keyword, and can live with it, by using the routine Strip_Redundant_Final_Paragraph. However, if I edit a text N times I get N extra paragraph keywords inserted into the text, one each time the text is saved on exit from the editor. Additional saves during an edit session without leaving the editor do not seem to add extra paragraphs. My suggestion would be to add an option to allow the user suppress the addition of the extra paragraph keyword, but that may not be possible for the reason you state.
The following show the addition of extra paragraph keywords:

{\rtf1\ansi\ansicpg1252\deff0{\fonttbl{\f0\fnil\fcharset0 Tahoma;}{\f1\fnil Tahoma;}}
\viewkind4\uc1\pard\lang3082\f0\fs20 Text when first created.\f1\par
}

{\rtf1\ansi\ansicpg1252\deff0\deflang3082{\fonttbl{\f0\fnil\fcharset0 Tahoma;}{\f1\fnil Tahoma;}}
\viewkind4\uc1\pard\lang0\f0\fs20 Text when first created, now with added text.\par
\lang3082\f1\par
}

{\rtf1\ansi\ansicpg1252\deff0\deflang3082{\fonttbl{\f0\fnil\fcharset0 Tahoma;}{\f1\fnil Tahoma;}}
\viewkind4\uc1\pard\lang0\f0\fs20 Text when first created, now with added text, and now editted three more times 1 2 3.\par
\f1\par
\par
\par
\lang3082\par
}
Sergey Tkachenko
Site Admin
Posts: 17557
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Re: Optimum use of RichView Action List Editor

Post by Sergey Tkachenko »

To avoid unnecessary conversion, you can change GetRTFText/SetRTFText to return/accept AnsiString instead of String.

Check the type of TField object representing this CLOB field.
If this is TWideMemoField, it must contain Unicode (UTF-16, two bytes per character) data.
If this is TMemoField, it must contain ANSI (one byte per character) text.
For TWideMemoField, conversion to Unicode string is unavoidable. For TMemoField, you can get/set data via streams to avoid conversion.
Unless you have special requirements, it does not make sense to use Unicode to store RTF data. It just occupies twice the memory to store the same document.

The fastest way to convert RTF data between ANSI and Unicode strings is using RVUnicodeStringToAnsiString7 and RVAnsiStringToUnicodeString7 (from RVStrFuncs unit). These functions assume that all characters have codes less than 128.
jfleming
Posts: 19
Joined: Tue May 25, 2010 4:58 pm

Re: Optimum use of RichView Action List Editor

Post by jfleming »

> Check the type of TField object representing this CLOB field.
> If this is TWideMemoField, it must contain Unicode (UTF-16, two bytes per character) data.
> If this is TMemoField, it must contain ANSI (one byte per character) text.

All CLOB fields are accessed through TMemoField objects.

> Unless you have special requirements, it does not make sense to use Unicode to store RTF data.

Correct. However, the TwwDBRichEdit component delivers the RTF as a string (GetRTFText) and I have no access to the source code to make it return an AnsiString. Hence the cast to AnsiString in my code.

On return from the editor I have the inverse situation: the returned RTF is AnsiString but the TwwDBRichEdit object requires a string (SetRTFText). Further, TwwDBRichEdit.CopyRichEdittoBlob seems to place the text into the CLOB as Unicode string data, but I have yet to confirm that.

One final query:
When a user wishes to create a new comment or text, the editor shows an empty screen corresponding to a null CLOB content. That is perfect -- there is nothing in the CLOB.
When the user enters some text, the RTF returned by the editor begins:
{\rtf1\ansi\ansicpg1252\
and further on:
\viewkind4\uc1\pard\lang3082
If I understand the RTF coding correctly this means that the users text is automatically set to Code Page 1252 and Language Spanish, International Sort, the OS code page and language of this development system.
However, the program calling the editor knows the language in which the comment is being created: English, French or Spanish !!

Is there some way for the program to tell the editor that the RTF should be English (UK) or whatever is appropriate for the occasion ?? Or must the program adjust the returned RTF ?? All text entered in a French comment, for example, can be taken as being French, even if some non-French words are used within that text.

Note: I am not referring here to the Editors UI language, but to that of the text typed by the user.

Thanking you for your help, and patience,

Jim Fleming
Sergey Tkachenko
Site Admin
Posts: 17557
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Re: Optimum use of RichView Action List Editor

Post by Sergey Tkachenko »

Different editor may use different logic for assigning language to text. I think most of them took language from the active keyboard layout. Also, very probably, MS Word may detect language by analyzing content.

Currently, our editor does not assign language to text at all.

(in RTF, language (\lang) does not affect text encoding; but it may affect how this text is processed; for example, in MS Word it affect spelling checking).
Post Reply