summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGraeme Geldenhuys <graeme@mastermaths.co.za>2009-10-08 13:40:03 +0200
committerGraeme Geldenhuys <graeme@mastermaths.co.za>2009-10-08 13:40:03 +0200
commit2393df75a17fa33821c917947056f46617566432 (patch)
treeafaea1ef44aa601504a0a514d722a47517b70fa9
parentede5956d705d3a5c213870e8e0aebb0468f4b0c2 (diff)
downloadfpGUI-2393df75a17fa33821c917947056f46617566432.tar.xz
Initial import of ported RichTextView component.
-rwxr-xr-x[-rw-r--r--]components/richtext/ACLStringUtility.pas0
-rwxr-xr-xcomponents/richtext/CanvasFontManager.pas1162
-rwxr-xr-xcomponents/richtext/RichTextDisplayUnit.pas422
-rwxr-xr-xcomponents/richtext/RichTextLayoutUnit.pas1005
-rwxr-xr-xcomponents/richtext/RichTextPrintUnit.pas75
-rwxr-xr-xcomponents/richtext/RichTextStyleUnit.pas622
-rwxr-xr-xcomponents/richtext/RichTextView.pas2785
-rwxr-xr-x[-rw-r--r--]components/richtext/fpgui_richtext.lpk31
-rwxr-xr-x[-rw-r--r--]components/richtext/fpgui_richtext.pas9
-rw-r--r--src/frm_main.pas19
-rw-r--r--src/newview_fpgui.lpi12
-rw-r--r--src/newview_fpgui.lpr9
-rw-r--r--src/nvUtilities.pas20
13 files changed, 6148 insertions, 23 deletions
diff --git a/components/richtext/ACLStringUtility.pas b/components/richtext/ACLStringUtility.pas
index 54282333..54282333 100644..100755
--- a/components/richtext/ACLStringUtility.pas
+++ b/components/richtext/ACLStringUtility.pas
diff --git a/components/richtext/CanvasFontManager.pas b/components/richtext/CanvasFontManager.pas
new file mode 100755
index 00000000..eb6149d3
--- /dev/null
+++ b/components/richtext/CanvasFontManager.pas
@@ -0,0 +1,1162 @@
+Unit CanvasFontManager;
+
+{$mode objfpc}{$H+}
+
+Interface
+
+Uses
+ Classes
+ ,fpg_base
+ ,fpg_main
+ ,fpg_widget
+ ;
+
+Const
+ // This defines the fraction of a pixel that
+ // font character widths will be given in
+ FontWidthPrecisionFactor = 1; //256; // 256 seems to be specific to OS/2 API
+
+Type
+ {Standard Font types}
+ TFontType=(ftBitmap,ftOutline);
+
+ {Standard Font Attributes}
+ TFontAttributes=Set Of(faItalic,faUnderScore,faOutline,faStrikeOut,faBold);
+
+ {Standard Font pitches}
+ TFontPitch=(fpFixed,fpProportional);
+
+ {Standard Font character Set}
+ TFontCharSet=(fcsSBCS,fcsDBCS,fcsMBCS); {Single,Double,mixed Byte}
+
+ // a user-oriented specification of a font;
+ TFontSpec = record
+ FaceName: string[ 64 ];
+ PointSize: integer; // if 0 then use x/y size
+ XSize: integer;
+ YSize: integer;
+ Attributes: TFontAttributes; // set of faBold, faItalic etc
+ end;
+
+ // NOTE: Char widths are in 1/FontWidthPrecisionFactor units
+ TCharWidthArray = array[ #0..#255 ] of longint;
+ TPCharWidthArray = ^TCharWidthArray;
+
+ // Used internally for storing full info on font
+ TLogicalFont= Class( TfpgComponent )
+ public
+ FaceName: String; // user-selected name
+ UseFaceName: String; // after substitutions.
+
+ // Selected bits of FONTMETRICS
+ fsSelection: word;
+
+ FontType: TFontType;
+ FixedWidth: boolean;
+ PointSize: integer;
+ ID: integer;
+ Attributes: TFontAttributes;
+
+ // this can be nil if not already fetched
+ pCharWidthArray: TPCharWidthArray;
+ lMaxbaselineExt: longint;
+ lAveCharWidth: longint;
+ lMaxCharInc: longint;
+ lMaxDescender: longint;
+ public
+ constructor Create(AOwner: TComponent); override;
+ destructor Destroy; override;
+ end;
+
+
+ TFontFace = class(TObject)
+ public
+ Name: string;
+ FixedWidth: boolean;
+ FontType: TFontType;
+ Sizes: TList; // relevant for bitmap fonts only - contains TLogicalFont objects
+ constructor Create;
+ destructor Destroy; override;
+ end;
+
+
+ TCanvasFontManager = class(TObject)
+ private
+ FWidget: TfpgWidget;
+ protected
+ FCanvas: TfpgCanvas;
+ FLogicalFonts: TList;
+ FCurrentFontSpec: TFontSpec;
+ FDefaultFontSpec: TFontSpec;
+ FCurrentFont: TLogicalFont;
+ FAllowBitmapFonts: boolean;
+ protected
+ function CreateFont( const FontSpec: TFontSpec ): TLogicalFont;
+ function GetFont( const FontSpec: TFontSpec ): TLogicalFont;
+ procedure RegisterFont( Font: TLogicalFont );
+ procedure SelectFont( Font: TLogicalFont; Scale: longint );
+ // Retrieve character widths for current font
+ procedure LoadMetrics;
+ // load metrics if needed
+ procedure EnsureMetricsLoaded;
+ public
+ constructor Create( Canvas: TfpgCanvas; AllowBitmapFonts: boolean; AWidget: TfpgWidget ); reintroduce;
+ destructor Destroy; override;
+ // Set the font for the associated canvas.
+ procedure SetFont( const FontSpec: TFontSpec );
+ // Retrieve the width of the given char, in the current font
+ function CharWidth( const C: Char ): longint;
+ function AverageCharWidth: longint;
+ function MaximumCharWidth: longint;
+ function IsFixed: boolean;
+ function CharHeight: longint;
+ function CharDescender: longint;
+ procedure DrawString(var Point: TPoint; const Length: longint; const S: PChar);
+ property Canvas: TfpgCanvas read FCanvas;
+ property Widget: TfpgWidget read FWidget;
+ property DefaultFontSpec: TFontSpec read FDefaultFontSpec write FDefaultFontSpec;
+ end;
+
+
+// Convert a Sibyl font to a FontSpec (Color is left the same)
+procedure FPGuiFontToFontSpec( Font: TfpgFont; Var FontSpec: TFontSpec );
+
+ // Thoughts on how it works....
+
+ // SelectFont looks for an existing logical font that
+ // matches the request. If found selects that logical font
+ // onto the canvas.
+
+ // If not found it creates a logical font and selects that onto
+ // the canvas.
+
+ // For bitmap fonts the logical font definition includes pointsize
+ // For outline fonts the defn is only face+attr; in this case
+ // selectfont also ses the 'CharBox' according to the point size.
+Implementation
+
+uses
+// PMWin, PMGpi, OS2Def, PmDev,
+ SysUtils
+ ,ACLStringUtility
+ ;
+
+{
+Imports
+ Function GpiQueryCharStringPosAt( PS: Hps;
+ StartPoint: PPointL;
+ Options: ULONG;
+ Count: LONG;
+ TheString: PChar;
+ IncrementsArray: PLONG;
+ CharacterPoints: PPointL ): BOOL;
+ ApiEntry; 'PMGPI' Index 585;
+ Function GpiQueryCharStringPos( PS: Hps;
+ Options: ULONG;
+ Count: LONG;
+ TheString: PChar;
+ IncrementsArray: PLONG;
+ CharacterPoints: PPointL ): BOOL;
+ ApiEntry; 'PMGPI' Index 584;
+end;
+}
+
+Type
+ // A little pretend window to send font name.size
+ // and get definite font info back. (See .CreateFont)
+ TFontWindow = class( TfpgWidget )
+ protected
+ procedure DoAllocateWindowHandle(AParent: TfpgWindowBase); override;
+ public
+// procedure CreateWnd; override;
+ function SetPPFontNameSize( Const FNS: String ): Boolean;
+ end;
+
+var
+ FontFaces: TList = nil; // of TFontface
+ //FontWindow: TFontWindow;
+
+ DefaultOutlineFixedFace: TFontFace;
+ DefaultOutlineProportionalFace: TFontFace;
+
+// TFontFace
+//------------------------------------------------------------------------
+
+constructor TFontface.Create;
+begin
+ Sizes := TList.Create;
+ FontType := ftOutline; // in fpGUI we treat all fonts as scalable (preference)
+end;
+
+destructor TFontface.Destroy;
+begin
+ Sizes.Destroy;
+end;
+
+// TLogicalFont
+//------------------------------------------------------------------------
+
+constructor TLogicalFont.Create(AOwner: TComponent);
+begin
+ inherited Create(AOwner);
+ FontType := ftOutline;
+ PointSize := 10;
+ Attributes := [];
+ FixedWidth := False;
+end;
+
+// frees allocated memory, if any.
+// Note - does not delete the Gpi Logical Font
+destructor TLogicalFont.Destroy;
+begin
+ if pCharWidthArray <> nil then
+ FreeMem( pCharWidthArray,
+ sizeof( TCharWidthArray ) );
+
+ inherited Destroy;
+end;
+
+// TFontWindow
+//------------------------------------------------------------------------
+
+procedure TFontWindow.DoAllocateWindowHandle(AParent: TfpgWindowBase);
+begin
+ inherited DoAllocateWindowHandle(AParent);
+end;
+
+Function TFontWindow.SetPPFontNameSize( Const FNS: String ): Boolean;
+//Var
+// CS: Cstring;
+Begin
+ Result := True;
+ { TODO -ograemeg -cAPI call : port API to fpGUI }
+ //CS := FNS;
+ //Result := WinSetPresParam( Handle,
+ // PP_FONTNAMESIZE,
+ // Length( CS ) + 1,
+ // CS );
+End;
+
+//------------------------------------------------------------------------
+
+// Convert a fpGUI Toolkit font to a FontSpec
+//------------------------------------------------------------------------
+procedure FPGuiFontToFontSpec( Font: TfpgFont; Var FontSpec: TFontSpec );
+var
+ s: string;
+ facename: string;
+ cp: integer;
+ c: char;
+ token: string;
+ prop, propval: string;
+ desc: string;
+
+ function NextC: char;
+ begin
+ Inc(cp);
+ if cp > length(desc) then
+ c := #0
+ else
+ c := desc[cp];
+ Result := c;
+ end;
+
+ procedure NextToken;
+ begin
+ token := '';
+ while (c <> #0) and (c in [' ', 'a'..'z', 'A'..'Z', '_', '0'..'9']) do
+ begin
+ token := token + c;
+ NextC;
+ end;
+ end;
+
+begin
+ cp := 0;
+ desc := Font.FontDesc;
+ // find fontface
+ NextC;
+ NextToken;
+ FontSpec.FaceName := token;
+ FontSpec.Attributes := [];
+ FontSpec.XSize := Font.TextWidth('v');
+ FontSpec.YSize := Font.Height;
+
+ // find font size
+ if c = '-' then
+ begin
+ NextC;
+ NextToken;
+ FontSpec.PointSize := StrToIntDef(token, 10);
+ end;
+
+ // find font attributes
+ while c = ':' do
+ begin
+ NextC;
+ NextToken;
+ prop := UpperCase(token);
+ propval := '';
+
+ if c = '=' then
+ begin
+ NextC;
+ NextToken;
+ propval := UpperCase(token);
+ end;
+ // convert fontdesc attributes to fontspec attributes
+ if prop = 'BOLD' then
+ include(FontSpec.Attributes, faBold)
+ else if prop = 'ITALIC' then
+ include(FontSpec.Attributes, faItalic)
+ else if prop = 'UNDERLINE' then
+ include(FontSpec.Attributes, faUnderScore)
+ end;
+end;
+
+// Find a font face with the given name
+//------------------------------------------------------------------------
+function FindFaceName( const name: string ): TFontFace;
+Var
+ FaceIndex: LongInt;
+ Face: TFontFace;
+begin
+ for FaceIndex := 0 to FontFaces.Count - 1 do
+ begin
+ Face := TFontFace(FontFaces[ FaceIndex ]);
+
+ if StringsSame( Face.Name, Name ) then
+ begin
+ Result := Face;
+ exit;
+ end;
+ end;
+ Result := nil;
+end;
+
+// Return the first font face of type = Outline (scalable)
+//------------------------------------------------------------------------
+function GetFirstOutlineFace( FixedWidth: boolean ): TFontFace;
+Var
+ FaceIndex: LongInt;
+ Face: TFontFace;
+begin
+ for FaceIndex := 0 to FontFaces.Count - 1 do
+ begin
+ Face := TFontFace(FontFaces[ FaceIndex ]);
+
+ if ( Face.FixedWidth = FixedWidth )
+ and ( Face.FontType = ftOutline ) then
+ begin
+ Result := Face;
+ exit;
+ end;
+ end;
+ Result := nil;
+end;
+
+// Find the bitmap font which best matches the given pointsize.
+//------------------------------------------------------------------------
+function GetClosestBitmapFixedFont( const PointSize: longint ): TLogicalFont;
+Var
+ FaceIndex: Longint;
+ FontIndex: longint;
+ Face: TFontFace;
+ Font: TLogicalFont;
+begin
+ Result := nil;
+ for FaceIndex := 0 to FontFaces.Count - 1 do
+ begin
+ Face := TFontFace(FontFaces[ FaceIndex ]);
+
+ if Face.FontType = ftBitmap then
+ begin
+ for FontIndex := 0 to Face.Sizes.Count - 1 do
+ begin
+ Font := TLogicalFont(Face.Sizes[ FontIndex ]);
+ if Font.FixedWidth then
+ begin
+ if ( Result = nil )
+ or ( Abs( Font.PointSize - PointSize )
+ < Abs( Result.PointSize - PointSize ) ) then
+ Result := Font;
+ end;
+ end;
+ end;
+ end;
+end;
+
+// Pick some nice default fonts.
+//------------------------------------------------------------------------
+procedure GetDefaultFonts;
+begin
+ // courier new is common and reasonably nice
+ DefaultOutlineFixedFace := FindFaceName( 'Courier New' );
+ if DefaultOutlineFixedFace = nil then
+ begin
+ DefaultOutlineFixedFace := GetFirstOutlineFace( true ); // first fixed outline face
+ end;
+
+ DefaultOutlineProportionalFace := FindFaceName( 'Arial' );
+ if DefaultOutlineProportionalFace = nil then
+ begin
+ DefaultOutlineProportionalFace := GetFirstOutlineFace( false ); // first prop outline face
+ end;
+end;
+
+// Fetch the global list of font faces and sizes
+//------------------------------------------------------------------------
+procedure GetFontList;
+Var
+ Count: LongInt;
+ T: LongInt;
+ Font: TLogicalFont;
+ Face: TFontFace;
+ FamilyName: string;
+ fl: TStringList;
+ f: TfpgFont;
+begin
+ FontFaces := TList.Create;
+ fl := fpgApplication.GetFontFaceList;
+
+ // Get font count
+ Count := fl.Count;
+ If Count > 0 Then
+ Begin
+ For T := 0 To Count - 1 Do
+ Begin
+ Font := TLogicalFont.Create( nil );
+ Font.FaceName := fl[T];
+ f := fpgGetFont(Font.FaceName + '-10');
+// FamilyName := pfm^[ T ].szFamilyName;
+ if (pos('Courier', Font.FaceName) > 0) or (pos('Mono', Font.FaceName) > 0) then
+ Font.FixedWidth := True;
+ Font.lAveCharWidth := f.TextWidth('g');
+ Font.lMaxbaselineExt := f.Height;
+ //Font.fsSelection := pfm^[ T ].fsSelection;
+ //Font.lMaxbaselineExt := pfm^[ T ].lMaxbaselineExt;
+ //Font.lAveCharWidth := pfm^[ T ].lAveCharWidth;
+ //Font.lMaxCharInc := pfm^[ T ].lMaxCharInc;
+ Font.ID := -1; // and always shall be so...
+ f.Free;
+
+ Face := FindFaceName( Font.FaceName );
+ if Face = nil then
+ begin
+ // new face found
+ Face := TFontFace.Create;
+ Face.Name := Font.FaceName; // point to the actual face name string!
+ Face.FixedWidth := Font.FixedWidth;
+ Face.FontType := Font.FontType;
+ FontFaces.Add( Face );
+ end;
+ Face.Sizes.Add( Font );
+ End;
+ End;
+
+ // pick some for defaults
+ GetDefaultFonts;
+
+ //FontWindow := TFontWindow.Create( Nil );
+ //FontWindow.OwnerDraw := True;
+ //FontWindow.CreateWnd;
+end;
+
+// Add .subscript to font name for attributes
+//------------------------------------------------------------------------
+Function ModifyFontName( const FontName: string;
+ const Attrs: TFontAttributes ): String;
+Begin
+ Result := FontName;
+ If faItalic in Attrs Then
+ Result := Result + '.Italic';
+ If faBold in Attrs Then
+ Result := Result + '.Bold';
+ If faOutline in Attrs Then
+ Result := Result + '.Outline';
+ If faStrikeOut in Attrs Then
+ Result := Result + '.Strikeout';
+ If faUnderScore in Attrs Then
+ Result := Result + '.Underscore';
+End;
+
+// Create a font without attributes
+//------------------------------------------------------------------------
+function CreateFontBasic( const FaceName: string; const PointSize: integer ): TLogicalFont;
+var
+ PPString: string;
+begin
+ Result := TLogicalFont.Create( nil );
+ if FindFaceName( FaceName ) = nil then
+ Exit; //==>
+ Result.PointSize := PointSize; // will use later if the result was an outline font...
+ Result.FaceName := FaceName;
+
+ // OK now we have found the font face...
+ PPString := IntToStr( PointSize) + '.' + FaceName;
+
+ PPString := ModifyFontName( PPString, [] );
+ //If Not FontWindow.SetPPFontNameSize( PPString ) Then
+ // Exit;
+end;
+
+// Provide outline substitutes for some common bitmap fonts
+// From Mozilla/2 source.
+//------------------------------------------------------------------------
+function SubstituteBitmapFontToOutline( const FaceName: string ): string;
+begin
+ if StringsSame( FaceName, 'Helv' ) then
+ result := 'Arial'
+ else if StringsSame( FaceName, 'Helvetica' ) then
+ result := 'Arial'
+ else if StringsSame( FaceName, 'Tms Rmn' ) then
+ result := 'Times New Roman'
+ else if StringsSame( FaceName, 'System Proportional' ) then
+ result := 'Arial'
+ else if StringsSame( FaceName, 'System Monospaced' ) then
+ result := 'Courier New'
+ else if StringsSame( FaceName, 'System VIO' ) then
+ result := 'Courier New'
+ else
+ result := FaceName; // no substitution
+end;
+
+// NOTE!!! Not currently used or working...
+// Find a font with exact width and height
+//------------------------------------------------------------------------
+function FindXYSizeFont( const Face: TFontFace;
+ const XSize: longint;
+ const YSize: longint ): TLogicalFont;
+var
+ SizeIndex: longint;
+ F: TLogicalFont;
+begin
+ for SizeIndex := 0 to Face.Sizes.Count - 1 do
+ begin
+ F := TLogicalFont(Face.Sizes[ SizeIndex ]);
+ if ( F.lMaxbaselineExt = YSize )
+ and ( F.lAveCharWidth = XSize ) then
+ begin
+ // found exact match
+ //FontInfo.lMaxbaselineExt := F.lMaxbaselineExt;
+ //FontInfo.lAveCharWidth := F.lAveCharWidth;
+ //Result.FontType := ftBitmap;
+ end;
+ end;
+end;
+
+// Ask OS/2 dummy font window to convert a font spec
+// into a FONTMETRICS.
+//------------------------------------------------------------------------
+//procedure AskOS2FontDetails( const FaceName: string;
+// const PointSize: longint;
+// const Attributes: TFontAttributes;
+// var FontInfo: FONTMETRICS );
+//var
+// PPString: string;
+// PresSpace: HPS;
+//begin
+// // Hack from Sibyl code - we don't know WTF the algorithm is
+// // for selecting between outline/bitmap and doing substitutions
+// // so send it to a dummy window and find out the resulting details
+// PPString := IntToStr( PointSize )
+// + '.'
+// + FaceName;
+//
+// PPString := ModifyFontName( PPString, Attributes );
+//
+// FontWindow.SetPPFontNameSize( PPString );
+//
+// PresSpace := WinGetPS( FontWindow.Handle );
+// GpiQueryFontMetrics( PresSpace,
+// SizeOf( FontInfo ),
+// FontInfo );
+// WinReleasePS( PresSpace );
+//end;
+
+// Look for the best match for the given face, size and attributes.
+// If FixedWidth is set then makes sure that the result is fixed
+// (if there is any fixed font on the system at all!)
+// This uses the OS/2 GPI and therefore makes some substitutions,
+// such as Helv 8 (bitmap) for Helvetica 8 (outline)
+//------------------------------------------------------------------------
+procedure FindBestFontMatch( const FaceName: string;
+ const PointSize: longint;
+ const Attributes: TFontAttributes;
+ const FixedWidth: boolean;
+ var FontInfo: string );
+var
+ BestBitmapFontMatch: TLogicalFont;
+ fl: TStringList;
+ i: integer;
+begin
+ { TODO -oGraeme -cfonts : This hack is very quick and dirty. Needs to be refined a lot }
+ fl := fpgApplication.GetFontFaceList;
+ for i := 0 to fl.Count-1 do
+ begin
+ if Pos(FaceName, fl[i]) > 0 then
+ FontInfo := fl[i] + '-' + IntToStr(PointSize);
+ end;
+
+ if Fontinfo = '' then
+ // nothing found se use default font of fpGUI
+ FontInfo := fpgApplication.DefaultFont.FontDesc;
+
+ //// First just ask GPI to give us a font
+ //AskOS2FontDetails( FaceName,
+ // PointSize,
+ // Attributes,
+ // FontInfo );
+ //
+ //if not FixedWidth then
+ // // OK, whatever it gave us.
+ // exit;
+ //
+ //// we want a fixed width font...
+ //if ( FontInfo.fsType and FM_TYPE_FIXED ) <> 0 then
+ // // got a suitable font
+ // exit;
+ //
+ //// the stoopid freaking OS/2 GPI has given us
+ //// a proportional font for that size
+ //if DefaultOutlineFixedFace <> nil then
+ // // use the default fixed width outline face
+ // AskOS2FontDetails( DefaultOutlineFixedFace.pName^,
+ // PointSize,
+ // Attributes,
+ // FontInfo );
+ //
+ //
+ //if ( FontInfo.fsType and FM_TYPE_FIXED ) <> 0 then
+ // // got a suitable font
+ // exit;
+ //
+ //// still got a proportional font,
+ //// or we didn't have any fixed width outline face
+ //// so see what we can find in the way of a bitmap fixed font
+ //
+ //BestBitmapFontMatch := GetClosestBitmapFixedFont( PointSize );
+ //if BestBitmapFontMatch <> nil then
+ //begin
+ // FontInfo.lMaxbaseLineExt := BestBitmapFontMatch.lMaxbaselineExt;
+ // FontInfo.lAveCharWidth := BestBitmapFontMatch.lAveCharWidth;
+ // FontInfo.fsDefn := 0;
+ // FontInfo.szFaceName := BestBitmapFontMatch.pFaceName^;
+ //end;
+ //// else - there are no fixed fonts of any kind on the system. Oh dear.
+
+end;
+
+//------------------------------------------------------------------------
+// Font manager
+//------------------------------------------------------------------------
+
+// constructor
+//------------------------------------------------------------------------
+constructor TCanvasFontManager.Create(Canvas: TfpgCanvas; AllowBitmapFonts: boolean;
+ AWidget: TfpgWidget);
+begin
+ inherited Create;
+ if FontFaces = nil then
+ GetFontList;
+ FCanvas := Canvas;
+ FWidget := AWidget;
+ FLogicalFonts := TList.Create;
+ FCurrentFontSpec.FaceName := 'Arial';
+ FCurrentFont := nil;
+ FAllowBitmapFonts := AllowBitmapFonts;
+ // get system default font spec
+ // as default default ;)
+ FPGuiFontToFontSpec( fpgApplication.DefaultFont, FDefaultFontSpec );
+end;
+
+// Destructor
+//------------------------------------------------------------------------
+destructor TCanvasFontManager.Destroy;
+var
+ i: integer;
+ Font: TLogicalFont;
+begin
+ // select default font so none of our logical fonts are in use
+ FCanvas.Font := fpgApplication.DefaultFont;
+
+ // delete each logical font and our record of it
+ for i := 0 to FLogicalFonts.Count - 1 do
+ begin
+ Font := TLogicalFont(FLogicalFonts[ i ]);
+ //if not GpiDeleteSetID( FCanvas.Handle, Font.ID ) then
+ // rc := WinGetLastError( AppHandle );
+ Font.Free;
+ end;
+ FLogicalFonts.Free;
+ inherited Destroy;
+end;
+
+// Create a logical font for the given spec
+//------------------------------------------------------------------------
+function TCanvasFontManager.CreateFont( const FontSpec: TFontSpec ): TLogicalFont;
+var
+ UseFaceName: string;
+ Face: TFontFace;
+ RemoveBoldFromSelection: boolean;
+ RemoveItalicFromSelection: boolean;
+ UseAttributes: TFontAttributes;
+ MatchAttributes: TFontAttributes;
+ BaseFont: TLogicalFont;
+ BaseFontIsBitmapFont: Boolean;
+ FontInfo: string;
+ FixedWidth: boolean;
+begin
+ Face := nil;
+ RemoveBoldFromSelection := false;
+ RemoveItalicFromSelection := false;
+
+ UseAttributes := FontSpec.Attributes;
+
+ // see if the originally specified font is a fixed width one.
+ FixedWidth := false;
+ Face := FindFaceName( FontSpec.FaceName );
+ if Face <> nil then
+ FixedWidth := Face.FixedWidth;
+
+ Face := nil;
+
+ if not FAllowBitmapFonts then
+ UseFaceName := SubstituteBitmapFontToOutline( FontSpec.FaceName )
+ else
+ UseFaceName := FontSpec.FaceName;
+
+ if FontSpec.Attributes <> [] then
+ begin
+ BaseFontIsBitmapFont := false;
+ if FAllowBitmapFonts then
+ begin
+ // First see if the base font (without attributes)
+ // would be a bitmap font...
+ BaseFont := CreateFontBasic( UseFaceName, FontSpec.PointSize );
+ if BaseFont <> nil then
+ begin
+ BaseFontIsBitmapFont := BaseFont.FontType = ftBitmap;
+ BaseFont.Destroy;
+ end;
+ end;
+
+ If not BaseFontIsBitmapFont Then
+ begin
+ // Result is an outline font so look for specific bold/italic fonts
+ if ( faBold in FontSpec.Attributes )
+ and ( faItalic in FontSpec.Attributes ) then
+ begin
+ Face := FindFaceName( UseFaceName + ' BOLD ITALIC' );
+ if Face <> nil then
+ begin
+ Exclude( UseAttributes, faBold );
+ Exclude( UseAttributes, faItalic );
+ RemoveBoldFromSelection := true;
+ RemoveItalicFromSelection := true;
+ end;
+ end;
+
+ if Face = nil then
+ if faBold in FontSpec.Attributes then
+ begin
+ Face := FindFaceName( UseFaceName + ' BOLD' );
+ if Face <> nil then
+ begin
+ Exclude( UseAttributes, faBold );
+ RemoveBoldFromSelection := true;
+ end;
+ end;
+
+ if Face = nil then
+ if faItalic in FontSpec.Attributes then
+ begin
+ Face := FindFaceName( UseFaceName + ' ITALIC' );
+ if Face <> nil then
+ begin
+ Exclude( UseAttributes, faItalic );
+ RemoveItalicFromSelection := true;
+ end;
+ end;
+ end;
+ end;
+
+ if Face <> nil then
+ // found a styled face, does it match fixed width?
+ if Face.FixedWidth <> FixedWidth then
+ // no so we don't want to use it.
+ Face := nil;
+
+ if Face = nil then
+ // didn't find a styled face (or no styles set)
+ // so find unmodified, we will use simulation bits
+ Face := FindFaceName( UseFaceName );
+
+ if not FAllowBitmapFonts then
+ if Face.FontType = ftBitmap then
+ // we aren't allowed bitmaps, but that's what this
+ // face is. So use the default outline face of the
+ // appropriate width type
+ if FixedWidth then
+ Face := DefaultOutlineFixedFace
+ else
+ Face := DefaultOutlineProportionalFace;
+
+ if Face = nil then
+ begin
+ // Could not find the specified font name. Bummer.
+ Result := nil;
+ exit;
+ end;
+
+ // OK now we have found the font face...
+ Result := TLogicalFont.Create( nil );
+ Result.PointSize := FontSpec.PointSize; // will use later if the result was an outline font...
+ Result.FaceName := FontSpec.FaceName;
+ Result.UseFaceName := Face.Name;
+ Result.Attributes := FontSpec.Attributes;
+ Result.fsSelection := 0;
+ Result.FixedWidth := Face.FixedWidth;
+
+ if FAllowBitmapFonts then
+ begin
+ if BaseFontIsBitmapFont then
+ MatchAttributes := []
+ else
+ MatchAttributes := UseAttributes;
+ FindBestFontMatch( Face.Name,
+ FontSpec.PointSize,
+ MatchAttributes,
+ FixedWidth,
+ FontInfo );
+
+ Result.UseFaceName := FontInfo;
+// AssignStr( Result.UseFaceName, FontInfo.FaceName );
+
+ // We may actually get a bitmap OR an outline font back
+ //If ( FontInfo.fsDefn And FM_DEFN_OUTLINE ) <> 0 Then
+ // Result.FontType := ftOutline
+ //else
+ // Result.FontType := ftBitmap;
+ end
+ else
+ begin
+ // no bitmap fonts please.
+ Result.FontType := ftOutline
+ end;
+
+ // store the baseline and average char width.
+ // For bitmap fonts, these tell GPI which font we really want
+ // For outline fonts, we are just storing them for later ref.
+ //Result.lMaxbaseLineExt := FontInfo.lMaxbaselineExt;
+ //Result.lAveCharWidth := FontInfo.lAveCharWidth;
+ //Result.lMaxCharInc := FontInfo.lMaxCharInc;
+ Result.lMaxBaseLineExt := FontSpec.YSize;
+ Result.lAveCharWidth := FontSpec.XSize;
+ Result.lMaxCharInc := FontSpec.YSize;
+
+ // Set style flags
+ with Result do
+ begin
+ //If faBold in UseAttributes Then
+ // fsSelection := fsSelection or FM_SEL_BOLD;
+ //If faItalic in UseAttributes Then
+ // fsSelection := fsSelection or FM_SEL_ITALIC;
+ //If faUnderScore in UseAttributes Then
+ // fsSelection := fsSelection or FM_SEl_UNDERSCORE;
+ //If faStrikeOut in UseAttributes Then
+ // fsSelection := fsSelection or FM_SEl_STRIKEOUT;
+ //If faOutline in UseAttributes Then
+ // fsSelection := fsSelection or FM_SEl_OUTlINE;
+ end;
+
+ Result.pCharWidthArray := nil;
+end;
+
+// Register the given logical font with GPI and store for later use
+//------------------------------------------------------------------------
+procedure TCanvasFontManager.RegisterFont( Font: TLogicalFont );
+var
+// fa: FATTRS;
+ rc: LongInt;
+begin
+ FLogicalFonts.Add( Font );
+ Font.ID := FLogicalFonts.Count + 1; // add 1 to stay out of Sibyl's way
+
+ //// Initialise GPI font attributes
+ //FillChar( fa, SizeOf( FATTRS ), 0 );
+ //fa.usRecordLength := SizeOf( FATTRS );
+ //
+ //// Copy facename and 'simulation' attributes from what we obtained
+ //// earlier
+ //fa.szFaceName := Font.pUseFaceName^;
+ //fa.fsSelection := Font.fsSelection;
+ //
+ //fa.lMatch := 0; // please Mr GPI be helpful and do clever stuff for us, we are ignorant
+ //
+ //fa.idRegistry := 0; // IBM magic number
+ //fa.usCodePage := 0; // use current codepage
+ //
+ //If Font.FontType = ftOutline then
+ // // Outline font wanted
+ // fa.fsFontUse := FATTR_FONTUSE_OUTLINE Or FATTR_FONTUSE_TRANSFORMABLE
+ //else
+ // // bitmap font
+ // fa.fsFontUse := 0;
+ //
+ //// don't need mixing with graphics (for now)
+ //fa.fsFontUse := fa.fsFontUse or FATTR_FONTUSE_NOMIX;
+ //
+ //// copy char cell width/height from the (valid) one we
+ //// found earlier in GetFont (will be zero for outline)
+ //fa.lMaxbaseLineExt := Font.lMaxbaselineExt;
+ //fa.lAveCharWidth := Font.lAveCharWidth;
+ //
+ //fa.fsType := 0;
+ //
+ //// create logical font
+ //rc := GpiCreateLogFont( FCanvas.Handle,
+ // nil,
+ // Font.ID,
+ // fa );
+end;
+
+// Select the given (existing) logical font
+//------------------------------------------------------------------------
+procedure TCanvasFontManager.SelectFont( Font: TLogicalFont;
+ Scale: longint );
+var
+ aHDC: integer;
+ //xRes: LongInt;
+ //yRes: LongInt;
+ //aSizeF: SIZEF;
+ f: TfpgFont;
+ s: string;
+begin
+writeln('TCanvasFontManager.SelectFont >>>>>>>>>');
+ // Select the logical font
+ //GpiSetCharSet( FCanvas.Handle, Font.ID );
+ if Font.FontType = ftOutline then
+ begin
+ s := Font.FaceName + '-' + IntToStr(Font.PointSize);
+ if faBold in Font.Attributes then
+ s := s + ':bold';
+ if faItalic in Font.Attributes then
+ s := s + ':italic';
+ if faUnderScore in Font.Attributes then
+ s := s + ':underline';
+ f := fpgGetFont(s);
+ writeln(' fontdesc=', s);
+ FCanvas.Font := f;
+ // // For outline fonts, also set character Box
+ // aHDC := GpiQueryDevice( FCanvas.Handle );
+ // DevQueryCaps( aHDC,
+ // CAPS_HORIZONTAL_FONT_RES,
+ // 1,
+ // xRes );
+ // DevQueryCaps( aHDC,
+ // CAPS_VERTICAL_FONT_RES,
+ // 1,
+ // yRes );
+ //
+ // aSizeF.CX := 65536 * xRes* Font.PointSize Div 72 * Scale;
+ // aSizeF.CY := 65536 * yRes* Font.PointSize Div 72 * Scale;
+ //
+ // GpiSetCharBox( FCanvas.Handle, aSizeF );
+ end;
+end;
+
+// Get a font to match the given spec, creating or re-using an
+// existing font as needed.
+//------------------------------------------------------------------------
+function TCanvasFontManager.GetFont( const FontSpec: TFontSpec ): TLogicalFont;
+var
+ AFont: TLogicalFont;
+ FontIndex: integer;
+begin
+ for FontIndex := 0 to FLogicalFonts.Count - 1 do
+ begin
+ AFont := TLogicalFont(FLogicalFonts[ FontIndex ]);
+ if AFont.PointSize = FontSpec.PointSize then
+ begin
+ if ( AFont.PointSize > 0 )
+ or ( ( AFont.lAveCharWidth = FontSpec.XSize )
+ and ( AFont.lMaxbaselineExt = FontSpec.YSize ) ) then
+ begin
+ if AFont.Attributes = FontSpec.Attributes then
+ begin
+ // search name last since it's the slowest thing
+ if AFont.FaceName = FontSpec.FaceName then
+ begin
+ // Found a logical font already created
+ Result := AFont;
+ // done
+ exit;
+ end;
+ end;
+ end;
+ end;
+ end;
+
+ // Need to create new logical font
+ Result := CreateFont( FontSpec );
+ if Result <> nil then
+ begin
+ RegisterFont( Result );
+ end;
+end;
+
+// Set the current font for the canvas to match the given
+// spec, creating or re-using fonts as needed.
+//------------------------------------------------------------------------
+procedure TCanvasFontManager.SetFont( const FontSpec: TFontSpec );
+var
+ Font: TLogicalFont;
+ lDefaultFontSpec: TFontSpec;
+begin
+ //if FCurrentFontSpec = FontSpec then
+ if (FCurrentFontSpec.FaceName = FontSpec.FaceName) and
+ (FCurrentFontSpec.PointSize = FontSpec.PointSize) and
+ (FCurrentFontSpec.Attributes = FontSpec.Attributes) then
+ // same font
+ exit;
+
+ Font := GetFont( FontSpec );
+
+ if Font = nil then
+ begin
+ // ack! Pfffbt! Couldn't find the font.
+
+ // Try to get the default font
+ Font := GetFont( FDefaultFontSpec );
+ if Font = nil then
+ begin
+ FPGuiFontToFontSpec( fpgApplication.DefaultFont, lDefaultFontSpec );
+ Font := GetFont( lDefaultFontSpec );
+ if Font = nil then
+ // Jimminy! We can't even get the default system font
+ raise Exception.Create( 'Could not access default font '
+ + 'in place of '
+ + FontSpec.FaceName
+ + ' '
+ + IntToStr( FontSpec.PointSize ) );
+ end;
+
+ end;
+
+ SelectFont( Font, 1 );
+ FCurrentFontSpec := FontSpec;
+ FCurrentFont := Font;
+end;
+
+// Get the widths of all characters for current font
+// and other dimensions
+//------------------------------------------------------------------------
+procedure TCanvasFontManager.LoadMetrics;
+var
+ TheChar: Char;
+begin
+ // Retrieve all character widths
+ if FCurrentFont.FontType = ftOutline then
+ begin
+ SelectFont( FCurrentFont, FontWidthPrecisionFactor );
+ end;
+
+ // allocate memory for storing the char widths
+ GetMem( FCurrentFont.pCharWidthArray, sizeof( TCharWidthArray ) );
+ //if not GpiQueryWidthTable( FCanvas.Handle,
+ // 0, 256,
+ // FCurrentFont.pCharWidthArray^[ #0 ] ) then
+ //begin
+ // raise Exception.Create( 'Error getting character width table: '
+ // + 'GpiQueryWidthTable error '
+ // + IntToStr( WinGetLastError( AppHandle ) ) );
+ //end;
+
+ // Convert all widths to positive!
+ // For unknown reason, sometimes GPI returns negative values...
+ for TheChar := #0 to #255 do
+ begin
+ FCurrentFont.pCharWidthArray^[ TheChar ] := Abs( FCurrentFont.pCharWidthArray^[ TheChar ] );
+ end;
+
+ if FCurrentFont.FontType = ftOutline then
+ begin
+ SelectFont( FCurrentFont, 1 );
+ end
+ else
+ begin
+ // For bitmap fonts, multiply by 256 manually
+ for TheChar := #0 to #255 do
+ begin
+ FCurrentFont.pCharWidthArray^[ TheChar ] :=
+ FCurrentFont.pCharWidthArray^[ TheChar ]
+ * FontWidthPrecisionFactor;
+ end;
+ end;
+
+ //GpiQueryFontMetrics( FCanvas.Handle,
+ // sizeof( fm ),
+ // fm );
+ //FCurrentFont.lMaxbaseLineExt := fm.lMaxbaselineExt;
+ //FCurrentFont.lAveCharWidth := fm.lAveCharWidth;
+ //FCurrentFont.lMaxCharInc := fm.lMaxCharInc;
+ //FCurrentFont.lMaxDescender := fm.lMaxDescender;
+end;
+
+procedure TCanvasFontManager.EnsureMetricsLoaded;
+begin
+ if FCurrentFont = nil then
+ raise( Exception.Create( 'No font selected before getting font metrics' ) );
+
+ if FCurrentFont.pCharWidthArray = Nil then
+ LoadMetrics;
+end;
+
+function TCanvasFontManager.CharWidth( const C: Char ): longint;
+begin
+ EnsureMetricsLoaded;
+ Result := FCurrentFont.pCharWidthArray^[ C ];
+ { TODO -ograemeg -chard-coded result : This is a temporary hard-code }
+ result := fpgApplication.DefaultFont.TextWidth(C);
+end;
+
+function TCanvasFontManager.AverageCharWidth: longint;
+begin
+ EnsureMetricsLoaded;
+ Result := FCurrentFont.lAveCharWidth;
+end;
+
+function TCanvasFontManager.MaximumCharWidth: longint;
+begin
+ EnsureMetricsLoaded;
+ Result := FCurrentFont.lMaxCharInc;
+end;
+
+function TCanvasFontManager.CharHeight: longint;
+begin
+ EnsureMetricsLoaded;
+ Result := FCurrentFont.lMaxBaseLineExt;
+end;
+
+function TCanvasFontManager.CharDescender: longint;
+begin
+ EnsureMetricsLoaded;
+ Result := FCurrentFont.lMaxDescender;
+end;
+
+function TCanvasFontManager.IsFixed: boolean;
+begin
+ Result := FCurrentFont.FixedWidth;
+end;
+
+procedure TCanvasFontManager.DrawString(var Point: TPoint; const Length: longint; const S: PChar);
+var
+ t: string;
+begin
+ t := s;
+ FCanvas.DrawString(Point.X, Point.Y, t);
+ Point.x := Point.X + Canvas.Font.TextWidth(t);
+end;
+
+end.
diff --git a/components/richtext/RichTextDisplayUnit.pas b/components/richtext/RichTextDisplayUnit.pas
new file mode 100755
index 00000000..2ef91792
--- /dev/null
+++ b/components/richtext/RichTextDisplayUnit.pas
@@ -0,0 +1,422 @@
+Unit RichTextDisplayUnit;
+
+{$mode objfpc}{$H+}
+
+Interface
+
+uses
+ Classes
+ ,CanvasFontManager
+ ,RichTextStyleUnit
+ ,RichTextLayoutUnit
+ ;
+
+// Selection start and end should both be nil if no selection is to be applied
+Procedure DrawRichTextLayout( var FontManager: TCanvasFontManager;
+ var Layout: TRichTextLayout;
+ const SelectionStart: PChar;
+ const SelectionEnd: PChar;
+ const StartLine: longint;
+ const EndLine: longint;
+ const StartPoint: TPoint );
+
+// Print as much of the given layout as will fit on the page,
+// starting at StartY and StartLine
+// EndY is set to the final Y output position used + 1.
+// EndLine is set to the last line printed + 1
+Procedure PrintRichTextLayout( var FontManager: TCanvasFontManager;
+ var Layout: TRichTextLayout;
+ const StartLine: longint;
+ var EndLine: longint;
+ const StartY: longint;
+ var EndY: longint );
+
+Implementation
+
+uses
+ SysUtils
+// ACLString, ACLUtility,
+ ,RichTextDocumentUnit
+ ,fpg_base
+ ,fpg_main
+ ;
+
+// For the given point in the text, update selected if the point
+// is at start or end of selection
+// Returns true if changed
+function SelectionChange( P: PChar;
+ SelectionStart: PChar;
+ SelectionEnd: PChar;
+ var NextSelected: boolean ): boolean;
+begin
+ Result := false;
+ if P = SelectionStart then
+ begin
+ Result := true;
+ if SelectionStart < SelectionEnd then
+ // reached start of selection
+ NextSelected := true
+ else
+ // reached end
+ NextSelected := false;
+ end
+ else if P = SelectionEnd then
+ begin
+ Result := true;
+ if SelectionStart < SelectionEnd then
+ // reached end of selection
+ NextSelected := false
+ else
+ // reached start
+ NextSelected := true;
+ end;
+end;
+
+function InvertRGB( Arg: TfpgColor ): TfpgColor;
+begin
+ Result := fpgColorToRGB( Arg ); // in case it's a system color e.g. button face
+ Result := Result xor $ffffff; // now invert the RGB components
+end;
+
+// Draw a string at the given location with given color/selected state
+procedure DrawRichTextString( var FontManager: TCanvasFontManager; var X: longint;
+ Y: longint; S: PChar; Len: longint; Selected: Boolean; PenColor: TfpgColor;
+ BackColor: TfpgColor );
+var
+ Point: TPoint;
+begin
+ if Len = 0 then
+ exit;
+
+ Point.X := X;
+ Point.Y := Y;
+
+ if Selected then
+ begin
+ FontManager.Canvas.Color := InvertRGB( BackColor );
+ FontManager.Canvas.TextColor := InvertRGB(PenColor);
+ end
+ else
+ begin
+ FontManager.Canvas.Color := BackColor;
+ FontManager.Canvas.TextColor := PenColor;
+ end;
+ FontManager.DrawString( Point, Len, S );
+ X := Point.X;
+end;
+
+var
+ // global, so that we don't reallocate every drawline
+ StringToDraw: String = '';
+
+// Draw the specified line at the specified
+// (physical) location
+Procedure DrawRichTextLine( var FontManager: TCanvasFontManager;
+ var Layout: TRichTextLayout; SelectionStart: PChar; SelectionEnd: PChar;
+ Line: TLayoutLine; Start: TPoint );
+var
+ X, Y: longint;
+ Element: TTextElement;
+ StartedDrawing: boolean;
+ Style: TTextDrawStyle;
+ P: PChar;
+ NextP: PChar;
+ EndP: PChar;
+ BitmapIndex: longint;
+ Bitmap: TfpgImage;
+ BitmapRect: TRect;
+ TextBlockStart: PChar;
+ Selected: boolean;
+ NextSelected: boolean;
+ NewMarginX: longint;
+
+ procedure DrawTextBlock;
+ var
+ PhysX: longint;
+ begin
+writeln('**** DrawTextBlock *****');
+ PhysX := X div FontWidthPrecisionFactor;
+ FontManager.Canvas.DrawText(PhysX, Y, StringToDraw);
+writeln(' PhysX=', PhysX, ' Y=', Y, ' String=', StringToDraw);
+ DrawRichTextString( FontManager,
+ PhysX,
+ Y,
+ PChar(StringToDraw),
+ Length(StringToDraw),
+ Selected,
+ Style.Color,
+ Style.BackgroundColor );
+ X := PhysX * FontWidthPrecisionFactor;
+ StringToDraw := '';
+ end;
+
+
+begin
+writeln('DrawRichTextLine >>>');
+ P := Line.Text;
+ EndP := Line.Text + Line.Length;
+
+ if P = EndP then
+ begin
+ // Empty line
+ exit;
+ end;
+
+ Selected := false;
+ if SelectionStart <= Line.Text then
+ // selection start is above.
+ Selected := true;
+ if SelectionEnd <= Line.Text then
+ // selection end is above.
+ Selected := not Selected;
+
+ StringToDraw := '';
+
+ Style := Line.Style;
+ FontManager.SetFont( Style.Font );
+ StartedDrawing := false;
+
+ TextBlockStart := P;
+
+ Y := Start.Y + Line.MaxDescender;
+
+ while P < EndP do
+ begin
+ Element := ExtractNextTextElement( P, NextP );
+
+ if SelectionChange( P,
+ SelectionStart,
+ SelectionEnd,
+ NextSelected ) then
+ begin
+ DrawTextBlock;
+ TextBlockStart := P;
+ Selected := NextSelected;
+ end;
+
+ case Element.ElementType of
+ teWordBreak,
+ teText,
+ teImage:
+ begin
+ if not StartedDrawing then
+ begin
+ // we haven't yet started drawing:
+ // so work out alignment
+ X := Start.X * FontWidthPrecisionFactor
+ + Layout.GetStartX( Style, Line );
+ StartedDrawing := true;
+ end;
+
+ // Now do the drawing
+ if Element.ElementType = teImage then
+ begin
+ DrawTextBlock;
+ TextBlockStart := NextP;
+
+ try
+ BitmapIndex := StrToInt( Element.Tag.Arguments );
+ except
+ BitmapIndex := -1;
+ end;
+ if Layout.IsValidBitmapIndex( BitmapIndex ) then
+ begin
+ Bitmap := Layout.Images.Item[BitmapIndex].Image;
+
+ BitmapRect.Left := X div FontWidthPrecisionFactor;
+ BitmapRect.Bottom := Start.Y;
+ BitmapRect.Right := Trunc(BitmapRect.Left
+ + Bitmap.Width
+ * Layout.HorizontalImageScale);
+ BitmapRect.Top := Trunc(BitmapRect.Bottom
+ + Bitmap.Height
+ * Layout.VerticalImageScale);
+
+ FontManager.Canvas.StretchDraw(BitmapRect.Left, BitMapRect.Top,
+ BitmapRect.Right-BitMapRect.Left, BitMapRect.Bottom-BitMapRect.Top, Bitmap);
+
+ inc( X, trunc( Bitmap.Width
+ * FontWidthPrecisionFactor
+ * Layout.HorizontalImageScale ) );
+ end;
+ end
+ else
+ begin
+ // character (or word break)
+ // build up the successive characters...
+ StringToDraw := StringToDraw + Element.Character;
+ end;
+ end;
+
+ teStyle:
+ begin
+ DrawTextBlock;
+ TextBlockStart := NextP;
+
+ if ( Element.Tag.TagType = ttItalicOff )
+ and ( faItalic in Style.Font.Attributes )
+ and ( not FontManager.IsFixed )
+ then
+ // end of italic; add a space
+ inc( X, FontManager.CharWidth( ' ' ) );
+
+ Layout.PerformStyleTag( Element.Tag, Style, X );
+ NewMarginX := ( Start.X + Style.LeftMargin ) * FontWidthPrecisionFactor;
+ if NewMarginX > X then
+ begin
+ //skip across...
+ X := NewMarginX;
+ end;
+ end;
+ end;
+ P := NextP;
+ end;
+
+ DrawTextBlock;
+ writeln('DrawRichTextLine <<<');
+end;
+
+Procedure DrawRichTextLayout( var FontManager: TCanvasFontManager;
+ var Layout: TRichTextLayout;
+ const SelectionStart: PChar;
+ const SelectionEnd: PChar;
+ const StartLine: longint;
+ const EndLine: longint;
+ const StartPoint: TPoint );
+Var
+ Line: TLayoutLine;
+ LineIndex: longint;
+ Y: longint;
+ BottomOfLine: longint;
+begin
+writeln('DEBUG: DrawRichTextLayout >>>>');
+writeln(' Layout.FNumLines = ', Layout.FNumLines);
+ assert( StartLine >= 0 );
+ assert( StartLine <= Layout.FNumLines );
+ assert( EndLine >= 0 );
+ assert( EndLine <= Layout.FNumLines );
+ assert( StartLine <= EndLine );
+
+ if Layout.FNumLines = 0 then
+ // no text to draw
+ exit;
+
+ Y := StartPoint.Y + Layout.FRichTextSettings.Margins.Top;
+writeln(' StartPoint.Y = ', StartPoint.Y);
+writeln(' StartPoint.X = ', StartPoint.X);
+writeln(' Y = ', Y);
+
+ LineIndex := 0;
+
+ repeat
+ Line := Layout.FLines^[ LineIndex ];
+writeln(' Line.Height = ', Line.Height);
+// Line.Height := 18;
+ BottomOfLine := Y + Line.Height + 1; // bottom pixel row is top + height + 1
+
+ if // the line is in the range to be drawn
+ ( LineIndex >= StartLine )
+ and ( LineIndex <= EndLine )
+
+ // and the line is within the cliprect
+ and ( BottomOfLine < FontManager.Canvas.GetClipRect.Bottom )
+ and ( Y >= FontManager.Canvas.GetClipRect.Top ) then
+ begin
+ // draw it. First decided whether selection is started or not.
+ DrawRichTextLine( FontManager,
+ Layout,
+ SelectionStart,
+ SelectionEnd,
+ Line,
+ Point( StartPoint.X, BottomOfLine ) );
+
+ end;
+ inc( Y, Line.Height );
+
+ { TODO 99 -oGraeme -cMUST FIX : Must remove this hard-coded value. It's just a test!!! }
+ // 4 is the Border Width of 2px times 2 borders.
+ if Y > (FontManager.Widget.Height-4) then
+ // past bottom of output canvas
+ break;
+
+ inc( LineIndex );
+
+ if LineIndex >= Layout.FNumLines then
+ // end of text
+ break;
+
+ until false;
+writeln('DEBUG: DrawRichTextLayout <<<<');
+End;
+
+Procedure PrintRichTextLayout( var FontManager: TCanvasFontManager;
+ var Layout: TRichTextLayout;
+ const StartLine: longint;
+ var EndLine: longint;
+ const StartY: longint;
+ var EndY: longint );
+Var
+ Selected: boolean;
+ Line: TLayoutLine;
+ LineIndex: longint;
+
+ Y: longint;
+
+ BottomOfLine: longint;
+
+ LinesPrinted: longint;
+begin
+ assert( StartLine >= 0 );
+ assert( StartLine <= Layout.FNumLines );
+
+ if Layout.FNumLines = 0 then
+ // no text to draw
+ exit;
+
+ Y := StartY
+ - Layout.FRichTextSettings.Margins.Top;
+
+ Selected := false; // it's not going to change.
+
+ LinesPrinted := 0;
+
+ LineIndex := StartLine;
+
+ repeat
+ Line := TLayoutLine(Layout.FLines[ LineIndex ]);
+ BottomOfLine := Y - Line.Height + 1; // bottom pixel row is top - height + 1
+
+ if BottomOfLine < Layout.FRichTextSettings.Margins.Bottom then
+ // past bottom of page (less margin)
+ if LinesPrinted > 0 then
+ // stop, as long as we've printed at least 1 line
+ break;
+
+ // draw it
+ DrawRichTextLine( FontManager,
+ Layout,
+ nil,
+ nil,
+ Line,
+ Point( 0,
+ BottomOfLine ) );
+
+ dec( Y, Line.Height );
+
+ inc( LinesPrinted );
+
+ inc( LineIndex );
+
+ if LineIndex >= Layout.FNumLines then
+ // end of text
+ break;
+
+ until false;
+
+ EndY := Y;
+ EndLine := LineIndex;
+end;
+
+
+end.
+
diff --git a/components/richtext/RichTextLayoutUnit.pas b/components/richtext/RichTextLayoutUnit.pas
new file mode 100755
index 00000000..13352b7d
--- /dev/null
+++ b/components/richtext/RichTextLayoutUnit.pas
@@ -0,0 +1,1005 @@
+Unit RichTextLayoutUnit;
+
+{$mode objfpc}{$H+}
+
+// Dynamically created layout class.
+// Represents a laid out rich text document
+
+Interface
+
+Uses
+ Classes,
+ CanvasFontManager,
+ RichTextDocumentUnit, RichTextStyleUnit,
+ fpg_imagelist;
+
+Type
+ TLayoutLine = record
+ Text: PChar;
+ Length: longint;
+ Height: longint;
+ Width: longint;
+ MaxDescender: longint;
+ MaxTextHeight: longint; // maximum height of text, doesn't include images
+ LinkIndex: longint; // link index at start of line, if any
+ Style: TTextDrawStyle;
+ Wrapped: boolean;
+ end;
+
+
+ TLinesArray = array[ 0..0 ] of TLayoutLine;
+
+
+ TTextPosition =
+ (
+ tpAboveTextArea,
+ tpAboveText,
+ tpWithinText,
+ tpBelowText,
+ tpBelowTextArea
+ );
+
+
+ // forward declaration
+ TRichTextLayout = class;
+
+
+ TLinkEvent = procedure( Sender: TRichTextLayout; Link: string ) of object;
+
+
+ TRichTextLayout = class(TObject)
+ Protected
+ FFontManager: TCanvasFontManager;
+ FText: PChar;
+ FImages: TfpgImageList;
+ FAllocatedNumLines: Longint;
+ FLayoutWidth: longint; // The target width for the layout. Used for centreing/right align
+ FWidth: longint; // The actual width of the text. May be wider due to unaligned
+ // parts or bitmaps or width so small individual characters don't fit.
+ FHeight: longint;
+ FLinks: TStringList;
+ FHorizontalImageScale: double;
+ FVerticalImageScale: double;
+ public
+ // Internal layout data
+ FLines: ^TLinesArray;
+ FNumLines: longword;
+ FRichTextSettings: TRichTextSettings;
+ // Drawing functions
+ Procedure PerformStyleTag( Const Tag: TTag;
+ Var Style: TTextDrawStyle;
+ const X: longint );
+ function GetElementWidth( Element: TTextElement ): longint;
+ // Queries
+ Function GetStartX( Style: TTextDrawStyle;
+ Line: TLayoutLine ): longint;
+ Procedure GetXFromOffset( const Offset: longint;
+ const LineIndex: longint;
+ Var X: longint );
+ Procedure GetOffsetFromX( const XToFind: longint;
+ const LineIndex: longint;
+ Var Offset: longint;
+ Var Link: string );
+ function FindPoint( XToFind, YToFind: longint;
+ Var LineIndex: longint;
+ Var Offset: longint;
+ Var Link: string ): TTextPosition;
+ function GetLineFromCharIndex( Index: longint ): longint;
+ function GetOffsetFromCharIndex( Index: longint;
+ Line: longint ): longint;
+ function GetLinePosition( Line: longint ): longint;
+ function GetLineFromPosition( YToFind: longint;
+ Var LineIndex: longint;
+ Var Remainder: longint ): TTextPosition;
+ // Layout functions
+ Procedure AddLineStart( Const Line: TLayoutLine );
+ Procedure CheckFontHeights( Var Line: TLayoutLine );
+ Procedure Layout;
+ function IsValidBitmapIndex( Index: longint ): boolean;
+ // property handlers
+ Function GetCharIndex( P: PChar ): longint;
+ Function GetTextEnd: longint;
+ Public
+ constructor Create( Text: PChar; Images: TfpgImageList; RichTextSettings: TRichTextSettings; FontManager: TCanvasFontManager; Width: longint );
+ Destructor Destroy; Override;
+ property TextEnd: longint read GetTextEnd;
+ function LinkFromIndex( const CharIndexToFind: longint): string;
+ property Images: TfpgImageList read FImages;
+ property Width: longint read FWidth;
+ property Height: longint read FHeight;
+ property HorizontalImageScale: double read FHorizontalImageScale;
+ property VerticalImageScale: double read FVerticalImageScale;
+ End;
+
+
+Implementation
+
+
+Uses
+ SysUtils
+// PMWin, BseDos, Dos, ClipBrd, Printers,
+// ACLUtility,
+ ,ACLStringUtility
+// ACLString,
+// ControlScrolling;
+ ,nvUtilities
+ ,fpg_main
+ ;
+
+Function TRichTextLayout.GetTextEnd: longint;
+begin
+ Result := StrLen( FText );
+end;
+
+// Create a layout of the specified rich text.
+constructor TRichTextLayout.Create(Text: PChar; Images: TfpgImageList;
+ RichTextSettings: TRichTextSettings; FontManager: TCanvasFontManager;
+ Width: longint);
+var
+ DefaultFontSpec: TFontSpec;
+Begin
+ Inherited Create;
+ FRichTextSettings := RichTextSettings;
+ FImages := Images;
+ FText := Text;
+ FAllocatedNumLines := 10;
+ GetMem( FLines, FAllocatedNumLines * sizeof( TLayoutLine ) );
+ FNumLines := 0;
+ FLinks := TStringList.Create;
+ FLinks.Duplicates := dupIgnore;
+ FFontManager := FontManager;
+ FLayoutWidth := Width * FontWidthPrecisionFactor;
+ FHorizontalImageScale := 1;
+ FVerticalImageScale := 1;
+ //FHorizontalImageScale := FFontManager.Canvas.HorizontalResolution
+ // / Screen.Canvas.HorizontalResolution;
+ //FVerticalImageScale := FFontManager.Canvas.VerticalResolution
+ // / Screen.Canvas.VerticalResolution;
+
+ // use normal font for default font when specified fonts can't be found
+ FPGuiFontToFontSpec( RichTextSettings.NormalFont, DefaultFontSpec );
+ FFontManager.DefaultFontSpec := DefaultFontSpec;
+ Layout;
+End;
+
+Destructor TRichTextLayout.Destroy;
+Begin
+ Finalize(FLines);
+ FreeMem( FLines); //, FAllocatedNumLines * sizeof( TLayoutLine ) );
+ FLines := nil;
+ FLinks.Free;
+ Inherited Destroy;
+End;
+
+Procedure TRichTextLayout.AddLineStart( Const Line: TLayoutLine );
+var
+ NewAllocation: longint;
+begin
+ if FNumLines >= FAllocatedNumLines then
+ begin
+ // reallocate the array twice the size
+ NewAllocation := FAllocatedNumLines * 2;
+ FLines := ReAllocMem( FLines,
+// FAllocatedNumLines * sizeof( TLayoutLine ),
+ NewAllocation * sizeof( TLayoutLine ) );
+ FAllocatedNumLines := NewAllocation;
+ end;
+ FLines^[ FNumLines ] := Line;
+ inc( FNumLines );
+ writeln(' DEBUG: TRichTextLayout.AddLineStart: FNumLines =', FNumLines);
+end;
+
+Procedure TRichTextLayout.PerformStyleTag( Const Tag: TTag;
+ Var Style: TTextDrawStyle;
+ const X: longint );
+begin
+ ApplyStyleTag( Tag,
+ Style,
+ FFontManager,
+ FRichTextSettings,
+ X );
+end;
+
+// Check the current font specifications and see if the
+// give line needs updating for max height/descender
+Procedure TRichTextLayout.CheckFontHeights( Var Line: TLayoutLine );
+var
+ FontHeight: longint;
+ Descender: longint;
+begin
+ FontHeight := FFontManager.CharHeight;
+ Descender := FFontManager.CharDescender;
+
+ if FontHeight > Line.Height then
+ Line.Height := FontHeight;
+
+ if FontHeight > Line.MaxTextHeight then
+ Line.MaxTextHeight := FontHeight;
+
+ if Descender > Line.MaxDescender then
+ Line.MaxDescender := Descender;
+end;
+
+function TRichTextLayout.IsValidBitmapIndex( Index: longint ): boolean;
+begin
+ if FImages = nil then
+ Result := false
+ else if FImages.Count = 0 then
+ Result := false
+ else
+ Result := Between( Index, 0, FImages.Count - 1 );
+end;
+
+// Main procedure: reads through the whole text currently stored
+// and breaks up into lines - each represented as a TLayoutLine in
+// the array FLines[ 0.. FNumLines ]
+Procedure TRichTextLayout.Layout;
+Var
+ CurrentLine: TLayoutLine;
+ CurrentLinkIndex: longint;
+ WrapX: longint; // X to wrap at
+ WordX: longint; // width of word so far
+ P: PChar;
+ NextP: PChar;
+ NextP2: PChar;
+ WordStart: PChar;
+ WordStarted: boolean; // if false, just skipping spaces..
+ WordStartX: longint; // X position of word start
+ LineWordsCompleted: longint; // how many words draw so far this line
+ CurrentElement: TTextElement;
+ NextElement: TTextElement;
+ CurrentCharWidth: longint;
+ Style: TTextDrawStyle;
+ DisplayedCharsSinceFontChange: boolean;
+ BitmapIndex: longint;
+ Bitmap: TfpgImage;
+ BitmapHeight: longint;
+ OnBreak: boolean;
+ DoWrap: boolean;
+
+ // Nested procedure
+ Procedure DoLine( EndPoint: PChar; NextLine: PChar; EndX: longint );
+ begin
+ // check if the max font
+ // height needs updating for the last string of the line
+ CheckFontHeights( CurrentLine );
+ inc( FHeight, CurrentLine.Height );
+ CurrentLine.Length := PCharDiff( EndPoint, CurrentLine.Text );
+ CurrentLine.Width := EndX;
+ if CurrentLine.Width > FWidth then
+ FWidth := CurrentLine.Width;
+ assert( CurrentLine.Height > 0 ); // we must have set the line height!
+ AddLineStart( CurrentLine );
+ CurrentLine.Text := NextLine;
+ CurrentLine.Style := Style;
+ CurrentLine.Height := 0;
+ CurrentLine.MaxDescender := 0;
+ CurrentLine.MaxTextHeight := 0;
+ CurrentLine.Width := 0;
+ CurrentLine.LinkIndex := CurrentLinkIndex;
+ CurrentLine.Wrapped := false;
+ assert( CurrentLinkIndex >= -1 );
+ assert( CurrentLinkIndex < FLinks.Count );
+ WordStartX := Style.LeftMargin * FontWidthPrecisionFactor;
+ // next line
+ // reset words completed count
+ LineWordsCompleted := 0;
+ WordStarted := false;
+ end;
+
+begin
+ FNumLines := 0;
+ FWidth := 0;
+ FHeight := FRichTextSettings.Margins.Top;
+ Style := GetDefaultStyle( FRichTextSettings );
+ ApplyStyle( Style, FFontManager );
+ CurrentLinkIndex := -1;
+ P := FText; // P is the current search position
+ CurrentLine.Text := P;
+ CurrentLine.Style := Style;
+ CurrentLine.Height := 0;
+ CurrentLine.MaxDescender := 0;
+ CurrentLine.MaxTextHeight := 0;
+ CurrentLine.Width := 0;
+ CurrentLine.LinkIndex := -1;
+ CurrentLine.Wrapped := false;
+ WordStartX := Style.LeftMargin * FontWidthPrecisionFactor;
+ WordX := 0;
+ WrapX := FLayoutWidth - (FRichTextSettings.Margins.Right * FontWidthPrecisionFactor);
+ LineWordsCompleted := 0;
+ WordStarted := false;
+ DisplayedCharsSinceFontChange := false;
+
+ repeat
+ CurrentElement := ExtractNextTextElement( P, NextP );
+ assert( NextP > P );
+ OnBreak := false;
+ case CurrentElement.ElementType of
+ teWordBreak:
+ begin
+ CurrentCharWidth := FFontManager.CharWidth( ' ' );
+ OnBreak := true;
+ end;
+
+ teLineBreak:
+ begin
+ DoLine( P, NextP, WordStartX + WordX );
+
+ // remember start of line
+ WordStart := NextP;
+ WordX := 0;
+
+ P := NextP;
+
+ continue;
+ end;
+
+ teTextEnd:
+ begin
+ DoLine( P, NextP, WordStartX + WordX );
+
+ // end of text, done
+ break;
+ end;
+
+ teImage:
+ begin
+ BitmapHeight := 0;
+ try
+ BitmapIndex := StrToInt( CurrentElement.Tag.Arguments );
+ except
+ BitmapIndex := -1;
+ end;
+ Bitmap := nil;
+ if IsValidBitmapIndex( BitmapIndex ) then
+ begin
+ Bitmap := FImages.Item[BitmapIndex].Image;
+ CurrentCharWidth := Trunc(Bitmap.Width * FontWidthPrecisionFactor * FHorizontalImageScale);
+ WordStarted := true;
+ BitmapHeight := Trunc(Bitmap.Height * FVerticalImageScale);
+ end;
+
+ end;
+
+ teText:
+ begin
+ // Normal (non-leading-space) character
+ CurrentCharWidth := FFontManager.CharWidth( CurrentElement.Character );
+ WordStarted := true;
+ end;
+
+ teStyle:
+ begin
+ case CurrentElement.Tag.TagType of
+ ttBeginLink:
+ begin
+ CurrentLinkIndex := FLinks.Add( CurrentElement.Tag.Arguments );
+ P := NextP;
+ continue;
+ end;
+
+ ttEndLink:
+ begin
+ CurrentLinkIndex := -1;
+ P := NextP;
+ continue;
+ end;
+
+ ttSetLeftMargin: // SPECIAL CASE... could affect display immediately
+ begin
+ PerformStyleTag( CurrentElement.Tag, Style, WordstartX + WordX );
+ if Style.LeftMargin * FontWidthPrecisionFactor < WordStartX then
+ begin
+ // we're already past the margin being set
+ if pos( 'breakifpast', CurrentElement.Tag.Arguments ) > 0 then
+ begin
+ // this argument means, do a line break
+ // if the margin is already past
+ // Seems unusual for most purposes, but needed for IPF rendering.
+ DoLine( P, NextP, WordStartX + WordX );
+
+ // remember start of line
+ WordStart := NextP;
+ WordX := 0;
+
+ P := NextP;
+
+ continue;
+ end;
+
+ // so ignore it for now.
+ P := NextP;
+ continue;
+ end;
+
+ // skip across to the new margin
+ CurrentCharWidth := (Style.LeftMargin * FontWidthPrecisionFactor)
+ - WordStartX - WordX;
+ // BUT! Don't treat it as a space, because you would not
+ // expect wrapping to take place in a margin change...
+ // at least not for IPF (NewView) :)
+
+ end;
+
+ else
+ begin
+ // before processing the tag see if font height needs updating
+ if DisplayedCharsSinceFontChange then
+ CheckFontHeights( CurrentLine );
+
+ if ( CurrentElement.Tag.TagType = ttItalicOff )
+ and ( faItalic in Style.Font.Attributes ) then
+ if not FFontManager.IsFixed then
+ // end of italic; add a space
+ inc( WordX, FFontManager.CharWidth( ' ' ) );
+
+ PerformStyleTag( CurrentElement.Tag,
+ Style,
+ WordX );
+
+ DisplayedCharsSinceFontChange := false;
+ P := NextP;
+ continue; // continue loop
+ end;
+ end;
+
+ end
+
+ end;
+
+ if OnBreak then
+ begin
+ // we just processed a space
+ if WordStarted then
+ begin
+ DisplayedCharsSinceFontChange := true;
+ // remember that we have now completed a word on this line
+ inc( LineWordsCompleted );
+ WordStarted := false;
+
+ // Add the word width, and the space width,
+ // to get the start of the next word
+ inc( WordStartX, WordX + CurrentCharWidth );
+ WordX := 0;
+
+ // remember the start of the next word
+ WordStart := NextP;
+
+ P := NextP;
+
+ continue;
+ end;
+ // else - starting spaces - fall through like normal char
+ end;
+
+ // if we're still going here we have a normal char
+ // (or leading spaces)
+ if not Style.Wrap then
+ begin
+ // No alignment
+ // We don't care about how wide it gets
+ inc( WordX, CurrentCharWidth );
+ DisplayedCharsSinceFontChange := true;
+
+ if CurrentElement.ElementType = teImage then
+ if Bitmap <> nil then
+ if BitmapHeight > CurrentLine.Height then
+ CurrentLine.Height := BitmapHeight;
+
+ P := NextP;
+ continue;
+ end;
+
+ DoWrap := false;
+
+ // Calculate position of end of character
+ // see if char would exceed width
+ if (WordStartX + WordX + CurrentCharWidth) >= WrapX then
+ begin
+ // reached right hand side before finding end of word
+ if LineWordsCompleted > 0 then
+ // always wrap after at least one word displayed
+ DoWrap := true
+ else if not FRichTextSettings.AtLeastOneWordBeforeWrap then
+ // only wrap during the first word, if the "at least 1 word" flag is not set.
+ DoWrap := true;
+ end;
+
+ if DoWrap then
+ begin
+ if LineWordsCompleted = 0 then
+ begin
+ // the first word did not fit on the line. so draw
+ // as much as will fit
+ if WordX = 0 then
+ begin
+ // even the first char doesn't fit,
+ // but draw it anyway (otherwise, infinite loop)
+ NextElement := ExtractNextTextElement( NextP, NextP2 );
+ if NextElement.ElementType <> teLineBreak then
+ // there is still more on the line...
+ CurrentLine.Wrapped := true
+ else
+ // the line ends after this one char or image, we can skip the line end
+ NextP := NextP2;
+
+ if CurrentElement.ElementType = teImage then
+ begin
+ // the only thing on the line is the image. so check height
+ if Bitmap <> nil then
+ if BitmapHeight > CurrentLine.Height then
+ CurrentLine.Height := BitmapHeight;
+ end;
+
+ DoLine( NextP, NextP, WordStartX + WordX + CurrentCharWidth );
+ WordStart := NextP;
+ WordX := 0;
+ end
+ else
+ begin
+ CurrentLine.Wrapped := true;
+ // at least 1 char fits
+ // so draw up to, but not including this char
+ DoLine( P,
+ P,
+ WordStartX + WordX );
+ WordStart := P;
+ WordX := CurrentCharWidth;
+ end;
+ end
+ else
+ begin
+ // Normal wrap; at least one word fitted on the line
+ CurrentLine.Wrapped := true;
+
+ // take the width of the last space of the
+ // previous word off the line width
+ DoLine( WordStart, // current line ends at start of this word
+ WordStart, // next line starts at start of this word
+ WordStartX - FFontManager.CharWidth( ' ' ) );
+ if CurrentElement.ElementType = teImage then
+ if Bitmap <> nil then
+ if BitmapHeight > CurrentLine.Height then
+ CurrentLine.Height := BitmapHeight;
+
+ // do NOT reset WordX to zero; as we are continuing
+ // from partway thru the word on the next line.
+ inc( WordX, CurrentCharWidth );
+ end;
+ WordStarted := true; // by definition, for wrapping
+ end
+ else
+ begin
+ // Character fits.
+ inc( WordX, CurrentCharWidth );
+ DisplayedCharsSinceFontChange := true;
+ if CurrentElement.ElementType = teImage then
+ if Bitmap <> nil then
+ if BitmapHeight > CurrentLine.Height then
+ CurrentLine.Height := BitmapHeight;
+ end;
+
+ P := NextP;
+ until false; // loop is exited by finding end of text
+
+ inc( FHeight, FRichTextSettings.Margins.Bottom );
+End;
+
+Function TRichTextLayout.GetStartX( Style: TTextDrawStyle;
+ Line: TLayoutLine ): longint;
+var
+ SpaceOnLine: longint;
+begin
+ case Style.Alignment of
+ taLeft:
+ Result := Style.LeftMargin * FontWidthPrecisionFactor;
+
+ taRight:
+ Result := Style.LeftMargin * FontWidthPrecisionFactor
+ + FLayoutWidth
+ - Style.RightMargin * FontWidthPrecisionFactor
+ - Line.Width;
+
+ taCenter:
+ begin
+ // |<------layout width------------------>|
+ // | |
+ // |<-lm->[aaaaaaaaaaaaaaa]<-space-><-rm->|
+ // |<-----line width------> |
+ // space = layoutw-rm-linew
+ SpaceOnLine := FLayoutWidth
+ - Style.RightMargin * FontWidthPrecisionFactor
+ - Line.Width; // Note: line width includes left margin
+ Result := Style.LeftMargin * FontWidthPrecisionFactor
+ + SpaceOnLine div 2;
+ end;
+ end;
+end;
+
+Procedure TRichTextLayout.GetOffsetFromX( const XToFind: longint;
+ const LineIndex: longint;
+ Var Offset: longint;
+ Var Link: string );
+Var
+ X: longint;
+ P: PChar;
+ NextP: PChar;
+ EndP: PChar;
+ Element: TTextElement;
+ CurrentLink: string;
+ Line: TLayoutLine;
+ Style: TTextDrawStyle;
+ NewMarginX: longint;
+ StartedDrawing: boolean;
+begin
+ Line := TLayoutLine(FLines[ LineIndex ]);
+ P := Line.Text;
+ EndP := Line.Text + Line.Length;
+
+ Style := Line.Style;
+ FFontManager.SetFont( Style.Font );
+
+ StartedDrawing := false;
+
+ Link := '';
+ if Line.LinkIndex <> -1 then
+ CurrentLink := FLinks[ Line.LinkIndex ]
+ else
+ CurrentLink := '';
+
+ while P < EndP do
+ begin
+ Element := ExtractNextTextElement( P, NextP );
+
+ case Element.ElementType of
+ teWordBreak,
+ teText,
+ teImage:
+ begin
+ if not StartedDrawing then
+ begin
+ // we haven't yet started drawing:
+ // so work out alignment
+ X := GetStartX( Style, Line );
+
+ if X div FontWidthPrecisionFactor
+ > XToFind then
+ begin
+ // found before the start of the line
+ // don't set link
+ Offset := 0;
+ exit;
+ end;
+
+ StartedDrawing := true;
+
+ end;
+
+ // Now find out how wide the thing is
+ inc( X, GetElementWidth( Element ) );
+
+ if X div FontWidthPrecisionFactor
+ > XToFind then
+ begin
+ // found
+ Offset := PCharDiff( P, Line.Text );
+ Link := CurrentLink;
+ exit;
+ end;
+
+ end;
+
+ teStyle:
+ case Element.Tag.TagType of
+ ttBeginLink:
+ CurrentLink := Element.Tag.Arguments;
+ ttEndLink:
+ CurrentLink := '';
+ else
+ begin
+ if ( Element.Tag.TagType = ttItalicOff )
+ and ( faItalic in Style.Font.Attributes )
+ and ( not FFontManager.IsFixed ) then
+ // end of italic; add a space
+ inc( X, FFontManager.CharWidth( ' ' ) );
+
+ PerformStyleTag( Element.Tag,
+ Style,
+ X );
+ NewMarginX := Style.LeftMargin * FontWidthPrecisionFactor;
+ if NewMarginX > X then
+ begin
+ //skip across...
+ X := NewMarginX;
+ end;
+ end;
+ end;
+ end;
+
+ P := NextP;
+ end;
+ Offset := Line.Length;
+end;
+
+Procedure TRichTextLayout.GetXFromOffset( const Offset: longint;
+ const LineIndex: longint;
+ Var X: longint );
+Var
+ P: PChar;
+ NextP: PChar;
+ EndP: PChar;
+ Element: TTextElement;
+ StartedDrawing: boolean;
+ Line: TLayoutLine;
+ Style: TTextDrawStyle;
+ NewMarginX: longint;
+begin
+ Line := TLayoutLine(FLines[ LineIndex ]);
+ P := Line.Text;
+ EndP := Line.Text + Line.Length;
+
+ Style := Line.Style;
+ FFontManager.SetFont( Style.Font );
+
+ StartedDrawing := false;
+
+ while P < EndP do
+ begin
+ Element := ExtractNextTextElement( P, NextP );
+
+ case Element.ElementType of
+ teWordBreak,
+ teText,
+ teImage:
+ begin
+ if not StartedDrawing then
+ begin
+ // we haven't yet started drawing:
+ // so work out alignment
+ X := GetStartX( Style, Line );
+ StartedDrawing := true;
+ end;
+
+ if GetCharIndex( P ) - GetCharIndex( Line.Text ) >= Offset then
+ begin
+ X := X div FontWidthPrecisionFactor;
+ // found
+ exit;
+ end;
+
+ // Now find out how wide the thing is
+ inc( X, GetElementWidth( Element ) );
+
+ end;
+
+ teStyle:
+ begin
+ if ( Element.Tag.TagType = ttItalicOff )
+ and ( faItalic in Style.Font.Attributes )
+ and ( not FFontManager.IsFixed ) then
+ // end of italic; add a space
+ inc( X, FFontManager.CharWidth( ' ' ) );
+
+ PerformStyleTag( Element.Tag,
+ Style,
+ X );
+
+ NewMarginX := Style.LeftMargin * FontWidthPrecisionFactor;
+ if NewMarginX > X then
+ begin
+ //skip across...
+ X := NewMarginX;
+ end;
+ end;
+ end;
+
+ P := NextP;
+ end;
+ // went thru the whole line without finding the point,
+ if not StartedDrawing then
+ X := GetStartX( Style, Line );
+
+ X := X div FontWidthPrecisionFactor;
+end;
+
+function TRichTextLayout.GetLineFromPosition( YToFind: longint;
+ Var LineIndex: longint;
+ Var Remainder: longint ): TTextPosition;
+var
+ Y: longint;
+ LineHeight: longint;
+begin
+ LineIndex := 0;
+ Remainder := 0;
+
+ Y := FRichTextSettings.Margins.Top;
+
+ if YToFind < Y then
+ begin
+ Result := tpAboveText;
+ exit;
+ end;
+
+ while LineIndex < FNumLines do
+ begin
+ LineHeight := TLayoutLine(FLines[ LineIndex ]).Height;
+ if ( YToFind >= Y )
+ and ( YToFind < Y + LineHeight ) then
+ begin
+ // YToFind is within the line
+ Result := tpWithinText;
+ Remainder := YToFind - Y;
+ exit;
+ end;
+
+ inc( Y, TLayoutLine(FLines[ LineIndex ]).Height );
+ inc( LineIndex );
+ end;
+
+ LineIndex := FNumLines - 1;
+ Remainder := TLayoutLine(FLines[ LineIndex ]).Height;
+
+ Result := tpBelowText;
+end;
+
+function TRichTextLayout.FindPoint( XToFind, YToFind: longint;
+ Var LineIndex: longint;
+ Var Offset: longint;
+ Var Link: string ): TTextPosition;
+var
+ Remainder: longint;
+begin
+ Link := '';
+ Result := GetLineFromPosition( YToFind,
+ LineIndex,
+ Remainder );
+ case Result of
+ tpAboveText:
+ begin
+ Offset := 0;
+ exit;
+ end;
+
+ tpBelowText:
+ begin
+ Offset := TLayoutLine(FLines[ LineIndex ]).Length;
+ exit;
+ end;
+ end;
+
+ // found the line
+ GetOffsetFromX( XToFind,
+ LineIndex,
+ Offset,
+ Link );
+end;
+
+function TRichTextLayout.GetLineFromCharIndex( Index: longint ): longint;
+var
+ LineCharIndex: longint;
+ LineLength: longint;
+begin
+ Result := 0;
+ if Index <= 0 then
+ exit;
+
+ while Result < FNumLines do
+ begin
+ LineCharIndex := GetCharIndex( TLayoutLine(FLines[ Result ]).Text );
+ LineLength := TLayoutLine(FLines[ Result ]).Length;
+ if LineCharIndex + LineLength
+ > Index then
+ begin
+ // found
+ exit;
+ end;
+ inc( Result );
+ end;
+ Result := FNumLines - 1;
+end;
+
+function TRichTextLayout.GetOffsetFromCharIndex( Index: longint;
+ Line: longint ): longint;
+begin
+ Result := Index - GetCharIndex( TLayoutLine( FLines[ Line ] ).Text );
+end;
+
+function TRichTextLayout.GetElementWidth( Element: TTextElement ): longint;
+var
+ Bitmap: TfpgImage;
+ BitmapIndex: longint;
+begin
+ // Now find out how wide the thing is
+ case Element.ElementType of
+ teImage:
+ begin
+ try
+ BitmapIndex := StrToInt( Element.Tag.Arguments );
+ except
+ BitmapIndex := -1;
+ end;
+ if IsValidBitmapIndex( BitmapIndex ) then
+ begin
+ Bitmap := FImages.Item[BitmapIndex].Image;
+ Result := Trunc(Bitmap.Width
+ * FontWidthPrecisionFactor
+ * FHorizontalImageScale);
+ end;
+ end;
+
+ teText, teWordBreak:
+ Result := FFontManager.CharWidth( Element.Character );
+
+ else
+ Assert( False ); // should never be trying to find the width of a style, etc
+
+ end;
+end;
+
+Function TRichTextLayout.GetCharIndex( P: PChar ): longint;
+begin
+ Result := PCharDiff( P, FText );
+end;
+
+function TRichTextLayout.GetLinePosition( Line: longint ): longint;
+begin
+ Result := FRichTextSettings.Margins.Top;
+ dec( line );
+ while line >= 0 do
+ begin
+ inc( Result,
+ TLayoutLine(Flines[ Line ]).Height );
+ dec( line );
+ end;
+end;
+
+function TRichTextLayout.LinkFromIndex( const CharIndexToFind: longint): string;
+Var
+ P: PChar;
+ NextP: PChar;
+ EndP: PChar;
+ Element: TTextElement;
+ LineIndex: longint;
+ Line: TLayoutLine;
+begin
+ if FNumLines = 0 then
+ begin
+ Result := '';
+ exit;
+ end;
+
+ LineIndex := GetLineFromCharIndex( CharIndexToFind );
+
+ Line := TLayoutLine(FLines[ LineIndex ]);
+ P := Line.Text;
+ EndP := Line.Text + Line.Length;
+
+ if Line.LinkIndex <> -1 then
+ Result := FLinks[ Line.LinkIndex ]
+ else
+ Result := '';
+
+ while P < EndP do
+ begin
+ if GetCharIndex( P ) >= CharIndexToFind then
+ exit;
+
+ Element := ExtractNextTextElement( P, NextP );
+
+ case Element.ElementType of
+ teStyle:
+ case Element.Tag.TagType of
+ ttBeginLink:
+ Result := Element.Tag.Arguments;
+ ttEndLink:
+ Result := '';
+ end;
+ end;
+
+ P := NextP;
+ end;
+end;
+
+Initialization
+End.
+
diff --git a/components/richtext/RichTextPrintUnit.pas b/components/richtext/RichTextPrintUnit.pas
new file mode 100755
index 00000000..01746c68
--- /dev/null
+++ b/components/richtext/RichTextPrintUnit.pas
@@ -0,0 +1,75 @@
+Unit RichTextPrintUnit;
+
+Interface
+
+uses
+ Graphics,
+ RichTextStyleUnit;
+
+// Prints the specified rich text, starting at page position PageY.
+// Starts new pages as needed; when done, PageY is the final position used
+// on the final page.
+Procedure PrintRichText( Text: PChar;
+ Images: TImageList;
+ Settings: TRichTextSettings;
+ var PageY: longint );
+
+Implementation
+
+uses
+ Classes,
+ Printers,
+ CanvasFontManager,
+ RichTextLayoutUnit, RichTextDisplayUnit, Forms
+ ;
+
+Procedure PrintRichText( Text: PChar;
+ Images: TImageList;
+ Settings: TRichTextSettings;
+ var PageY: longint );
+var
+ Layout: TRichTextLayout;
+ FontManager: TCanvasFontManager;
+ LineIndex: longint;
+ Y: longint;
+ FinishLine: longint;
+ FinishY: longint;
+Begin
+ FontManager := TCanvasFontManager.Create( Printer.Canvas,
+ false // don't allow bitmap fonts
+ );
+
+ Layout := TRichTextLayout.Create( Text,
+ Images,
+ Settings,
+ FontManager,
+ Printer.PageWidth );
+
+ LineIndex := 0;
+ Y := PageY;
+ repeat
+ PrintRichTextLayout( FontManager,
+ Layout,
+ LineIndex,
+ FinishLine,
+ Y,
+ FinishY );
+ LineIndex := FinishLine;
+ Y := FinishY;
+
+ if LineIndex < Layout.FNumLines then
+ begin
+ // didn't all fit on page, so new page
+ Printer.NewPage;
+ Y := Printer.PageHeight - 1;
+ end;
+
+ until LineIndex >= Layout.FNumLines;
+
+ Layout.Destroy;
+ FontManager.Destroy;
+ PageY := Y;
+end;
+
+Initialization
+End.
diff --git a/components/richtext/RichTextStyleUnit.pas b/components/richtext/RichTextStyleUnit.pas
new file mode 100755
index 00000000..44044cab
--- /dev/null
+++ b/components/richtext/RichTextStyleUnit.pas
@@ -0,0 +1,622 @@
+Unit RichTextStyleUnit;
+
+{$mode objfpc}{$H+}
+
+Interface
+
+uses
+ Classes, fpg_base, fpg_main, CanvasFontManager, RichTextDocumentUnit;
+
+type
+ TTextDrawStyle = record
+ Font: TFontSpec;
+ Color: TfpgColor;
+ BackgroundColor: TfpgColor;
+ Alignment: TTextAlignment;
+ Wrap: boolean;
+ LeftMargin: longint;
+ RightMargin: longint;
+ end;
+
+ TMarginSizeStyle = ( msAverageCharWidth, msMaximumCharWidth, msSpecifiedChar );
+
+ TRichTextSettings = class( TfpgComponent )
+ protected
+ FHeading1Font: TfpgFont;
+ FHeading2Font: TfpgFont;
+ FHeading3Font: TfpgFont;
+ FFixedFont: TfpgFont;
+ FNormalFont: TfpgFont;
+ FDefaultBackgroundColor: TfpgColor;
+ FDefaultColor: TfpgColor;
+ FDefaultAlignment: TTextAlignment;
+ FDefaultWrap: boolean;
+ FAtLeastOneWordBeforeWrap: boolean;
+ FMarginSizeStyle: TMarginSizeStyle;
+ FMarginChar: longint;
+ FOnChange: TNotifyEvent;
+ FMargins: TRect;
+ FUpdateCount: longint;
+ FChangesPending: boolean;
+ Procedure Change;
+ Procedure SetNormalFont( NewFont: TfpgFont );
+ Procedure SetFixedFont( NewFont: TfpgFont );
+ Procedure SetHeading1Font( NewFont: TfpgFont );
+ Procedure SetHeading2Font( NewFont: TfpgFont );
+ Procedure SetHeading3Font( NewFont: TfpgFont );
+ Procedure SetDefaultColor( NewColor: TfpgColor );
+ Procedure SetDefaultBackgroundColor( NewColor: TfpgColor );
+ Procedure SetDefaultAlignment( Alignment: TTextAlignment );
+ Procedure SetDefaultWrap( Wrap: boolean );
+ Procedure SetAtLeastOneWordBeforeWrap( NewValue: boolean );
+ Procedure SetMarginSizeStyle( NewValue: TMarginSizeStyle );
+ Procedure SetMarginChar( NewValue: longint );
+ Procedure SetMargins( const NewMargins: TRect );
+ function GetMargin_Left: longint;
+ Procedure SetMargin_Left( NewValue: longint );
+ function GetMargin_Bottom: longint;
+ Procedure SetMargin_Bottom( NewValue: longint );
+ function GetMargin_Right: longint;
+ Procedure SetMargin_Right( NewValue: longint );
+ function GetMargin_Top: longint;
+ Procedure SetMargin_Top( NewValue: longint );
+ Procedure SetupComponent;
+ constructor Create(AOwner: TComponent); override;
+ destructor Destroy; override;
+ Procedure AssignFont( Var Font: TfpgFont;
+ NewFont: TfpgFont );
+
+ // Hide properties...
+ property Name;
+
+ public
+ property OnChange: TNotifyEvent read FOnChange write FOnChange;
+
+ procedure BeginUpdate;
+ procedure EndUpdate;
+
+ // Stream in/out
+ //Procedure ReadSCUResource( Const ResName: TResourceName;
+ // Var Data;
+ // DataLen: LongInt ); override;
+ //Function WriteSCUResource( Stream: TResourceStream ): boolean; override;
+
+ property Margins: TRect read FMargins write SetMargins;
+
+ property Heading1Font: TfpgFont read FHeading1Font write SetHeading1Font;
+ property Heading2Font: TfpgFont read FHeading2Font write SetHeading2Font;
+ property Heading3Font: TfpgFont read FHeading3Font write SetHeading3Font;
+ property FixedFont: TfpgFont read FFixedFont write SetFixedFont;
+ property NormalFont: TfpgFont read FNormalFont write SetNormalFont;
+
+ published
+
+ property DefaultBackgroundColor: TfpgColor read FDefaultBackgroundColor write SetDefaultBackgroundColor;
+ property DefaultColor: TfpgColor read FDefaultColor write SetDefaultColor;
+
+ property DefaultAlignment: TTextAlignment read FDefaultAlignment write SetDefaultAlignment;
+ property DefaultWrap: boolean read FDefaultWrap write SetDefaultWrap default True;
+ property AtLeastOneWordBeforeWrap: boolean read FAtLeastOneWordBeforeWrap write SetAtLeastOneWordBeforeWrap;
+
+ property MarginSizeStyle: TMarginSizeStyle read FMarginSizeStyle write SeTMarginSizeStyle;
+ property MarginChar: longint read FMarginChar write SetMarginChar;
+
+ // margins are exposed as individual properties here
+ // since the Sibyl IDE cannot cope with editing a record property
+ // within a class property (as in RichTextView)
+ property Margin_Left: longint read GetMargin_Left write SetMargin_Left;
+ property Margin_Bottom: longint read GetMargin_Bottom write SetMargin_Bottom;
+ property Margin_Right: longint read GetMargin_Right write SetMargin_Right;
+ property Margin_Top: longint read GetMargin_Top write SetMargin_Top;
+ end;
+
+// pRichTextSettings = ^TRichTextSettings;
+ Procedure ApplyStyle( const Style: TTextDrawStyle;
+ FontManager: TCanvasFontManager );
+
+ Procedure ApplyStyleTag( const Tag: TTag;
+ Var Style: TTextDrawStyle;
+ FontManager: TCanvasFontManager;
+ const Settings: TRichTextSettings;
+ const X: longint );
+
+ function GetDefaultStyle( const Settings: TRichTextSettings ): TTextDrawStyle;
+
+//Exports
+// TRichTextSettings,'User','';
+
+Implementation
+
+uses
+ SysUtils,
+ ACLStringUtility
+ ,nvUtilities
+// , ACLProfile
+ ;
+
+Procedure ApplyStyle( const Style: TTextDrawStyle; FontManager: TCanvasFontManager );
+begin
+ FontManager.SetFont( Style.Font );
+ FontManager.Canvas.TextColor := Style.Color;
+end;
+
+Procedure ApplyStyleTag( Const Tag: TTag;
+ var Style: TTextDrawStyle;
+ FontManager: TCanvasFontManager;
+ const Settings: TRichTextSettings;
+ const X: longint );
+var
+ MarginParam1: string;
+ MarginParam2: string;
+ NewMargin: longint;
+ FontFaceName: string;
+ FontSizeString: string;
+ NewStyle: TTextDrawStyle;
+ ParseIndex: longint;
+ XSizeStr: string;
+ YSizeStr: string;
+
+ MarginSize: longint;
+ ParsePoint: longint;
+begin
+ case Tag.TagType of
+ ttBold:
+ Include( Style.Font.Attributes, faBold );
+ ttBoldOff:
+ Exclude( Style.Font.Attributes, faBold );
+ ttItalic:
+ Include( Style.Font.Attributes, faItalic );
+ ttItalicOff:
+ Exclude( Style.Font.Attributes, faItalic );
+ ttUnderline:
+ Include( Style.Font.Attributes, faUnderscore );
+ ttUnderlineOff:
+ Exclude( Style.Font.Attributes, faUnderscore );
+
+ ttFixedWidthOn:
+ FPGuiFontToFontSpec( Settings.FFixedFont, Style.Font );
+ ttFixedWidthOff:
+ FPGuiFontToFontSpec( Settings.FNormalFont, Style.Font );
+
+ ttHeading1:
+ FPGuiFontToFontSpec( Settings.FHeading1Font, Style.Font );
+ ttHeading2:
+ FPGuiFontToFontSpec( Settings.FHeading2Font, Style.Font );
+ ttHeading3:
+ FPGuiFontToFontSpec( Settings.FHeading3Font, Style.Font );
+ ttHeadingOff:
+ FPGuiFontToFontSpec( Settings.FNormalFont, Style.Font );
+
+ ttFont:
+ begin
+ ParseIndex := 1;
+ GetNextQuotedValue( Tag.Arguments, ParseIndex, FontFaceName, DoubleQuote );
+ GetNextQuotedValue( Tag.Arguments, ParseIndex, FontSizeString, DoubleQuote );
+ NewStyle := Style;
+ try
+ NewStyle.Font.FaceName := FontFaceName;
+
+ if Pos( 'x', FontSizeString ) > 0 then
+ begin
+ XSizeStr := ExtractNextValue( FontSizeString, 'x' );
+ YSizeStr := FontSizeString;
+ NewStyle.Font.XSize := StrToInt( XSizeStr );
+ NewStyle.Font.YSize := StrToInt( YSizeStr );
+ NewStyle.Font.PointSize := 0;
+ end
+ else
+ begin
+ NewStyle.Font.PointSize := StrToInt( FontSizeString );
+ end;
+
+ if ( NewStyle.Font.FaceName <> '' )
+ and ( NewStyle.Font.PointSize >= 1 ) then
+ begin
+ Style := NewStyle;
+ end;
+
+ except
+ end;
+ end;
+
+ ttFontOff:
+ // restore default
+ FPGuiFontToFontSpec( Settings.FNormalFont, Style.Font );
+
+ ttColor:
+ GetTagColor( Tag.Arguments, Style.Color );
+ ttColorOff:
+ Style.Color := Settings.FDefaultColor;
+ ttBackgroundColor:
+ GetTagColor( Tag.Arguments, Style.BackgroundColor );
+ ttBackgroundColorOff:
+ Style.BackgroundColor := Settings.FDefaultBackgroundColor;
+
+ ttRed:
+ Style.Color := clRed;
+ ttBlue:
+ Style.Color := clBlue;
+ ttGreen:
+ Style.Color := clGreen;
+ ttBlack:
+ Style.Color := clBlack;
+
+ ttAlign:
+ Style.Alignment := GetTagTextAlignment( Tag.Arguments,
+ Settings.FDefaultAlignment );
+
+ ttWrap:
+ Style.Wrap := GetTagTextWrap( Tag.Arguments );
+
+ ttSetLeftMargin,
+ ttSetRightMargin:
+ begin
+ ParsePoint := 1;
+ GetNextValue( Tag.Arguments, ParsePoint, MarginParam1, ' ' );
+ if ( Tag.TagType = ttSetLeftMargin )
+ and ( MarginParam1 = 'here' ) then
+ begin
+ Style.LeftMargin := X div FontWidthPrecisionFactor;
+ end
+ else
+ begin
+ try
+ MarginSize := StrToInt( MarginParam1 );
+ GetNextValue( Tag.Arguments, ParsePoint, MarginParam2, ' ' );
+ if MarginParam2 = 'pixels' then
+ NewMargin := MarginSize
+
+ else if MarginParam2 = 'deffont' then
+ NewMargin := MarginSize * Settings.NormalFont.TextWidth('w') // .Width
+
+ else
+ begin
+ case Settings.MarginSizeStyle of
+ msAverageCharWidth:
+ NewMargin := MarginSize * FontManager.AverageCharWidth;
+ msMaximumCharWidth:
+ NewMargin := MarginSize * FontManager.MaximumCharWidth;
+ msSpecifiedChar:
+ NewMargin := MarginSize
+ * FontManager.CharWidth( Chr( Settings.MarginChar ) )
+ div FontWidthPrecisionFactor;
+ end;
+ end;
+ except
+ NewMargin := 0;
+ end;
+
+ if Tag.TagType = ttSetLeftMargin then
+ Style.LeftMargin := Settings.Margins.Left
+ + NewMargin
+ else
+ Style.RightMargin := Settings.Margins.Right
+ + NewMargin;
+ end;
+ end;
+
+ end;
+
+ ApplyStyle( Style, FontManager );
+
+end;
+
+function GetDefaultStyle( const Settings: TRichTextSettings ): TTextDrawStyle;
+begin
+ FillChar(Result, SizeOf(TTextDrawStyle), 0);
+ FPGuiFontToFontSpec( Settings.FNormalFont, Result.Font );
+ Result.Alignment := Settings.FDefaultAlignment;
+ Result.Wrap := Settings.FDefaultWrap;
+ Result.Color := Settings.FDefaultColor;
+ Result.BackgroundColor := Settings.FDefaultBackgroundColor;
+ Result.LeftMargin := Settings.Margins.Left;
+ Result.RightMargin := Settings.Margins.Right;
+end;
+
+
+Procedure TRichTextSettings.SetupComponent;
+begin
+ Name := 'RichTextSettings';
+
+ FNormalFont := fpgGetFont('Arial-10');
+ FFixedFont := fpgGetFont('Courier New-10');
+ FHeading1Font := fpgGetFont('Arial-20');
+ FHeading2Font := fpgGetFont('Arial-14');
+ FHeading3Font := fpgGetFont('Arial-10:bold');
+
+ FDefaultColor := clBlack;
+ FDefaultBackgroundColor := clWhite;
+
+ FDefaultAlignment := taLeft;
+ FDefaultWrap := true;
+ FAtLeastOneWordBeforeWrap := false;
+
+ FMarginSizeStyle := msMaximumCharWidth;
+ FMarginChar := Ord( ' ' );
+
+ FMargins.Left := 0;
+ FMargins.Right := 0;
+ FMargins.Top := 0;
+ FMargins.Bottom := 0;
+
+ FUpdateCount := 0;
+ FChangesPending := false;
+end;
+
+constructor TRichTextSettings.Create(AOwner: TComponent);
+begin
+ inherited Create(AOwner);
+ SetupComponent;
+end;
+
+destructor TRichTextSettings.Destroy;
+begin
+ FNormalFont.Free;
+ FFixedFont.Free;
+ FHeading1Font.Free;
+ FHeading2Font.Free;
+ FHeading3Font.Free;
+ Inherited Destroy;
+end;
+
+// Font read/write from SCU. I have NO IDEA why I have to do this manually. But
+// this way works and everything else I tried doesn't
+//Procedure TRichTextSettings.ReadSCUResource( Const ResName: TResourceName;
+// Var Data;
+// DataLen: LongInt );
+//Begin
+// If ResName = 'Heading1Font' Then
+// Begin
+// If DataLen <> 0 Then
+// FHeading1Font := ReadSCUFont( Data, DataLen );
+// End
+// Else If ResName = 'Heading2Font' Then
+// Begin
+// If DataLen <> 0 Then
+// FHeading2Font := ReadSCUFont( Data, DataLen );
+// End
+// Else If ResName = 'Heading3Font' Then
+// Begin
+// If DataLen <> 0 Then
+// FHeading3Font := ReadSCUFont( Data, DataLen );
+// End
+// Else If ResName = 'FixedFont' Then
+// Begin
+// If DataLen <> 0 Then
+// FFixedFont := ReadSCUFont( Data, DataLen );
+// End
+// Else if ResName = 'NormalFont' then
+// Begin
+// If DataLen <> 0 Then
+// FNormalFont := ReadSCUFont( Data, DataLen );
+// End
+// Else
+// Inherited ReadSCUResource( ResName, Data, DataLen );
+//End;
+
+//Function TRichTextSettings.WriteSCUResource( Stream: TResourceStream ): boolean;
+//begin
+// Result := Inherited WriteSCUResource( Stream );
+// If Not Result Then
+// Exit;
+//
+// If FHeading1Font <> Nil then
+// Result := FHeading1Font.WriteSCUResourceName( Stream, 'Heading1Font' );
+// If FHeading2Font <> Nil then
+// Result := FHeading2Font.WriteSCUResourceName( Stream, 'Heading2Font' );
+// If FHeading3Font <> Nil then
+// Result := FHeading3Font.WriteSCUResourceName( Stream, 'Heading3Font' );
+// If FFixedFont <> Nil then
+// Result := FFixedFont.WriteSCUResourceName( Stream, 'FixedFont' );
+// If FNormalFont <> Nil then
+// Result := FNormalFont.WriteSCUResourceName( Stream, 'NormalFont' );
+//
+//end;
+
+Procedure TRichTextSettings.Change;
+begin
+ if FUpdateCount > 0 then
+ begin
+ FChangesPending := true;
+ exit;
+ end;
+
+ if FOnChange <> nil then
+ FOnChange( self );
+end;
+
+Procedure TRichTextSettings.SetDefaultAlignment( Alignment: TTextAlignment );
+begin
+ if Alignment = FDefaultAlignment then
+ exit; // no change
+
+ FDefaultAlignment := Alignment;
+ Change;
+end;
+
+Procedure TRichTextSettings.SetDefaultWrap( Wrap: boolean );
+begin
+ if Wrap = FDefaultWrap then
+ exit; // no change
+
+ FDefaultWrap := Wrap;
+ Change;
+end;
+
+Procedure TRichTextSettings.SetAtLeastOneWordBeforeWrap( NewValue: boolean );
+begin
+ if NewValue = FAtLeastOneWordBeforeWrap then
+ exit; // no change
+
+ FAtLeastOneWordBeforeWrap := NewValue;
+ Change;
+end;
+
+Procedure TRichTextSettings.SetMarginChar( NewValue: longint );
+begin
+ if NewValue = FMarginChar then
+ exit; // no change
+
+ FMarginChar := NewValue;
+
+ if FMarginSizeStyle <> msSpecifiedChar then
+ // doesn't matter, will be ignored
+ exit;
+ Change;
+end;
+
+Procedure TRichTextSettings.SetMarginSizeStyle( NewValue: TMarginSizeStyle );
+begin
+ if NewValue = FMarginSizeStyle then
+ exit; // no change
+
+ FMarginSizeStyle := NewValue;
+ Change;
+end;
+
+Function FontSame( FontA: TfpgFont; FontB: TfpgFont ): boolean;
+begin
+ if ( FontA = nil )
+ or ( FontB = nil ) then
+ begin
+ Result := FontA = FontB;
+ exit;
+ end;
+
+ Result := FontA.FontDesc = FontB.FontDesc;
+end;
+
+Procedure TRichTextSettings.AssignFont( Var Font: TfpgFont;
+ NewFont: TfpgFont );
+begin
+ If NewFont = Nil Then
+ NewFont := fpgApplication.DefaultFont;
+
+ if FontSame( NewFont, Font ) then
+ exit; // no change
+
+ Font.Free;
+ Font := NewFont;
+// Font.Free;
+
+ Change;
+End;
+
+Procedure TRichTextSettings.SetHeading1Font( NewFont: TfpgFont );
+begin
+ ProfileEvent( 'TRichTextSettings.SetHeading1Font' );
+ AssignFont( FHeading1Font, NewFont );
+
+ if FHeading1FOnt = nil then
+ ProfileEvent( ' Set to nil' );
+
+end;
+
+Procedure TRichTextSettings.SetHeading2Font( NewFont: TfpgFont );
+begin
+ AssignFont( FHeading2Font, NewFont );
+End;
+
+Procedure TRichTextSettings.SetHeading3Font( NewFont: TfpgFont );
+begin
+ AssignFont( FHeading3Font, NewFont );
+End;
+
+Procedure TRichTextSettings.SetFixedFont( NewFont: TfpgFont );
+begin
+ AssignFont( FFixedFont, NewFont );
+end;
+
+Procedure TRichTextSettings.SetNormalFont( NewFont: TfpgFont );
+begin
+ AssignFont( FNormalFont, NewFont );
+end;
+
+Procedure TRichTextSettings.SetMargins( const NewMargins: TRect );
+begin
+ if NewMargins = FMargins then
+ exit; // no change
+ FMargins := NewMargins;
+ Change;
+end;
+
+function TRichTextSettings.GetMargin_Left: longint;
+begin
+ Result := FMargins.Left;
+end;
+
+Procedure TRichTextSettings.SetMargin_Left( NewValue: longint );
+begin
+ FMargins.Left := NewValue;
+end;
+
+function TRichTextSettings.GetMargin_Bottom: longint;
+begin
+ Result := FMargins.Bottom;
+end;
+
+Procedure TRichTextSettings.SetMargin_Bottom( NewValue: longint );
+begin
+ FMargins.Bottom := NewValue;
+end;
+
+function TRichTextSettings.GetMargin_Right: longint;
+begin
+ Result := FMargins.Right;
+end;
+
+Procedure TRichTextSettings.SetMargin_Right( NewValue: longint );
+begin
+ FMargins.Right := NewValue;
+end;
+
+function TRichTextSettings.GetMargin_Top: longint;
+begin
+ Result := FMargins.Top;
+end;
+
+Procedure TRichTextSettings.SetMargin_Top( NewValue: longint );
+begin
+ FMargins.Top := NewValue;
+end;
+
+Procedure TRichTextSettings.SetDefaultColor( NewColor: TfpgColor );
+begin
+ if NewColor = FDefaultColor then
+ exit;
+ FDefaultColor := NewColor;
+ Change;
+end;
+
+Procedure TRichTextSettings.SetDefaultBackgroundColor( NewColor: TfpgColor );
+begin
+ if NewColor = FDefaultBackgroundColor then
+ exit;
+ FDefaultBackgroundColor := NewColor;
+ Change;
+end;
+
+procedure TRichTextSettings.BeginUpdate;
+begin
+ inc( FUpdateCount );
+end;
+
+procedure TRichTextSettings.EndUpdate;
+begin
+ if FUpdateCount = 0 then
+ exit;
+
+ dec( FUpdateCount );
+ if FUpdateCount = 0 then
+ begin
+ if FChangesPending then
+ begin
+ Change;
+ FChangesPending := false;
+ end;
+ end;
+end;
+
+Initialization
+ RegisterClasses( [ TRichTextSettings ] );
+End.
diff --git a/components/richtext/RichTextView.pas b/components/richtext/RichTextView.pas
new file mode 100755
index 00000000..f7a3b724
--- /dev/null
+++ b/components/richtext/RichTextView.pas
@@ -0,0 +1,2785 @@
+Unit RichTextView;
+
+{$mode objfpc}{$H+}
+
+Interface
+
+Uses
+ Classes,
+ fpg_base,
+ fpg_main,
+ fpg_widget,
+ fpg_scrollbar,
+ fpg_menu,
+ fpg_imagelist,
+ RichTextStyleUnit,
+ RichTextLayoutUnit,
+// RichTextDocumentUnit,
+ CanvasFontManager;
+
+{
+Remaining keyboard support
+- cursor down to go to end of line (this is tricky)
+ I don't understand what I mean here!
+- If scrolllock is on, then scroll the screen, not move cursor.
+ Really? So few things obey it...
+}
+
+const
+ // for dragtext support, primarily.
+ RT_QUERYTEXT = FPGM_USER + 500;
+ // Param1: pointer to buffer (may be nil)
+ // Param2: buffer size (-1 to ignore)
+ // Returns: number of bytes copied
+
+ RT_QUERYSELTEXT = FPGM_USER + 501;
+ // Param1: pointer to buffer (may be nil)
+ // Param2: buffer size (-1 to ignore)
+ // Returns: number of bytes copied
+
+Type
+ TFindOrigin = ( foFromStart, foFromCurrent );
+
+ TScrollingDirection = ( sdUp, sdDown );
+
+Type
+
+ TRichTextView = class;
+
+ TLinkEvent = procedure( Sender: TRichTextView; Link: string ) of object;
+
+
+ TRichTextView = Class( TfpgWidget )
+ private
+ FPopupMenu: TfpgPopupMenu;
+ procedure FVScrollbarScroll(Sender: TObject; position: integer);
+ protected
+ FFontManager: TCanvasFontManager;
+ FRichTextSettings: TRichTextSettings;
+
+ // Properties
+// FBorderStyle:TfpgBorderStyle;
+ FScrollbarWidth: longint;
+ FSmoothScroll: boolean;
+ FUseDefaultMenu: boolean;
+ FDebug: boolean;
+
+ FOnOverLink: TLinkEvent;
+ FOnNotOverLink: TLinkEvent;
+ FOnClickLink: TLinkEvent;
+
+ FDefaultMenu: TfpgPopupMenu;
+ FSelectAllMI: TfpgMenuItem;
+ FCopyMI: TfpgMenuItem;
+ FRefreshMI: TfpgMenuItem;
+ FWordWrapMI: TfpgMenuItem;
+ FSmoothScrollMI: TfpgMenuItem;
+ FDebugMI: TfpgMenuItem;
+
+ // Internal layout data
+ FNeedVScroll, FNeedHScroll: boolean;
+
+ FLayoutRequired: boolean;
+ FLayout: TRichTextLayout;
+
+ // Child controls
+ FHScrollbar: TfpgScrollbar;
+ FVScrollbar: TfpgScrollbar;
+
+ // Text
+ FText: PChar;
+
+ FTopCharIndex: longint; // only applies until following flag set.
+ FVerticalPositionInitialised: boolean;
+
+ FCursorRow: longint;
+ FCursorOffset: longint;
+ FSelectionStart: longint;
+ FSelectionEnd: longint;
+ FImages: TfpgImageList;
+
+ // Selection scrolling
+ FScrollTimer: TfpgTimer;
+ FOldMousePoint: TPoint;
+ FScrollingDirection: TScrollingDirection;
+
+ // Scroll information
+ // we use these rather than the scrollbar positions direct,
+ // since those are not updated during tracking
+ FXScroll: longint;
+ FYScroll: longint;
+
+ FLastXScroll: longint;
+ FLastYScroll: longint;
+
+ // Link
+ FLastLinkOver: string;
+ FClickedLink: string;
+
+ procedure DoAllocateWindowHandle(AParent: TfpgWindowBase); override;
+ Procedure CreateWnd;
+ Procedure DisposeWnd;
+ procedure HandleResize(AWidth, AHeight: TfpgCoord); override;
+ procedure UpdateScrollBarCoords;
+ procedure HandlePaint; override;
+ procedure HandleHide; override;
+ procedure HandleKeyPress(var keycode: word; var shiftstate: TShiftState; var consumed: boolean); override;
+
+ //procedure ScanEvent( Var KeyCode: TKeyCode;
+ // RepeatCount: Byte ); override;
+
+ //Procedure MouseDown( Button: TMouseButton;
+ // ShiftState: TShiftState;
+ // X, Y: Longint ); override;
+ //Procedure MouseUp( Button: TMouseButton;
+ // ShiftState: TShiftState;
+ // X, Y: Longint ); override;
+
+ //Procedure MouseDblClick( Button: TMouseButton;
+ // ShiftState: TShiftState;
+ // X, Y: Longint ); override;
+
+ //Procedure MouseMove( ShiftState: TShiftState;
+ // X, Y: Longint ); override;
+
+ //Procedure Scroll( Sender: TScrollbar;
+ // ScrollCode: TScrollCode;
+ // Var ScrollPos: Longint ); override;
+
+ //Procedure KillFocus; override;
+ //Procedure SetFocus; override;
+
+ // Messages for DragText
+ Procedure RTQueryText( Var Msg: TfpgMessageRec ); message RT_QUERYTEXT;
+ Procedure RTQuerySelText( Var Msg: TfpgMessageRec ); message RT_QUERYSELTEXT;
+
+ procedure Layout;
+
+ function FindPoint( XToFind: longint;
+ YToFind: longint;
+ Var LineIndex: longint;
+ Var Offset: longint;
+ Var Link: string ): TTextPosition;
+
+ // Scroll functions
+
+ // Scroll display to given positions (does NOT
+ // update scrollbars as this may be called during
+ // scrolling)
+ Procedure DoVerticalScroll( NewY: longint );
+ Procedure DoHorizontalScroll( NewX: longint );
+
+ // Set scrollbar position, and update display
+ Procedure SetVerticalPosition( NewY: longint );
+ Procedure SetHorizontalPosition( NewX: longint );
+
+ procedure OnScrollTimer( Sender: TObject );
+ Function GetLineDownPosition: longint;
+ Function GetLineUpPosition: longint;
+ Function GetSmallDownScrollPosition: longint;
+ Function GetSmallUpScrollPosition: longint;
+ Function GetSmallRightScrollPosition: longint;
+ Function GetSmallLeftScrollPosition: longint;
+
+ // Calculates line down position given the last line and displayed pixels
+ Function GetLineDownPositionFrom( LastLine: longint; PixelsDisplayed: longint ): longint;
+ Function GetLineUpPositionFrom( FirstVisibleLine: longint; Offset: longint ): longint;
+
+ // Drawing functions
+ Procedure DrawBorder;
+ Procedure Draw( StartLine, EndLine: longint );
+
+ Function GetDrawRect: TfpgRect;
+ Function GetTextAreaRect: TfpgRect;
+ Function GetTextAreaHeight: longint;
+ Function GetTextAreaWidth: longint;
+
+ // Queries
+ procedure GetFirstVisibleLine( Var LineIndex: longint; Var Offset: longint );
+ procedure GetBottomLine( Var LineIndex: longint; Var PixelsDisplayed: longint );
+
+ // Layout functions
+ Procedure SetupScrollbars;
+ Procedure SetupCursor;
+ procedure RemoveCursor;
+
+ function GetTextEnd: longint;
+
+ // property handlers
+// procedure SetBorder( BorderStyle: TBorderStyle );
+ Procedure SetDebug( Debug: boolean );
+ Procedure SetScrollBarWidth( NewValue: longint );
+
+ Procedure OnRichTextSettingsChanged( Sender: TObject );
+
+ function GetCursorIndex: longint;
+
+ Function GetTopCharIndex: longint;
+ Procedure SetTopCharIndex( NewValue: longint );
+ Function GetTopCharIndexPosition( NewValue: longint ): longint;
+
+ // Update the cursor row/column for the selction start/end
+ procedure RefreshCursorPosition;
+
+ procedure SetCursorIndex( Index: longint;
+ PreserveSelection: boolean );
+ procedure SetCursorPosition( Offset: longint;
+ Row: longint;
+ PreserveSelection: boolean );
+
+ procedure MakeRowVisible( Row: longint );
+ procedure MakeRowAndColumnVisible( Row: longint;
+ Column: longint );
+
+ // These two methods set selection start and end,
+ // and redraw the screen, but do not set up cursor.
+ Procedure SetSelectionStartInternal( SelectionStart: longint );
+ Procedure SetSelectionEndInternal( SelectionEnd: longint );
+
+ // Property handlers. These are for programmatic access
+ // where a complete setup of selection is needed
+ Procedure SetSelectionStart( SelectionStart: longint );
+ Procedure SetSelectionEnd( SelectionEnd: longint );
+
+ Procedure SetImages( Images: TfpgImageList );
+ Procedure Notification( AComponent: TComponent;
+ Operation: TOperation ); override;
+
+ // Default Menu
+ Procedure CreateDefaultMenu;
+ Procedure SelectAllMIClick( Sender: TObject );
+ Procedure CopyMIClick( Sender: TObject );
+ Procedure RefreshMIClick( Sender: TObject );
+ Procedure WordWrapMIClick( Sender: TObject );
+ Procedure SmoothScrollMIClick( Sender: TObject );
+ Procedure DebugMIClick( Sender: TObject );
+ Procedure DefaultMenuPopup( Sender: TObject );
+
+ Public
+ constructor Create(AOwner: TComponent); override;
+ destructor Destroy; Override;
+ function GetClientRect: TfpgRect; override;
+ procedure AddText( Text: PChar; ADelay: boolean = False );
+ procedure AddParagraph( Text: PChar );
+ procedure AddSelectedParagraph( Text: PChar );
+ procedure Clear;
+ procedure InsertText( CharIndexToInsertAt: longword; TextToInsert: PChar );
+ property Text: PChar read FText;
+ property TextEnd: longint read GetTextEnd;
+ property SelectionStart: longint read FSelectionStart write SetSelectionStart;
+ property SelectionEnd: longint read FSelectionEnd write SetSelectionEnd;
+ property CursorIndex: longint read GetCursorIndex;
+
+ // Copy all text to buffer
+ // Buffer can be nil to simply get size.
+ // If BufferLength is negative, it is ignored
+ Function CopyTextToBuffer( Buffer: PChar; BufferLength: longint ): longint;
+
+ // Clipboard
+ Procedure CopySelectionToClipboard;
+
+ // returns number of chars (that would be) copied.
+ // Buffer can be nil to simply get size.
+ // If BufferLength is negative, it is ignored
+ Function CopySelectionToBuffer( Buffer: PChar;
+ BufferLength: longint ): longint;
+
+ Function GetSelectionAsString: string; // returns up to 255 chars obviously
+
+ // Selection queries
+ Function SelectionLength: longint; // Note: includes formatting
+ Function SelectionSet: boolean; // returns true if there is a selection
+
+ // Selection actions
+ Procedure ClearSelection;
+ Procedure SelectAll;
+
+ property CursorRow: longint read FCursorRow;
+
+ // Navigation
+ procedure GoToTop;
+ procedure GotoBottom;
+ Procedure UpLine;
+ Procedure DownLine;
+ Procedure UpPage;
+ Procedure DownPage;
+
+ Procedure SmallScrollUp;
+ Procedure SmallScrollDown;
+ Procedure SmallScrollLeft;
+ Procedure SmallScrollRight;
+
+ Procedure MakeCharVisible( CharIndex: longint );
+ Property TopCharIndex: longint read GetTopCharIndex write SetTopCharIndex;
+
+ Procedure CursorLeft( PreserveSelection: boolean );
+ Procedure CursorRight( PreserveSelection: boolean );
+ Procedure CursorDown( PreserveSelection: boolean );
+ Procedure CursorUp( PreserveSelection: boolean );
+ Procedure CursorPageDown( PreserveSelection: boolean );
+ Procedure CursorPageUp( PreserveSelection: boolean );
+
+ Procedure CursorToLineStart( PreserveSelection: boolean );
+ Procedure CursorToLineEnd( PreserveSelection: boolean );
+
+ Procedure CursorWordLeft( PreserveSelection: boolean );
+ Procedure CursorWordRight( PreserveSelection: boolean );
+
+ function HighlightNextLink: boolean;
+ function HighlightPreviousLink: boolean;
+
+ // Search for the given text
+ // if found, returns true, MatchIndex is set to the first match,
+ // and MatchLength returns the length of the match
+ // (which may be greater than the length of Text due to
+ // to skipping tags)
+ // if not found, returns false, pMatch is set to -1
+ function FindString( Origin: TFindOrigin;
+ const AText: string;
+ var MatchIndex: longint;
+ var MatchLength: longint ): boolean;
+
+ // Searches for text and selects it found
+ // returns true if found, false if not
+ function Find( Origin: TFindOrigin;
+ const AText: string ): boolean;
+
+ function LinkFromIndex( const CharIndexToFind: longint): string;
+
+ Published
+ property Align;
+ property BackgroundColor default clBoxColor;
+ //property ParentColor;
+ //property ParentFont;
+ //property ParentPenColor;
+ property ParentShowHint;
+ property PopupMenu: TfpgPopupMenu read FPopupMenu write FPopupMenu;
+ property ShowHint;
+ Property TabOrder;
+ Property Focusable;
+ property Visible;
+ property RichTextSettings: TRichTextSettings read FRichTextSettings;
+ property ScrollBarWidth: longint read FScrollBarWidth write SetScrollBarWidth default 15;
+ property SmoothScroll: boolean read FSmoothScroll write FSmoothScroll;
+ property UseDefaultMenu: boolean read FUseDefaultMenu write FUseDefaultMenu default True;
+ property Debug: boolean read FDebug write SetDebug default False;
+ property Images: TfpgImageList read FImages write SetImages;
+
+ // ------- EVENTS ----------
+
+ // Called with the name of the link when the mouse first moves over it
+ property OnOverLink: TLinkEvent read FOnOverLink write FOnOverLink;
+
+ // Called with the name of the link when the mouse leaves it
+ property OnNotOverLink: TLinkEvent read FOnNotOverLink write FOnNotOverLink;
+
+ // Called when the link is clicked.
+ property OnClickLink: TLinkEvent read FOnClickLink write FOnClickLink;
+
+ Property OnClick;
+ Property OnDoubleClick;
+ //property OnDragOver;
+ //property OnDragDrop;
+ //property OnEndDrag;
+ Property OnEnter;
+ Property OnExit;
+ //Property OnFontChange;
+ //Property OnMouseClick;
+ //Property OnMouseDblClick;
+ //Property OnSetupShow;
+
+ //Property OnScan;
+ Protected
+ //Property Font;
+
+ End;
+
+
+implementation
+
+uses
+ SysUtils
+ ,ACLStringUtility
+ ,nvUtilities
+// ControlScrolling, ControlsUtility,
+ ,RichTextDocumentUnit
+ ,RichTextDisplayUnit
+ ;
+
+Procedure TRichTextView.SetSelectionStart( SelectionStart: longint );
+begin
+ RemoveCursor;
+ SetSelectionStartInternal( SelectionStart );
+ RefreshCursorPosition;
+ SetupCursor;
+end;
+
+Procedure TRichTextView.SetSelectionEnd( SelectionEnd: longint );
+begin
+ RemoveCursor;
+ SetSelectionEndInternal( SelectionEnd );
+ RefreshCursorPosition;
+ SetupCursor;
+end;
+
+Procedure TRichTextView.SetSelectionStartInternal( SelectionStart: longint );
+begin
+ if SelectionStart = FSelectionStart then
+ exit;
+
+ if SelectionSet then
+ if SelectionStart = -1 then
+ // small side effect here - also sets selectionend to -1
+ ClearSelection;
+
+ FSelectionStart := SelectionStart;
+ if FSelectionEnd = -1 then
+ // still no selection
+ exit;
+ RePaint;
+end;
+
+Procedure TRichTextView.SetSelectionEndInternal( SelectionEnd: longint );
+var
+ StartRedrawLine: longint;
+ EndRedrawLine: longint;
+ OldClip: TfpgRect;
+begin
+ if SelectionEnd = FSelectionEnd then
+ exit;
+
+ if FSelectionStart = -1 then
+ begin
+ FSelectionEnd := SelectionEnd;
+ // still not a valid selection, no need to redraw
+ exit;
+ end;
+
+ if SelectionEnd = FSelectionStart then
+ SelectionEnd := -1;
+
+ if ( FSelectionEnd = -1 ) then
+ begin
+ // there is currently no selection,
+ // and we are setting one: need to draw it all
+ StartRedrawLine := FLayout.GetLineFromCharIndex( FSelectionStart );
+ EndRedrawLine := FLayout.GetLineFromCharIndex( SelectionEnd );
+ end
+ else
+ begin
+ // there is already a selection
+ if SelectionEnd = -1 then
+ begin
+ // and we're clearing it
+ StartRedrawLine := FLayout.GetLineFromCharIndex( FSelectionStart );
+ EndRedrawLine := FLayout.GetLineFromCharIndex( FSelectionEnd );
+ end
+ else
+ begin
+ // and we're setting a new one, so draw from the old end to the new
+ StartRedrawLine := FLayout.GetLineFromCharIndex( FSelectionEnd );
+ EndRedrawLine := FLayout.GetLineFromCharIndex( SelectionEnd );
+ end;
+ end;
+
+ FSelectionEnd := SelectionEnd;
+
+ OldClip := Canvas.GetClipRect;
+ Canvas.SetClipRect(GetTextAreaRect);
+
+ // (re)draw selection
+ { TODO -ograeme : Draw must not be called here }
+// Draw( StartRedrawLine, EndRedrawLine );
+ Canvas.SetClipRect(OldClip);
+end;
+
+Procedure TRichTextView.ClearSelection;
+var
+ OldClip: TfpgRect;
+ StartLine: longint;
+ EndLine: longint;
+begin
+
+ if SelectionSet then
+ begin
+ OldClip := Canvas.GetClipRect;
+ Canvas.SetClipRect(GetTextAreaRect);
+
+ StartLine := FLayout.GetLineFromCharIndex( FSelectionStart );
+ EndLine := FLayout.GetLineFromCharIndex( FSelectionEnd );
+
+ FSelectionEnd := -1;
+ FSelectionStart := -1;
+
+ // clear display of selection
+ { TODO -oGraeme : Draw must not be called here }
+// Draw( StartLine, EndLine );
+
+ Canvas.SetClipRect(OldClip);
+ end;
+
+ FSelectionEnd := -1;
+ FSelectionStart := -1;
+end;
+
+Function TRichTextView.GetTextEnd: longint;
+begin
+ Result := StrLen( FText );
+end;
+
+Procedure TRichTextView.CreateDefaultMenu;
+begin
+ FDefaultMenu := TfpgPopupMenu.Create( self );
+ FDefaultMenu.OnShow := @DefaultMenuPopup;
+
+ //FSelectAllMI := TfpgMenuItem.Create( self );
+ //FSelectAllMI.Text := 'Select &All';
+ //FSelectAllMI.OnClick := @SelectAllMIClick;
+ //FDefaultMenu.Items.Add( FSelectAllMI );
+ FSelectAllMI := FDefaultMenu.AddMenuItem('Select &All', '', @SelectAllMIClick);
+
+ //FCopyMI := TMenuItem.Create( self );
+ //FCopyMI.Caption := '&Copy';
+ //FCopyMI.OnClick := CopyMIClick;
+ //FDefaultMenu.Items.Add( FCopyMI );
+ FCopyMI := FDefaultMenu.AddMenuItem('&Copy', '', @CopyMIClick);
+
+ FDefaultMenu.AddMenuItem('-', '', nil);
+
+ //FRefreshMI := TMenuItem.Create( self );
+ //FRefreshMI.Caption := '&Refresh';
+ //FRefreshMI.OnClick := RefreshMIClick;
+ //FDefaultMenu.Items.Add( FRefreshMI );
+ FRefreshMI := FDefaultMenu.AddMenuItem('&Refresh', '', @RefreshMIClick);
+
+ FDefaultMenu.AddMenuItem('-', '', nil);
+
+ //FSmoothScrollMI := TMenuItem.Create( self );
+ //FSmoothScrollMI.Caption := '&Smooth Scrolling';
+ //FSmoothScrollMI.OnClick := SmoothScrollMIClick;
+ //FDefaultMenu.Items.Add( FSmoothScrollMI );
+ FSmoothScrollMI := FDefaultMenu.AddMenuItem('&Smooth Scrolling', '', @SmoothScrollMIClick);
+
+ //FWordWrapMI := TMenuItem.Create( self );
+ //FWordWrapMI.Caption := '&Word Wrap';
+ //FWordWrapMI.OnClick := WordWrapMIClick;
+ //FDefaultMenu.Items.Add( FWordWrapMI );
+ FWordWrapMI := FDefaultMenu.AddMenuItem('&Word Wrap', '', @WordWrapMIClick);
+
+ //FDebugMI := TMenuItem.Create( self );
+ //FDebugMI.Caption := '&Debug';
+ //FDebugMI.OnClick := DebugMIClick;
+ //FDefaultMenu.Items.Add( FDebugMI );
+ FDebugMI := FDefaultMenu.AddMenuItem('&Debug', '', @DebugMIClick);
+end;
+
+Procedure TRichTextView.SelectAllMIClick( Sender: TObject );
+begin
+ SelectAll;
+end;
+
+Procedure TRichTextView.CopyMIClick( Sender: TObject );
+begin
+ CopySelectionToClipBoard;
+end;
+
+Procedure TRichTextView.RefreshMIClick( Sender: TObject );
+begin
+ RePaint;
+end;
+
+Procedure TRichTextView.WordWrapMIClick( Sender: TObject );
+begin
+ FRichTextSettings.DefaultWrap := not FRichTextSettings.DefaultWrap;
+end;
+
+Procedure TRichTextView.SmoothScrollMIClick( Sender: TObject );
+begin
+ SmoothScroll := not SmoothScroll;
+end;
+
+Procedure TRichTextView.DebugMIClick( Sender: TObject );
+begin
+ Debug := not Debug;
+end;
+
+Procedure TRichTextView.DefaultMenuPopup( Sender: TObject );
+begin
+ FWordWrapMI.Checked := FRichTextSettings.DefaultWrap;
+ FSmoothScrollMI.Checked := SmoothScroll;
+ FDebugMI.Checked := Debug;
+end;
+
+constructor TRichTextView.Create(AOwner: TComponent);
+begin
+ inherited Create(AOwner);
+ Name := 'RichTextView';
+ FWidth := 100;
+ FHeight := 100;
+ FFocusable := True;
+
+ FNeedVScroll := False;
+ FNeedHScroll := False;
+ FSmoothScroll := True;
+ FScrollbarWidth := 15;
+ FUseDefaultMenu := True;
+ FDebug := False;
+ FLayoutRequired := True;
+
+ FTextColor := Parent.TextColor;
+ FBackgroundColor := clBoxColor;
+
+ FRichTextSettings := TRichTextSettings.Create( self );
+ FRichTextSettings.Margins := Rect( 5, 5, 5, 5 );
+ FRichTextSettings.OnChange := @OnRichTextSettingsChanged;
+
+ FImages := nil;
+
+ if not InDesigner then
+ begin
+ FFontManager := nil;
+
+ FText := StrAlloc( 100 );
+ FText[ 0 ] := #0;
+
+ FTopCharIndex := 0;
+ FVerticalPositionInitialised := false;
+ end;
+end;
+
+procedure TRichTextView.HandlePaint;
+Var
+ CornerRect: TfpgRect;
+ DrawRect: TfpgRect;
+ TextRect: TfpgRect;
+begin
+writeln('DEBUG: TRichTextView.HandlePaint >>>');
+ Canvas.ClearClipRect;
+ DrawBorder;
+
+ TextRect := GetTextAreaRect;
+ DrawRect := GetDrawRect;
+ Canvas.SetClipRect(TextRect);
+
+ Canvas.Color := BackgroundColor;
+ Canvas.FillRectangle(TextRect);
+
+ if InDesigner then
+ begin
+ Canvas.TextColor := clInactiveWgFrame;
+ Canvas.DrawString(10, 10, '<Rich text will appear here>');
+ Canvas.ClearClipRect;
+ Exit; //==>
+ end;
+
+ Assert(FLayout <> nil, 'FLayout may not be nil at this point!');
+// Draw( 0, FLayout.FNumLines );
+ Canvas.ClearClipRect;
+
+ if FNeedHScroll then
+ begin
+ // blank out corner between scrollbars
+ CornerRect.Left := Width - FScrollBarWidth;
+ CornerRect.Top := FScrollBarWidth;
+ CornerRect.Width := FScrollBarWidth;
+ CornerRect.Height := FScrollBarWidth;
+ Canvas.Color := clButtonFace;
+ Canvas.FillRectangle(CornerRect);
+ end;
+writeln('DEBUG: TRichTextView.HandlePaint <<<<');
+end;
+
+procedure TRichTextView.HandleHide;
+begin
+ fpgCaret.UnSetCaret (Canvas);
+ inherited HandleHide;
+end;
+
+procedure TRichTextView.HandleKeyPress(var keycode: word; var shiftstate: TShiftState;
+ var consumed: boolean);
+begin
+writeln('HandleKeyPress');
+ case keycode of
+ keyPageDown:
+ begin
+ consumed := True;
+ UpPage;
+ end;
+ keyPageUp:
+ begin
+ consumed := True;
+ DownPage;
+ end;
+
+ end;
+ inherited HandleKeyPress(keycode, shiftstate, consumed);
+end;
+
+Destructor TRichTextView.Destroy;
+Begin
+ // destroy the font manager NOW
+ // while the canvas is still valid
+ // (it will be freed in TControl.DisposeWnd)
+ // in order to release logical fonts
+ if FFontManager <> nil then
+ FFontManager.Free;
+ if Assigned(FLayout) then
+ FreeAndNil(FLayout);
+
+ FScrollTimer.Free;
+ if not InDesigner then
+ begin
+ RemoveCursor;
+ StrDispose( FText );
+ end;
+ Inherited Destroy;
+End;
+
+//Procedure TRichTextView.KillFocus;
+//begin
+// RemoveCursor;
+// inherited KillFocus;
+//end;
+
+//Procedure TRichTextView.SetFocus;
+//begin
+// inherited SetFocus;
+// SetupCursor;
+//end;
+
+// Custom window messages for DragText support
+Procedure TRichTextView.RTQueryText( Var Msg: TfpgMessageRec );
+begin
+ //Msg.Handled := true;
+ //Msg.Result :=
+ // CopyPlainTextToBuffer( FText,
+ // FText + strlen( FText ),
+ // PChar( Msg.Param1 ),
+ // Msg.Param2 );
+end;
+
+Procedure TRichTextView.RTQuerySelText( Var Msg: TfpgMessageRec );
+begin
+ //Msg.Handled := true;
+ //Msg.Result :=
+ // CopySelectionToBuffer( PChar( Msg.Param1 ),
+ // Msg.Param2 );
+end;
+
+Procedure TRichTextView.SetDebug( Debug: boolean );
+begin
+ if Debug = FDebug then
+ exit;
+ FDebug := Debug;
+ RePaint;
+end;
+
+Procedure TRichTextView.SetScrollBarWidth( NewValue: longint );
+begin
+ if ( NewValue < 0 )
+ or ( NewValue = FScrollBarWidth ) then
+ exit;
+ FScrollBarWidth := NewValue;
+ Layout;
+ RePaint;
+end;
+
+procedure TRichTextView.FVScrollbarScroll(Sender: TObject; position: integer);
+begin
+ SetVerticalPosition(position);
+end;
+
+procedure TRichTextView.DoAllocateWindowHandle(AParent: TfpgWindowBase);
+begin
+ inherited DoAllocateWindowHandle(AParent);
+ CreateWnd;
+end;
+
+Procedure TRichTextView.CreateWnd;
+begin
+writeln('DEBUG: TRichTextView.CreateWnd >>>>');
+ if InDesigner then
+ exit;
+
+ { TODO -ograeme : I disabled bitmap fonts }
+ FFontManager := TCanvasFontManager.Create( Canvas,
+ False, // allow bitmap fonts
+ Self
+ );
+
+ FLastLinkOver := '';
+ FSelectionStart := -1;
+ FSelectionEnd := -1;
+
+ if FUseDefaultMenu then
+ begin
+ CreateDefaultMenu;
+ FPopupMenu := FDefaultMenu;
+ end;
+
+ FHScrollbar := TfpgScrollBar.Create( self );
+ FHScrollbar.Visible := False;
+ FHScrollbar.Orientation := orHorizontal;
+ FHScrollBar.SetPosition(2, Height-2-FScrollbarWidth, Width-4-FScrollbarWidth, FScrollbarWidth);
+
+ FVScrollbar := TfpgScrollBar.Create( self );
+ FVScrollBar.Visible := False;
+ FVScrollBar.Orientation := orVertical;
+ FVScrollbar.SetPosition(Width-2-FScrollbarWidth, 2, FScrollbarWidth, Height-4-FScrollbarWidth);
+
+ FScrollTimer := TfpgTimer.Create( 100 );
+ FScrollTimer.OnTimer := @OnScrollTimer;
+
+// FLinkCursor := GetLinkCursor;
+
+ if FLayoutRequired then
+ // we haven't yet done a layout
+ Layout;
+writeln('DEBUG: TRichTextView.CreateWnd <<<<');
+end;
+
+Procedure TRichTextView.DisposeWnd;
+begin
+
+end;
+
+procedure TRichTextView.HandleResize(AWidth, AHeight: TfpgCoord);
+begin
+ inherited HandleResize(AWidth, AHeight);
+ if InDesigner then
+ exit;
+
+ if WinHandle = 0 then
+ exit;
+
+ RemoveCursor;
+ UpdateScrollbarCoords;
+
+ if FVerticalPositionInitialised then
+ begin
+ // Preserve current position
+ if FLayout.FNumLines > 0 then
+ FTopCharIndex := GetTopCharIndex
+ else
+ FTopCharIndex := 0;
+ end;
+
+ Layout;
+
+ // This is the point at which vertical position
+ // is initialised during first window show
+ FVScrollBar.Position := GetTopCharIndexPosition( FTopCharIndex );
+
+ FYScroll := FVScrollBar.Position;
+ FLastYScroll := FYScroll;
+ FVerticalPositionInitialised := true;
+
+ SetupCursor;
+end;
+
+procedure TRichTextView.UpdateScrollBarCoords;
+var
+ HWidth: integer;
+ VHeight: integer;
+begin
+ VHeight := Height - 4;
+ HWidth := Width - 4;
+
+ if FVScrollBar.Visible then
+ Dec(HWidth, FVScrollBar.Width);
+ if FHScrollBar.Visible then
+ Dec(VHeight, FHScrollBar.Height);
+
+ FHScrollBar.Top := Height -FHScrollBar.Height - 2;
+ FHScrollBar.Left := 2;
+ FHScrollBar.Width := HWidth;
+
+ FVScrollBar.Top := 2;
+ FVScrollBar.Left := Width - FVScrollBar.Width - 2;
+ FVScrollBar.Height := VHeight;
+
+ FVScrollBar.UpdateWindowPosition;
+ FHScrollBar.UpdateWindowPosition;
+end;
+
+
+// Main procedure: reads through the whole text currently stored
+// and breaks up into lines - each represented as a TLayoutLine in
+// the array FLines[ 0.. FNumLines ]
+Procedure TRichTextView.Layout;
+Var
+ DrawWidth: longint;
+begin
+writeln('DEBUG: TRichTextView.Layout >>>>');
+ FLayoutRequired := true;
+
+ if InDesigner then
+ exit;
+ if WinHandle = 0 then
+ exit;
+
+ FSelectionEnd := -1;
+ FSelectionStart := -1;
+ RemoveCursor;
+
+ DrawWidth := GetTextAreaRect.Width;
+
+ try
+ FLayout := nil;
+ if Assigned(FLayout) then
+ begin
+ writeln('DEBUG: Before*** FLayout.FNumLines = ', FLayout.FNumLines);
+ FLayout.Free;
+ FLayout := nil;
+ end;
+ except
+ on E: Exception do
+ raise Exception.Create('Failed to free FLayout. Error msg: ' + E.Message);
+ end;
+
+ FLayout := TRichTextLayout.Create( FText,
+ FImages,
+ FRichTextSettings,
+ FFontManager,
+ DrawWidth );
+writeln('DEBUG: After*** FLayout.FNumLines = ', FLayout.FNumLines);
+
+ SetupScrollBars;
+ RefreshCursorPosition;
+
+ FLayoutRequired := false;
+writeln('DEBUG: TRichTextView.Layout <<<<');
+End;
+
+procedure TRichTextView.GetFirstVisibleLine( Var LineIndex: longint;
+ Var Offset: longint );
+begin
+ FLayout.GetLineFromPosition( FYScroll,
+ LineIndex,
+ Offset );
+end;
+
+procedure TRichTextView.GetBottomLine( Var LineIndex: longint;
+ Var PixelsDisplayed: longint );
+begin
+ FLayout.GetLineFromPosition( FYScroll + GetTextAreaHeight,
+ LineIndex,
+ PixelsDisplayed );
+end;
+
+function TRichTextView.FindPoint( XToFind: longint;
+ YToFind: longint;
+ Var LineIndex: longint;
+ Var Offset: longint;
+ Var Link: string ): TTextPosition;
+var
+ TextHeight: longint;
+begin
+ LineIndex := 0;
+ Offset := 0;
+ Link := '';
+
+ TextHeight := GetTextAreaHeight;
+
+ YToFind := Height - YToFind;
+
+ //if FBorderStyle = bsSingle then
+ //begin
+ // dec( YToFind, 2 );
+ // dec( XToFind, 2 );
+ //end;
+
+ if YToFind < 0 then
+ begin
+ // above the top
+ Result := tpAboveTextArea;
+ exit;
+ end;
+
+ if YToFind >= TextHeight then
+ begin
+ // below the bottom
+ Result := tpBelowTextArea;
+ LineIndex := FLayout.FNumLines;
+ Offset := FLayout.FLines^[ FLayout.FNumLines - 1 ].Length - 1;
+ exit;
+ end;
+
+ Result := FLayout.FindPoint( XToFind + FXScroll,
+ YToFind + FYScroll,
+ LineIndex,
+ Offset,
+ Link );
+end;
+
+Procedure TRichTextView.DrawBorder;
+var
+ Rect: TfpgRect;
+begin
+ Canvas.GetWinRect(Rect);
+ Canvas.DrawControlFrame(Rect);
+end;
+
+Procedure TRichTextView.Draw( StartLine, EndLine: longint );
+Var
+ DrawRect: TfpgRect;
+ X: longint;
+ Y: longint;
+ SelectionStartP: PChar;
+ SelectionEndP: PChar;
+ Temp: longint;
+begin
+ DrawRect := GetTextAreaRect;
+ if StartLine > EndLine then
+ begin
+ // swap
+ Temp := EndLine;
+ EndLine := StartLine;
+ StartLine := Temp;
+ end;
+ // calculate selection ptrs
+ if SelectionSet then
+ begin
+ SelectionStartP := FText + FSelectionStart;
+ SelectionEndP := FText + FSelectionEnd;
+ end
+ else
+ begin
+ SelectionStartP := nil;
+ SelectionEndP := nil;
+ end;
+ // calculate destination point
+ Y := DrawRect.Top + FYScroll;
+ X := DrawRect.Left - FXScroll;
+ DrawRichTextLayout( FFontManager,
+ FLayout,
+ SelectionStartP,
+ SelectionEndP,
+ StartLine,
+ EndLine,
+ Point(X, Y)
+ );
+End;
+
+// This gets the area of the control that we can draw on
+// (not taken up by vertical scroll bar)
+Function TRichTextView.GetDrawRect: TfpgRect;
+begin
+ Result := GetClientRect;
+ if InDesigner then
+ exit;
+
+ if FNeedHScroll then
+ inc( Result.Height, FScrollbarWidth );
+
+ if FNeedVScroll then
+ dec( Result.Width, FScrollbarWidth );
+end;
+
+// Gets the area that we are drawing text on, which is the
+// draw rect minus borders
+Function TRichTextView.GetTextAreaRect: TfpgRect;
+begin
+ Result := GetDrawRect;
+// InflateRect(Result, -2, -2);
+end;
+
+Function TRichTextView.GetTextAreaHeight: longint;
+var
+ TextArea: TfpgRect;
+begin
+ TextArea := GetTextAreaRect;
+ Result := TextArea.Height;
+end;
+
+Function TRichTextView.GetTextAreaWidth: longint;
+begin
+ Result := Width;
+ //if FBorderStyle <> bsNone then
+ // dec( Result, 4 );
+ dec( Result, FScrollBarWidth ); // always allow space for vscrollbar
+end;
+
+Procedure TRichTextView.SetupScrollbars;
+var
+ AvailableWidth: longint;
+ MaxDisplayWidth: longint;
+ AvailableHeight: longint;
+Begin
+
+ // Calculate used and available width
+ AvailableWidth := GetTextAreaWidth;
+
+ MaxDisplayWidth := FLayout.Width div FontWidthPrecisionFactor;
+
+ // Defaults
+ FNeedVScroll := false;
+ FNeedHScroll := false;
+
+ // Horizontal scroll setup
+ if MaxDisplayWidth
+ > AvailableWidth then
+ FNeedHScroll := true;
+
+ FHScrollbar.SliderSize := AvailableWidth;
+ FHScrollbar.Min := 0;
+
+ if FNeedHScroll then
+ begin
+ FHScrollbar.Max := MaxDisplayWidth;
+ end
+ else
+ begin
+ FHScrollBar.Position := 0;
+ FHScrollbar.Max := 0;
+ end;
+
+ // Calculate available height.
+ // Note: this depends on whether a h scroll bar is needed.
+
+ AvailableHeight := Height;
+ //if FBorderStyle <> bsNone then
+ // dec( AvailableHeight, 4 );
+ if FNeedHScroll then
+ dec( AvailableHeight, FScrollBarWidth );
+
+ // Vertical scroll setup
+
+ if FLayout.Height > AvailableHeight then
+ FNeedVScroll := true;
+
+ FVScrollBar.SliderSize := AvailableHeight;
+ FVScrollBar.Min := 0;
+
+ if FNeedVScroll then
+ begin
+ FVScrollBar.Max := FLayout.Height - 1;
+ end
+ else
+ begin
+ FVScrollBar.Position := 0;
+ FVScrollBar.Max := 0;
+ end;
+
+ FHScrollBar.ScrollStep := 15; // pixels
+ FHScrollBar.PageSize := AvailableWidth div 2;
+ FVScrollBar.ScrollStep := 1; // not used (line up/down calculated explicitly)
+ FVScrollBar.PageSize := GetTextAreaHeight div 2;
+
+ // Physical horizontal scroll setup
+ FHScrollbar.Visible := FNeedHScroll;
+ FHScrollbar.Left := 0;
+ FHScrollbar.Top := Height - FScrollBarWidth;
+ FHScrollbar.Width := Width - FScrollBarWidth;
+ FHScrollbar.Height := FScrollbarWidth;
+
+ // Physical vertical scroll setup
+ FVScrollbar.Visible := True;
+ FVScrollbar.Enabled := FNeedVScroll;
+ FVScrollbar.Left := Width - FScrollbarWidth;
+ FVScrollbar.Top := 0;
+ FVScrollbar.Width := FScrollbarWidth;
+
+ if FNeedHScroll then
+ begin
+ FVScrollbar.Height := Height - FScrollbarWidth
+ end
+ else
+ begin
+ FVScrollbar.Height := Height;
+ end;
+
+ // Initialise scroll
+ FYScroll := FVScrollBar.Position;
+ FLastYScroll := FYScroll;
+ FXScroll := FHScrollBar.Position;
+ FLastXScroll := FXScroll;
+
+ FVScrollbar.OnScroll := @FVScrollbarScroll;
+End;
+
+Procedure TRichTextView.SetupCursor;
+var
+ Line: TLayoutLine;
+ X, Y: longint;
+ TextRect: TfpgRect;
+ DrawHeight: longint;
+ DrawWidth: longint;
+ CursorHeight: longint;
+ TextHeight: longint;
+ LineHeight: longint;
+ Descender: longint;
+ MaxDescender: longint;
+begin
+ RemoveCursor;
+ if FSelectionStart = -1 then
+ exit;
+
+ TextRect := GetTextAreaRect;
+ DrawHeight := TextRect.Top - TextRect.Bottom;
+ DrawWidth := TextRect.Right - TextRect.Left;
+
+ Line := FLayout.FLines^[ CursorRow ];
+ LineHeight := Line.Height;
+
+ Y := DrawHeight
+ - ( FLayout.GetLinePosition( CursorRow )
+ - FVScrollbar.Position );
+ // Now Y is the top of the line
+ if Y < 0 then
+ // off bottom
+ exit;
+ if ( Y - LineHeight ) > DrawHeight then
+ // off top
+ exit;
+
+ FLayout.GetXFromOffset( FCursorOffset, CursorRow, X );
+
+ X := X - FHScrollBar.Position;
+
+ if X < 0 then
+ // offscreen to left
+ exit;
+
+ if X > DrawWidth then
+ // offscreen to right
+ exit;
+
+ TextHeight := FFontManager.CharHeight;
+ Descender := FFontManager.CharDescender;
+ MaxDescender := FLayout.FLines^[ CursorRow ].MaxDescender;
+ CursorHeight := TextHeight;
+
+ dec( Y, LineHeight - 1 );
+ // now Y is the BOTTOM of the line
+
+ // move Y up to the bottom of the cursor;
+ // since the current text may be smaller than the highest in the line
+ inc( Y, MaxDescender - Descender );
+
+ if Y < 0 then
+ begin
+ // bottom of line will be below bottom of display.
+ dec( CursorHeight, 1 - Y );
+ Y := 0;
+ end;
+
+ if Y + CursorHeight - 1 > DrawHeight then
+ begin
+ // top of cursor will be above top of display
+ CursorHeight := DrawHeight - Y + 1;
+ end;
+
+ fpgCaret.SetCaret(Canvas, TextRect.Left + X, TextRect.Bottom + Y, 2, CursorHeight);
+end;
+
+procedure TRichTextView.RemoveCursor;
+begin
+ fpgCaret.UnSetCaret(Canvas);
+end;
+
+Function TRichTextView.GetLineDownPosition: longint;
+var
+ LastLine: longint;
+ PixelsDisplayed: longint;
+begin
+ GetBottomLine( LastLine,
+ PixelsDisplayed );
+
+ Result := GetLineDownPositionFrom( LastLine, PixelsDisplayed );
+end;
+
+Function TRichTextView.GetLineDownPositionFrom( LastLine: longint;
+ PixelsDisplayed: longint ): longint;
+var
+ LineHeight: longint;
+begin
+ if LastLine = -1 then
+ exit;
+
+ LineHeight := FLayout.FLines^[ LastLine ].Height;
+
+ if LastLine = FLayout.FNumLines - 1 then
+ begin
+ // last line
+ if PixelsDisplayed >= LineHeight then
+ begin
+ // and it's fully displayed, so scroll to show margin
+ Result := FLayout.Height - GetTextAreaHeight;
+ exit;
+ end;
+ end;
+
+ // Scroll to make last line fully visible...
+ Result := FVScrollBar.Position
+ + LineHeight
+ - PixelsDisplayed;
+ if PixelsDisplayed > LineHeight div 2 then
+ // more than half line already displayed so
+ if LastLine < FLayout.FNumLines - 1 then
+ // AND to make next line fully visible
+ inc( Result, FLayout.FLines^[ LastLine + 1 ].Height );
+end;
+
+Function TRichTextView.GetSmallDownScrollPosition: longint;
+var
+ LastLine: longint;
+ PixelsDisplayed: longint;
+ LineTextHeight: longint;
+ Diff: longint;
+begin
+ GetBottomLine( LastLine,
+ PixelsDisplayed );
+
+ Result := GetLineDownPositionFrom( LastLine, PixelsDisplayed );
+
+ // Now limit the scrolling to max text height for the bottom line
+ Diff := Result - FVScrollBar.Position;
+
+ LineTextHeight := FLayout.FLines^[ LastLine ].MaxTextHeight;
+ if Diff > LineTextHeight then
+ Diff := LineTextHeight;
+ Result := FVScrollBar.Position + Diff;
+end;
+
+Function TRichTextView.GetSmallUpScrollPosition: longint;
+var
+ FirstVisibleLine: longint;
+ Offset: longint;
+ LineTextHeight: longint;
+ Diff: longint;
+begin
+ GetFirstVisibleLine( FirstVisibleLine,
+ Offset );
+ Result := GetLineUpPositionFrom( FirstVisibleLine,
+ Offset );
+ // Now limit the scrolling to max text height for the bottom line
+ Diff := FVScrollBar.Position - Result;
+
+ LineTextHeight := FLayout.FLines^[ FirstVisibleLine ].MaxTextHeight;
+ if Diff > LineTextHeight then
+ Diff := LineTextHeight;
+ Result := FVScrollBar.Position - Diff;
+end;
+
+Function TRichTextView.GetSmallRightScrollPosition: longint;
+begin
+ Result := FHScrollBar.Position + FHScrollBar.ScrollStep;
+ if Result > FHScrollBar.Max then
+ Result := FHScrollBar.Max;
+end;
+
+Function TRichTextView.GetSmallLeftScrollPosition: longint;
+begin
+ Result := FHScrollBar.Position - FHScrollBar.ScrollStep;
+ if Result < 0 then
+ Result := 0;
+end;
+
+Function TRichTextView.GetLineUpPosition: longint;
+var
+ FirstVisibleLine: longint;
+ Offset: longint;
+begin
+ GetFirstVisibleLine( FirstVisibleLine, Offset );
+ Result := GetLineUpPositionFrom( FirstVisibleLine, Offset );
+end;
+
+Function TRichTextView.GetLineUpPositionFrom( FirstVisibleLine: longint;
+ Offset: longint ): longint;
+begin
+ // we should never have scrolled all lines off the top!!
+ assert( FirstVisibleLine <> -1 );
+
+ if FirstVisibleLine = 0 then
+ begin
+ // first line
+ if Offset = 0 then
+ begin
+ // and it's already fully visible, so scroll to show margin
+ Result := 0;
+ exit;
+ end;
+ end;
+
+ // scroll so that top line is fully visible...
+ Result := FVScrollBar.Position
+ - Offset;
+
+ if Offset < FLayout.FLines^[ FirstVisibleLine ].Height div 2 then
+ // more than half the line was already displayed so
+ if FirstVisibleLine > 0 then
+ // AND to make next line up visible
+ dec( Result, FLayout.FLines^[ FirstVisibleLine - 1 ].Height );
+
+end;
+
+Function Sign( arg: longint ): longint;
+begin
+ if arg>0 then
+ Result := 1
+ else if arg<0 then
+ Result := -1
+ else
+ Result := 0;
+end;
+
+Function FSign( arg: double ): double;
+begin
+ if arg>0 then
+ Result := 1
+ else if arg<0 then
+ Result := -1
+ else
+ Result := 0;
+end;
+
+Procedure ExactDelay( MS: Cardinal );
+begin
+ Sleep(MS);
+end;
+
+(*
+Procedure TRichTextView.Scroll( Sender: TScrollbar;
+ ScrollCode: TScrollCode;
+ Var ScrollPos: Longint );
+
+begin
+ case ScrollCode of
+// scVertEndScroll,
+// scVertPosition,
+ scPageUp,
+ scPageDown,
+ scVertTrack:
+ DoVerticalScroll( ScrollPos );
+
+ // Line up and down positions are calculated for each case
+ scLineDown:
+ begin
+ ScrollPos := GetSmallDownScrollPosition;
+ DoVerticalScroll( ScrollPos );
+ end;
+
+ scLineUp:
+ begin
+ ScrollPos := GetSmallUpScrollPosition;
+ DoVerticalScroll( ScrollPos );
+ end;
+
+ scHorzPosition,
+ scPageRight,
+ scPageLeft,
+ scHorzTrack,
+ scColumnRight,
+ scColumnLeft:
+ begin
+ DoHorizontalScroll( ScrollPos );
+ end;
+ end;
+end;
+*)
+
+Procedure TRichTextView.DoVerticalScroll( NewY: longint );
+
+var
+ ScrollDistance: longint;
+begin
+ FYScroll := NewY;
+
+ if not Visible then
+ begin
+ FLastYScroll := FYScroll;
+ exit;
+ end;
+
+ ScrollDistance := FYScroll - FLastYScroll;
+
+ { TODO -ograeme -cscrolling : Implement vertical scrolling here }
+ //ScrollControlRect( Self,
+ // GetTextAreaRect,
+ // 0,
+ // ScrollDistance,
+ // Color,
+ // FSmoothScroll );
+
+ FLastYScroll := FYScroll;
+ RePaint;
+ SetupCursor;
+end;
+
+Procedure TRichTextView.DoHorizontalScroll( NewX: longint );
+var
+ ScrollDistance: longint;
+begin
+ FXScroll := NewX;
+
+ if not Visible then
+ begin
+ FLastXScroll := FXScroll;
+ exit;
+ end;
+
+ ScrollDistance := FXScroll - FLastXScroll;
+
+ { TODO -ograemeg -cscrolling : Implement horizontal scrolling }
+ //ScrollControlRect( Self,
+ // GetTextAreaRect,
+ // - ScrollDistance,
+ // 0,
+ // Color,
+ // FSmoothScroll );
+
+ FLastXScroll := FXScroll;
+ RePaint;
+ SetupCursor;
+end;
+
+Procedure TRichTextView.SetVerticalPosition( NewY: longint );
+begin
+ FVScrollbar.Position := NewY;
+ DoVerticalScroll( FVScrollbar.Position );
+end;
+
+Procedure TRichTextView.SetHorizontalPosition( NewX: longint );
+begin
+ FHScrollbar.Position := NewX;
+ DoHorizontalScroll( FHScrollbar.Position );
+end;
+
+Procedure TRichTextView.AddParagraph( Text: PChar );
+begin
+ if GetTextEnd > 0 then
+ begin
+ AddText( #13, True );
+ AddText( #10, True );
+ end;
+ AddText( Text );
+end;
+
+Procedure TRichTextView.AddSelectedParagraph( Text: PChar );
+begin
+ if GetTextEnd > 0 then
+ begin
+ AddText( #13, True);
+ AddText( #10, True);
+ end;
+ SelectionStart := GetTextEnd;
+ AddText( Text );
+ SelectionEnd := GetTextEnd;
+ MakeCharVisible( SelectionStart );
+end;
+
+// ADelay = True means that we hold off on redoing the Layout and Painting.
+Procedure TRichTextView.AddText( Text: PChar; ADelay: boolean );
+begin
+ AddAndResize( FText, Text );
+ if not ADelay then
+ begin
+ Layout;
+ RePaint;
+ end;
+end;
+
+// Insert at current point
+Procedure TRichTextView.InsertText( CharIndexToInsertAt: longword;
+ TextToInsert: PChar );
+var
+ NewText: PChar;
+begin
+ if CharIndexToInsertAt < 0 then
+ exit;
+
+ NewText := StrAlloc( StrLen( FText ) + StrLen( TextToInsert ) + 1 );
+ StrLCopy( NewText, FText, CharIndexToInsertAt );
+ StrCat( NewText, TextToInsert );
+ StrCat( NewText, FText + CharIndexToInsertAt );
+
+ Clear;
+ AddText( NewText );
+ StrDispose( NewText );
+end;
+
+Procedure TRichTextView.Clear;
+begin
+ ClearSelection;
+ FText[ 0 ] := #0;
+ FTopCharIndex := 0;
+ Layout;
+ if FLayout.FNumLines > 1 then
+ raise Exception.Create('FLayout.FNumLines should have been 0 but it was ' + IntToStr(FLayout.FNumLines));
+// RePaint;
+end;
+
+//procedure TRichTextView.SetBorder( BorderStyle: TBorderStyle );
+//begin
+// FBorderStyle := BorderStyle;
+// Refresh;
+//end;
+
+Procedure TRichTextView.SetImages( Images: TfpgImageList );
+begin
+ if Images = FImages then
+ exit; // no change
+
+ { TODO -oGraeme : TfpgImageList is not a TComponent descendant. Maybe it should be? }
+ //if FImages <> nil then
+ // // Tell the old imagelist not to inform us any more
+ // FImages.Notification( Self, opRemove );
+
+ FImages := Images;
+ //if FImages <> nil then
+ // // request notification when other is freed
+ // FImages.FreeNotification( Self );
+
+ if GetTextEnd = 0 then
+ // no text - can't be any image references - no need to layout
+ exit;
+
+ Layout;
+ RePaint;
+end;
+
+Procedure TRichTextView.OnRichTextSettingsChanged( Sender: TObject );
+begin
+ if not InDesigner then
+ begin
+ Layout;
+ RePaint;
+ end;
+end;
+
+Procedure TRichTextView.Notification( AComponent: TComponent;
+ Operation: TOperation );
+begin
+ inherited Notification( AComponent, Operation );
+ { TODO -oGraeme : TfpgImageList is not a TComponent descendant. Maybe it should be? }
+ //if AComponent = FImages then
+ // if Operation = opRemove then
+ // FImages := nil;
+end;
+
+(*
+Procedure TRichTextView.MouseDown( Button: TMouseButton;
+ ShiftState: TShiftState;
+ X, Y: Longint );
+var
+ Line: longint;
+ Offset: longint;
+ Link: string;
+ Position: TTextPosition;
+ Shift: boolean;
+begin
+ Focus;
+
+ inherited MouseDown( Button, ShiftState, X, Y );
+
+ if Button <> mbLeft then
+ begin
+ if Button = mbRight then
+ begin
+ if MouseCapture then
+ begin
+ // this is a shortcut - left mouse drag to select, right mouse to copy
+ CopySelectionToClipboard;
+ end;
+ end;
+ exit;
+ end;
+
+// if FText[ 0 ] = #0 then
+// exit;
+
+ Position := FindPoint( X, Y, Line, Offset, Link );
+ FClickedLink := Link;
+
+ if Position in [ tpAboveTextArea,
+ tpBelowTextArea ] then
+ // not on the control (this probably won't happen)
+ exit;
+
+ // if shift is pressed then keep the same selection start.
+
+ Shift := ssShift in ShiftState;
+ RemoveCursor;
+
+ if not Shift then
+ ClearSelection;
+
+ SetCursorPosition( Offset, Line, Shift );
+ MouseCapture := true;
+
+end;
+*)
+
+(*
+Procedure TRichTextView.MouseUp( Button: TMouseButton;
+ ShiftState: TShiftState;
+ X, Y: Longint );
+begin
+ if Button = mbRight then
+ if MouseCapture then
+ // don't popup menu for shortcut - left mouse drag to select, right mouse to copy
+ exit;
+
+ inherited MouseUp( Button, ShiftState, X, Y );
+
+ if Button <> mbLeft then
+ exit;
+
+ if not MouseCapture then
+ // not a mouse up from a link click
+ exit;
+
+ if FScrollTimer.Running then
+ FScrollTimer.Stop;
+
+ MouseCapture := false;
+
+ SetupCursor;
+
+ if FClickedLink <> '' then
+ if Assigned( FOnClickLink ) then
+ FOnClickLink( Self, FClickedLink );
+
+end;
+*)
+
+(*
+Procedure TRichTextView.MouseDblClick( Button: TMouseButton;
+ ShiftState: TShiftState;
+ X, Y: Longint );
+var
+ Row: longint;
+ Offset: longint;
+ Link: string;
+ Position: TTextPosition;
+ P: PChar;
+ pWordStart: PChar;
+ WordLength: longint;
+begin
+ inherited MouseDblClick( Button, ShiftState, X, Y );
+
+ if Button <> mbLeft then
+ exit;
+
+// if FText[ 0 ] = #0 then
+// exit;
+
+ Position := FindPoint( X, Y, Row, Offset, Link );
+
+ if Position in [ tpAboveTextArea,
+ tpBelowTextArea ] then
+ // not on the control (this probably won't happen)
+ exit;
+
+ Assert( Row >= 0 );
+ Assert( Row < FLayout.FNumLines );
+
+ P := FLayout.FLines[ Row ].Text + Offset;
+
+ RemoveCursor;
+
+ if not RichTextWordAt( FText,
+ P,
+ pWordStart,
+ WordLength ) then
+ begin
+ // not in a word
+ SetCursorPosition( Offset, Row, false );
+ SetupCursor;
+ exit;
+ end;
+
+ SetSelectionStartInternal( FLayout.GetCharIndex( pWordStart ) );
+ SetSelectionEndInternal( FLayout.GetCharIndex( pWordStart )
+ + WordLength );
+ RefreshCursorPosition;
+ SetupCursor;
+end;
+*)
+
+(*
+Procedure TRichTextView.MouseMove( ShiftState: TShiftState;
+ X, Y: Longint );
+var
+ Line: longint;
+ Offset: longint;
+ Link: string;
+ Position: TTextPosition;
+begin
+ inherited MouseMove( ShiftState, X, Y );
+
+ Position := FindPoint( X, Y, Line, Offset, Link );
+
+ if not MouseCapture then
+ begin
+ if Link <> FLastLinkOver then
+ begin
+ if Link <> '' then
+ begin
+ if Assigned( FOnOverLink ) then
+ FOnOverLink( Self, Link )
+ end
+ else
+ begin
+ if Assigned( FOnNotOverLink ) then
+ FOnNotOverLink( Self, FLastLinkOver );
+ end;
+
+ FLastLinkOver := Link;
+ end;
+
+ if Link <> '' then
+ Cursor := FLinkCursor
+ else
+ Cursor := crIBeam;
+ exit;
+ end;
+
+ // We are holding mouse down and dragging to set a selection:
+
+ if Position in [ tpAboveTextArea,
+ tpBelowTextArea ] then
+ begin
+ // above top or below bottom of control
+ FOldMousePoint := Point( X, Y );
+
+ if Position = tpAboveTextArea then
+ FScrollingDirection := sdUp
+ else
+ FScrollingDirection := sdDown;
+
+ if not FScrollTimer.Running then
+ begin
+ FScrollTimer.Start;
+ OnScrollTimer( self );
+ end;
+ exit;
+ end;
+
+ // Normal selection, cursor within text rect
+ if FScrollTimer.Running then
+ FScrollTimer.Stop;
+
+ SetCursorPosition( Offset,
+ Line,
+ true );
+
+ if SelectionSet then
+ begin
+ FClickedLink := ''; // if they move while on a link we don't want to follow it.
+ Cursor := crIBeam;
+ end;
+
+end;
+*)
+
+procedure TRichTextView.OnScrollTimer( Sender: TObject );
+var
+ Line, Offset: longint;
+ MousePoint: TPoint;
+ TextRect: TRect;
+begin
+ exit;
+ //MousePoint := Screen.MousePos;
+ //MousePoint := ScreenToClient( MousePoint );
+ //TextRect := GetTextAreaRect;
+ //
+ //if FScrollingDirection = sdDown then
+ // // scrolling down
+ // if FVScrollbar.Position = FVScrollbar.Max then
+ // exit
+ // else
+ // begin
+ // if ( TextRect.Bottom - MousePoint.Y ) < 20 then
+ // DownLine
+ // else
+ // DownPage;
+ //
+ // GetBottomLine( Line, Offset );
+ // SetSelectionEndInternal( FLayout.GetCharIndex( FLayout.Flines[ Line ].Text )
+ // + FLayout.FLines[ Line ].Length );
+ // end
+ //else
+ // // scrolling up
+ // if FVScrollbar.Position = FVScrollbar.Min then
+ // exit
+ // else
+ // begin
+ // if ( MousePoint.Y - TextRect.Top ) < 20 then
+ // UpLine
+ // else
+ // UpPage;
+ // GetFirstVisibleLine( Line, Offset );
+ // SetSelectionEndInternal( FLayout.GetCharIndex( FLayout.FLines[ Line ].Text ) );
+ // end;
+
+end;
+
+Procedure TRichTextView.UpLine;
+begin
+ SetVerticalPosition( GetLineUpPosition );
+end;
+
+Procedure TRichTextView.DownLine;
+begin
+ SetVerticalPosition( GetLineDownPosition );
+end;
+
+Procedure TRichTextView.UpPage;
+begin
+ SetVerticalPosition( FVScrollbar.Position - FVScrollbar.PageSize );
+end;
+
+Procedure TRichTextView.DownPage;
+begin
+ SetVerticalPosition( FVScrollbar.Position + FVScrollbar.PageSize );
+end;
+
+Procedure TRichTextView.SmallScrollUp;
+begin
+ SetVerticalPosition( GetSmallUpScrollPosition );
+end;
+
+Procedure TRichTextView.SmallScrollDown;
+begin
+ SetVerticalPosition( GetSmallDownScrollPosition );
+end;
+
+Procedure TRichTextView.SmallScrollRight;
+begin
+ SetHorizontalPosition( GetSmallRightScrollPosition );
+end;
+
+Procedure TRichTextView.SmallScrollLeft;
+begin
+ SetHorizontalPosition( GetSmallLeftScrollPosition );
+end;
+
+function TRichTextView.GetCursorIndex: longint;
+begin
+ if FCursorRow = -1 then
+ begin
+ Result := -1;
+ exit;
+ end;
+ Result := FLayout.GetCharIndex( FLayout.FLines^[ FCursorRow ].Text ) + FCursorOffset;
+end;
+
+procedure TRichTextView.RefreshCursorPosition;
+var
+ Index: longint;
+ Row: longint;
+begin
+ if SelectionSet then
+ begin
+ Index := FSelectionEnd
+ end
+ else
+ begin
+ Index := FSelectionStart;
+ end;
+
+ if Index = -1 then
+ begin
+ FCursorRow := -1;
+ FCursorOffset := 0;
+ RemoveCursor;
+ exit;
+ end;
+
+ Row := FLayout.GetLineFromCharIndex( Index );
+ SetCursorPosition( Index - FLayout.GetCharIndex( FLayout.FLines^[ Row ].Text ),
+ Row,
+ true );
+end;
+
+procedure TRichTextView.SetCursorIndex( Index: longint;
+ PreserveSelection: boolean );
+var
+ Row: longint;
+begin
+ Row := FLayout.GetLineFromCharIndex( Index );
+ SetCursorPosition( Index - FLayout.GetCharIndex( FLayout.FLines^[ Row ].Text ),
+ Row,
+ PreserveSelection );
+ SetupCursor;
+end;
+
+procedure TRichTextView.SetCursorPosition( Offset: longint;
+ Row: longint;
+ PreserveSelection: boolean );
+var
+ Index: longint;
+begin
+ RemoveCursor;
+ FCursorOffset := Offset;
+ FCursorRow := Row;
+ Index := FLayout.GetCharIndex( FLayout.FLines^[ Row ].Text ) + Offset;
+ if PreserveSelection then
+ begin
+ SetSelectionEndInternal( Index )
+ end
+ else
+ begin
+ SetSelectionEndInternal( -1 );
+ SetSelectionStartInternal( Index );
+ end;
+ MakeRowAndColumnVisible( FCursorRow, Offset );
+end;
+
+Procedure TRichTextView.CursorRight( PreserveSelection: boolean );
+Var
+ P: PChar;
+ NextP: PChar;
+ Element: TTextElement;
+ NewOffset: longint;
+ Line: TLayoutLine;
+begin
+ P := FText + CursorIndex;
+
+ Element := ExtractNextTextElement( P, NextP );
+ P := NextP;
+ while Element.ElementType = teStyle do
+ begin
+ Element := ExtractNextTextElement( P, NextP );
+ P := NextP;
+ end;
+
+// if Element.ElementType = teTextEnd then
+// exit;
+
+// SetCursorIndex( GetCharIndex( P ), PreserveSelection );
+ Line := FLayout.FLines^[ CursorRow ];
+ NewOffset := PCharDiff( P, Line.Text );
+ if NewOffset < Line.Length then
+ begin
+ SetCursorPosition( NewOffset, FCursorRow, PreserveSelection )
+ end
+ else if ( NewOffset = Line.Length )
+ and not Line.Wrapped then
+ begin
+ SetCursorPosition( NewOffset, FCursorRow, PreserveSelection )
+ end
+ else
+ begin
+ if FCursorRow >= FLayout.FNumLines - 1 then
+ exit;
+ SetCursorPosition( 0, FCursorRow + 1, PreserveSelection );
+ end;
+ SetupCursor;
+end;
+
+Procedure TRichTextView.CursorLeft( PreserveSelection: boolean );
+Var
+ P: PChar;
+ NextP: PChar;
+ Element: TTextElement;
+ Line: TLayoutLine;
+ NewOffset: longint;
+begin
+ P := FText + CursorIndex;
+
+ Element := ExtractPreviousTextElement( FText, P, NextP );
+ P := NextP;
+ while Element.ElementType = teStyle do
+ begin
+ Element := ExtractPreviousTextElement( FText, P, NextP );
+ P := NextP;
+ end;
+
+// if Element.ElementType = teTextEnd then
+// exit;
+ Line := FLayout.FLines^[ CursorRow ];
+ NewOffset := PCharDiff( P, Line.Text );
+ if NewOffset >= 0 then
+ begin
+ SetCursorPosition( NewOffset, FCursorRow, PreserveSelection )
+ end
+ else
+ begin
+ if FCursorRow <= 0 then
+ exit;
+ Line := FLayout.FLines^[ CursorRow - 1 ];
+ if Line.Wrapped then
+ SetCursorPosition( Line.Length - 1, FCursorRow - 1, PreserveSelection )
+ else
+ SetCursorPosition( Line.Length, FCursorRow - 1, PreserveSelection )
+ end;
+ SetupCursor;
+
+end;
+
+Procedure TRichTextView.CursorWordLeft( PreserveSelection: boolean );
+Var
+ P: PChar;
+begin
+ P := FText + CursorIndex;
+
+ P := RichTextWordLeft( FText, P );
+
+ SetCursorIndex( FLayout.GetCharIndex( P ),
+ PreserveSelection );
+end;
+
+Procedure TRichTextView.CursorWordRight( PreserveSelection: boolean );
+Var
+ P: PChar;
+begin
+ P := FText + CursorIndex;
+
+ P := RichTextWordRight( P );
+
+ SetCursorIndex( FLayout.GetCharIndex( P ),
+ PreserveSelection );
+end;
+
+Procedure TRichTextView.CursorToLineStart( PreserveSelection: boolean );
+Var
+ Line: TLayoutLine;
+begin
+ Line := FLayout.FLines^[ FCursorRow ];
+ SetCursorPosition( 0, FCursorRow, PreserveSelection );
+ SetupCursor;
+end;
+
+Procedure TRichTextView.CursorToLineEnd( PreserveSelection: boolean );
+Var
+ Line: TLayoutLine;
+begin
+ Line := FLayout.FLines^[ FCursorRow ];
+ SetCursorPosition( Line.Length, FCursorRow, PreserveSelection );
+ SetupCursor;
+end;
+
+Procedure TRichTextView.CursorDown( PreserveSelection: boolean );
+var
+ X: longint;
+ Link: string;
+ Offset: longint;
+begin
+ if CursorRow >= FLayout.FNumLines - 1 then
+ exit;
+
+ FLayout.GetXFromOffset( FCursorOffset, FCursorRow, X );
+ FLayout.GetOffsetFromX( X,
+ FCursorRow + 1,
+ Offset,
+ Link );
+
+ SetCursorPosition( Offset, FCursorRow + 1, PreserveSelection );
+ SetupCursor;
+end;
+
+Procedure TRichTextView.CursorUp( PreserveSelection: boolean );
+var
+ X: longint;
+ Link: string;
+ Offset: longint;
+begin
+ if CursorRow <= 0 then
+ exit;
+
+ FLayout.GetXFromOffset( FCursorOffset,
+ FCursorRow,
+ X );
+ FLayout.GetOffsetFromX( X,
+ FCursorRow - 1,
+ Offset,
+ Link );
+
+ SetCursorPosition( Offset, FCursorRow - 1, PreserveSelection );
+ SetupCursor;
+
+end;
+
+Procedure TRichTextView.CursorPageDown( PreserveSelection: boolean );
+var
+ X: longint;
+ Link: string;
+ Offset: longint;
+ Distance: longint;
+ NewRow: longint;
+begin
+ NewRow := CursorRow;
+ Distance := 0;
+ while ( Distance < GetTextAreaHeight ) do
+ begin
+ if NewRow >= FLayout.FNumLines - 1 then
+ break;
+
+ Distance := Distance + FLayout.FLines^[ NewRow ].Height;
+ inc( NewRow );
+ end;
+
+ FLayout.GetXFromOffset( FCursorOffset, FCursorRow, X );
+ FLayout.GetOffsetFromX( X, NewRow, Offset, Link );
+ SetCursorPosition( Offset, NewRow, PreserveSelection );
+ SetupCursor;
+end;
+
+Procedure TRichTextView.CursorPageUp( PreserveSelection: boolean );
+var
+ X: longint;
+ Link: string;
+ Offset: longint;
+ Distance: longint;
+ NewRow: longint;
+begin
+ NewRow := CursorRow;
+ Distance := 0;
+ while ( Distance < GetTextAreaHeight ) do
+ begin
+ if NewRow <= 0 then
+ break;
+ dec( NewRow );
+ Distance := Distance + FLayout.FLines^[ NewRow ].Height;
+ end;
+
+ FLayout.GetXFromOffset( FCursorOffset, FCursorRow, X );
+ FLayout.GetOffsetFromX( X, NewRow, Offset, Link );
+ SetCursorPosition( Offset, NewRow, PreserveSelection );
+ SetupCursor;
+end;
+
+Function TRichTextView.GetSelectionAsString: string; // returns up to 255 chars obviously
+var
+ Buffer: array[ 0..255 ] of char;
+ Length: longint;
+begin
+ Length := CopySelectionToBuffer( Addr( Buffer ), 255 );
+
+ Result := StrNPas( Buffer, Length );
+end;
+
+Procedure TRichTextView.CopySelectionToClipboard;
+var
+ SelLength: Longint;
+ Buffer: PChar;
+begin
+ SelLength := SelectionLength;
+ if SelectionLength = 0 then
+ exit;
+
+ Buffer := StrAlloc( SelLength + 1 );
+
+ CopySelectionToBuffer( Buffer, SelLength + 1 );
+
+ fpgClipboard.Text := Buffer;
+
+ StrDispose( Buffer );
+end;
+
+function TRichTextView.CopySelectionToBuffer( Buffer: PChar;
+ BufferLength: longint ): longint;
+var
+ P, EndP: PChar;
+begin
+ Result := 0;
+ if ( FSelectionStart = -1 )
+ or ( FSelectionEnd = -1 ) then
+ exit;
+
+ if FSelectionStart < FSelectionEnd then
+ begin
+ P := FText + FSelectionStart;
+ EndP := FText + FSelectionEnd;
+ end
+ else
+ begin
+ P := FText + FSelectionEnd;
+ EndP := FText + FSelectionStart;
+ end;
+
+ Result := CopyPlainTextToBuffer( P,
+ EndP,
+ Buffer,
+ BufferLength );
+end;
+
+function TRichTextView.CopyTextToBuffer( Buffer: PChar;
+ BufferLength: longint ): longint;
+begin
+ Result := CopyPlainTextToBuffer( FText,
+ FText + strlen( FText ),
+ Buffer,
+ BufferLength );
+end;
+
+Function TRichTextView.SelectionLength: longint;
+begin
+ Result := 0;
+ if ( FSelectionStart = -1 )
+ or ( FSelectionEnd = -1 ) then
+ exit;
+
+ Result := FSelectionEnd - FSelectionStart;
+ if Result < 0 then
+ Result := FSelectionStart - FSelectionEnd;
+end;
+
+Function TRichTextView.SelectionSet: boolean;
+begin
+ Result := ( FSelectionStart <> -1 )
+ and ( FSelectionEnd <> - 1 )
+ and ( FSelectionStart <> FSelectionEnd );
+end;
+
+Procedure TRichTextView.SelectAll;
+begin
+ ClearSelection;
+ SelectionStart := FLayout.GetCharIndex( FText );
+ SelectionEnd := FLayout.GetTextEnd;
+end;
+
+(*
+procedure TRichTextView.ScanEvent( Var KeyCode: TKeyCode;
+ RepeatCount: Byte );
+var
+ CursorVisible: boolean;
+ Shift: boolean;
+ Key: TKeyCode;
+begin
+ CursorVisible := FSelectionStart <> -1;
+
+ Case KeyCode of
+ kbTab:
+ begin
+ if HighlightNextLink then
+ begin
+ KeyCode := kbNull;
+ exit;
+ end;
+ end;
+
+ kbShiftTab:
+ begin
+ if HighlightPreviousLink then
+ begin
+ KeyCode := kbNull;
+ exit;
+ end;
+ end;
+
+ kbEnter:
+ begin
+
+ end;
+ end;
+
+ Shift := KeyCode and kb_Shift > 0 ;
+ Key := KeyCode and ( not kb_Shift );
+
+ // Keys which work the same regardless of whether
+ // cursor is present or not
+ case Key of
+ kbCtrlC, kbCtrlIns:
+ CopySelectionToClipboard;
+ kbCtrlA:
+ SelectAll;
+
+ kbAltCUp:
+ SmallScrollUp;
+ kbAltCDown:
+ SmallScrollDown;
+ kbAltCLeft:
+ SmallScrollLeft;
+ kbAltCRight:
+ SmallScrollRight;
+ end;
+
+ // Keys which change behaviour if cursor is present
+ if CursorVisible then
+ begin
+ case Key of
+ kbCUp:
+ CursorUp( Shift );
+ kbCDown:
+ CursorDown( Shift );
+
+ // these next two are not exactly orthogonal or required,
+ // but better match other text editors.
+ kbCtrlCUp:
+ if Shift then
+ CursorUp( Shift )
+ else
+ SmallScrollUp;
+ kbCtrlCDown:
+ if Shift then
+ CursorDown( Shift )
+ else
+ SmallScrollDown;
+
+ kbCRight:
+ CursorRight( Shift );
+ kbCLeft:
+ CursorLeft( Shift );
+
+ kbCtrlCLeft:
+ CursorWordLeft( Shift );
+ kbCtrlCRight:
+ CursorWordRight( Shift );
+
+ kbCtrlHome, kbCtrlPageUp:
+ SetCursorIndex( 0, Shift );
+ kbCtrlEnd, kbCtrlPageDown:
+ SetCursorIndex( GetTextEnd, Shift );
+
+ kbPageUp:
+ CursorPageUp( Shift );
+ kbPageDown:
+ CursorPageDown( Shift );
+
+ kbHome:
+ CursorToLineStart( Shift );
+ kbEnd:
+ CursorToLineEnd( Shift );
+ end
+ end
+ else // no cursor visible
+ begin
+ case Key of
+ kbCUp, kbCtrlCUp:
+ SmallScrollUp;
+ kbCDown, kbCtrlCDown:
+ SmallScrollDown;
+
+ kbCLeft, kbCtrlCLeft:
+ SmallScrollLeft;
+ kbCRight, kbCtrlCRight:
+ SmallScrollRight;
+
+ kbPageUp:
+ UpPage;
+ kbPageDown:
+ DownPage;
+
+ kbHome, kbCtrlHome, kbCtrlPageUp:
+ GotoTop;
+ kbEnd, kbCtrlEnd, kbCtrlPageDown:
+ GotoBottom;
+ end;
+ end;
+
+ inherited ScanEvent( KeyCode, RepeatCount );
+
+end;
+*)
+
+function TRichTextView.HighlightNextLink: boolean;
+Var
+ P: PChar;
+ NextP: PChar;
+ T: TTextElement;
+ StartP: PChar;
+begin
+ if CursorIndex = -1 then
+ P := FText // no cursor yet
+ else
+ P := FText + CursorIndex;
+
+ result := false;
+
+ // if we're sitting on a begin-link, skip it...
+ T := ExtractNextTextElement( P, NextP );
+ if T.ElementType = teStyle then
+ if T.Tag.TagType = ttBeginLink then
+ P := NextP;
+
+ while true do
+ begin
+ T := ExtractNextTextElement( P, NextP );
+ if T.ElementType = teTextEnd then
+ // no link found
+ exit;
+
+ if T.ElementType = teStyle then
+ if T.Tag.TagType = ttBeginLink then
+ break;
+
+ p := NextP;
+
+ end;
+
+ StartP := P;
+ p := NextP; // skip begin link
+
+ while true do
+ begin
+ T := ExtractNextTextElement( P, NextP );
+ if T.ElementType = teTextEnd then
+ break; // no explicit link end...
+
+ if T.ElementType = teStyle then
+ if T.Tag.TagType = ttEndLink then
+ break;
+
+ p := NextP;
+ end;
+
+ SetSelectionStart( FLayout.GetCharIndex( StartP ) );
+ SetSelectionEnd( FLayout.GetCharIndex( NextP ) );
+
+ result := true;
+end;
+
+function TRichTextView.HighlightPreviousLink: boolean;
+Var
+ P: PChar;
+ PreviousP: PChar;
+ T: TTextElement;
+ EndP: PChar;
+begin
+ result := false;
+ if CursorIndex = -1 then
+ exit; // no cursor yet
+
+ P := FText + CursorIndex;
+
+ // if we're sitting on an end-of-link, skip it...
+ T := ExtractPreviousTextElement( FText, P, PreviousP );
+ if T.ElementType = teStyle then
+ if T.Tag.TagType = ttEndLink then
+ P := PreviousP;
+
+ while true do
+ begin
+ T := ExtractPreviousTextElement( FText, P, PreviousP );
+ if T.ElementType = teTextEnd then
+ // no link found
+ exit;
+
+ if T.ElementType = teStyle then
+ if T.Tag.TagType = ttEndLink then
+ break;
+
+ p := PreviousP;
+
+ end;
+
+ EndP := P;
+ p := PreviousP; // skip end link
+
+ while true do
+ begin
+ T := ExtractPreviousTextElement( FText, P, PreviousP );
+ if T.ElementType = teTextEnd then
+ break; // no explicit link end...
+
+ if T.ElementType = teStyle then
+ if T.Tag.TagType = ttBeginLink then
+ break;
+
+ p := PreviousP;
+ end;
+
+ SetSelectionStart( FLayout.GetCharIndex( EndP ) );
+ SetSelectionEnd( FLayout.GetCharIndex( PreviousP ) );
+
+ result := true;
+end;
+
+procedure TRichTextView.GoToTop;
+begin
+ SetVerticalPosition( 0 );
+end;
+
+procedure TRichTextView.GotoBottom;
+begin
+ SetVerticalPosition( FVScrollBar.Max );
+end;
+
+Function TRichTextView.GetTopCharIndex: longint;
+var
+ LineIndex: longint;
+ Y: longint;
+begin
+ if not FVerticalPositionInitialised then
+ begin
+ Result := FTopCharIndex;
+ exit;
+ end;
+ GetFirstVisibleLine( LineIndex,
+ Y );
+ if LineIndex >= 0 then
+ Result := FLayout.GetCharIndex( FLayout.FLines^[ LineIndex ].Text )
+ else
+ Result := 0;
+end;
+
+Function TRichTextView.GetTopCharIndexPosition( NewValue: longint ): longint;
+var
+ Line: longint;
+ lHeight: longint;
+begin
+ if NewValue > GetTextEnd then
+ begin
+ Result := FVScrollBar.Max;
+ exit;
+ end;
+ Line := FLayout.GetLineFromCharIndex( NewValue );
+ if Line = 0 then
+ begin
+ Result := 0; // include top margin
+ exit;
+ end;
+
+ if Line < 0 then
+ begin
+ Result := FVScrollBar.Position;
+ exit;
+ end;
+ lHeight := FLayout.GetLinePosition( Line );
+ Result := lHeight;
+end;
+
+Procedure TRichTextView.SetTopCharIndex( NewValue: longint );
+var
+ NewPosition: longint;
+begin
+ if not FVerticalPositionInitialised then
+ begin
+ if ( NewValue >= 0 )
+ and ( NewValue < GetTextEnd ) then
+ begin
+ FTopCharIndex := NewValue;
+ end;
+ exit;
+ end;
+ NewPosition := GetTopCharIndexPosition( NewValue );
+ SetVerticalPosition( NewPosition );
+end;
+
+procedure TRichTextView.MakeCharVisible( CharIndex: longint );
+var
+ Line: longint;
+begin
+ Line := FLayout.GetLineFromCharIndex( CharIndex );
+
+ MakeRowAndColumnVisible( Line,
+ FLayout.GetOffsetFromCharIndex( CharIndex, Line ) );
+end;
+
+procedure TRichTextView.MakeRowVisible( Row: longint );
+var
+ TopLine: longint;
+ BottomLine: longint;
+ Offset: longint;
+ NewPosition: longint;
+begin
+ GetFirstVisibleLine( TopLine, Offset );
+ GetBottomLine( BottomLine, Offset );
+
+ if ( Row > TopLine )
+ and ( Row < BottomLine ) then
+ // already visible
+ exit;
+
+ if ( Row = BottomLine )
+ and ( Offset >= FLayout.FLines^[ BottomLine ].Height - 1 ) then
+ // bottom row already entirely visible
+ exit;
+
+ if Row <= TopLine then
+ begin
+ // need to scroll up, desird row above top line
+ if Row = 0 then
+ NewPosition := 0 // include margins
+ else
+ NewPosition := FLayout.GetLinePosition( Row );
+
+ if NewPosition > FVScrollbar.Position then
+ // no need to scroll
+ exit;
+ SetVerticalPosition( NewPosition );
+ end
+ else
+ begin
+ // need to scroll down, desired row below bottom line
+ if ( BottomLine <> -1 )
+ and ( Row >= BottomLine ) then
+ SetVerticalPosition( FLayout.GetLinePosition( Row )
+ + FLayout.FLines^[ Row ].Height
+ - GetTextAreaHeight );
+ end;
+end;
+
+procedure TRichTextView.MakeRowAndColumnVisible( Row: longint;
+ Column: longint );
+var
+ X: Longint;
+begin
+ MakeRowVisible( Row );
+ FLayout.GetXFromOffset( Column, Row, X );
+
+ if X > FXScroll + GetTextAreaWidth then
+ // off the right
+ SetHorizontalPosition( X - GetTextAreaWidth + 5 )
+ else if X < FXScroll then
+ // off to left
+ SetHorizontalPosition( X );
+
+end;
+
+function TRichTextView.LinkFromIndex( const CharIndexToFind: longint): string;
+begin
+ Result := FLayout.LinkFromIndex( CharIndexToFind );
+end;
+
+function TRichTextView.FindString( Origin: TFindOrigin;
+ const AText: string;
+ var MatchIndex: longint;
+ var MatchLength: longint ): boolean;
+var
+ P: PChar;
+ pMatch: pchar;
+begin
+ if ( Origin = foFromCurrent )
+ and ( FSelectionStart <> -1 ) then
+ begin
+ // start at current cursor position
+ P := FText + GetCursorIndex;
+ end
+ else
+ begin
+ P := FText;
+ end;
+
+ Result := RichTextFindString( P, AText, pMatch, MatchLength );
+
+ if Result then
+ // found
+ MatchIndex := FLayout.GetCharIndex( pMatch )
+ else
+ MatchIndex := -1;
+
+end;
+
+function TRichTextView.Find( Origin: TFindOrigin;
+ const AText: string ): boolean;
+var
+ MatchIndex: longint;
+ MatchLength: longint;
+begin
+ Result := FindString( Origin,
+ AText,
+ MatchIndex,
+ MatchLength );
+ if Result then
+ begin
+ MakeCharVisible( MatchIndex );
+ FSelectionStart := MatchIndex;
+ SelectionEnd := MatchIndex + MatchLength;
+ end;
+end;
+
+function TRichTextView.GetClientRect: TfpgRect;
+begin
+ // Standard border of 2px on all sides
+ Result.SetRect(0, 0, Width, Height);
+ InflateRect(Result, -2, -2);
+end;
+
+
+end.
+
diff --git a/components/richtext/fpgui_richtext.lpk b/components/richtext/fpgui_richtext.lpk
index 89dbad7c..f3b4823c 100644..100755
--- a/components/richtext/fpgui_richtext.lpk
+++ b/components/richtext/fpgui_richtext.lpk
@@ -2,11 +2,12 @@
<CONFIG>
<Package Version="3">
<Name Value="fpgui_richtext"/>
+ <AddToProjectUsesSection Value="False"/>
<Author Value="Graeme Geldenhuys"/>
<CompilerOptions>
<Version Value="8"/>
<SearchPaths>
- <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)/"/>
+ <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
<Parsing>
<Style Value="1"/>
@@ -28,16 +29,40 @@
<Description Value="RichTextView component"/>
<License Value="LGPL2 with static linking exception."/>
<Version Minor="1"/>
- <Files Count="2">
+ <Files Count="8">
<Item1>
<Filename Value="RichTextDocumentUnit.pas"/>
+ <UnitName Value="RichTextDocumentUnit"/>
</Item1>
<Item2>
<Filename Value="ACLStringUtility.pas"/>
<UnitName Value="ACLStringUtility"/>
</Item2>
+ <Item3>
+ <Filename Value="CanvasFontManager.pas"/>
+ <UnitName Value="CanvasFontManager"/>
+ </Item3>
+ <Item4>
+ <Filename Value="RichTextStyleUnit.pas"/>
+ <UnitName Value="RichTextStyleUnit"/>
+ </Item4>
+ <Item5>
+ <Filename Value="nvUtilities.pas"/>
+ <UnitName Value="nvUtilities"/>
+ </Item5>
+ <Item6>
+ <Filename Value="RichTextLayoutUnit.pas"/>
+ <UnitName Value="RichTextLayoutUnit"/>
+ </Item6>
+ <Item7>
+ <Filename Value="RichTextDisplayUnit.pas"/>
+ <UnitName Value="RichTextDisplayUnit"/>
+ </Item7>
+ <Item8>
+ <Filename Value="RichTextView.pas"/>
+ <UnitName Value="RichTextView"/>
+ </Item8>
</Files>
- <Type Value="RunAndDesignTime"/>
<RequiredPkgs Count="2">
<Item1>
<PackageName Value="fpgui_toolkit"/>
diff --git a/components/richtext/fpgui_richtext.pas b/components/richtext/fpgui_richtext.pas
index 0b15f543..0c049f2a 100644..100755
--- a/components/richtext/fpgui_richtext.pas
+++ b/components/richtext/fpgui_richtext.pas
@@ -7,14 +7,9 @@ unit fpgui_richtext;
interface
uses
- RichTextDocumentUnit, ACLStringUtility, LazarusPackageIntf;
+ RichTextDocumentUnit, ACLStringUtility, CanvasFontManager, RichTextStyleUnit,
+ nvUtilities, RichTextLayoutUnit, RichTextDisplayUnit, RichTextView;
implementation
-procedure Register;
-begin
-end;
-
-initialization
- RegisterPackage('fpgui_richtext', @Register);
end.
diff --git a/src/frm_main.pas b/src/frm_main.pas
index 1563569f..84a3855a 100644
--- a/src/frm_main.pas
+++ b/src/frm_main.pas
@@ -8,7 +8,7 @@ uses
SysUtils, Classes, fpg_base, fpg_main, fpg_form, fpg_panel, fpg_tab,
fpg_tree, fpg_splitter, fpg_menu, fpg_memo, fpg_button, fpg_listbox,
fpg_label, fpg_edit, fpg_radiobutton, fpg_progressbar,
- HelpFile;
+ HelpFile, RichTextView;
type
@@ -25,7 +25,7 @@ type
tsHistory: TfpgTabSheet;
tvContents: TfpgTreeView;
Splitter1: TfpgSplitter;
- Memo1: TfpgMemo;
+ Memo1: TRichTextView;
MainMenu: TfpgMenuBar;
miFile: TfpgPopupMenu;
miSettings: TfpgPopupMenu;
@@ -177,6 +177,7 @@ var
f: THelpFile;
i: integer;
begin
+{
Memo1.Lines.Clear;
Memo1.Lines.BeginUpdate;
f := THelpFile(Files[0]);
@@ -194,6 +195,7 @@ begin
Add('[' + IntToStr(i) + '] = <' + f.DictionaryWords[i] + '>');
end;
Memo1.Lines.EndUpdate;
+}
end;
procedure TMainForm.btnShowIndex(Sender: TObject);
@@ -476,7 +478,8 @@ begin
tvContents.Selection := nil;
tvContents.RootNode.Clear;
tvContents.Invalidate;
- Memo1.Lines.Clear;
+ Memo1.Clear;
+// Memo1.Lines.Clear;
// First save notes. It's important we do this first
// since we scan all notes each time to find the ones
@@ -650,7 +653,8 @@ Begin
end;
end;
- Memo1.Lines.Clear;
+// Memo1.Clear;
+// Memo1.Lines.Clear;
ImageIndices := TList.Create;
ProfileEvent('Cleared memo...');
@@ -672,7 +676,8 @@ Begin
{ TODO -oGraeme : We do not support images yet }
ImageIndices.Free;
- Memo1.Lines.Text := lText;
+ Memo1.AddText(PChar(lText));
+// Memo1.Lines.Text := lText;
end;
procedure TMainForm.ResetProgress;
@@ -818,12 +823,12 @@ begin
Align := alLeft;
end;
- Memo1 := TfpgMemo.Create(bvlBody);
+ Memo1 := TRichTextView.Create(bvlBody);
with Memo1 do
begin
Name := 'Memo1';
SetPosition(276, 36, 244, 232);
- FontDesc := '#Edit1';
+// FontDesc := '#Edit1';
TabOrder := 2;
Align := alClient;
end;
diff --git a/src/newview_fpgui.lpi b/src/newview_fpgui.lpi
index 19a5f9c9..d2306dce 100644
--- a/src/newview_fpgui.lpi
+++ b/src/newview_fpgui.lpi
@@ -27,10 +27,13 @@
<LaunchingApplication PathPlusParams="/usr/X11R6/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/>
</local>
</RunParams>
- <RequiredPackages Count="1">
+ <RequiredPackages Count="2">
<Item1>
- <PackageName Value="fpgui_toolkit"/>
+ <PackageName Value="fpgui_richtext"/>
</Item1>
+ <Item2>
+ <PackageName Value="fpgui_toolkit"/>
+ </Item2>
</RequiredPackages>
<Units Count="16">
<Unit0>
@@ -99,7 +102,7 @@
<UnitName Value="nvNullObjects"/>
</Unit12>
<Unit13>
- <Filename Value="../../../../../../opt/git/dunit2/3rdparty/epiktimer/epiktimer.pas"/>
+ <Filename Value="../../../../../opt/git/dunit2/3rdparty/epiktimer/epiktimer.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="EpikTimer"/>
</Unit13>
@@ -118,11 +121,10 @@
<Version Value="8"/>
<SearchPaths>
<IncludeFiles Value="$(ProjOutDir)/"/>
- <OtherUnitFiles Value="/opt/git/dunit2/3rdparty/epiktimer/"/>
+ <OtherUnitFiles Value="/home/graemeg/Desktop/newview/components/richtext/"/>
<UnitOutputDirectory Value="units"/>
</SearchPaths>
<Parsing>
- <Style Value="1"/>
<SyntaxOptions>
<CStyleOperator Value="False"/>
<AllowLabel Value="False"/>
diff --git a/src/newview_fpgui.lpr b/src/newview_fpgui.lpr
index 5eef5ee4..fe72b25e 100644
--- a/src/newview_fpgui.lpr
+++ b/src/newview_fpgui.lpr
@@ -10,7 +10,8 @@ uses
{$IFDEF Timing}EpikTimer,{$ENDIF}
fpg_main, frm_main, DataTypes, HelpFileHeader, HelpWindow,
IPFEscapeCodes, HelpTopic, CompareWordUnit, SearchTable, TextSearchQuery,
- nvUtilities, nvNullObjects, HelpFile, SearchUnit;
+ nvUtilities, nvNullObjects, HelpFile, SearchUnit,
+ fpg_cmdlineparams, customstyle;
procedure MainProc;
@@ -18,6 +19,12 @@ var
frm: TMainForm;
begin
fpgApplication.Initialize;
+
+ // always load custom style for help viewer
+ //if Assigned(fpgStyle) then
+ // fpgStyle.Free;
+ //fpgStyle := TMyStyle.Create;
+
frm := TMainForm.Create(nil);
try
frm.Show;
diff --git a/src/nvUtilities.pas b/src/nvUtilities.pas
index a8e3eb96..87cd83a3 100644
--- a/src/nvUtilities.pas
+++ b/src/nvUtilities.pas
@@ -37,6 +37,10 @@ function GetFileSize(const AFilename: string): integer;
function IsDigit(const AChar: TfpgChar): boolean;
function IsAlpha(const AChar: TfpgChar): boolean;
+function Between( const Value: longint; const Limit1: longint; const Limit2: longint ): boolean;
+
+
+Operator = (ARect: TRect; BRect: TRect): boolean;
implementation
@@ -122,5 +126,21 @@ Begin
//Result := TCharacter.IsLetter(AChar);
end;
+function Between( const Value: longint; const Limit1: longint; const Limit2: longint ): boolean;
+begin
+ if Limit1 < Limit2 then
+ Result:= ( Value >= Limit1 ) and ( Value <= Limit2 )
+ else
+ Result:= ( Value >= Limit2 ) and ( Value <= Limit1 )
+end;
+
+operator = (ARect: TRect; BRect: TRect): boolean;
+begin
+ result := (ARect.Top = BRect.Top) and
+ (ARect.Left = BRect.Left) and
+ (ARect.Bottom = BRect.Bottom) and
+ (ARect.Right = BRect.Right);
+end;
+
end.